diff --git a/Makefile b/Makefile index 7b422c6..578bd4f 100644 --- a/Makefile +++ b/Makefile @@ -64,6 +64,7 @@ NVG_COMMON_SRCS = \ c_src/device/nvg/nvg_script_ops.c CAIRO_COMMON_SRCS = \ + c_src/device/cairo/cairo_common.c \ c_src/device/cairo/cairo_font_ops.c \ c_src/device/cairo/cairo_image_ops.c \ c_src/device/cairo/cairo_script_ops.c @@ -143,6 +144,16 @@ else ifeq ($(SCENIC_LOCAL_TARGET),cairo-fb) $(CAIRO_COMMON_SRCS) \ c_src/device/cairo/cairo_fb.c +else ifeq ($(SCENIC_LOCAL_TARGET),cairo-gtk) + LDFLAGS += `pkg-config --static --libs freetype2 cairo gtk+-3.0` + CFLAGS += `pkg-config --static --cflags freetype2 cairo gtk+-3.0` + LDFLAGS += -lm + CFLAGS ?= -O2 -Wall -Wextra -Wno-unused-parameter -pedantic + CFLAGS += -std=gnu99 + + DEVICE_SRCS += \ + $(CAIRO_COMMON_SRCS) \ + c_src/device/cairo/cairo_gtk.c else $(info ------ no SCENIC_LOCAL_TARGET set ------) $(info If you get here, then you are probably using a custom Nerves system) diff --git a/README.md b/README.md index 30e0b32..b2d3667 100644 --- a/README.md +++ b/README.md @@ -43,11 +43,18 @@ For example, for apps running on a Mac/PC/Linux, it is usually set to `host`, wh If you are building for Nerves, it will use `bcm` (Broadcom Manager) for any of `rpi`, `rpi0`, `rip2`, `rpi3`, and `rpi3a`. +### Cairo +There is now a rendering backend to use `cairo`. You can set SCENIC_LOCAL_TARGET to `cairo-fb` which renders to `/dev/fb0` on an embedded device or `cairo-gtk` which renders to a window on a host (as an alternative to `glfw`). + +`cairo-fb` will require that your `nerves_system_*` has the `cairo` library selected. + ### Nerves rpi4 & bbb Need Work / Help `Scenic.Driver.Local` uses `drm` (Direct Render Manager) for the `rpi4` and `bbb`. It currently renders on the rpi4, but is __very slow__. I haven't figured out why yet and if anyone wants to dig in, that would be appreciated. It should be really fast, so is probably a hardware configuration issue. The `bbb` doesn't work (is close in theory??) as the Nerves `bbb` system doesn't have the needed graphics support in it yet. There are others who have gotten SGX support working for the `bbb` and I could use some help from them. +Please try using `cairo-fb` to see if it will work for you on `rpi4` or `bbb`. + ### Custom Nerves Targets For custom systems (example - figuring out how to add SGX support to the `bbb`) You will need to set SCENIC_LOCAL_TARGET manually. You may also need to set the SCENIC_LOCAL_GL as well. @@ -92,11 +99,18 @@ Once these components have been installed, you should be able to build the `scen The easiest way to install on Ubuntu is to use apt-get. Just run the following: +For `glfw`: ```bash apt-get update apt-get install pkgconf libglfw3 libglfw3-dev libglew2.1 libglew-dev ``` +For `cairo-gtk`: +```bash +apt-get update +apt-get install pkgconf libgtk-3-0 libgtk-3-dev libsystemd-dev libwebp-dev libzstd-dev +``` + Once these components have been installed, you should be able to build the `scenic_driver_local` driver. ### Installing on Arch Linux diff --git a/c_src/device/cairo/cairo_common.c b/c_src/device/cairo/cairo_common.c new file mode 100644 index 0000000..efcfec9 --- /dev/null +++ b/c_src/device/cairo/cairo_common.c @@ -0,0 +1,99 @@ +#include "cairo_ctx.h" +#include "device.h" + +extern device_info_t g_device_info; + +scenic_cairo_ctx_t* scenic_cairo_init(const device_opts_t* p_opts, + device_info_t* p_info) +{ + scenic_cairo_ctx_t* p_ctx = calloc(1, sizeof(scenic_cairo_ctx_t)); + + FT_Error status = FT_Init_FreeType(&p_ctx->ft_library); + if (status != 0) { + log_error("cairo: FT_Init_FreeType: Error: %d", status); + free(p_ctx); + + return NULL; + } + + p_ctx->ratio = 1.0f; + p_ctx->dist_tolerance = 0.1f * p_ctx->ratio; + + p_info->width = p_opts->width; + p_info->height = p_opts->height; + + p_ctx->font_size = 10.0; // Cairo default + p_ctx->text_align = TEXT_ALIGN_LEFT; + p_ctx->text_base = TEXT_BASE_ALPHABETIC; + + p_ctx->clear_color = (color_rgba_t){ + // black opaque + .red = 0.0, + .green = 0.0, + .blue = 0.0, + .alpha = 1.0 + }; + + p_info->v_ctx = p_ctx; + + p_ctx->surface = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, + p_info->width, p_info->height); + + return p_ctx; +} + +void scenic_cairo_fini(scenic_cairo_ctx_t* p_ctx) +{ + cairo_surface_destroy(p_ctx->surface); + free(p_ctx); +} + +void device_begin_cursor_render(driver_data_t* p_data) +{ + scenic_cairo_ctx_t* p_ctx = (scenic_cairo_ctx_t*)p_data->v_ctx; + cairo_translate(p_ctx->cr, p_data->cursor_pos[0], p_data->cursor_pos[1]); +} + +void device_clear_color(float red, float green, float blue, float alpha) +{ + scenic_cairo_ctx_t* p_ctx = (scenic_cairo_ctx_t*)g_device_info.v_ctx; + p_ctx->clear_color = (color_rgba_t){ + .red = red, + .green = green, + .blue = blue, + .alpha = alpha + }; +} + +char* device_gl_error() +{ + return NULL; +} + +void pattern_stack_push(scenic_cairo_ctx_t* p_ctx) +{ + pattern_stack_t* ptr = (pattern_stack_t*)malloc(sizeof(pattern_stack_t)); + + ptr->pattern = p_ctx->pattern; + + if (!p_ctx->pattern_stack_head) { + ptr->next = NULL; + p_ctx->pattern_stack_head = ptr; + } else { + ptr->next = p_ctx->pattern_stack_head; + p_ctx->pattern_stack_head = ptr; + } +} + +void pattern_stack_pop(scenic_cairo_ctx_t* p_ctx) +{ + pattern_stack_t* ptr = p_ctx->pattern_stack_head; + + if (!ptr) { + log_error("pattern stack underflow"); + } else { + p_ctx->pattern = ptr->pattern; + p_ctx->pattern_stack_head = p_ctx->pattern_stack_head->next; + free(ptr); + } +} diff --git a/c_src/device/cairo/cairo_ctx.h b/c_src/device/cairo/cairo_ctx.h index 3f67075..8528cc5 100644 --- a/c_src/device/cairo/cairo_ctx.h +++ b/c_src/device/cairo/cairo_ctx.h @@ -32,11 +32,6 @@ typedef struct { float font_size; text_align_t text_align; text_base_t text_base; - union { - u_int8_t *c; - u_int16_t *s; - u_int32_t *i; - } fbbuff; cairo_surface_t* surface; cairo_t* cr; pattern_stack_t* pattern_stack_head; @@ -53,6 +48,10 @@ typedef struct { float ratio; } scenic_cairo_ctx_t; +scenic_cairo_ctx_t* scenic_cairo_init(const device_opts_t* p_opts, + device_info_t* p_info); +void scenic_cairo_fini(scenic_cairo_ctx_t* p_ctx); + void pattern_stack_push(scenic_cairo_ctx_t* p_ctx); void pattern_stack_pop(scenic_cairo_ctx_t* p_ctx); diff --git a/c_src/device/cairo/cairo_fb.c b/c_src/device/cairo/cairo_fb.c index 51ff871..4670fca 100644 --- a/c_src/device/cairo/cairo_fb.c +++ b/c_src/device/cairo/cairo_fb.c @@ -18,6 +18,12 @@ const char* device = "/dev/fb0"; typedef struct { int fd; + union { + u_int8_t *c; + u_int16_t *s; + u_int32_t *i; + } rgb_buff; + struct fb_var_screeninfo var; struct fb_fix_screeninfo fix; } cairo_fb_t; @@ -81,6 +87,13 @@ int device_init(const device_opts_t* p_opts, log_info("cairo %s", __func__); } + scenic_cairo_ctx_t* p_ctx = scenic_cairo_init(p_opts, p_info); + if (!p_ctx) { + return -1; + } + + p_info->v_ctx = p_ctx; + if ((g_cairo_fb.fd = open(device, O_RDWR)) == -1) { log_error("Failed to open device %s: %s", device, strerror(errno)); return -1; @@ -96,65 +109,33 @@ int device_init(const device_opts_t* p_opts, return -1; } - scenic_cairo_ctx_t* p_ctx = calloc(1, sizeof(scenic_cairo_ctx_t)); - - FT_Error status = FT_Init_FreeType(&p_ctx->ft_library); - if (status != 0) { - log_error("cairo: FT_Init_FreeType: Error: %d", status); - close(g_cairo_fb.fd); - free(p_ctx); - - return -1; - } - - p_ctx->ratio = 1.0f; - p_ctx->dist_tolerance = 0.1f * p_ctx->ratio; + uint32_t width = cairo_image_surface_get_width(p_ctx->surface); + uint32_t height = cairo_image_surface_get_height(p_ctx->surface); + size_t pix_count = width * height; - size_t pix_count = p_opts->width * p_opts->height; switch (g_cairo_fb.var.bits_per_pixel) { case 8: - p_ctx->fbbuff.c = (uint8_t*)malloc(pix_count * sizeof(uint8_t)); + g_cairo_fb.rgb_buff.c = (uint8_t*)malloc(pix_count * sizeof(uint8_t)); + + get8map(g_cairo_fb.fd, &map_back); + set332map(g_cairo_fb.fd); break; case 15: case 16: - p_ctx->fbbuff.c = (uint8_t*)malloc(pix_count * sizeof(uint16_t)); + g_cairo_fb.rgb_buff.c = (uint8_t*)malloc(pix_count * sizeof(uint16_t)); break; case 24: - p_ctx->fbbuff.c = (uint8_t*)malloc(pix_count * 3 * sizeof(uint8_t)); + g_cairo_fb.rgb_buff.c = (uint8_t*)malloc(pix_count * 3 * sizeof(uint8_t)); break; case 32: - p_ctx->fbbuff.c = (uint8_t*)malloc(pix_count * sizeof(uint32_t)); + g_cairo_fb.rgb_buff.c = (uint8_t*)malloc(pix_count * sizeof(uint32_t)); break; default: log_error("cairo: Unsupported video mode: %dbpp", g_cairo_fb.var.bits_per_pixel); return -1; } - if (g_cairo_fb.var.bits_per_pixel == 8) { - get8map(g_cairo_fb.fd, &map_back); - set332map(g_cairo_fb.fd); - } - - p_info->width = p_opts->width; - p_info->height = p_opts->height; - - p_ctx->font_size = 10.0; // Cairo default - p_ctx->text_align = TEXT_ALIGN_LEFT; - p_ctx->text_base = TEXT_BASE_ALPHABETIC; - - p_ctx->clear_color = (color_rgba_t){ - // black opaque - .red = 0.0, - .green = 0.0, - .blue = 0.0, - .alpha = 1.0 - }; - - p_info->v_ctx = p_ctx; - - p_ctx->surface = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, - p_info->width, p_info->height); return 0; } @@ -169,11 +150,10 @@ int device_close(device_info_t* p_info) } close(g_cairo_fb.fd); + free(g_cairo_fb.rgb_buff.c); scenic_cairo_ctx_t* p_ctx = (scenic_cairo_ctx_t*)p_info->v_ctx; - free(p_ctx->fbbuff.c); - cairo_surface_destroy(p_ctx->surface); - free(p_ctx); + scenic_cairo_fini(p_ctx); } void device_poll() @@ -188,6 +168,7 @@ void device_begin_render(driver_data_t* p_data) scenic_cairo_ctx_t* p_ctx = (scenic_cairo_ctx_t*)p_data->v_ctx; + cairo_destroy(p_ctx->cr); p_ctx->cr = cairo_create(p_ctx->surface); // Paint surface to clear color @@ -199,12 +180,6 @@ void device_begin_render(driver_data_t* p_data) cairo_paint(p_ctx->cr); } -void device_begin_cursor_render(driver_data_t* p_data) -{ - scenic_cairo_ctx_t* p_ctx = (scenic_cairo_ctx_t*)p_data->v_ctx; - cairo_translate(p_ctx->cr, p_data->cursor_pos[0], p_data->cursor_pos[1]); -} - inline static uint8_t to_8_color(uint8_t r, uint8_t g, uint8_t b) { return ((((r >> 5) & 7) << 5) | @@ -254,24 +229,24 @@ void render_cairo_surface_to_fb(scenic_cairo_ctx_t* p_ctx) case 8: cpp = 1; for (uint32_t i = 0, j = 0; i < pix_count; i++, j += 4) { - p_ctx->fbbuff.c[i] = to_8_color(cairo_buff[j+2], - cairo_buff[j+1], - cairo_buff[j+0]); + g_cairo_fb.rgb_buff.c[i] = to_8_color(cairo_buff[j+2], + cairo_buff[j+1], + cairo_buff[j+0]); } break; case 15: cpp = 2; if (is_bgr555) { for (uint32_t i = 0, j = 0; i < pix_count; i++, j += 4) { - p_ctx->fbbuff.s[i] = to_15_color_bgr(cairo_buff[j+2], - cairo_buff[j+1], - cairo_buff[j+0]); + g_cairo_fb.rgb_buff.s[i] = to_15_color_bgr(cairo_buff[j+2], + cairo_buff[j+1], + cairo_buff[j+0]); } } else { for (uint32_t i = 0, j = 0; i < pix_count; i++, j += 4) { - p_ctx->fbbuff.s[i] = to_15_color(cairo_buff[j+2], - cairo_buff[j+1], - cairo_buff[j+0]); + g_cairo_fb.rgb_buff.s[i] = to_15_color(cairo_buff[j+2], + cairo_buff[j+1], + cairo_buff[j+0]); } } break; @@ -279,32 +254,32 @@ void render_cairo_surface_to_fb(scenic_cairo_ctx_t* p_ctx) cpp = 2; if (is_bgr555) { for (uint32_t i = 0, j = 0; i < pix_count; i++, j += 4) { - p_ctx->fbbuff.s[i] = to_15_color_bgr(cairo_buff[j+2], - cairo_buff[j+1], - cairo_buff[j+0]); + g_cairo_fb.rgb_buff.s[i] = to_15_color_bgr(cairo_buff[j+2], + cairo_buff[j+1], + cairo_buff[j+0]); } } else { for (uint32_t i = 0, j = 0; i < pix_count; i++, j += 4) { - p_ctx->fbbuff.s[i] = to_16_color(cairo_buff[j+2], - cairo_buff[j+1], - cairo_buff[j+0]); + g_cairo_fb.rgb_buff.s[i] = to_16_color(cairo_buff[j+2], + cairo_buff[j+1], + cairo_buff[j+0]); } } break; case 24: cpp = 3; for (uint32_t i = 0, j = 0; i < (cpp * pix_count); i += 3, j += 4) { - p_ctx->fbbuff.c[i+0] = cairo_buff[j+0]; - p_ctx->fbbuff.c[i+1] = cairo_buff[j+1]; - p_ctx->fbbuff.c[i+2] = cairo_buff[j+2]; + g_cairo_fb.rgb_buff.c[i+0] = cairo_buff[j+0]; + g_cairo_fb.rgb_buff.c[i+1] = cairo_buff[j+1]; + g_cairo_fb.rgb_buff.c[i+2] = cairo_buff[j+2]; } break; case 32: cpp = 4; for (uint32_t i = 0, j = 0; i < pix_count; i++, j += 4) { - p_ctx->fbbuff.i[i] = ((cairo_buff[j+2] << 16)) | - (cairo_buff[j+1] << 8) | - (cairo_buff[j+0]); + g_cairo_fb.rgb_buff.i[i] = ((cairo_buff[j+2] << 16)) | + (cairo_buff[j+1] << 8) | + (cairo_buff[j+0]); } break; } @@ -335,7 +310,7 @@ void render_cairo_surface_to_fb(scenic_cairo_ctx_t* p_ctx) } uint8_t* p_fb = fb + (y_offs * scr_xs + x_offs) * cpp; - uint8_t* p_image = p_ctx->fbbuff.c; + uint8_t* p_image = g_cairo_fb.rgb_buff.c; for (uint32_t i = 0; i < yc; i++, p_fb += scr_xs * cpp, p_image += pic_xs * cpp) memcpy(p_fb, p_image, xc * cpp); @@ -351,50 +326,5 @@ void device_end_render(driver_data_t* p_data) scenic_cairo_ctx_t* p_ctx = (scenic_cairo_ctx_t*)p_data->v_ctx; render_cairo_surface_to_fb(p_ctx); - - cairo_destroy(p_ctx->cr); -} - -void device_clear_color(float red, float green, float blue, float alpha) -{ - scenic_cairo_ctx_t* p_ctx = (scenic_cairo_ctx_t*)g_device_info.v_ctx; - p_ctx->clear_color = (color_rgba_t){ - .red = red, - .green = green, - .blue = blue, - .alpha = alpha - }; } -char* device_gl_error() -{ - return NULL; -} - -void pattern_stack_push(scenic_cairo_ctx_t* p_ctx) -{ - pattern_stack_t* ptr = (pattern_stack_t*)malloc(sizeof(pattern_stack_t)); - - ptr->pattern = p_ctx->pattern; - - if (!p_ctx->pattern_stack_head) { - ptr->next = NULL; - p_ctx->pattern_stack_head = ptr; - } else { - ptr->next = p_ctx->pattern_stack_head; - p_ctx->pattern_stack_head = ptr; - } -} - -void pattern_stack_pop(scenic_cairo_ctx_t* p_ctx) -{ - pattern_stack_t* ptr = p_ctx->pattern_stack_head; - - if (!ptr) { - log_error("pattern stack underflow"); - } else { - p_ctx->pattern = ptr->pattern; - p_ctx->pattern_stack_head = p_ctx->pattern_stack_head->next; - free(ptr); - } -} diff --git a/c_src/device/cairo/cairo_gtk.c b/c_src/device/cairo/cairo_gtk.c new file mode 100644 index 0000000..1ff0557 --- /dev/null +++ b/c_src/device/cairo/cairo_gtk.c @@ -0,0 +1,213 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "cairo_ctx.h" +#include "comms.h" +#include "device.h" +#include "fontstash.h" +#include "script_ops.h" + +typedef struct { + GThread* main; + GtkWidget* window; + GMutex render_mutex; + float last_x; + float last_y; +} cairo_gtk_t; + +cairo_gtk_t g_cairo_gtk; + +extern device_info_t g_device_info; +extern device_opts_t g_opts; + +static gpointer cairo_gtk_main(gpointer user_data) +{ + gtk_widget_show_all((GtkWidget*)g_cairo_gtk.window); + gtk_main(); + + return NULL; +} + +static gboolean on_draw(GtkWidget* widget, + cairo_t* cr, + gpointer data) +{ + // Don't allow scenic to create a new rendering + // on p_ctx->surface while gtk is drawing + g_mutex_lock (&g_cairo_gtk.render_mutex); + + scenic_cairo_ctx_t* p_ctx = (scenic_cairo_ctx_t*)data; + cairo_set_source_surface(cr, p_ctx->surface, 0, 0); + cairo_paint(cr); + + g_mutex_unlock(&g_cairo_gtk.render_mutex); + + return TRUE; +} + +static gboolean on_delete_event(GtkWidget* widget, + GdkEvent* event, + gpointer data) +{ + send_close(0); + return TRUE; +} + +static gboolean on_motion_event(GtkWidget* widget, + GdkEventMotion* event, + gpointer data) +{ + if ((g_cairo_gtk.last_x != event->x) && (g_cairo_gtk.last_y != event->y)) { + send_cursor_pos(event->x, event->y); + g_cairo_gtk.last_x = event->x; + g_cairo_gtk.last_y = event->y; + } + + return TRUE; +} + +static gboolean on_button_event(GtkWidget* widget, + GdkEventButton* event, + gpointer data) +{ + int action; + switch (event->type) { + case GDK_BUTTON_PRESS: + action = 1; + break; + case GDK_BUTTON_RELEASE: + action = 0; + break; + default: + return FALSE; + } + + send_mouse_button(KEYMAP_GDK, + event->button, + action, + event->state, + event->x, event->y); + + return TRUE; +} + +static gboolean on_key_event(GtkWidget* widget, + GdkEventKey* event, + gpointer data) +{ + int action = (event->type == GDK_KEY_PRESS) ? 1 : 0; + uint32_t unicode = gdk_keyval_to_unicode(event->keyval); + send_key(KEYMAP_GDK, event->keyval, event->hardware_keycode, action, event->state); + if (!(event->keyval & 0xF000) && event->type == GDK_KEY_PRESS) { + send_codepoint(KEYMAP_GDK, unicode, event->state); + } + return TRUE; +} + +int device_init(const device_opts_t* p_opts, + device_info_t* p_info, + driver_data_t* p_data) +{ + if (g_opts.debug_mode) { + log_info("cairo %s", __func__); + } + + scenic_cairo_ctx_t* p_ctx = scenic_cairo_init(p_opts, p_info); + if (!p_ctx) { + log_error("cairo %s failed", __func__); + return -1; + } + + g_cairo_gtk.last_x = -1.0f; + g_cairo_gtk.last_y = -1.0f; + + gtk_init(NULL, NULL); + + g_cairo_gtk.window = gtk_window_new(GTK_WINDOW_TOPLEVEL); + gtk_window_set_title(GTK_WINDOW(g_cairo_gtk.window), p_opts->title); + gtk_window_set_default_size(GTK_WINDOW(g_cairo_gtk.window), p_info->width, p_info->height); + gtk_window_set_resizable(GTK_WINDOW(g_cairo_gtk.window), FALSE); + g_signal_connect(G_OBJECT(g_cairo_gtk.window), "delete-event", G_CALLBACK(on_delete_event), NULL); + + gtk_widget_set_events(g_cairo_gtk.window, + GDK_POINTER_MOTION_MASK | + GDK_BUTTON_PRESS_MASK | + GDK_BUTTON_RELEASE_MASK | + GDK_KEY_PRESS_MASK | + GDK_KEY_RELEASE_MASK); + + g_signal_connect(G_OBJECT(g_cairo_gtk.window), "motion-notify-event", G_CALLBACK(on_motion_event), NULL); + g_signal_connect(G_OBJECT(g_cairo_gtk.window), "button-press-event", G_CALLBACK(on_button_event), NULL); + g_signal_connect(G_OBJECT(g_cairo_gtk.window), "button-release-event", G_CALLBACK(on_button_event), NULL); + g_signal_connect(G_OBJECT(g_cairo_gtk.window), "key-press-event", G_CALLBACK(on_key_event), NULL); + g_signal_connect(G_OBJECT(g_cairo_gtk.window), "key-release-event", G_CALLBACK(on_key_event), NULL); + + GtkDrawingArea* drawing_area = (GtkDrawingArea*)gtk_drawing_area_new(); + gtk_container_add(GTK_CONTAINER(g_cairo_gtk.window), (GtkWidget*)drawing_area); + g_signal_connect((GtkWidget*)drawing_area, "draw", G_CALLBACK(on_draw), p_ctx); + + g_cairo_gtk.main = g_thread_new("gtk_main", cairo_gtk_main, NULL); + + return 0; +} + +int device_close(device_info_t* p_info) +{ + if (g_opts.debug_mode) { + log_info("cairo %s", __func__); + } + + scenic_cairo_ctx_t* p_ctx = (scenic_cairo_ctx_t*)p_info->v_ctx; + gtk_main_quit(); + g_thread_join(g_cairo_gtk.main); + scenic_cairo_fini(p_ctx); +} + +void device_poll() +{ +} + +void device_begin_render(driver_data_t* p_data) +{ + if (g_opts.debug_mode) { + log_info("cairo %s", __func__); + } + + scenic_cairo_ctx_t* p_ctx = (scenic_cairo_ctx_t*)p_data->v_ctx; + + // Don't allow gtk to draw while p_ctx->surface is being rendered + g_mutex_lock(&g_cairo_gtk.render_mutex); + + cairo_destroy(p_ctx->cr); + p_ctx->cr = cairo_create(p_ctx->surface); + + // Paint surface to clear color + cairo_set_source_rgba(p_ctx->cr, + p_ctx->clear_color.red, + p_ctx->clear_color.green, + p_ctx->clear_color.blue, + p_ctx->clear_color.alpha); + cairo_paint(p_ctx->cr); +} + +void device_end_render(driver_data_t* p_data) +{ + if (g_opts.debug_mode) { + log_info("cairo %s", __func__); + } + + scenic_cairo_ctx_t* p_ctx = (scenic_cairo_ctx_t*)p_data->v_ctx; + cairo_surface_flush(p_ctx->surface); + g_mutex_unlock(&g_cairo_gtk.render_mutex); + + g_idle_add((GSourceFunc)gtk_widget_queue_draw, (void*)g_cairo_gtk.window); +} diff --git a/c_src/device/nvg/glfw.c b/c_src/device/nvg/glfw.c index 6c24955..0f3ca28 100644 --- a/c_src/device/nvg/glfw.c +++ b/c_src/device/nvg/glfw.c @@ -94,13 +94,13 @@ void reshape_window(GLFWwindow* window, int w, int h) //--------------------------------------------------------- void key_callback(GLFWwindow* window, int key, int scancode, int action, int mods) { - send_key(key, scancode, action, mods); + send_key(KEYMAP_GLFW, key, scancode, action, mods); } //--------------------------------------------------------- void charmods_callback(GLFWwindow* window, unsigned int codepoint, int mods) { - send_codepoint(codepoint, mods); + send_codepoint(KEYMAP_GLFW, codepoint, mods); } //--------------------------------------------------------- @@ -123,7 +123,7 @@ void mouse_button_callback(GLFWwindow* window, int button, int action, int mods) { double x, y; glfwGetCursorPos(window, &x, &y); - send_mouse_button(button, action, mods, x, y); + send_mouse_button(KEYMAP_GLFW, button, action, mods, x, y); } //--------------------------------------------------------- diff --git a/c_src/scenic/comms.c b/c_src/scenic/comms.c index 6f70f15..7c698bd 100644 --- a/c_src/scenic/comms.c +++ b/c_src/scenic/comms.c @@ -241,15 +241,16 @@ void send_reshape( int window_width, int window_height ) PACK(typedef struct msg_key_t { uint32_t msg_id; + uint32_t keymap; uint32_t key; uint32_t scancode; uint32_t action; uint32_t mods; }) msg_key_t; -void send_key(int key, int scancode, int action, int mods) +void send_key(keymap_t keymap, int key, int scancode, int action, int mods) { - msg_key_t msg = { MSG_OUT_KEY, key, scancode, action, mods }; + msg_key_t msg = { MSG_OUT_KEY, keymap, key, scancode, action, mods }; write_cmd((byte*) &msg, sizeof(msg_key_t)); } @@ -257,13 +258,14 @@ void send_key(int key, int scancode, int action, int mods) PACK(typedef struct msg_codepoint_t { uint32_t msg_id; + uint32_t keymap; uint32_t codepoint; uint32_t mods; }) msg_codepoint_t; -void send_codepoint(unsigned int codepoint, int mods) +void send_codepoint(keymap_t keymap, unsigned int codepoint, int mods) { - msg_codepoint_t msg = { MSG_OUT_CODEPOINT, codepoint, mods }; + msg_codepoint_t msg = { MSG_OUT_CODEPOINT, keymap, codepoint, mods }; write_cmd((byte*) &msg, sizeof(msg_codepoint_t)); } @@ -285,6 +287,7 @@ void send_cursor_pos(float xpos, float ypos) PACK(typedef struct msg_mouse_button_t { uint32_t msg_id; + uint32_t keymap; uint32_t button; uint32_t action; uint32_t mods; @@ -292,10 +295,11 @@ PACK(typedef struct msg_mouse_button_t float ypos; }) msg_mouse_button_t; -void send_mouse_button(int button, int action, int mods, float xpos, float ypos) +void send_mouse_button(keymap_t keymap, int button, int action, int mods, float xpos, float ypos) { msg_mouse_button_t msg = { MSG_OUT_MOUSE_BUTTON, + keymap, button, action, mods, diff --git a/c_src/scenic/comms.h b/c_src/scenic/comms.h index 0f705aa..d2cca80 100644 --- a/c_src/scenic/comms.h +++ b/c_src/scenic/comms.h @@ -91,6 +91,13 @@ typedef enum { _MSG_OUT_SIZE_ = 0XFFFFFFFF, } msg_out_t; +typedef enum { + KEYMAP_GLFW = 0x01, + KEYMAP_GDK = 0x02, + + _KEYMAP_SIZE_ = 0xFFFFFFFF, +} keymap_t; + int read_exact(byte* buf, int len); int write_exact(byte* buf, int len); int read_msg_length(struct timeval * ptv); @@ -127,11 +134,11 @@ void render(driver_data_t* p_data); void send_image_miss(unsigned int img_id); -void send_reshape( int window_width, int window_height ); -void send_key(int key, int scancode, int action, int mods); -void send_codepoint(unsigned int codepoint, int mods); +void send_reshape(int window_width, int window_height); +void send_key(keymap_t keymap, int key, int scancode, int action, int mods); +void send_codepoint(keymap_t keymap, unsigned int codepoint, int mods); void send_cursor_pos(float xpos, float ypos); -void send_mouse_button(int button, int action, int mods, float xpos, +void send_mouse_button(keymap_t keymap, int button, int action, int mods, float xpos, float ypos); void send_scroll(float xoffset, float yoffset, float xpos, float ypos); void send_cursor_enter(int entered, float xpos, float ypos); diff --git a/c_src/scenic/unix_comms.c b/c_src/scenic/unix_comms.c index b21047b..54d59d4 100644 --- a/c_src/scenic/unix_comms.c +++ b/c_src/scenic/unix_comms.c @@ -26,9 +26,9 @@ int write_exact(byte* buf, int len) do { - if ((i = write(1, buf + wrote, len - wrote)) <= 0) + if ((i = write(1, buf + wrote, len - wrote)) <= 0) return (i); - wrote += i; + wrote += i; } while (wrote < len); return (len); diff --git a/lib/from_port.ex b/lib/from_port.ex index 51c8a3c..7f75258 100644 --- a/lib/from_port.ex +++ b/lib/from_port.ex @@ -13,6 +13,8 @@ defmodule Scenic.Driver.Local.FromPort do require Logger + import Bitwise + import Scenic.Driver, only: [ assign: 3, @@ -49,6 +51,9 @@ defmodule Scenic.Driver.Local.FromPort do # @msg_font_miss 0x22 # @msg_texture_miss 0x23 + @keymap_glfw 0x01 + @keymap_gdk 0x02 + # ============================================================================ @doc false @@ -186,6 +191,7 @@ defmodule Scenic.Driver.Local.FromPort do def handle_port_message( << @msg_key_id::unsigned-integer-size(32)-native, + keymap::unsigned-integer-native-size(32), key::unsigned-integer-native-size(32), _scancode::unsigned-integer-native-size(32), action::integer-native-size(32), @@ -193,8 +199,19 @@ defmodule Scenic.Driver.Local.FromPort do >>, driver ) do - key = key_to_atom(key) - mods = prep_mods(mods) + key = + case keymap do + @keymap_glfw -> glfw_key_to_atom(key) + @keymap_gdk -> gdk_key_to_atom(key) + _ -> :key_unknown + end + + mods = + case keymap do + @keymap_glfw -> glfw_prep_mods(mods) + @keymap_gdk -> gdk_prep_mods(mods) + _ -> [] + end send_input(driver, {:key, {key, action, mods}}) @@ -205,13 +222,21 @@ defmodule Scenic.Driver.Local.FromPort do def handle_port_message( << @msg_char_id::unsigned-integer-size(32)-native, + keymap::unsigned-integer-native-size(32), codepoint::unsigned-integer-native-size(32), mods::unsigned-integer-native-size(32) >>, driver ) do codepoint = codepoint_to_char(codepoint) - mods = prep_mods(mods) + + mods = + case keymap do + @keymap_glfw -> glfw_prep_mods(mods) + @keymap_gdk -> gdk_prep_mods(mods) + _ -> [] + end + send_input(driver, {:codepoint, {codepoint, mods}}) {:noreply, driver} @@ -236,6 +261,7 @@ defmodule Scenic.Driver.Local.FromPort do def handle_port_message( << @msg_mouse_button_id::unsigned-integer-size(32)-native, + keymap::unsigned-integer-native-size(32), button::unsigned-integer-native-size(32), action::unsigned-integer-native-size(32), mods::unsigned-integer-native-size(32), @@ -245,8 +271,20 @@ defmodule Scenic.Driver.Local.FromPort do driver ) do # action = action_to_atom(action) - button = button_to_atom(button) - mods = prep_mods(mods) + button = + case keymap do + @keymap_glfw -> glfw_button_to_atom(button) + @keymap_gdk -> gdk_button_to_atom(button) + _ -> :unknown + end + + mods = + case keymap do + @keymap_glfw -> glfw_prep_mods(mods) + @keymap_gdk -> gdk_prep_mods(mods) + _ -> [] + end + pos = scene_coords({x, y}, driver) send_input(driver, {:cursor_button, {button, action, mods, pos}}) {:noreply, driver} @@ -309,7 +347,170 @@ defmodule Scenic.Driver.Local.FromPort do end # ============================================================================ - # utilities to translate Glfw input to standardized input + # utilities to translate GDK input to standardized input + + @gdk_button_atoms %{ + 1 => :btn_left, + 2 => :btn_middle, + 3 => :btn_right + } + defp gdk_button_to_atom(code), do: Map.get(@gdk_button_atoms, code, :unknown) + + # ============================================================================ + # keyboard input helpers + # these are for reading the keyboard directly. If you are trying to do text input + # use the text/char helpers instead + + # -------------------------------------------------------- + + # key codes use the standards defined by gdk, which generates them with + # https://gitlab.gnome.org/GNOME/gtk/-/blob/gtk-3-24/gdk/gdkkeysyms-update.pl + # which sources it's values from + # https://cgit.freedesktop.org/xorg/proto/x11proto/plain/keysymdef.h + + # -------------------------------------------------------- + @gdk_key_atoms %{ + 32 => :key_space, + 39 => :key_apostrophe, + 44 => :key_comma, + 45 => :key_minus, + 46 => :key_dot, + 47 => :key_slash, + 48 => :key_0, + 49 => :key_1, + 50 => :key_2, + 51 => :key_3, + 52 => :key_4, + 53 => :key_5, + 54 => :key_6, + 55 => :key_7, + 56 => :key_8, + 57 => :key_9, + 59 => :key_semicolon, + 61 => :key_equal, + 65 => :key_a, + 66 => :key_b, + 67 => :key_c, + 68 => :key_d, + 69 => :key_e, + 70 => :key_f, + 71 => :key_g, + 72 => :key_h, + 73 => :key_i, + 74 => :key_j, + 75 => :key_k, + 76 => :key_l, + 77 => :key_m, + 78 => :key_n, + 79 => :key_o, + 80 => :key_p, + 81 => :key_q, + 82 => :key_r, + 83 => :key_s, + 84 => :key_t, + 85 => :key_u, + 86 => :key_v, + 87 => :key_w, + 88 => :key_x, + 89 => :key_y, + 90 => :key_z, + 91 => :key_leftbrace, + 92 => :key_backslash, + 93 => :key_rightbrace, + 96 => :key_grave, + 0xFF1B => :key_esc, + 0xFF0D => :key_enter, + 0xFF09 => :key_tab, + 0xFF08 => :key_backspace, + 0xFF63 => :key_insert, + 0xFFFF => :key_delete, + 0xFF53 => :key_right, + 0xFF51 => :key_left, + 0xFF54 => :key_down, + 0xFF52 => :key_up, + 0xFF55 => :key_pageup, + 0xFF56 => :key_pagedown, + 0xFF50 => :key_home, + 0xFF57 => :key_end, + 0xFFE5 => :key_capslock, + 0xFF14 => :key_scrolllock, + 0xFF7F => :key_numlock, + 0xFF61 => :key_screen, + 0xFF13 => :key_pause, + 0xFFBE => :key_f1, + 0xFFBF => :key_f2, + 0xFFC0 => :key_f3, + 0xFFC1 => :key_f4, + 0xFFC2 => :key_f5, + 0xFFC3 => :key_f6, + 0xFFC4 => :key_f7, + 0xFFC5 => :key_f8, + 0xFFC6 => :key_f9, + 0xFFC7 => :key_f10, + 0xFFC8 => :key_f11, + 0xFFC9 => :key_f12, + 0xFFCA => :key_f13, + 0xFFCB => :key_f14, + 0xFFCC => :key_f15, + 0xFFCD => :key_f16, + 0xFFCE => :key_f17, + 0xFFCF => :key_f18, + 0xFFD0 => :key_f19, + 0xFFD1 => :key_f20, + 0xFFD2 => :key_f21, + 0xFFD3 => :key_f22, + 0xFFD4 => :key_f23, + 0xFFD5 => :key_f24, + 0xFFD6 => :key_f25, + 0xFFB0 => :key_kp0, + 0xFFB1 => :key_kp1, + 0xFFB2 => :key_kp2, + 0xFFB3 => :key_kp3, + 0xFFB4 => :key_kp4, + 0xFFB5 => :key_kp5, + 0xFFB6 => :key_kp6, + 0xFFB7 => :key_kp7, + 0xFFB8 => :key_kp8, + 0xFFB9 => :key_kp9, + 0xFFAE => :key_kpdot, + 0xFFAF => :key_kpslash, + 0xFFAA => :key_kpasterisk, + 0xFFAD => :key_kpminus, + 0xFFAB => :key_kpplus, + 0xFF8D => :key_kpenter, + 0xFFBD => :key_kpequal, + 0xFFE1 => :key_leftshift, + 0xFFE3 => :key_leftctrl, + 0xFFE9 => :key_leftalt, + # 0xffeb => "left_super" + + 0xFFE2 => :key_rightshift, + 0xFFE4 => :key_rightctrl, + 0xFFEA => :key_rightalt, + # 0xffec => "right_super" + + 0xFF67 => :key_menu + } + defp gdk_key_to_atom(code), do: Map.get(@gdk_key_atoms, code, :key_unknown) + + # -------------------------------------------------------- + + @gdk_mod_shift 1 <<< 0 + @gdk_mod_ctrl 1 <<< 2 + @gdk_mod_alt 1 <<< 3 + @gdk_mod_super 1 <<< 26 + @gdk_mod_caps_lock 1 <<< 1 + # @gdk_mod_num_lock 0x020 + defp gdk_prep_mods(mods) do + [] + |> add_if_masked(mods, @gdk_mod_shift, :shift) + |> add_if_masked(mods, @gdk_mod_ctrl, :ctrl) + |> add_if_masked(mods, @gdk_mod_alt, :alt) + |> add_if_masked(mods, @gdk_mod_super, :meta) + |> add_if_masked(mods, @gdk_mod_caps_lock, :caps_lock) + + # |> add_if_masked(mods, @gdk_mod_num_lock, :num_lock) + end # ============================================================================ # utilities to translate Glfw input to standardized input @@ -319,12 +520,9 @@ defmodule Scenic.Driver.Local.FromPort do 1 => :btn_right, 2 => :btn_middle } - defp button_to_atom(code), do: Map.get(@glfw_button_atoms, code, :unknown) + defp glfw_button_to_atom(code), do: Map.get(@glfw_button_atoms, code, :unknown) - # ============================================================================ - # keyboard input helpers - # these are for reading the keyboard directly. If you are trying to do text input - # use the text/char helpers instead + # -------------------------------------------------------- # key codes use the standards defined by Glfw # http://www.Glfw.org/docs/latest/group__keys.html @@ -452,7 +650,7 @@ defmodule Scenic.Driver.Local.FromPort do 348 => :key_menu } - defp key_to_atom(code), do: Map.get(@glfw_key_atoms, code, :key_unknown) + defp glfw_key_to_atom(code), do: Map.get(@glfw_key_atoms, code, :key_unknown) # -------------------------------------------------------- @glfw_mod_shift 0x001 @@ -461,7 +659,7 @@ defmodule Scenic.Driver.Local.FromPort do @glfw_mod_super 0x008 @glfw_mod_caps_lock 0x010 @glfw_mod_num_lock 0x020 - defp prep_mods(mods) do + defp glfw_prep_mods(mods) do [] |> add_if_masked(mods, @glfw_mod_shift, :shift) |> add_if_masked(mods, @glfw_mod_ctrl, :ctrl)