summaryrefslogtreecommitdiff
path: root/src/window_wayland.c
diff options
context:
space:
mode:
authorAndrew Opalach <andrew@akon.city> 2026-04-13 16:19:42 -0400
committerAndrew Opalach <andrew@akon.city> 2026-04-13 16:39:06 -0400
commitcbad92b78cb9901314dd3304d61bcee3b56312eb (patch)
tree66f26c0015337c8c63f2aadd0e81903ff5ea6c0e /src/window_wayland.c
parent9913b7d590f46e59a475abbe85e70e02979d8d37 (diff)
downloadstela-cbad92b78cb9901314dd3304d61bcee3b56312eb.tar.gz
stela-cbad92b78cb9901314dd3304d61bcee3b56312eb.tar.bz2
stela-cbad92b78cb9901314dd3304d61bcee3b56312eb.zip
Wayland icon support and strcmp experiments
Signed-off-by: Andrew Opalach <andrew@akon.city>
Diffstat (limited to 'src/window_wayland.c')
-rw-r--r--src/window_wayland.c241
1 files changed, 160 insertions, 81 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 <al/log.h>
#include <al/lib.h>
+#include <al/random.h>
+#include <nnwt/file.h>
+#include <nnwt/error.h>
#ifdef STELA_API_VULKAN
#include <dlfcn.h>
#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);