From cbad92b78cb9901314dd3304d61bcee3b56312eb Mon Sep 17 00:00:00 2001 From: Andrew Opalach Date: Mon, 13 Apr 2026 16:19:42 -0400 Subject: Wayland icon support and strcmp experiments Signed-off-by: Andrew Opalach --- src/window_wayland.c | 241 ++++++++++++++++++++++++++++++++++----------------- src/window_wayland.h | 14 ++- 2 files changed, 173 insertions(+), 82 deletions(-) diff --git a/src/window_wayland.c b/src/window_wayland.c index 2ab409b..a4a31f2 100644 --- a/src/window_wayland.c +++ b/src/window_wayland.c @@ -1,6 +1,9 @@ #define AL_LOG_SECTION "wayland" #include #include +#include +#include +#include #ifdef STELA_API_VULKAN #include #endif @@ -10,6 +13,8 @@ #include "poll_common.h" #include "keys.h" +#define ASSERT_MIN(v, max) (al_assert(v <= max), v) + #define FRAME_NO_VALUE ((f64)0xffffffffffffffff) f64 stl_scale_scroll(f64 v, f64 factor) @@ -111,10 +116,10 @@ static void global_add(void *data, struct wl_registry *registry, u32 name, const char *interface, u32 version) { (void)data; - if (strcmp(interface, wl_output_interface.name) == 0) { + if (al_strscmp(interface, "wl_output") == 0) { struct stl_monitor_wayland *monitor = al_alloc_object(struct stl_monitor_wayland); al_array_push(global.monitors, (struct stl_monitor *)monitor); - monitor->output = (struct wl_output *)wl_registry_bind(registry, name, &wl_output_interface, MIN(4u, version)); + monitor->output = (struct wl_output *)wl_registry_bind(registry, name, &wl_output_interface, ASSERT_MIN(4u, version)); wl_output_add_listener(monitor->output, &output_listener, monitor); } } @@ -137,6 +142,12 @@ void stl_set_output_discovered_callback(void (*output_discovered)(void *, struct global.userdata = userdata; } +void stl_set_icon_data(u8 *data, u32 size) +{ + global.icon.data = data; + global.icon.size = size; +} + static void set_keycodes_for_wayland(void) { al_memset(global.keycodes, 0, sizeof(global.keycodes)); @@ -266,8 +277,22 @@ static void set_keycodes_for_wayland(void) global.keycodes[KEY_COMPOSE] = STELA_KEY_MENU; } +static void assert_wl_interface_names() +{ + al_assert(al_strscmp(wl_output_interface.name, "wl_output") == 0); + al_assert(al_strscmp(wl_compositor_interface.name, "wl_compositor") == 0); + al_assert(al_strscmp(wl_shm_interface.name, "wl_shm") == 0); + al_assert(al_strscmp(wl_seat_interface.name, "wl_seat") == 0); + al_assert(al_strscmp(xdg_wm_base_interface.name, "xdg_wm_base") == 0); + al_assert(al_strscmp(zxdg_decoration_manager_v1_interface.name, "zxdg_decoration_manager_v1") == 0); + al_assert(al_strscmp(wp_cursor_shape_manager_v1_interface.name, "wp_cursor_shape_manager_v1") == 0); + al_assert(al_strscmp(xdg_toplevel_icon_manager_v1_interface.name, "xdg_toplevel_icon_manager_v1") == 0); +} + bool stl_global_init(bool skip_graphics) { + assert_wl_interface_names(); + global.display = wl_display_connect(NULL); if (!global.display) { log_error("Failed to connect to the display."); @@ -376,7 +401,6 @@ static void set_cursor_internal(struct stl_window_wayland *wl, u8 shape) } } - static void pointer_enter(void *data, struct wl_pointer *pointer, u32 serial, struct wl_surface *surface, wl_fixed_t surface_x, wl_fixed_t surface_y) { @@ -513,26 +537,6 @@ static void pointer_axis_discrete(void *data, struct wl_pointer *pointer, (void)discrete; } -static void pointer_axis_value120(void *data, struct wl_pointer *pointer, - u32 axis, s32 value120) -{ - (void)data; - (void)pointer; - (void)axis; - (void)value120; -} - -/* -static void pointer_axis_relative_direction(void *data, struct wl_pointer *pointer, - u32 axis, u32 direction) -{ - (void)data; - (void)pointer; - (void)axis; - (void)direction; -} -*/ - static const struct wl_pointer_listener pointer_listener = { .enter = pointer_enter, .leave = pointer_leave, @@ -542,9 +546,7 @@ static const struct wl_pointer_listener pointer_listener = { .frame = pointer_frame, .axis_source = pointer_axis_source, .axis_stop = pointer_axis_stop, - .axis_discrete = pointer_axis_discrete, - .axis_value120 = pointer_axis_value120, - //.axis_relative_direction = pointer_axis_relative_direction + .axis_discrete = pointer_axis_discrete }; static void keyboard_keymap(void *data, struct wl_keyboard *keyboard, @@ -630,7 +632,7 @@ static const struct wl_keyboard_listener keyboard_listener = { static void seat_capabilities(void *data, struct wl_seat *seat, u32 capabilities) { struct stl_window_wayland *wl = (struct stl_window_wayland *)data; - (void)seat; + al_assert(seat == wl->seat); if (capabilities & WL_SEAT_CAPABILITY_POINTER) { if (!wl->pointer) { @@ -642,7 +644,8 @@ static void seat_capabilities(void *data, struct wl_seat *seat, u32 capabilities } } else { if (wl->pointer) { - wl_pointer_release(wl->pointer); + // @TODO: Why or why not wl_keyboard/pointer_release()? + wl_pointer_destroy(wl->pointer); wl->pointer = NULL; } } @@ -654,7 +657,7 @@ static void seat_capabilities(void *data, struct wl_seat *seat, u32 capabilities } } else { if (wl->keyboard) { - wl_keyboard_release(wl->keyboard); + wl_keyboard_destroy(wl->keyboard); wl->keyboard = NULL; } } @@ -672,24 +675,34 @@ static const struct wl_seat_listener seat_listener = { .name = seat_name }; +#define SUPPORT_PREFERRED_BUFFER_SCALE + static void context_global_add(void *data, struct wl_registry *registry, u32 name, const char *interface, u32 version) { struct stl_window_wayland *wl = (struct stl_window_wayland *)data; - if (strcmp(interface, wl_compositor_interface.name) == 0) { - wl->compositor = (struct wl_compositor *)wl_registry_bind(registry, name, &wl_compositor_interface, MIN(5u, version)); - } else if (strcmp(interface, wl_seat_interface.name) == 0) { - wl->seat = (struct wl_seat *)wl_registry_bind(registry, name, &wl_seat_interface, MIN(8u, version)); + if (al_strscmp(interface, "wl_compositor") == 0) { +#ifdef SUPPORT_PREFERRED_BUFFER_SCALE + wl->compositor = (struct wl_compositor *)wl_registry_bind(registry, name, &wl_compositor_interface, ASSERT_MIN(6u, version)); +#else + wl->compositor = (struct wl_compositor *)wl_registry_bind(registry, name, &wl_compositor_interface, ASSERT_MIN(3u, version)); +#endif + } else if (al_strscmp(interface, "wl_shm") == 0) { + wl->shm = (struct wl_shm *)wl_registry_bind(registry, name, &wl_shm_interface, ASSERT_MIN(1u, version)); + } else if (al_strscmp(interface, "wl_seat") == 0) { + wl->seat = (struct wl_seat *)wl_registry_bind(registry, name, &wl_seat_interface, ASSERT_MIN(5u, version)); wl_seat_add_listener(wl->seat, &seat_listener, wl); - } else if (strcmp(interface, xdg_wm_base_interface.name) == 0) { - wl->xdg_wm_base = (struct xdg_wm_base *)wl_registry_bind(registry, name, &xdg_wm_base_interface, MIN(2u, version)); + } else if (al_strscmp(interface, "xdg_wm_base") == 0) { + wl->xdg_wm_base = (struct xdg_wm_base *)wl_registry_bind(registry, name, &xdg_wm_base_interface, ASSERT_MIN(1u, version)); xdg_wm_base_add_listener(wl->xdg_wm_base, &xdg_wm_base_listener, wl); - } else if (strcmp(interface, zxdg_decoration_manager_v1_interface.name) == 0) { - wl->zxdg_decoration_manager = (struct zxdg_decoration_manager_v1 *)wl_registry_bind(registry, name, &zxdg_decoration_manager_v1_interface, MIN(1u, version)); - } else if (strcmp(interface, wp_cursor_shape_manager_v1_interface.name) == 0) { - wl->cursor_shape_manager = (struct wp_cursor_shape_manager_v1 *)wl_registry_bind(registry, name, &wp_cursor_shape_manager_v1_interface, MIN(1u, version)); - } else if (strcmp(interface, zwlr_layer_shell_v1_interface.name) == 0) { - wl->layer_shell = (struct zwlr_layer_shell_v1 *)wl_registry_bind(registry, name, &zwlr_layer_shell_v1_interface, MIN(4u, version)); + } else if (al_strscmp(interface, "zxdg_decoration_manager_v1") == 0) { + wl->zxdg_decoration_manager = (struct zxdg_decoration_manager_v1 *)wl_registry_bind(registry, name, &zxdg_decoration_manager_v1_interface, ASSERT_MIN(1u, version)); + } else if (al_strscmp(interface, "wp_cursor_shape_manager_v1") == 0) { + wl->cursor_shape_manager = (struct wp_cursor_shape_manager_v1 *)wl_registry_bind(registry, name, &wp_cursor_shape_manager_v1_interface, ASSERT_MIN(1u, version)); + } else if (al_strscmp(interface, "xdg_toplevel_icon_manager_v1") == 0) { + wl->icon.manager = (struct xdg_toplevel_icon_manager_v1 *)wl_registry_bind(registry, name, &xdg_toplevel_icon_manager_v1_interface, ASSERT_MIN(1u, version)); + } else if (al_strscmp(interface, "zwlr_layer_shell_v1") == 0) { + wl->layer_shell = (struct zwlr_layer_shell_v1 *)wl_registry_bind(registry, name, &zwlr_layer_shell_v1_interface, ASSERT_MIN(3u, version)); } } @@ -719,7 +732,7 @@ static void handle_surface_leave(void *data, struct wl_surface *surface, struct (void)output; } -/* +#ifdef SUPPORT_PREFERRED_BUFFER_SCALE static void preferred_buffer_scale(void *data, struct wl_surface *surface, s32 factor) { (void)data; @@ -733,15 +746,15 @@ static void preferred_buffer_transform(void *data, struct wl_surface *surface, u (void)surface; (void)transform; } -*/ +#endif static const struct wl_surface_listener surface_listener = { .enter = handle_surface_enter, .leave = handle_surface_leave, - /* +#ifdef SUPPORT_PREFERRED_BUFFER_SCALE .preferred_buffer_scale = preferred_buffer_scale, .preferred_buffer_transform = preferred_buffer_transform - */ +#endif }; static bool check_and_set_dimensions(struct stl_window_wayland *wl, s32 event_width, s32 event_height) @@ -814,26 +827,8 @@ static void handle_xdg_toplevel_close(void *data, struct xdg_toplevel *toplevel) stl_window_send_should_close_event(&wl->w); } -static void handle_xdg_toplevel_configure_bounds(void *data, struct xdg_toplevel *toplevel, s32 width, s32 height) -{ - (void)data; - (void)toplevel; - (void)width; - (void)height; -} - -static void handle_xdg_toplevel_wm_capabilities(void *data, struct xdg_toplevel *toplevel, - struct wl_array *capabilities) -{ - (void)data; - (void)toplevel; - (void)capabilities; -} - static const struct xdg_toplevel_listener xdg_toplevel_listener = { .configure = handle_xdg_toplevel_configure, - .configure_bounds = handle_xdg_toplevel_configure_bounds, - .wm_capabilities = handle_xdg_toplevel_wm_capabilities, .close = handle_xdg_toplevel_close }; @@ -907,14 +902,85 @@ static struct stl_monitor_wayland *monitor_from_name(const char *name) { for (u32 i = 0; i < global.monitors.count; i++) { struct stl_monitor_wayland *monitor = (struct stl_monitor_wayland *)al_array_at(global.monitors, i); - if (strcmp(monitor->m.name, name) == 0) { + if (al_strncmp(monitor->m.name, name, STELA_MAX_MONITOR_NAME) == 0) { return monitor; } } return NULL; } -static bool create_toplevel(struct stl_window_wayland *wl, s32 scale, const char *name) +// https://wayland-book.com/surfaces/shared-memory.html +static s32 create_shm_file(char *name, char *suffix) +{ + s32 retries = 100; + do { + al_random_hex_chars(suffix); + --retries; + s32 fd = shm_open(name, O_RDWR | O_CREAT | O_EXCL, 0600); + if (fd >= 0) { + shm_unlink(name); + return fd; + } + } while (retries > 0 && errno == EEXIST); + return -1; +} + +static s32 allocate_shm_file(char *name, char *suffix, size_t size) +{ + s32 fd = create_shm_file(name, suffix); + if (fd < 0) return -1; + s32 ret = 0; + do { + ret = ftruncate(fd, size); + } while (ret < 0 && errno == EINTR); + if (ret < 0) { + close(fd); + return -1; + } + return fd; +} + +static struct xdg_toplevel_icon_v1 *create_icon(struct stl_window_wayland *wl, u32 size) +{ + u32 stride = size * 4; + u32 shm_pool_size = size * stride; + char shm_name[] = "/wl_shm-XXXXXXXX"; + s32 fd = allocate_shm_file(shm_name, shm_name + sizeof(shm_name) - 9, shm_pool_size); + if (fd < 0) { + log_error("Failed to allocate %s.", shm_name); + return NULL; + } + + u8 *data = mmap(NULL, shm_pool_size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0); + if (!data) { + log_error("mmap(%s) failed (%s).", shm_name, nn_strerror(errno)); + return NULL; + } + wl->icon.pool = wl_shm_create_pool(wl->shm, fd, shm_pool_size); + if (!wl->icon.pool) { + log_error("Failed to create pool for %s.", shm_name); + return NULL; + } + u32 format = WL_SHM_FORMAT_XRGB8888; + wl->icon.buffer = wl_shm_pool_create_buffer(wl->icon.pool, 0, size, size, stride, format); + if (!wl->icon.buffer) { + log_error("Failed to create %ux%u(%u) buffer in %s.", size, size, format, shm_name); + return NULL; + } + al_assert(global.icon.size <= shm_pool_size); + al_memcpy(data, global.icon.data, global.icon.size); + + struct xdg_toplevel_icon_v1 *icon = xdg_toplevel_icon_manager_v1_create_icon(wl->icon.manager); + if (!icon) { + log_error("Failed to create icon."); + return NULL; + } + xdg_toplevel_icon_v1_add_buffer(icon, wl->icon.buffer, 1); + + return icon; +} + +static bool create_toplevel(struct stl_window_wayland *wl, s32 scale, const char *name, const char *app_id) { wl->w.scale = scale; wl_surface_set_buffer_scale(wl->surface, scale); @@ -937,22 +1003,15 @@ static bool create_toplevel(struct stl_window_wayland *wl, s32 scale, const char xdg_toplevel_add_listener(wl->xdg_toplevel, &xdg_toplevel_listener, wl); xdg_toplevel_set_title(wl->xdg_toplevel, name); - xdg_toplevel_set_app_id(wl->xdg_toplevel, name); + xdg_toplevel_set_app_id(wl->xdg_toplevel, app_id); xdg_toplevel_set_min_size(wl->xdg_toplevel, STELA_MIN_WIDTH, STELA_MIN_HEIGHT); - // If xdg_surface_set_window_geometry() is never set, it will be equal to the - // entire surface and update on every commit. If we do set it, we need to manually - // update it on any resize. Also, it's meant to be set to the unscaled width and height. - // For our application, if using the event buffer, window events and rendering are - // on seperate threads. This means we have to manually control xdg surface geometry - // for responsiveness. Though there's currently a bug where toggling fullscreen - // misaligns the surface position within the window. + // If xdg_surface_set_window_geometry() is never set, it will be equal to the entire + // surface and update on every commit. If we do set it, we need to manually update it + // on any resize. Note that it's meant to be set to the unscaled width and height. + // For our application, if using the event buffer, window events and rendering are on + // seperate threads. This means we must manually control the surface geometry for responsiveness. xdg_surface_set_window_geometry(wl->xdg_surface, 0, 0, wl->geom_width, wl->geom_height); - wl->cursor.shape = STELA_CURSOR_NORMAL; -#ifdef STELA_MOUSE2_DRAG - wl->cursor.grabbed_by_toplevel_move = false; -#endif - if (wl->zxdg_decoration_manager) { wl->zxdg_toplevel_decoration = zxdg_decoration_manager_v1_get_toplevel_decoration( wl->zxdg_decoration_manager, wl->xdg_toplevel); @@ -961,6 +1020,18 @@ static bool create_toplevel(struct stl_window_wayland *wl, s32 scale, const char wl->decorated = true; } + wl->cursor.shape = STELA_CURSOR_NORMAL; +#ifdef STELA_MOUSE2_DRAG + wl->cursor.grabbed_by_toplevel_move = false; +#endif + + if (wl->icon.manager && global.icon.data) { + wl->icon.icon = create_icon(wl, 256); + if (wl->icon.icon) { + xdg_toplevel_icon_manager_v1_set_icon(wl->icon.manager, wl->xdg_toplevel, wl->icon.icon); + } + } + return true; } @@ -988,8 +1059,8 @@ static bool create_layer_shell(struct stl_window_wayland *wl, struct stl_monitor return true; } -static bool window_wayland_create_window(struct stl_window *window, u32 width, u32 height, - const char *monitor, const char *name, u32 flags) +static bool window_wayland_create_window(struct stl_window *window, u32 width, u32 height, u32 flags, + const char *monitor, const char *name, const char *app_id) { struct stl_window_wayland *wl = (struct stl_window_wayland *)window; @@ -1011,7 +1082,7 @@ static bool window_wayland_create_window(struct stl_window *window, u32 width, u struct stl_monitor_wayland *selected = NULL; if (!(flags & STELA_WINDOW_WALLPAPER)) { selected = (struct stl_monitor_wayland *)al_array_at(global.monitors, 0); - if (!create_toplevel(wl, selected ? selected->m.scale : 1, name)) { + if (!create_toplevel(wl, selected ? selected->m.scale : 1, name, app_id)) { log_error("Failed to create toplevel surface."); goto err; } @@ -1076,6 +1147,7 @@ static void window_wayland_toggle_fullscreen(struct stl_window *window) { struct stl_window_wayland *wl = (struct stl_window_wayland *)window; wl->w.fullscreen = !wl->w.fullscreen; + // https://gitlab.freedesktop.org/wayland/wayland-protocols/-/merge_requests/377 if (wl->w.fullscreen) { xdg_toplevel_set_fullscreen(wl->xdg_toplevel, NULL); } else { @@ -1169,11 +1241,18 @@ void window_wayland_free(struct stl_window **window) if (wl->pointer) wl_pointer_destroy(wl->pointer); if (wl->cursor_shape_manager) wp_cursor_shape_manager_v1_destroy(wl->cursor_shape_manager); if (wl->cursor_shape_device) wp_cursor_shape_device_v1_destroy(wl->cursor_shape_device); + if (wl->icon.manager) { + if (wl->icon.icon) xdg_toplevel_icon_v1_destroy(wl->icon.icon); + if (wl->icon.buffer) wl_buffer_destroy(wl->icon.buffer); + if (wl->icon.pool) wl_shm_pool_destroy(wl->icon.pool); + xdg_toplevel_icon_manager_v1_destroy(wl->icon.manager); + } if (wl->keyboard) wl_keyboard_destroy(wl->keyboard); destroy_surface_internal(wl); if (wl->zxdg_decoration_manager) zxdg_decoration_manager_v1_destroy(wl->zxdg_decoration_manager); if (wl->xdg_wm_base) xdg_wm_base_destroy(wl->xdg_wm_base); if (wl->layer_shell) zwlr_layer_shell_v1_destroy(wl->layer_shell); + if (wl->shm) wl_shm_destroy(wl->shm); if (wl->compositor) wl_compositor_destroy(wl->compositor); #ifdef STELA_API_OPENGL if (stl_egl_is_ready(&wl->egl)) stl_egl_destroy_context(&wl->egl); diff --git a/src/window_wayland.h b/src/window_wayland.h index 3df2794..ad0f572 100644 --- a/src/window_wayland.h +++ b/src/window_wayland.h @@ -11,6 +11,7 @@ #include #include #include +#include #include //#include #include @@ -27,6 +28,10 @@ struct stl_wayland_global { struct wl_display *display; + struct { + u8 *data; + u32 size; + } icon; s32 display_fd; struct wl_registry *registry; u16 keycodes[STELA_WAYLAND_KEY_COUNT]; @@ -50,10 +55,11 @@ struct stl_window_wayland { struct stl_window w; struct wl_display *display; struct wl_registry *registry; + struct wl_compositor *compositor; + struct wl_shm *shm; struct wl_seat *seat; struct wl_pointer *pointer; struct wl_keyboard *keyboard; - struct wl_compositor *compositor; struct wl_surface *surface; struct xdg_wm_base *xdg_wm_base; struct xdg_surface *xdg_surface; @@ -61,6 +67,12 @@ struct stl_window_wayland { struct zxdg_decoration_manager_v1 *zxdg_decoration_manager; struct wp_cursor_shape_manager_v1 *cursor_shape_manager; struct wp_cursor_shape_device_v1 *cursor_shape_device; + struct { + struct xdg_toplevel_icon_manager_v1 *manager; + struct wl_shm_pool *pool; + struct wl_buffer *buffer; + struct xdg_toplevel_icon_v1 *icon; + } icon; struct zxdg_toplevel_decoration_v1 *zxdg_toplevel_decoration; struct zwlr_layer_shell_v1 *layer_shell; struct stl_monitor_wayland *monitor; -- cgit v1.2.3-101-g0448