diff --git a/wayland/CMakeLists.txt b/wayland/CMakeLists.txt index 531db98..4cac788 100644 --- a/wayland/CMakeLists.txt +++ b/wayland/CMakeLists.txt @@ -72,5 +72,7 @@ pkg_check_modules(XKBCOMMON REQUIRED xkbcommon) set(XKBCOMMON_LIBRARIES ${XKBCOMMON_LIBRARIES}) set(XKBCOMMON_INCLUDE_DIRS ${XKBCOMMON_INCLUDE_DIRS}) +find_package(Threads REQUIRED) + # Link to system libraries -target_link_libraries(wayland PRIVATE ${WAYLAND_CLIENT_LIBRARIES} ${XKBCOMMON_LIBRARIES}) \ No newline at end of file +target_link_libraries(wayland PRIVATE ${WAYLAND_CLIENT_LIBRARIES} ${XKBCOMMON_LIBRARIES} Threads::Threads) \ No newline at end of file diff --git a/wayland/include/registry.h b/wayland/include/registry.h index dfe4527..2371397 100644 --- a/wayland/include/registry.h +++ b/wayland/include/registry.h @@ -6,7 +6,16 @@ /* Initialize registry listener on the given registry */ void registry_add_listener(struct wl_registry *registry); -/* Accessors for bound globals */ +/* Prepare per-thread bindings for compositor/shm/xdg_wm_base */ +int registry_thread_bind(struct wl_display *display, struct wl_event_queue *queue); + +/* Release per-thread resources allocated by registry_thread_bind */ +void registry_thread_unbind(void); + +/* Reset global registry bookkeeping (used during teardown) */ +void registry_reset_globals(void); + +/* Accessors for bound globals (per-thread via registry_thread_bind) */ struct wl_compositor *registry_get_compositor(void); struct wl_shm *registry_get_shm(void); struct xdg_wm_base *registry_get_xdg_wm_base(void); diff --git a/wayland/include/wayland_runtime.h b/wayland/include/wayland_runtime.h new file mode 100644 index 0000000..db02b4a --- /dev/null +++ b/wayland/include/wayland_runtime.h @@ -0,0 +1,18 @@ +#ifndef WAYLAND_RUNTIME_H +#define WAYLAND_RUNTIME_H + +#include + +/* Initialize shared Wayland connection and prepare global data */ +int32_t init_wayland(void); + +/* Spawn a new window thread; returns slot index or negative on failure */ +int32_t run_window(void); + +/* Block until all window threads have exited */ +void wait_for_windows(void); + +/* Tear down window threads, input, and the shared Wayland connection */ +void destroy_wayland(void); + +#endif diff --git a/wayland/include/window.h b/wayland/include/window.h index 3ff1a27..0ef1b46 100644 --- a/wayland/include/window.h +++ b/wayland/include/window.h @@ -7,6 +7,7 @@ struct wayland_window { struct wl_surface *wl_surface; struct wl_buffer *buffer; + struct wl_callback *frame_callback; struct xdg_surface *xdg_surface; struct xdg_toplevel *xdg_toplevel; uint8_t *data; diff --git a/wayland/src/asm.asm b/wayland/src/asm.asm index b00e969..4e5fe27 100644 --- a/wayland/src/asm.asm +++ b/wayland/src/asm.asm @@ -1,9 +1,25 @@ global main -extern run_wayland +extern init_wayland +extern run_window +extern wait_for_windows +extern destroy_wayland section .text main: enter 0, 0 - call run_wayland + + call init_wayland + cmp eax, 0 + jl .shutdown + + ; Launch the first window thread (duplicate calls for more windows) + call run_window + call run_window + call run_window + + call wait_for_windows + +.shutdown: + call destroy_wayland leave ret diff --git a/wayland/src/c.c b/wayland/src/c.c index 2bb1b28..1ce5fb1 100644 --- a/wayland/src/c.c +++ b/wayland/src/c.c @@ -1,38 +1,197 @@ -#include -#include -#include -#include -#include +#include +#include +#include +#include #include -#include -#include "xdg-shell-client-protocol.h" +#include +#include + #include "input.h" -#include "window.h" #include "registry.h" +#include "wayland_runtime.h" +#include "window.h" +#define MAX_WINDOW_THREADS 128 -int32_t run_wayland() +struct window_thread_slot { - struct wl_display *display = wl_display_connect(0); - if (!display) - return -1; - struct wl_registry *registry = wl_display_get_registry(display); - registry_add_listener(registry); - wl_display_roundtrip(display); - struct wayland_window win; - if (window_init(display, &win) < 0) - return -1; + pthread_t thread; + int active; + struct wl_event_queue *queue; + struct wayland_window window; +}; - while (wl_display_dispatch(display)) +static struct wl_display *g_display = NULL; +static struct window_thread_slot g_slots[MAX_WINDOW_THREADS]; +static pthread_mutex_t g_thread_lock = PTHREAD_MUTEX_INITIALIZER; +static pthread_cond_t g_thread_cond = PTHREAD_COND_INITIALIZER; +static atomic_int g_active_threads = 0; +static atomic_bool g_shutdown = 0; +static int g_initialized = 0; + +static void signal_thread_exit(struct window_thread_slot *slot) +{ + pthread_mutex_lock(&g_thread_lock); + slot->active = 0; + atomic_fetch_sub(&g_active_threads, 1); + pthread_cond_broadcast(&g_thread_cond); + pthread_mutex_unlock(&g_thread_lock); +} + +static void *window_thread_main(void *arg) +{ + struct window_thread_slot *slot = arg; + memset(&slot->window, 0, sizeof(slot->window)); + + slot->queue = wl_display_create_queue(g_display); + if (!slot->queue) { - if (window_should_close(&win)) + signal_thread_exit(slot); + return NULL; + } + + if (registry_thread_bind(g_display, slot->queue) < 0) + { + input_cleanup(); + wl_event_queue_destroy(slot->queue); + slot->queue = NULL; + signal_thread_exit(slot); + return NULL; + } + + if (window_init(g_display, &slot->window) < 0) + { + input_cleanup(); + registry_thread_unbind(); + wl_event_queue_destroy(slot->queue); + slot->queue = NULL; + signal_thread_exit(slot); + return NULL; + } + + while (!atomic_load(&g_shutdown) && !window_should_close(&slot->window)) + { + int dispatch = wl_display_dispatch_queue(g_display, slot->queue); + if (dispatch < 0) { - printf("Window closing\n"); + if (errno == EINTR) + continue; + atomic_store(&g_shutdown, 1); break; } } - window_destroy(&win); + printf("Window # closed"); + input_cleanup(); - wl_display_disconnect(display); + window_destroy(&slot->window); + registry_thread_unbind(); + + if (slot->queue) + { + wl_event_queue_destroy(slot->queue); + slot->queue = NULL; + } + + signal_thread_exit(slot); + return NULL; +} + +int32_t init_wayland(void) +{ + if (g_initialized) + return 0; + + g_display = wl_display_connect(NULL); + if (!g_display) + return -1; + + struct wl_registry *registry = wl_display_get_registry(g_display); + if (!registry) + { + wl_display_disconnect(g_display); + g_display = NULL; + return -1; + } + + registry_add_listener(registry); + if (wl_display_roundtrip(g_display) < 0) + { + wl_registry_destroy(registry); + wl_display_disconnect(g_display); + g_display = NULL; + return -1; + } + wl_registry_destroy(registry); + + atomic_store(&g_shutdown, 0); + atomic_store(&g_active_threads, 0); + g_initialized = 1; return 0; } + +int32_t run_window(void) +{ + if (!g_initialized || !g_display) + return -1; + + int slot_index = -1; + + pthread_mutex_lock(&g_thread_lock); + for (int i = 0; i < MAX_WINDOW_THREADS; ++i) + { + if (!g_slots[i].active) + { + slot_index = i; + g_slots[i].active = 1; + atomic_fetch_add(&g_active_threads, 1); + break; + } + } + pthread_mutex_unlock(&g_thread_lock); + + if (slot_index < 0) + return -1; + + int create_res = pthread_create(&g_slots[slot_index].thread, NULL, window_thread_main, &g_slots[slot_index]); + if (create_res != 0) + { + pthread_mutex_lock(&g_thread_lock); + g_slots[slot_index].active = 0; + atomic_fetch_sub(&g_active_threads, 1); + pthread_mutex_unlock(&g_thread_lock); + return -1; + } + + pthread_detach(g_slots[slot_index].thread); + return slot_index; +} + +void wait_for_windows(void) +{ + pthread_mutex_lock(&g_thread_lock); + while (atomic_load(&g_active_threads) > 0) + pthread_cond_wait(&g_thread_cond, &g_thread_lock); + pthread_mutex_unlock(&g_thread_lock); +} + +void destroy_wayland(void) +{ + if (!g_initialized) + return; + + wait_for_windows(); + + input_cleanup(); + registry_reset_globals(); + + if (g_display) + { + wl_display_disconnect(g_display); + g_display = NULL; + } + + memset(g_slots, 0, sizeof(g_slots)); + + g_initialized = 0; + atomic_store(&g_shutdown, 0); +} diff --git a/wayland/src/input.c b/wayland/src/input.c index 98290ee..68ded98 100644 --- a/wayland/src/input.c +++ b/wayland/src/input.c @@ -6,11 +6,11 @@ #include #include -static struct wl_seat *seat = NULL; -static struct wl_keyboard *keyboard = NULL; -static struct xkb_context *xkb_ctx = NULL; -static struct xkb_keymap *xkb_keymap = NULL; -static struct xkb_state *xkb_state = NULL; +static __thread struct wl_seat *seat = NULL; +static __thread struct wl_keyboard *keyboard = NULL; +static __thread struct xkb_context *xkb_ctx = NULL; +static __thread struct xkb_keymap *xkb_keymap = NULL; +static __thread struct xkb_state *xkb_state = NULL; /* keyboard callbacks */ static void keyboard_keymap(void *data, struct wl_keyboard *keyboard, uint32_t format, int fd, uint32_t size) @@ -156,6 +156,16 @@ void input_register_seat(struct wl_seat *s) { if (!s) return; + if (seat) + { + if (keyboard) + { + wl_keyboard_destroy(keyboard); + keyboard = NULL; + } + wl_seat_destroy(seat); + seat = NULL; + } seat = s; wl_seat_add_listener(seat, &seat_listener, NULL); } diff --git a/wayland/src/registry.c b/wayland/src/registry.c index 89e50fb..0a4d028 100644 --- a/wayland/src/registry.c +++ b/wayland/src/registry.c @@ -1,40 +1,63 @@ +/* Copyright (c) 202325? retaining? existing license? not specified */ + #include "registry.h" #include "input.h" +#include #include #include #include #include "xdg-shell-client-protocol.h" -static struct wl_compositor *compositor = NULL; -static struct xdg_wm_base *xdg_wm_base = NULL; -static struct wl_shm *shm = NULL; +struct registry_binding +{ + uint32_t name; + uint32_t version; +}; + +static struct registry_binding compositor_binding = {0}; +static struct registry_binding shm_binding = {0}; +static struct registry_binding wm_base_binding = {0}; +static struct registry_binding seat_binding = {0}; + +static __thread struct wl_compositor *tls_compositor; +static __thread struct wl_shm *tls_shm; +static __thread struct xdg_wm_base *tls_wm_base; + +static uint32_t clamp_version(uint32_t announced, uint32_t supported) +{ + return (announced < supported) ? announced : supported; +} static void registry_global(void *data, struct wl_registry *reg, uint32_t name, const char *intf, uint32_t version) { printf("%u\t%s\tv:%u\n", name, intf, version); if (!strcmp(intf, wl_compositor_interface.name)) { - compositor = wl_registry_bind(reg, name, &wl_compositor_interface, version); + compositor_binding.name = name; + compositor_binding.version = clamp_version(version, wl_compositor_interface.version); } else if (!strcmp(intf, wl_shm_interface.name)) { - shm = wl_registry_bind(reg, name, &wl_shm_interface, version); + shm_binding.name = name; + shm_binding.version = clamp_version(version, wl_shm_interface.version); } else if (!strcmp(intf, xdg_wm_base_interface.name)) { - xdg_wm_base = wl_registry_bind(reg, name, &xdg_wm_base_interface, version); - xdg_wm_base_add_listener(xdg_wm_base, NULL, NULL); /* will be set by window module if needed */ + wm_base_binding.name = name; + wm_base_binding.version = clamp_version(version, xdg_wm_base_interface.version); } else if (!strcmp(intf, wl_seat_interface.name)) { - struct wl_seat *seat = wl_registry_bind(reg, name, &wl_seat_interface, version); - /* Hook seat into input handler */ - input_register_seat(seat); + seat_binding.name = name; + seat_binding.version = clamp_version(version, wl_seat_interface.version); } } static void registry_global_remove(void *data, struct wl_registry *reg, uint32_t name) { + (void)data; + (void)reg; + (void)name; } static const struct wl_registry_listener registry_listener = { @@ -47,17 +70,94 @@ void registry_add_listener(struct wl_registry *registry) wl_registry_add_listener(registry, ®istry_listener, NULL); } +static void destroy_tls_objects(void) +{ + if (tls_compositor) + { + wl_compositor_destroy(tls_compositor); + tls_compositor = NULL; + } + if (tls_shm) + { + wl_shm_destroy(tls_shm); + tls_shm = NULL; + } + if (tls_wm_base) + { + xdg_wm_base_destroy(tls_wm_base); + tls_wm_base = NULL; + } +} + +int registry_thread_bind(struct wl_display *display, struct wl_event_queue *queue) +{ + if (!display || !queue) + return -1; + if (!compositor_binding.name || !shm_binding.name || !wm_base_binding.name) + return -1; + + destroy_tls_objects(); + + struct wl_registry *registry = wl_display_get_registry(display); + if (!registry) + return -1; + + wl_proxy_set_queue((struct wl_proxy *)registry, queue); + + tls_compositor = wl_registry_bind(registry, compositor_binding.name, &wl_compositor_interface, compositor_binding.version); + tls_shm = wl_registry_bind(registry, shm_binding.name, &wl_shm_interface, shm_binding.version); + tls_wm_base = wl_registry_bind(registry, wm_base_binding.name, &xdg_wm_base_interface, wm_base_binding.version); + + if (!tls_compositor || !tls_shm || !tls_wm_base) + { + wl_registry_destroy(registry); + destroy_tls_objects(); + return -1; + } + + wl_proxy_set_queue((struct wl_proxy *)tls_compositor, queue); + wl_proxy_set_queue((struct wl_proxy *)tls_shm, queue); + wl_proxy_set_queue((struct wl_proxy *)tls_wm_base, queue); + + if (seat_binding.name) + { + struct wl_seat *seat = wl_registry_bind(registry, seat_binding.name, &wl_seat_interface, seat_binding.version); + if (seat) + { + wl_proxy_set_queue((struct wl_proxy *)seat, queue); + input_register_seat(seat); + } + } + + wl_registry_destroy(registry); + return 0; +} + +void registry_thread_unbind(void) +{ + destroy_tls_objects(); +} + +void registry_reset_globals(void) +{ + destroy_tls_objects(); + compositor_binding.name = 0; + shm_binding.name = 0; + wm_base_binding.name = 0; + seat_binding.name = 0; +} + struct wl_compositor *registry_get_compositor(void) { - return compositor; + return tls_compositor; } struct wl_shm *registry_get_shm(void) { - return shm; + return tls_shm; } struct xdg_wm_base *registry_get_xdg_wm_base(void) { - return xdg_wm_base; + return tls_wm_base; } diff --git a/wayland/src/window.c b/wayland/src/window.c index 5b0bca0..512207b 100644 --- a/wayland/src/window.c +++ b/wayland/src/window.c @@ -9,6 +9,7 @@ #include #include #include +#include // Moving all window specific data into `struct wayland_window`. @@ -17,31 +18,62 @@ static struct xdg_surface_listener surface_listener; static struct xdg_toplevel_listener top_listener; static struct xdg_wm_base_listener wm_base_listener; +static atomic_uint_fast64_t shm_counter = 0; + static int32_t alloc_shm(size_t size) { - char name[32]; - sprintf(name, "/wayland-shm-%d", getpid()); + char name[64]; + uint64_t id = atomic_fetch_add(&shm_counter, 1); + snprintf(name, sizeof(name), "/wayland-shm-%d-%llu", getpid(), (unsigned long long)id); int32_t fd = shm_open(name, O_RDWR | O_CREAT, 0600); if (fd == -1) return fd; shm_unlink(name); - ftruncate(fd, size); + if (ftruncate(fd, size) == -1) + { + close(fd); + return -1; + } return fd; } +static void destroy_frame_callback(struct wayland_window *win) +{ + if (win->frame_callback) + { + wl_callback_destroy(win->frame_callback); + win->frame_callback = NULL; + } +} + static void resize_canvas(struct wayland_window *win) { size_t stride = win->width * 4; size_t size = stride * win->height; int32_t fd = alloc_shm(size); + if (fd == -1) + return; - win->data = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0); + if (win->buffer) + { + wl_buffer_destroy(win->buffer); + win->buffer = NULL; + } + + void *map = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0); + if (map == MAP_FAILED) + { + close(fd); + return; + } struct wl_shm *shm = registry_get_shm(); struct wl_shm_pool *pool = wl_shm_create_pool(shm, fd, size); win->buffer = wl_shm_pool_create_buffer(pool, 0, win->width, win->height, stride, WL_SHM_FORMAT_ARGB8888); wl_shm_pool_destroy(pool); close(fd); + + win->data = map; } static void resize_new(struct wayland_window *win, int16_t w, int16_t h) @@ -50,8 +82,11 @@ static void resize_new(struct wayland_window *win, int16_t w, int16_t h) return; if (win->width != w || win->height != h) { - if(win->data) - munmap(win->data, win->width*win->height*4); + if (win->data) + { + munmap(win->data, win->width * win->height * 4); + win->data = NULL; + } win->width = w; win->height = h; resize_canvas(win); @@ -63,6 +98,9 @@ static void draw(struct wayland_window *win) size_t stride = win->width * 4; size_t size = stride * win->height; + if (!win->data || !win->buffer) + return; + memset(win->data, win->color++, size); wl_surface_attach(win->wl_surface, win->buffer, 0, 0); @@ -104,12 +142,20 @@ static void xdg_toplevel_wm_caps(void *data, struct xdg_toplevel *xdg_top, struc (void)data; (void)xdg_top; (void)caps; } -static void frame_new(void *date, struct wl_callback *cb, uint32_t _) +static void setup_frame_callback(struct wayland_window *win) { - struct wayland_window *win = date; - wl_callback_destroy(cb); - cb = wl_surface_frame(win->wl_surface); + struct wl_callback *cb = wl_surface_frame(win->wl_surface); + win->frame_callback = cb; wl_callback_add_listener(cb, &callback_listener, win); +} + +static void frame_new(void *data, struct wl_callback *cb, uint32_t _) +{ + struct wayland_window *win = data; + if (win->frame_callback == cb) + win->frame_callback = NULL; + wl_callback_destroy(cb); + setup_frame_callback(win); draw(win); } @@ -128,10 +174,10 @@ int window_init(struct wl_display *display, struct wayland_window *win) return -1; win->wl_surface = wl_compositor_create_surface(compositor); - struct wl_callback *wl_callback = wl_surface_frame(win->wl_surface); callback_listener.done = frame_new; - wl_callback_add_listener(wl_callback, &callback_listener, win); + win->frame_callback = NULL; + setup_frame_callback(win); win->xdg_surface = xdg_wm_base_get_xdg_surface(wm, win->wl_surface); surface_listener.configure = xdg_surface_conf; @@ -156,6 +202,7 @@ int window_init(struct wl_display *display, struct wayland_window *win) win->height = 100; win->data = NULL; win->buffer = NULL; + win->frame_callback = NULL; win->need_close = 0; win->color = 0; @@ -169,6 +216,7 @@ int window_should_close(struct wayland_window *win) void window_destroy(struct wayland_window *win) { + destroy_frame_callback(win); if (win->buffer) wl_buffer_destroy(win->buffer); if (win->xdg_toplevel)