From e9c46a04f0bdbb8c799737a1e452961f6d4d3f81 Mon Sep 17 00:00:00 2001 From: Allen Webster Date: Tue, 3 Mar 2026 17:10:16 -0800 Subject: [PATCH] [wayland_gtk_egl] setup and start building a gtk window implementation helper layer --- build.sh | 2 +- digesting_libdecor.c | 1 - wayland_egl.c | 4 +- wayland_egl.h | 3 +- wayland_gtk_egl.c | 1511 ++++++++++++++++++++++++++++++++++++++++++ wayland_gtk_egl.h | 220 ++++++ 6 files changed, 1735 insertions(+), 6 deletions(-) create mode 100755 wayland_gtk_egl.c create mode 100644 wayland_gtk_egl.h diff --git a/build.sh b/build.sh index ba08184..281dc84 100755 --- a/build.sh +++ b/build.sh @@ -1,2 +1,2 @@ #!/bin/bash -./wayland_egl.c \ No newline at end of file +./wayland_gtk_egl.c \ No newline at end of file diff --git a/digesting_libdecor.c b/digesting_libdecor.c index 4a258a0..4de8cfe 100755 --- a/digesting_libdecor.c +++ b/digesting_libdecor.c @@ -51,7 +51,6 @@ exit 0 #include #include - #include #include "xdg-shell-client-protocol.h" diff --git a/wayland_egl.c b/wayland_egl.c index ced3329..18efbe9 100755 --- a/wayland_egl.c +++ b/wayland_egl.c @@ -236,7 +236,7 @@ xdg_toplevel_configure(void *udata, struct xdg_toplevel *xdg_toplevel, ctx.config_staged.dim[0] = ctx.config.dim[0]; ctx.config_staged.dim[1] = ctx.config.dim[1]; } - ctx.config_staged.flags = window_flags_from_states_array(states); + ctx.config_staged.flags = csd_window_flags_from_states_array(states); } static void @@ -683,7 +683,7 @@ csd_impl_calculate_frame(void){ /* wayland helpers */ static WindowFlags -window_flags_from_states_array(struct wl_array *states){ +csd_window_flags_from_states_array(struct wl_array *states){ WindowFlags flags = 0; uint32_t *p; wl_array_for_each(p, states){ diff --git a/wayland_egl.h b/wayland_egl.h index df5cb69..8abfbf4 100644 --- a/wayland_egl.h +++ b/wayland_egl.h @@ -141,10 +141,9 @@ typedef struct Ctx{ static CSD_Frame csd_impl_calculate_frame(void); - /* wayland helpers */ -static WindowFlags window_flags_from_states_array(struct wl_array *states); +static WindowFlags csd_window_flags_from_states_array(struct wl_array *states); /* os */ diff --git a/wayland_gtk_egl.c b/wayland_gtk_egl.c new file mode 100755 index 0000000..b45ca41 --- /dev/null +++ b/wayland_gtk_egl.c @@ -0,0 +1,1511 @@ +#if 0 +libdecor_path="/home/mr4th/mr4th/libdecor" +root_path="$PWD" +gtk_flags="$(pkg-config --cflags --libs gtk+-3.0)" +dbus_flags="$(pkg-config --cflags --libs dbus-1)" +my_flags="-Iwayland -I$libdecor_path/src -I$libdecor_path/src/plugins -I$libdecor_path/build" +my_flags+=" -lwayland-client -lwayland-cursor -lwayland-egl -lEGL -lm" +mkdir -p build +clang -o build/demo -g $root_path/wayland_gtk_egl.c $gtk_flags $dbus_flags $my_flags +exit 0 +#endif + +#define _GNU_SOURCE + +#include +#include +#include + +#include +/*~ NOTE: wayland-egl.h *before* EGL/ */ +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include +#include + +#include "xdg-shell-client-protocol.h" +#include "xdg-decoration-client-protocol.h" + +#include "xdg-shell-client-protocol.c" +#include "xdg-decoration-client-protocol.c" + +#include "wayland_gtk_egl.h" + +// X(N:name,R:return,P:params) +#define GL_FUNCS_XLIST(X)\ +X(glDrawBuffer, void, (GLenum buf)) \ +X(glViewport, void, (GLint x, GLint y, GLsizei w, GLsizei h)) \ +X(glClear, void, (GLbitfield mask)) \ +X(glClearColor, void, (GLfloat r, GLfloat g, GLfloat b, GLfloat a)) + +#define X(N,R,P) R (*N)P = 0; +GL_FUNCS_XLIST(X) +#undef X + +static Ctx ctx = {0}; + +static void +shm_format(void *udata, struct wl_shm *wl_shm, uint32_t format){ +} + +const struct wl_shm_listener shm_listener = { + shm_format +}; + +static void +xdg_wm_base_ping(void *udata, struct xdg_wm_base *xdg_wm_base, uint32_t serial){ + xdg_wm_base_pong(xdg_wm_base, serial); +} + +const struct xdg_wm_base_listener xdg_wm_base_listener = { + xdg_wm_base_ping +}; + +static void +pointer_enter(void *udata, struct wl_pointer *wl_pointer, uint32_t serial, + struct wl_surface *surface, wl_fixed_t fx, wl_fixed_t fy){ + int32_t x = wl_fixed_to_int(fx); + int32_t y = wl_fixed_to_int(fy); + if (surface == ctx.main_wl_surface){ + printf("pointer_enter (main) %d,%d\n", x, y); + } + else if (surface == ctx.gtk_window.titlebar.wl_surface){ + x += ctx.gtk_window.titlebar.p[0]; + y += ctx.gtk_window.titlebar.p[1]; + printf("pointer_enter (titlebar) %d,%d\n", x, y); + } + else if (surface == ctx.gtk_window.shadow.wl_surface){ + x += ctx.gtk_window.shadow.p[0]; + y += ctx.gtk_window.shadow.p[1]; + printf("pointer_enter (shadow) %d,%d\n", x, y); + } + else{ + printf("pointer_enter (unidentified) %d,%d\n", x, y); + } + ctx.serial = serial; + ctx.hover_surface = surface; +} + +static void +pointer_leave(void *udata, struct wl_pointer *wl_pointer, uint32_t serial, + struct wl_surface *surface){ + if (surface == ctx.main_wl_surface){ + printf("pointer_leave (main)\n"); + } + else if (surface == ctx.gtk_window.titlebar.wl_surface){ + printf("pointer_leave (titlebar)\n"); + } + else if (surface == ctx.gtk_window.shadow.wl_surface){ + printf("pointer_leave (shadow)\n"); + } + else{ + printf("pointer_leave (unidentified)\n"); + } + ctx.hover_surface = 0; +} + +static void +pointer_motion(void *udata, struct wl_pointer *wl_pointer, uint32_t time, + wl_fixed_t fx, wl_fixed_t fy){ + int32_t x = wl_fixed_to_int(fx); + int32_t y = wl_fixed_to_int(fy); + if (ctx.hover_surface == ctx.main_wl_surface){ + printf("pointer_motion (main) %d,%d\n", x, y); + } + else if (ctx.hover_surface == ctx.gtk_window.titlebar.wl_surface){ + x += ctx.gtk_window.titlebar.p[0]; + y += ctx.gtk_window.titlebar.p[1]; + printf("pointer_motion (titlebar) %d,%d\n", x, y); + } + else if (ctx.hover_surface == ctx.gtk_window.shadow.wl_surface){ + x += ctx.gtk_window.shadow.p[0]; + y += ctx.gtk_window.shadow.p[1]; + printf("pointer_motion (shadow) %d,%d\n", x, y); + } + else{ + printf("pointer_motion (unidentified) %d,%d\n", x, y); + } +} + +static void +pointer_button(void *udata, struct wl_pointer *wl_pointer, uint32_t serial, + uint32_t time, uint32_t button, uint32_t state){ + char *state_str = (state == 1?"press":"release"); + if (button == BTN_LEFT){ + printf("pointer_button (left) %s\n", state_str); + } + else if (button == BTN_RIGHT){ + printf("pointer_button (right) %s\n", state_str); + } + else if (button == BTN_MIDDLE){ + printf("pointer_button (middle) %s\n", state_str); + } + else{ + printf("pointer_button (unidentified) %s\n", state_str); + } + if (state){ + ctx.gtk_window.button = button; + } + ctx.serial = serial; +} + +static void +pointer_axis(void *udata, struct wl_pointer *wl_pointer, + uint32_t time, uint32_t axis, wl_fixed_t fv){ + int v = wl_fixed_to_int(fv); + printf("pointer_axis %u,%d\n", axis, v); +} + +const struct wl_pointer_listener pointer_listener = { + pointer_enter, + pointer_leave, + pointer_motion, + pointer_button, + pointer_axis +}; + +static void +seat_capabilities(void *udata, struct wl_seat *wl_seat, uint32_t capabilities){ + if ((capabilities & WL_SEAT_CAPABILITY_POINTER) && ctx.wl_pointer == 0){ + ctx.wl_pointer = wl_seat_get_pointer(ctx.wl_seat); + wl_pointer_add_listener(ctx.wl_pointer, &pointer_listener, 0); + } + else if (!(capabilities & WL_SEAT_CAPABILITY_POINTER) && ctx.wl_pointer != 0){ + wl_pointer_release(ctx.wl_pointer); + ctx.wl_pointer = 0; + } +} + +static void +seat_name(void *udata, struct wl_seat *wl_seat, const char *name){ +} + +const struct wl_seat_listener seat_listener = { + seat_capabilities, + seat_name +}; + +static void +registry_global(void *udata, struct wl_registry *wl_registry, + uint32_t name, const char *interface, + uint32_t version){ + if (strcmp(interface, "wl_compositor") == 0){ + ctx.wl_compositor = (struct wl_compositor*) + wl_registry_bind(wl_registry, name, &wl_compositor_interface, Min(version, 4)); + } + else if (strcmp(interface, "wl_subcompositor") == 0){ + ctx.wl_subcompositor = wl_registry_bind(ctx.wl_registry, name, &wl_subcompositor_interface, 1); + } + else if (strcmp(interface, "wl_shm") == 0){ + ctx.wl_shm = wl_registry_bind(ctx.wl_registry, name, &wl_shm_interface, 1); + wl_shm_add_listener(ctx.wl_shm, &shm_listener, 0); + } + else if (!strcmp(interface, xdg_wm_base_interface.name)){ + ctx.xdg_wm_base = wl_registry_bind(ctx.wl_registry, name, &xdg_wm_base_interface, Min(version, 2)); + xdg_wm_base_add_listener(ctx.xdg_wm_base, &xdg_wm_base_listener, 0); + } + else if (!strcmp(interface, zxdg_decoration_manager_v1_interface.name)){ + ctx.zxdg_decoration_manager = wl_registry_bind(wl_registry, name, &zxdg_decoration_manager_v1_interface, Min(version, 2)); + } + else if (strcmp(interface, "wl_seat") == 0){ + ctx.wl_seat = wl_registry_bind(ctx.wl_registry, name, &wl_seat_interface, 3); + wl_seat_add_listener(ctx.wl_seat, &seat_listener, 0); + ctx.cursor_surface = wl_compositor_create_surface(ctx.wl_compositor); + } +} + +static void +registry_global_remove(void *udata, struct wl_registry *registry, + uint32_t name){ +} + +const struct wl_registry_listener registry_listener = { + registry_global, + registry_global_remove, +}; + +static void +xdg_surface_configure(void *udata, struct xdg_surface *xdg_surface, + uint32_t serial){ + ctx.config_staged.serial = serial; +} + +const struct xdg_surface_listener xdg_surface_listener = { xdg_surface_configure, }; + +static void +xdg_toplevel_configure(void *udata, struct xdg_toplevel *xdg_toplevel, + int32_t width, int32_t height, struct wl_array *states){ + if (width != 0 && height != 0){ + ctx.config_staged.dim[0] = width; + ctx.config_staged.dim[1] = height; + } + else{ + ctx.config_staged.dim[0] = ctx.config.dim[0]; + ctx.config_staged.dim[1] = ctx.config.dim[1]; + } + ctx.config_staged.flags = csd_window_flags_from_states_array(states); +} + +static void +xdg_toplevel_close(void *udata, struct xdg_toplevel *xdg_toplevel){ + ctx.close_signal = 1; +} + +static void +xdg_toplevel_configure_bounds(void *udata, struct xdg_toplevel *xdg_toplevel, + int32_t w, int32_t h){ +} + +static void +xdg_toplevel_wm_capabilities(void *udata, struct xdg_toplevel *xdg_toplevel, + struct wl_array *capabilities){ +} + +const struct xdg_toplevel_listener xdg_toplevel_listener = { + xdg_toplevel_configure, + xdg_toplevel_close, + xdg_toplevel_configure_bounds, + xdg_toplevel_wm_capabilities, +}; + +static void +xdg_toplevel_decoration_configure(void *udata, struct zxdg_toplevel_decoration_v1 *toplevel, + uint32_t mode){ + ctx.config_staged.decoration_mode = mode; +} + +const struct zxdg_toplevel_decoration_v1_listener zxdg_toplevel_decoration_listener = { + xdg_toplevel_decoration_configure +}; + +int main(){ + /* desktop settings */ + ctx.color_scheme = ds_get_color_scheme(); + ctx.cursor_theme = ds_get_cursor_theme(); + + /* setup gtk */ + csd_gtk_init(); + + /* setup Wayland */ + { + ctx.wl_display = wl_display_connect(0); + ctx.wl_registry = wl_display_get_registry(ctx.wl_display); + wl_registry_add_listener(ctx.wl_registry, ®istry_listener, 0); + + wl_display_flush(ctx.wl_display); + wl_display_dispatch(ctx.wl_display); + + ctx.wl_cursor_theme = wl_cursor_theme_load(ctx.cursor_theme.name, + ctx.cursor_theme.size, + ctx.wl_shm); + +#define X(k,n) ctx.wl_cursors[k] = wl_cursor_theme_get_cursor(ctx.wl_cursor_theme, n); + X(CursorShape_Pointer, "left_ptr"); + X(CursorShape_Resize_Top, "top_side"); + X(CursorShape_Resize_Bottom, "bottom_side"); + X(CursorShape_Resize_Left, "left_side"); + X(CursorShape_Resize_Right, "right_side"); + X(CursorShape_Resize_TopLeft, "top_left_corner"); + X(CursorShape_Resize_BottomLeft, "bottom_left_corner"); + X(CursorShape_Resize_TopRight, "top_right_corner"); + X(CursorShape_Resize_BottomRight, "bottom_right_corner"); +#undef X + } + + /* setup EGL */ + { + ctx.egl_display = eglGetDisplay(ctx.wl_display); + + EGLint major = 0, minor = 0; + eglInitialize(ctx.egl_display, &major, &minor); + eglBindAPI(EGL_OPENGL_API); + + { + EGLint attr[] = { + EGL_CONTEXT_MAJOR_VERSION, 3, + EGL_CONTEXT_MINOR_VERSION, 3, + EGL_CONTEXT_OPENGL_PROFILE_MASK, EGL_CONTEXT_OPENGL_CORE_PROFILE_BIT, + EGL_NONE, + }; + ctx.egl_context = eglCreateContext(ctx.egl_display, EGL_NO_CONFIG_KHR, EGL_NO_CONTEXT, attr); + } + + eglMakeCurrent(ctx.egl_display, EGL_NO_SURFACE, EGL_NO_SURFACE, ctx.egl_context); + +#define X(N,R,P) N = (R(*)P)(eglGetProcAddress(#N)); + GL_FUNCS_XLIST(X) +#undef X + } + + /* create a window */ + { + /* window main surface */ + ctx.main_wl_surface = wl_compositor_create_surface(ctx.wl_compositor); + ctx.main_xdg_surface = xdg_wm_base_get_xdg_surface(ctx.xdg_wm_base, ctx.main_wl_surface); + xdg_surface_add_listener(ctx.main_xdg_surface, &xdg_surface_listener, 0); + ctx.main_xdg_toplevel = xdg_surface_get_toplevel(ctx.main_xdg_surface); + xdg_toplevel_add_listener(ctx.main_xdg_toplevel, &xdg_toplevel_listener, 0); + + ctx.control_flags = ~0; + + ctx.dim[0] = 640; + ctx.dim[1] = 480; + ctx.config_staged.decoration_mode = ZXDG_TOPLEVEL_DECORATION_V1_MODE_CLIENT_SIDE; + if (ctx.zxdg_decoration_manager != 0){ + ctx.main_zxdg_toplevel_decoration = zxdg_decoration_manager_v1_get_toplevel_decoration(ctx.zxdg_decoration_manager, ctx.main_xdg_toplevel); + zxdg_toplevel_decoration_v1_add_listener(ctx.main_zxdg_toplevel_decoration, &zxdg_toplevel_decoration_listener, 0); + } + + for (int k = 0; k < 2; k += 1){ + ctx.mmbox[k][0] = 0; + ctx.mmbox[k][1] = (1 << 30); + } + + xdg_toplevel_set_app_id(ctx.main_xdg_toplevel, "demo"); + xdg_toplevel_set_title(ctx.main_xdg_toplevel, "Example Window"); + wl_surface_commit(ctx.main_wl_surface); + + /* window subsurface */ + ctx.gtk_window.shadow = csd_subsurface_new(); + ctx.gtk_window.titlebar = csd_subsurface_new(); + + /* window gtk */ + csd_gtk_window_init(); + + /* window egl */ + ctx.main_wl_egl_window = wl_egl_window_create(ctx.main_wl_surface, + ctx.dim[0], ctx.dim[1]); + + EGLConfig configs[64]; + EGLint config_cap = sizeof(configs)/sizeof(*configs); + EGLint config_count = 0; + { + EGLint attributes[] = { + EGL_SURFACE_TYPE, EGL_WINDOW_BIT, + EGL_CONFORMANT, EGL_OPENGL_BIT, + EGL_RENDERABLE_TYPE, EGL_OPENGL_BIT, + EGL_COLOR_BUFFER_TYPE, EGL_RGB_BUFFER, + EGL_RED_SIZE, 8, + EGL_GREEN_SIZE, 8, + EGL_BLUE_SIZE, 8, + EGL_DEPTH_SIZE, 24, + EGL_STENCIL_SIZE, 8, + EGL_NONE, + }; + eglChooseConfig(ctx.egl_display, attributes, configs, config_cap, &config_count); + } + + { + EGLint attributes[] = { + EGL_RENDER_BUFFER, EGL_BACK_BUFFER, + EGL_NONE, + }; + for (EGLint i = 0; i < config_count; i += 1){ + ctx.main_egl_surface = eglCreateWindowSurface(ctx.egl_display, configs[i], ctx.main_wl_egl_window, attributes); + if (ctx.main_egl_surface != EGL_NO_SURFACE){ + break; + } + } + } + + eglMakeCurrent(ctx.egl_display, ctx.main_egl_surface, + ctx.main_egl_surface, ctx.egl_context); + + eglSwapInterval(ctx.egl_display, 1); + } + + /* main loop */ + int exit_loop = 0; + for (;!exit_loop;){ + /* poll for events */ + { + struct pollfd fds[1] = {0}; + + /* register fds[0] ~ wayland events */ + bool wayland_started_read = false; + bool wayland_did_read = false; + { + fds[0].fd = -1; + + wl_display_dispatch_pending(ctx.wl_display); + wayland_started_read = (wl_display_prepare_read(ctx.wl_display) != -1); + if (wayland_started_read){ + fds[0].fd = wl_display_get_fd(ctx.wl_display); + fds[0].events = POLLIN; + wl_display_flush(ctx.wl_display); + } + } + + /* poll and handle events */ + int ret = poll(fds, sizeof(fds)/sizeof(*fds), 0); + if (ret > 0){ + + /* handle fds[0] ~ wayland events */ + if (fds[0].revents & POLLIN){ + wayland_did_read = true; + wl_display_read_events(ctx.wl_display); + wl_display_dispatch_pending(ctx.wl_display); + } + } + + /* wayland event read cleanup */ + if (wayland_started_read && !wayland_did_read){ + wl_display_cancel_read(ctx.wl_display); + } + } + + /* apply config */ + if (ctx.config.serial != ctx.config_staged.serial){ + ctx.config = ctx.config_staged; + xdg_surface_ack_configure(ctx.main_xdg_surface, ctx.config.serial); + } + + int csd = (ctx.config.decoration_mode == + ZXDG_TOPLEVEL_DECORATION_V1_MODE_CLIENT_SIDE); + + /* window sizing */ + memset(&ctx.csd_frame, 0, sizeof ctx.csd_frame); + memset(ctx.csd_dim, 0, sizeof ctx.csd_dim); + if (csd){ + ctx.csd_frame = csd_gtk_calculate_frame(); + } + for (int i = 0; i < 2; i += 1){ + ctx.csd_dim[i] = ctx.csd_frame.border[i][0] + ctx.csd_frame.border[i][1]; + } + for (int i = 0; i < 2; i += 1){ + int32_t d = ctx.config.dim[i] - ctx.csd_dim[i]; + if (!ctx.handled_first_size){ + d = ctx.dim[i]; + ctx.config.dim[i] = d + ctx.csd_dim[i]; + } + d = ClampBot(d, ctx.mmbox[i][0]); + d = ClampTop(d, ctx.mmbox[i][1]); + d = ClampBot(d, ctx.csd_frame.minbox[i]); + ctx.dim[i] = d; + } + ctx.handled_first_size = 1; + + /* window size commit */ + if (!(ctx.control_flags & WindowControlFlag_Resize)){ + xdg_toplevel_set_min_size(ctx.main_xdg_toplevel, ctx.dim[0], ctx.dim[1]); + xdg_toplevel_set_max_size(ctx.main_xdg_toplevel, ctx.dim[0], ctx.dim[1]); + } + else{ + for (int i = 0; i < 2; i += 1){ + int32_t mw = ClampBot(ctx.mmbox[0][i], ctx.csd_frame.minbox[i]) + ctx.csd_dim[0]; + int32_t mh = ctx.mmbox[1][i] + ctx.csd_dim[1]; + if (i == 0){ + xdg_toplevel_set_min_size(ctx.main_xdg_toplevel, mw, mh); + } + else{ + xdg_toplevel_set_max_size(ctx.main_xdg_toplevel, mw, mh); + } + } + } + xdg_surface_set_window_geometry(ctx.main_xdg_surface, + -ctx.csd_frame.border[0][0], + -ctx.csd_frame.border[1][0], + ctx.dim[0] + ctx.csd_dim[0], + ctx.dim[1] + ctx.csd_dim[1]); + wl_egl_window_resize(ctx.main_wl_egl_window, ctx.dim[0], ctx.dim[1], 0, 0); + + /* frame update and render */ + ctx.cursor_shape = CursorShape_Pointer; + csd_gtk_update_and_render(); + + /* app update & render */ + { + if (ctx.close_signal){ + exit_loop = 1; + } + + glDrawBuffer(GL_BACK); + glViewport(0, 0, ctx.dim[0], ctx.dim[1]); + glClearColor(0.40f, 0.90f, 0.15f, 1.f); + glClear(GL_COLOR_BUFFER_BIT); + } + eglSwapBuffers(ctx.egl_display, ctx.main_egl_surface); + wl_surface_commit(ctx.main_wl_surface); + + /* commit new cursor */ + { + struct wl_cursor *cursor = ctx.wl_cursors[ctx.cursor_shape]; + if (cursor != 0){ + struct wl_cursor_image *cursor_image = cursor->images[0]; + struct wl_buffer *cursor_buffer = wl_cursor_image_get_buffer(cursor_image); + wl_surface_set_buffer_scale(ctx.cursor_surface, 1); + wl_surface_attach(ctx.cursor_surface, cursor_buffer, 0, 0); + wl_surface_damage_buffer(ctx.cursor_surface, 0, 0, + cursor_image->width, cursor_image->height); + wl_surface_commit(ctx.cursor_surface); + wl_pointer_set_cursor(ctx.wl_pointer, ctx.serial, ctx.cursor_surface, + cursor_image->hotspot_x, cursor_image->hotspot_y); + } + } + + } + + return(0); +} + + +/* csd helpers */ + +static CSD_SubSurface +csd_subsurface_new(void){ + CSD_SubSurface result = {0}; + result.wl_surface = wl_compositor_create_surface(ctx.wl_compositor); + result.wl_subsurface = + wl_subcompositor_get_subsurface(ctx.wl_subcompositor, + result.wl_surface, + ctx.main_wl_surface); + return(result); +} + +static void +csd_subsurface_buffer_clear(CSD_SubSurface *subsurface){ + if (subsurface->data != 0){ + munmap(subsurface->data, subsurface->size); + subsurface->data = 0; + } + if (subsurface->wl_buffer != 0){ + wl_buffer_destroy(subsurface->wl_buffer); + subsurface->wl_buffer = 0; + } +} + +static void +csd_subsurface_buffer_alloc(CSD_SubSurface *subsurface, int32_t dim[2]){ + int stride = 4*dim[0]; + uint64_t size = 4*dim[0]*dim[1]; + + int fd = os_create_anonymous_file(size); + if (fd >= 0){ + void *data = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0); + if (data != MAP_FAILED){ + struct wl_shm_pool *pool = wl_shm_create_pool(ctx.wl_shm, fd, size); + struct wl_buffer *wl_buffer = + wl_shm_pool_create_buffer(pool, 0, dim[0], dim[1], + stride, WL_SHM_FORMAT_ARGB8888); + wl_shm_pool_destroy(pool); + + subsurface->wl_buffer = wl_buffer; + subsurface->data = data; + subsurface->size = size; + for (int i = 0; i < 2; i += 1){ + subsurface->dim[i] = dim[i]; + } + } + + close(fd); + } +} + +static void +csd_subsurface_set_position(CSD_SubSurface *subsurface, int32_t x, int32_t y){ + wl_subsurface_set_position(subsurface->wl_subsurface, x, y); + subsurface->p[0] = x; + subsurface->p[1] = y; +} + +static void +csd_subsurface_commit(CSD_SubSurface *subsurface){ + wl_surface_attach(subsurface->wl_surface, subsurface->wl_buffer, 0, 0); + wl_surface_set_buffer_scale(subsurface->wl_surface, 1); + wl_surface_damage_buffer(subsurface->wl_surface, 0, 0, + subsurface->dim[0], subsurface->dim[1]); + wl_surface_commit(subsurface->wl_surface); +} + +static WindowFlags +csd_window_flags_from_states_array(struct wl_array *states){ + WindowFlags flags = 0; + uint32_t *p; + wl_array_for_each(p, states){ + switch (*p) { + case XDG_TOPLEVEL_STATE_FULLSCREEN: flags |= WindowFlag_IsFullscreen; break; + case XDG_TOPLEVEL_STATE_MAXIMIZED: flags |= WindowFlag_IsMax; break; + case XDG_TOPLEVEL_STATE_ACTIVATED: flags |= WindowFlag_IsActivated; break; + case XDG_TOPLEVEL_STATE_TILED_LEFT: flags |= WindowFlag_IsTiledLeft; break; + case XDG_TOPLEVEL_STATE_TILED_RIGHT: flags |= WindowFlag_IsTiledRight; break; + case XDG_TOPLEVEL_STATE_TILED_TOP: flags |= WindowFlag_IsTiledTop; break; + case XDG_TOPLEVEL_STATE_TILED_BOTTOM: flags |= WindowFlag_IsTiledBottom; break; + case XDG_TOPLEVEL_STATE_RESIZING: flags |= WindowFlag_IsResizing; break; + case XDG_TOPLEVEL_STATE_SUSPENDED: flags |= WindowFlag_IsSuspended; break; + case XDG_TOPLEVEL_STATE_CONSTRAINED_LEFT: flags |= WindowFlag_IsConstrainedLeft; break; + case XDG_TOPLEVEL_STATE_CONSTRAINED_RIGHT: flags |= WindowFlag_IsConstrainedRight; break; + case XDG_TOPLEVEL_STATE_CONSTRAINED_TOP: flags |= WindowFlag_IsConstrainedTop; break; + case XDG_TOPLEVEL_STATE_CONSTRAINED_BOTTOM: flags |= WindowFlag_IsConstrainedBottom; break; + default: break; + } + } + return(flags); +} + +/* os */ + +static int +os_resize_anonymous_file(int fd, off_t size){ +#ifdef HAVE_POSIX_FALLOCATE + + sigset_t mask; + sigset_t old_mask; + sigemptyset(&mask); + sigaddset(&mask, SIGALRM); + sigprocmask(SIG_BLOCK, &mask, &old_mask); + + do { + errno = posix_fallocate(fd, 0, size); + } while (errno == EINTR); + sigprocmask(SIG_SETMASK, &old_mask, NULL); + + int result = 0; + if (errno != 0){ + result = -1; + if (errno == EINVAL || errno == EOPNOTSUPP){ + if (ftruncate(fd, size) >= 0){ + result = 0; + } + } + } + return(result); + +#else + + int result = -1; + if (ftruncate(fd, size) >= 0){ + result = 0; + } + return(result); + +#endif +} + +static int +os_create_anonymous_file(off_t size){ + static const char key[] = "/libdecor-shared-XXXXXX"; + int fd = -1; + +#ifdef HAVE_MEMFD_CREATE + fd = memfd_create("libdecor", MFD_CLOEXEC | MFD_ALLOW_SEALING); + if (fd >= 0){ + fcntl(fd, F_ADD_SEALS, F_SEAL_SHRINK | F_SEAL_SEAL); + } +#endif + + if (fd < 0){ + const char *path = getenv("XDG_RUNTIME_DIR"); + if (path == 0){ + errno = ENOENT; + } + else{ + char *name = malloc(strlen(path) + sizeof(key)); + if (name != 0){ + strcpy(name, path); + strcat(name, key); + +#ifdef HAVE_MKOSTEMP + fd = mkostemp(name, O_CLOEXEC); + if (fd >= 0){ + unlink(name); + } +#else + fd = mkstemp(name); + + if (fd >= 0){ + if (fcntl(fd, F_GETFD) == -1){ + close(fd); + fd = -1; + } + else if (fcntl(fd, F_SETFD, FD_CLOEXEC) == -1){ + close(fd); + fd = -1; + } + } + + if (fd >= 0) { + unlink(name); + } +#endif + + free(name); + } + } + } + + if (fd >= 0 && os_resize_anonymous_file(fd, size) < 0){ + close(fd); + fd = -1; + } + + return(fd); +} + +/* desktop settings */ + +static CursorTheme +ds__get_cursor_theme_from_env(void){ + CursorTheme result = {0}; + char *env_xtheme = getenv("XCURSOR_THEME"); + char *env_xsize = getenv("XCURSOR_SIZE"); + if (env_xtheme != 0 && env_xsize != 0){ + result.name = strdup(env_xtheme); + result.size = atoi(env_xsize); + } + else{ + result.name = 0; + result.size = 24; + } + return(result); +} + +#ifdef HAS_DBUS +#include + +static DBusMessage * +ds__get_setting_sync(DBusConnection *const connection, const char *key1, const char *key2){ + DBusMessage *reply = 0; + DBusMessage *message = + dbus_message_new_method_call("org.freedesktop.portal.Desktop", + "/org/freedesktop/portal/desktop", + "org.freedesktop.portal.Settings", + "Read"); + + if (message != 0){ + dbus_bool_t success = dbus_message_append_args(message, + DBUS_TYPE_STRING, &key1, + DBUS_TYPE_STRING, &key2, + DBUS_TYPE_INVALID); + if (success){ + DBusError error; + dbus_error_init(&error); + reply = dbus_connection_send_with_reply_and_block(connection, message, DBUS_TIMEOUT_USE_DEFAULT, &error); + if (dbus_error_is_set(&error) && reply != 0){ + dbus_message_unref(reply); + reply = 0; + } + dbus_error_free(&error); + } + dbus_message_unref(message); + } + + return(reply); +} + +static int +ds__parse_type(DBusMessage *const reply, const int type, void *value){ + int result = 0; + DBusMessageIter iter[3]; + dbus_message_iter_init(reply, &iter[0]); + if (dbus_message_iter_get_arg_type(&iter[0]) == DBUS_TYPE_VARIANT){ + dbus_message_iter_recurse(&iter[0], &iter[1]); + if (dbus_message_iter_get_arg_type(&iter[1]) == DBUS_TYPE_VARIANT){ + dbus_message_iter_recurse(&iter[1], &iter[2]); + if (dbus_message_iter_get_arg_type(&iter[2]) == type){ + dbus_message_iter_get_basic(&iter[2], value); + result = 1; + } + } + } + return(result); +} + +static CursorTheme +ds_get_cursor_theme(void){ + static const char key[] = "org.gnome.desktop.interface"; + static const char key_theme[] = "cursor-theme"; + static const char key_size[] = "cursor-size"; + + char *name = 0; + int size = 0; + int success = 0; + + DBusError error; + dbus_error_init(&error); + DBusConnection *connection = dbus_bus_get(DBUS_BUS_SESSION, &error); + + if (!dbus_error_is_set(&error)){ + DBusMessage *reply = ds__get_setting_sync(connection, key, key_theme); + if (reply != 0){ + if (!ds__parse_type(reply, DBUS_TYPE_STRING, &name)){ + name = 0; + } + dbus_message_unref(reply); + } + } + + if (name != 0){ + DBusMessage *reply = ds__get_setting_sync(connection, key, key_size); + if (reply){ + if (ds__parse_type(reply, DBUS_TYPE_INT32, &size)){ + success = 1; + } + dbus_message_unref(reply); + } + } + + CursorTheme result = {0}; + if (success){ + result.name = name; + result.size = size; + } + else{ + result = ds__get_cursor_theme_from_env(); + } + + return(result); +} + +static ColorScheme +ds_get_color_scheme(){ + static const char name[] = "org.freedesktop.appearance"; + static const char key_color_scheme[] = "color-scheme"; + uint32_t color = 0; + + DBusError error; + dbus_error_init(&error); + DBusConnection *connection = dbus_bus_get(DBUS_BUS_SESSION, &error); + + if (!dbus_error_is_set(&error)){ + DBusMessage *reply = ds__get_setting_sync(connection, name, key_color_scheme); + if (reply){ + if (!ds__parse_type(reply, DBUS_TYPE_UINT32, &color)) { + color = 0; + } + dbus_message_unref(reply); + } + } + return(color); +} + +#else + +static CursorTheme +ds_get_cursor_them(void){ + return(ds__get_cursor_theme_from_env()); +} + +static ColorScheme +ds_get_color_scheme(){ + return(ColorScheme_Default); +} + +#endif + + + +/* csd gtk implementation */ + +static void +csd_gtk_init(void){ + gdk_set_allowed_backends("wayland"); + gtk_disable_setlocale(); + gtk_init_check(0, 0); + + g_object_set(gtk_settings_get_default(), + "gtk-application-prefer-dark-theme", + (ctx.color_scheme == ColorScheme_Dark), + NULL); + + { + static const int size = 128; + static const int boundary = 32; + cairo_surface_t *shadow_blur = + cairo_image_surface_create(CAIRO_FORMAT_ARGB32, size, size); + cairo_t *cr = cairo_create(shadow_blur); + cairo_set_operator(cr, CAIRO_OPERATOR_OVER); + cairo_set_source_rgba(cr, 0, 0, 0, 1); + cairo_rectangle(cr, boundary, boundary, size - 2*boundary, size - 2*boundary); + cairo_fill(cr); + cairo_destroy(cr); + + cr_blur_surface(shadow_blur, 64); + ctx.gtk_ctx.shadow_blur = shadow_blur; + } +} + +static void +csd_gtk_window_init(void){ + GTK_Window *gtk_window = &ctx.gtk_window; + + gtk_window->header = gtk_header_bar_new(); + gtk_window->window = gtk_offscreen_window_new(); + + g_object_get(gtk_widget_get_settings(gtk_window->window), + "gtk-double-click-time", >k_window->double_click_time_ms, + NULL); + g_object_set(gtk_window->header, + "title", "Example Window", + "has-subtitle", FALSE, + "show-close-button", TRUE, + NULL); + + GtkStyleContext *context_hdr = gtk_widget_get_style_context(gtk_window->header); + gtk_style_context_add_class(context_hdr, GTK_STYLE_CLASS_TITLEBAR); + gtk_style_context_add_class(context_hdr, "default-decoration"); + gtk_window_set_titlebar(GTK_WINDOW(gtk_window->window), gtk_window->header); + gtk_header_bar_set_show_close_button(GTK_HEADER_BAR(gtk_window->header), TRUE); + gtk_widget_show_all(gtk_window->window); +} + +static CSD_Frame +csd_gtk_calculate_frame(void){ + GTK_Window *gtk_window = &ctx.gtk_window; + CSD_Frame frame = {0}; + bool show_title = (!(ctx.config.flags & WindowFlag_IsFullscreen)); + if (show_title){ + gtk_widget_get_preferred_height(gtk_window->header, 0, &frame.border[1][0]); + gtk_header_bar_set_title(GTK_HEADER_BAR(gtk_window->header), ""); + gtk_widget_get_preferred_width(gtk_window->header, 0, &frame.minbox[0]); + gtk_header_bar_set_title(GTK_HEADER_BAR(gtk_window->header), "Example Window"); + } + return(frame); +} + +typedef struct CSD_GTK_FindWidgetVars{ + char *name; + GtkWidget *widget; +} CSD_GTK_FindWidgetVars; + +static void +csd_gtk__widget_from_name__r(GtkWidget *root, void *data){ + CSD_GTK_FindWidgetVars *vars = (CSD_GTK_FindWidgetVars*)data; + if (vars->widget == 0 && GTK_IS_WIDGET(root)){ + GtkStyleContext *stylectx = gtk_widget_get_style_context(root); + char *style_str = gtk_style_context_to_string(stylectx, GTK_STYLE_CONTEXT_PRINT_SHOW_STYLE); + if (strstr(style_str, vars->name) != 0){ + vars->widget = root; + } + free(style_str); + } + if (vars->widget == 0 && GTK_IS_CONTAINER(root)){ + gtk_container_forall(GTK_CONTAINER(root), csd_gtk__widget_from_name__r, data); + } +} + +static GtkWidget* +csd_gtk__widget_from_name(GtkWidget *root, char *name){ + CSD_GTK_FindWidgetVars slot = {0}; + slot.name = name; + csd_gtk__widget_from_name__r(root, &slot); + return(slot.widget); +} + +#define SHADOW_MARGIN 24 + +static void +csd_gtk_update_and_render(void){ + GTK_Window *gtk_window = &ctx.gtk_window; + + if (ctx.control_flags & WindowControlFlag_Resize){ + static const CursorShape cursor_box[] = { + CursorShape_Resize_TopLeft, CursorShape_Resize_Top, CursorShape_Resize_TopRight, + CursorShape_Resize_Left, CursorShape_Pointer, CursorShape_Resize_Right, + CursorShape_Resize_BottomLeft, CursorShape_Resize_Bottom, CursorShape_Resize_BottomRight, + }; + + static const enum xdg_toplevel_resize_edge xedge_box[] = { + XDG_TOPLEVEL_RESIZE_EDGE_TOP_LEFT, XDG_TOPLEVEL_RESIZE_EDGE_TOP, XDG_TOPLEVEL_RESIZE_EDGE_TOP_RIGHT, + XDG_TOPLEVEL_RESIZE_EDGE_LEFT, XDG_TOPLEVEL_RESIZE_EDGE_NONE, XDG_TOPLEVEL_RESIZE_EDGE_RIGHT, + XDG_TOPLEVEL_RESIZE_EDGE_BOTTOM_LEFT, XDG_TOPLEVEL_RESIZE_EDGE_BOTTOM, XDG_TOPLEVEL_RESIZE_EDGE_BOTTOM_RIGHT, + }; + + int l = (gtk_window->p[0] < 10); + int r = (!l && gtk_window->p[0] >= ctx.dim[0] + 10); + int t = (gtk_window->p[1] < 10); + int b = (!t && gtk_window->p[1] >= ctx.dim[1] + 10); + int loc = 3*(b - t) + (r - l); + ctx.cursor_shape = cursor_box[4 + loc]; + if (ctx.gtk_window.button == BTN_LEFT){ + ctx.gtk_window.button = 0; + if (loc != 0){ + xdg_toplevel_resize(ctx.main_xdg_toplevel, ctx.wl_seat, + ctx.serial, xedge_box[4 + loc]); + } + else{ + xdg_toplevel_move(ctx.main_xdg_toplevel, ctx.wl_seat, ctx.serial); + } + } + } + + { + CSD_SubSurface *subsurface = >k_window->shadow; + + int32_t shadow_dim[2]; + shadow_dim[0] = ctx.dim[0] + 2*SHADOW_MARGIN; + shadow_dim[1] = ctx.dim[1] + 2*SHADOW_MARGIN + ctx.csd_frame.border[1][0]; + + csd_subsurface_buffer_clear(subsurface); + csd_subsurface_buffer_alloc(subsurface, shadow_dim); + memset(subsurface->data, 0, subsurface->size); + + { + int stride = cairo_format_stride_for_width(CAIRO_FORMAT_ARGB32, shadow_dim[0]); + cairo_surface_t *surface = + cairo_image_surface_create_for_data(subsurface->data, CAIRO_FORMAT_ARGB32, + shadow_dim[0], shadow_dim[1], stride); + cairo_t *cr = cairo_create(surface); + cairo_surface_set_device_scale(surface, 1, 1); + + cr_render_shadow(cr, ctx.gtk_ctx.shadow_blur, + -(int)SHADOW_MARGIN/2, -(int)SHADOW_MARGIN/2, + shadow_dim[0] + SHADOW_MARGIN, shadow_dim[1] + SHADOW_MARGIN, + 64, 64); + cairo_set_operator(cr, CAIRO_OPERATOR_CLEAR); + cairo_rectangle(cr, + SHADOW_MARGIN, SHADOW_MARGIN + ctx.csd_frame.border[1][0], + ctx.dim[0], ctx.dim[1]); + cairo_fill(cr); + + cairo_destroy(cr); + cairo_surface_destroy(surface); + } + + csd_subsurface_set_position(subsurface, -SHADOW_MARGIN, -SHADOW_MARGIN - ctx.csd_frame.border[1][0]); + csd_subsurface_commit(subsurface); + } + + { + CSD_SubSurface *subsurface = >k_window->titlebar; + + int32_t title_dim[2]; + title_dim[0] = ctx.dim[0]; + title_dim[1] = ctx.csd_frame.border[1][0]; + + csd_subsurface_buffer_clear(subsurface); + csd_subsurface_buffer_alloc(subsurface, title_dim); + memset(subsurface->data, 0, subsurface->size); + + { + int is_resizable = ((ctx.control_flags & WindowControlFlag_Resize) != 0); + gtk_window_set_resizable(GTK_WINDOW(gtk_window->window), is_resizable); + if (!(ctx.config.flags & WindowFlag_IsActivated)){ + gtk_widget_set_state_flags(gtk_window->window, GTK_STATE_FLAG_BACKDROP, true); + } + else{ + gtk_widget_unset_state_flags(gtk_window->window, GTK_STATE_FLAG_BACKDROP); + } + } + + { + GtkStyleContext *style = gtk_widget_get_style_context(gtk_window->window); + if (ctx.config.flags & WindowMask_IsAnchored){ + gtk_style_context_add_class(style, "maximized"); + } + else{ + gtk_style_context_remove_class(style, "maximized"); + } + } + + { + GtkAllocation allocation = { 0, 0, title_dim[0], title_dim[1]}; + gtk_widget_size_allocate(gtk_window->header, &allocation); + } + + { + int stride = cairo_format_stride_for_width(CAIRO_FORMAT_ARGB32, title_dim[0]); + cairo_surface_t *cr_surface = + cairo_image_surface_create_for_data(subsurface->data, CAIRO_FORMAT_ARGB32, + title_dim[0], title_dim[1], stride); + cairo_t *cr = cairo_create(cr_surface); + cairo_surface_set_device_scale(cr_surface, 1, 1); + + /* background */ + { + GtkAllocation allocation; + gtk_widget_get_allocation(GTK_WIDGET(gtk_window->header), &allocation); + GtkStyleContext* style = gtk_widget_get_style_context(gtk_window->header); + gtk_render_background(style, cr, + allocation.x, allocation.y, + allocation.width, allocation.height); + } + + /* title */ + { + GtkWidget *label_title = + csd_gtk__widget_from_name(gtk_window->header, "label.title:"); + + GtkAllocation allocation; + gtk_widget_get_allocation(label_title, &allocation); + + cairo_surface_t *cr_label_surface = + cairo_surface_create_for_rectangle(cr_surface, + allocation.x, allocation.y, + allocation.width, allocation.height); + cairo_t *cr2 = cairo_create(cr_label_surface); + gtk_widget_draw(label_title, cr2); + cairo_destroy(cr2); + cairo_surface_destroy(cr_label_surface); + } + + /* buttons */ + uint32_t buttons[3] = {0}; + int button_count = 0; + if (ctx.control_flags & WindowControlFlag_Min){ + buttons[button_count] = 0; + button_count += 1; + } + if ((ctx.control_flags & WindowControlFlag_Max) && + (ctx.control_flags & WindowControlFlag_Resize)){ + buttons[button_count] = 1; + button_count += 1; + } + if (ctx.control_flags & WindowControlFlag_Close){ + buttons[button_count] = 2; + button_count += 1; + } + + for (int i = 0; i < button_count; i += 1){ + uint32_t button_code = buttons[i]; + + char *button_name = ""; + char *icon_name = ""; + switch (button_code){ + case 0: { + button_name = ".minimize"; + icon_name = "window-minimize-symbolic"; + }break; + case 1: { + button_name = ".maximize"; + icon_name = ((ctx.config.flags & WindowFlag_IsMax) ? + "window-restore-symbolic" : "window-maximize-symbolic"); + }break; + case 2: { + button_name = ".close"; + icon_name = "window-close-symbolic"; + }break; + } + + GtkWidget *button_widget = + csd_gtk__widget_from_name(gtk_window->header, button_name); + + if (button_widget != 0){ + GtkStyleContext *style = gtk_widget_get_style_context(button_widget); + + /* change style based on window state and focus */ + GtkStateFlags style_state = 0; + if (!(ctx.config.flags & WindowFlag_IsActivated)){ + style_state |= GTK_STATE_FLAG_BACKDROP; + } + if (gtk_window->hover_button_code == button_code){ + style_state |= GTK_STATE_FLAG_PRELIGHT; + if (gtk_window->active_button_code == button_code){ + style_state |= GTK_STATE_FLAG_ACTIVE; + } + } + + /* background */ + GtkAllocation allocation; + gtk_widget_get_clip(button_widget, &allocation); + + gtk_style_context_save(style); + gtk_style_context_set_state(style, style_state); + gtk_render_background(style, cr, allocation.x, allocation.y, + allocation.width, allocation.height); + gtk_render_frame(style, cr, allocation.x, allocation.y, + allocation.width, allocation.height); + gtk_style_context_restore(style); + + /* get scale */ + double sx, sy; + cairo_surface_get_device_scale(cr_surface, &sx, &sy); + int scale = (sx + sy)/2.0; + + /* get original icon dimensions */ + GtkWidget *icon_widget = gtk_bin_get_child(GTK_BIN(button_widget)); + + /* icon info */ + gint icon_width, icon_height; + if (!gtk_icon_size_lookup(GTK_ICON_SIZE_MENU, &icon_width, &icon_height)){ + icon_width = 16; + icon_height = 16; + } + GtkIconInfo* icon_info = + gtk_icon_theme_lookup_icon_for_scale(gtk_icon_theme_get_default(), + icon_name, icon_width, scale, 0); + + /* icon pixel buffer*/ + gtk_style_context_save(style); + gtk_style_context_set_state(style, style_state); + GdkPixbuf *icon_pixbuf = gtk_icon_info_load_symbolic_for_context(icon_info, style, 0, 0); + cairo_surface_t *icon_surface = gdk_cairo_surface_create_from_pixbuf(icon_pixbuf, scale, 0); + gtk_style_context_restore(style); + + /* dimensions and position */ + gint width = 0; + gint height = 0; + gtk_style_context_get(style, + gtk_style_context_get_state(style), + "min-width", &width, + "min-height", &height, NULL); + width = ClampBot(width, icon_width); + height = ClampBot(height, icon_height); + + gint left = 0; + gint top = 0; + gint right = 0; + gint bottom = 0; + + GtkBorder border; + gtk_style_context_get_border(style, gtk_style_context_get_state(style), &border); + left += border.left; + right += border.right; + top += border.top; + bottom += border.bottom; + + GtkBorder padding; + gtk_style_context_get_padding(style, gtk_style_context_get_state(style), &padding); + left += padding.left; + right += padding.right; + top += padding.top; + bottom += padding.bottom; + + width += left + right; + height += top + bottom; + + gtk_render_icon_surface(gtk_widget_get_style_context(icon_widget), + cr, icon_surface, + allocation.x + (width - icon_width)/2, + allocation.y + (height - icon_height)/2); + cairo_paint(cr); + cairo_surface_destroy(icon_surface); + g_object_unref(icon_pixbuf); + } + } + + cairo_destroy(cr); + cairo_surface_destroy(cr_surface); + } + + csd_subsurface_set_position(subsurface, 0, -ctx.csd_frame.border[1][0]); + csd_subsurface_commit(subsurface); + } +} + +/* cairo shadow rendering */ + +static int +cr_blur_surface(cairo_surface_t *surface, int margin){ + int32_t width, height, stride, x, y, z, w; + uint8_t *src, *dst; + uint32_t *s, *d, a, p; + int i, j, k, size, half; + uint32_t kernel[71]; + double f; + + size = sizeof(kernel)/sizeof(*kernel); + width = cairo_image_surface_get_width(surface); + height = cairo_image_surface_get_height(surface); + stride = cairo_image_surface_get_stride(surface); + src = cairo_image_surface_get_data(surface); + + dst = malloc(height * stride); + if (dst == NULL) + return -1; + + half = size / 2; + a = 0; + for (i = 0; i < size; i++) { + f = (i - half); + kernel[i] = exp(- f * f / (sizeof(kernel)/sizeof(*kernel))) * 10000; + a += kernel[i]; + } + + for (i = 0; i < height; i++) { + s = (uint32_t *) (src + i * stride); + d = (uint32_t *) (dst + i * stride); + for (j = 0; j < width; j++) { + if (margin < j && j < width - margin) { + d[j] = s[j]; + continue; + } + + x = 0; + y = 0; + z = 0; + w = 0; + for (k = 0; k < size; k++) { + if (j - half + k < 0 || j - half + k >= width) + continue; + p = s[j - half + k]; + + x += (p >> 24) * kernel[k]; + y += ((p >> 16) & 0xff) * kernel[k]; + z += ((p >> 8) & 0xff) * kernel[k]; + w += (p & 0xff) * kernel[k]; + } + d[j] = (x / a << 24) | (y / a << 16) | (z / a << 8) | w / a; + } + } + + for (i = 0; i < height; i++) { + s = (uint32_t *) (dst + i * stride); + d = (uint32_t *) (src + i * stride); + for (j = 0; j < width; j++) { + if (margin <= i && i < height - margin) { + d[j] = s[j]; + continue; + } + + x = 0; + y = 0; + z = 0; + w = 0; + for (k = 0; k < size; k++) { + if (i - half + k < 0 || i - half + k >= height) + continue; + s = (uint32_t *) (dst + (i - half + k) * stride); + p = s[j]; + + x += (p >> 24) * kernel[k]; + y += ((p >> 16) & 0xff) * kernel[k]; + z += ((p >> 8) & 0xff) * kernel[k]; + w += (p & 0xff) * kernel[k]; + } + d[j] = (x / a << 24) | (y / a << 16) | (z / a << 8) | w / a; + } + } + + free(dst); + cairo_surface_mark_dirty(surface); + + return 0; +} + +static void +cr_render_shadow(cairo_t *cr, cairo_surface_t *surface, + int x, int y, int width, int height, + int margin, int top_margin){ + cairo_pattern_t *pattern; + cairo_matrix_t matrix; + int i, fx, fy, shadow_height, shadow_width; + + cairo_set_source_rgba(cr, 0, 0, 0, 0.45); + cairo_set_operator(cr, CAIRO_OPERATOR_OVER); + pattern = cairo_pattern_create_for_surface (surface); + cairo_pattern_set_filter(pattern, CAIRO_FILTER_NEAREST); + + for (i = 0; i < 4; i++) { + /* when fy is set, then we are working with lower corners, + * when fx is set, then we are working with right corners + * + * 00 ------- 01 + * | | + * | | + * 10 ------- 11 + */ + fx = i & 1; + fy = i >> 1; + + cairo_matrix_init_translate(&matrix, + -x + fx * (128 - width), + -y + fy * (128 - height)); + cairo_pattern_set_matrix(pattern, &matrix); + + shadow_width = margin; + shadow_height = fy ? margin : top_margin; + + /* if the shadows together are greater than the surface, we need + * to fix it - set the shadow size to the half of + * the size of surface. Also handle the case when the size is + * not divisible by 2. In that case we need one part of the + * shadow to be one pixel greater. !fy or !fx, respectively, + * will do the work. + */ + if (height < 2 * shadow_height) + shadow_height = (height + !fy) / 2; + + if (width < 2 * shadow_width) + shadow_width = (width + !fx) / 2; + + cairo_reset_clip(cr); + cairo_rectangle(cr, + x + fx * (width - shadow_width), + y + fy * (height - shadow_height), + shadow_width, shadow_height); + cairo_clip (cr); + cairo_mask(cr, pattern); + } + + + shadow_width = width - 2 * margin; + shadow_height = top_margin; + if (height < 2 * shadow_height) + shadow_height = height / 2; + + if (shadow_width > 0 && shadow_height) { + /* Top stretch */ + cairo_matrix_init_translate(&matrix, 60, 0); + cairo_matrix_scale(&matrix, 8.0 / width, 1); + cairo_matrix_translate(&matrix, -x - width / 2, -y); + cairo_pattern_set_matrix(pattern, &matrix); + cairo_rectangle(cr, x + margin, y, shadow_width, shadow_height); + + cairo_reset_clip(cr); + cairo_rectangle(cr, + x + margin, y, + shadow_width, shadow_height); + cairo_clip (cr); + cairo_mask(cr, pattern); + + /* Bottom stretch */ + cairo_matrix_translate(&matrix, 0, -height + 128); + cairo_pattern_set_matrix(pattern, &matrix); + + cairo_reset_clip(cr); + cairo_rectangle(cr, x + margin, y + height - margin, + shadow_width, margin); + cairo_clip (cr); + cairo_mask(cr, pattern); + } + + shadow_width = margin; + if (width < 2 * shadow_width) + shadow_width = width / 2; + + shadow_height = height - margin - top_margin; + + /* if height is smaller than sum of margins, + * then the shadow is already done by the corners */ + if (shadow_height > 0 && shadow_width) { + /* Left stretch */ + cairo_matrix_init_translate(&matrix, 0, 60); + cairo_matrix_scale(&matrix, 1, 8.0 / height); + cairo_matrix_translate(&matrix, -x, -y - height / 2); + cairo_pattern_set_matrix(pattern, &matrix); + cairo_reset_clip(cr); + cairo_rectangle(cr, x, y + top_margin, + shadow_width, shadow_height); + cairo_clip (cr); + cairo_mask(cr, pattern); + + /* Right stretch */ + cairo_matrix_translate(&matrix, -width + 128, 0); + cairo_pattern_set_matrix(pattern, &matrix); + cairo_rectangle(cr, x + width - shadow_width, y + top_margin, + shadow_width, shadow_height); + cairo_reset_clip(cr); + cairo_clip (cr); + cairo_mask(cr, pattern); + } + + cairo_pattern_destroy(pattern); + cairo_reset_clip(cr); +} \ No newline at end of file diff --git a/wayland_gtk_egl.h b/wayland_gtk_egl.h new file mode 100644 index 0000000..0677dd0 --- /dev/null +++ b/wayland_gtk_egl.h @@ -0,0 +1,220 @@ +#ifndef WAYLAND_GTK_EGL_EXAMPLE_H +#define WAYLAND_GTK_EGL_EXAMPLE_H + +#define HAS_DBUS +#define HAVE_MEMFD_CREATE +#define HAVE_POSIX_FALLOCATE + +#define Min(a, b) ((a)>(b)?(b):(a)) +#define Max(a, b) ((a)<(b)?(b):(a)) + +#define ClampTop(x, a) Min(x, a) +#define ClampBot(x, a) Max(x, a) + +typedef enum ColorScheme{ + ColorScheme_Default, + ColorScheme_Dark, + ColorScheme_Light +} ColorScheme; + +typedef struct CursorTheme{ + char *name; + int size; +} CursorTheme; + +typedef uint32_t CursorShape; +enum{ + CursorShape_Hidden, + CursorShape_Pointer, + CursorShape_Resize_Top, + CursorShape_Resize_Bottom, + CursorShape_Resize_Left, + CursorShape_Resize_Right, + CursorShape_Resize_TopLeft, + CursorShape_Resize_TopRight, + CursorShape_Resize_BottomLeft, + CursorShape_Resize_BottomRight, + CursorShape_COUNT +}; + +typedef uint32_t WindowFlags; +enum{ + WindowFlag_IsFullscreen = (1 << 0), + WindowFlag_IsMax = (1 << 1), + WindowFlag_IsActivated = (1 << 2), + WindowFlag_IsTiledLeft = (1 << 3), + WindowFlag_IsTiledRight = (1 << 4), + WindowFlag_IsTiledTop = (1 << 5), + WindowFlag_IsTiledBottom = (1 << 6), + WindowFlag_IsResizing = (1 << 7), + WindowFlag_IsSuspended = (1 << 8), + WindowFlag_IsConstrainedLeft = (1 << 9), + WindowFlag_IsConstrainedRight = (1 << 10), + WindowFlag_IsConstrainedTop = (1 << 11), + WindowFlag_IsConstrainedBottom = (1 << 12), +}; + +#define WindowMask_IsAnchored \ +(WindowFlag_IsFullscreen|WindowFlag_IsMax| \ +WindowFlag_IsTiledLeft |WindowFlag_IsTiledRight| \ +WindowFlag_IsTiledTop |WindowFlag_IsTiledBottom) + +typedef uint32_t WindowControlFlags; +enum{ + WindowControlFlag_Move = (1 << 0), + WindowControlFlag_Resize = (1 << 1), + WindowControlFlag_Min = (1 << 2), + WindowControlFlag_Max = (1 << 3), + WindowControlFlag_Close = (1 << 4), +}; + +typedef struct Config{ + uint32_t serial; + int32_t dim[2]; + WindowFlags flags; + uint32_t decoration_mode; +} Config; + +typedef struct CSD_Frame{ + int32_t border[2][2]; // [0][]:x [1][]:y [][0]:min [][1]:max + int32_t minbox[2]; +} CSD_Frame; + +typedef struct CSD_SubSurface{ + struct wl_surface *wl_surface; + struct wl_subsurface *wl_subsurface; + struct wl_buffer *wl_buffer; + void *data; + uint64_t size; + int32_t dim[2]; + int32_t p[2]; +} CSD_SubSurface; + +typedef struct GTK_Ctx{ + cairo_surface_t *shadow_blur; +} GTK_Ctx; + +typedef struct GTK_Window{ + int32_t p[2]; + uint32_t button; + int double_click_time_ms; + + CSD_SubSurface titlebar; + CSD_SubSurface shadow; + + GtkWidget *window; + GtkWidget *header; + + uint32_t hover_button_code; + uint32_t active_button_code; +} GTK_Window; + +typedef struct Ctx{ + /* "application variables" */ + int close_signal; + + /* globals: desktop settings */ + ColorScheme color_scheme; + CursorTheme cursor_theme; + + /* globals: wayland */ + struct wl_display *wl_display; + struct wl_registry *wl_registry; + struct wl_compositor *wl_compositor; + struct wl_subcompositor *wl_subcompositor; + struct wl_shm *wl_shm; + struct xdg_wm_base *xdg_wm_base; + struct zxdg_decoration_manager_v1 *zxdg_decoration_manager; + + struct wl_cursor_theme *wl_cursor_theme; + struct wl_cursor *wl_cursors[CursorShape_COUNT]; + + /* globals: egl */ + EGLDisplay egl_display; + EGLContext egl_context; + + /* globals: gtk */ + GTK_Ctx gtk_ctx; + + /* per-seat: wayland */ + struct wl_seat *wl_seat; + struct wl_pointer *wl_pointer; + struct wl_surface *cursor_surface; + + struct wl_surface *hover_surface; + + CursorShape cursor_shape; + + /* per-window: wayland */ + struct wl_surface *main_wl_surface; + struct xdg_surface *main_xdg_surface; + struct xdg_toplevel *main_xdg_toplevel; + struct zxdg_toplevel_decoration_v1 *main_zxdg_toplevel_decoration; + + /* per-window: egl */ + struct wl_egl_window *main_wl_egl_window; + EGLSurface main_egl_surface; + + /* per-window */ + WindowControlFlags control_flags; + int32_t dim[2]; + int32_t mmbox[2][2]; // [0][]:x [1][]:y [][0]:min [][1]:max + + Config config; + Config config_staged; + uint32_t serial; + + int handled_first_size; + CSD_Frame csd_frame; + int32_t csd_dim[2]; + + GTK_Window gtk_window; +} Ctx; + +/* csd helpers */ + +static CSD_SubSurface csd_subsurface_new(void); +static void csd_subsurface_buffer_clear(CSD_SubSurface *subsurface); +static void csd_subsurface_buffer_alloc(CSD_SubSurface *subsurface, int32_t dim[2]); +static void csd_subsurface_set_position(CSD_SubSurface *subsurface, int32_t x, int32_t y); +static void csd_subsurface_commit(CSD_SubSurface *subsurface); + +static WindowFlags csd_window_flags_from_states_array(struct wl_array *states); + +/* os */ + +static int os_resize_anonymous_file(int fd, off_t size); +static int os_create_anonymous_file(off_t size); + +/* desktop settings */ + +static CursorTheme ds_get_cursor_theme(void); +static ColorScheme ds_get_color_scheme(void); + +static CursorTheme ds__get_cursor_theme_from_env(void); + +#if defined(HAS_DBUS) +#include +static DBusMessage* ds__get_setting_sync(DBusConnection *const connection, const char *k, const char *v); +static int ds__parse_type(DBusMessage *const reply, const int type, void *value); +#endif /* defined(HAS_DBUS) */ + + + +/* csd gtk implementation */ + +static void csd_gtk_init(void); +static void csd_gtk_window_init(void); +static CSD_Frame csd_gtk_calculate_frame(void); + +static void csd_gtk_update_and_render(void); +static void csd_gtk_render(void); + +/* cairo shadow rendering */ + +static int cr_blur_surface(cairo_surface_t *surface, int margin); +static void cr_render_shadow(cairo_t *cr, cairo_surface_t *surface, + int x, int y, int width, int height, + int margin, int top_margin); + +#endif /* WAYLAND_GTK_EGL_EXAMPLE_H */