From 40611124607681b1f597fa7a259425b53ecdf438 Mon Sep 17 00:00:00 2001 From: IoIxD Date: Mon, 28 Sep 2026 12:08:32 -0700 Subject: [PATCH 1/3] fix memleaks --- include/Mw/LowLevel/Wayland.h | 2 +- src/backend/cairo.c | 16 ++- src/backend/wayland/buffer.c | 50 +++++++--- src/backend/wayland/interfaces.c | 148 +++++++++++++++++++++++----- src/backend/wayland/wayland.c | 164 +++++++++++++++++++++++-------- src/core.c | 2 +- 6 files changed, 294 insertions(+), 88 deletions(-) diff --git a/include/Mw/LowLevel/Wayland.h b/include/Mw/LowLevel/Wayland.h index e94ed950..6c4a4e65 100644 --- a/include/Mw/LowLevel/Wayland.h +++ b/include/Mw/LowLevel/Wayland.h @@ -396,7 +396,7 @@ struct _MwLLWayland { struct wl_data_device_manager* wl; struct zwp_primary_selection_device_manager_v1* zwp; } clipboard_manager; - union { + struct { struct wl_data_source* wl; struct zwp_primary_selection_source_v1* zwp; } clipboard_source; diff --git a/src/backend/cairo.c b/src/backend/cairo.c index 02ed9d0a..4c6a0174 100644 --- a/src/backend/cairo.c +++ b/src/backend/cairo.c @@ -134,16 +134,28 @@ void MwLLCairoBackSetup(struct _MwLLCairo* cairo, MwU8* data, MwU32 width, MwU32 cairo->back_cairo = cairo_create(cairo->back_cs); } +/* Pointers are cleared so that destroying twice (or before any setup) is harmless; cairo ignores NULL. */ void MwLLCairoFrontDestroy(struct _MwLLCairo* cairo) { cairo_destroy(cairo->front_cairo); cairo_destroy(cairo->front_cairo_back); - cairo_surface_destroy(cairo->front_cs); - cairo_surface_destroy(cairo->frontbuffer_cs); cairo_destroy(cairo->frontbuffer_cairo); + cairo_surface_destroy(cairo->front_cs); + cairo_surface_destroy(cairo->front_cs_back); + cairo_surface_destroy(cairo->frontbuffer_cs); + + cairo->front_cairo = NULL; + cairo->front_cairo_back = NULL; + cairo->frontbuffer_cairo = NULL; + cairo->front_cs = NULL; + cairo->front_cs_back = NULL; + cairo->frontbuffer_cs = NULL; }; void MwLLCairoBackDestroy(struct _MwLLCairo* cairo) { cairo_destroy(cairo->back_cairo); cairo_surface_destroy(cairo->back_cs); + + cairo->back_cairo = NULL; + cairo->back_cs = NULL; }; void MwLLCairoDestroy(struct _MwLLCairo handle) { diff --git a/src/backend/wayland/buffer.c b/src/backend/wayland/buffer.c index 04ba04b1..992b84c2 100644 --- a/src/backend/wayland/buffer.c +++ b/src/backend/wayland/buffer.c @@ -5,6 +5,7 @@ void MwLLWaylandFramebufferSetup(struct _MwLLWayland* wayland) { MwLLWaylandBufferSetup(&wayland->framebuffer, wayland->ww, wayland->wh); + MwLLCairoFrontDestroy(&wayland->cairo); MwLLCairoFrontSetup(&wayland->cairo, wayland->framebuffer.buf_back, wayland->ww, wayland->wh); memset(wayland->framebuffer.buf_back, 255, wayland->framebuffer.buf_size); @@ -38,6 +39,7 @@ void MwLLWaylandBackbufferSetup(struct _MwLLWayland* wayland) { } MwLLWaylandBufferSetup(&wayland->backbuffer, w, h); + MwLLCairoBackDestroy(&wayland->cairo); MwLLCairoBackSetup(&wayland->cairo, wayland->backbuffer.buf_back, w, h); memset(wayland->backbuffer.buf_back, 0, wayland->backbuffer.buf_size); @@ -57,40 +59,44 @@ void MwLLWaylandBackbufferDestroy(struct _MwLLWayland* wayland) { void MwLLWaylandBufferSetup(struct _MwLLWaylandShmBuffer* buffer, MwU32 width, MwU32 height) { int stride = width * 4; + int err; char temp_name[] = "/tmp/milsko-wl-shm-XXXXXXXX"; char temp_name_back[] = "/tmp/milsko-wl-shm-back-XXXXXXXX"; + MwLLWaylandBufferDestroy(buffer); + buffer->buf_size = width * height * 4; buffer->fd = mkstemp(temp_name); buffer->fd_back = mkstemp(temp_name_back); if(buffer->fd >= 65536) { MwDispatchError(-1, "Amount of allocated buffers has gone above 65536! Cannot continue.\n"); - return; + goto fail; + } + if(buffer->fd == -1 || buffer->fd_back == -1) { + printf("failure setting up wl_shm: could not create file. %s.\n", strerror(errno)); + goto fail; } unlink(temp_name); unlink(temp_name_back); - if(posix_fallocate(buffer->fd, 0, buffer->buf_size) != 0) { - printf("failure setting up wl_shm (front buf): could not fallocate. %s.\n", strerror(errno)); - close(buffer->fd); - return; + /* posix_fallocate returns the error rather than setting errno */ + if((err = posix_fallocate(buffer->fd, 0, buffer->buf_size)) != 0) { + printf("failure setting up wl_shm (front buf): could not fallocate %llu bytes. %s.\n", (unsigned long long)buffer->buf_size, strerror(err)); + goto fail; } - if(posix_fallocate(buffer->fd_back, 0, buffer->buf_size) != 0) { - printf("failure setting up wl_shm (back buf): could not fallocate. %s.\n", strerror(errno)); - close(buffer->fd_back); - return; + if((err = posix_fallocate(buffer->fd_back, 0, buffer->buf_size)) != 0) { + printf("failure setting up wl_shm (back buf): could not fallocate %llu bytes. %s.\n", (unsigned long long)buffer->buf_size, strerror(err)); + goto fail; } if(ftruncate(buffer->fd, buffer->buf_size) != 0) { printf("failure setting up wl_shm: could not truncate. %s.\n", strerror(errno)); - close(buffer->fd); - return; + goto fail; } if(ftruncate(buffer->fd_back, buffer->buf_size) != 0) { printf("failure setting up wl_shm: could not truncate. %s.\n", strerror(errno)); - close(buffer->fd_back); - return; + goto fail; } buffer->buf = mmap(NULL, buffer->buf_size, PROT_WRITE, MAP_SHARED, buffer->fd, 0); @@ -110,6 +116,13 @@ void MwLLWaylandBufferSetup(struct _MwLLWaylandShmBuffer* buffer, MwU32 width, M buffer->shm_buffer = wl_shm_pool_create_buffer(buffer->shm_pool, 0, width, height, stride, WL_SHM_FORMAT_ARGB8888); buffer->shm_buffer_back = wl_shm_pool_create_buffer(buffer->shm_pool_back, 0, width, height, stride, WL_SHM_FORMAT_ARGB8888); buffer->setup = MwTRUE; + return; + +fail: + if(buffer->fd >= 0) close(buffer->fd); + if(buffer->fd_back >= 0) close(buffer->fd_back); + buffer->fd = -1; + buffer->fd_back = -1; } void MwLLWaylandBufferDestroy(struct _MwLLWaylandShmBuffer* buffer) { @@ -124,7 +137,16 @@ void MwLLWaylandBufferDestroy(struct _MwLLWaylandShmBuffer* buffer) { if(buffer->shm_pool_back) wl_shm_pool_destroy(buffer->shm_pool_back); close(buffer->fd); close(buffer->fd_back); - buffer->setup = MwFALSE; + + buffer->buf = NULL; + buffer->buf_back = NULL; + buffer->shm_buffer = NULL; + buffer->shm_buffer_back = NULL; + buffer->shm_pool = NULL; + buffer->shm_pool_back = NULL; + buffer->fd = -1; + buffer->fd_back = -1; + buffer->setup = MwFALSE; } void MwLLWaylandBufferResize(struct _MwLLWaylandShmBuffer* buffer, MwU32 width, MwU32 height) { diff --git a/src/backend/wayland/interfaces.c b/src/backend/wayland/interfaces.c index 02d5a85d..e86586af 100644 --- a/src/backend/wayland/interfaces.c +++ b/src/backend/wayland/interfaces.c @@ -17,8 +17,8 @@ static void setup_clipboard(MwLL self, struct wl_seat* wl_seat); static void setup_zwp_clipboard(MwLL self, struct wl_seat* wl_seat); -static void destroy_clipboard(MwLL self, struct wl_seat* wl_seat); -static void destroy_zwp_clipboard(MwLL self, struct wl_seat* wl_seat); +static void destroy_clipboard(MwLL self); +static void destroy_zwp_clipboard(MwLL self); static MwBool hit_detect(MwLL child, MwLL* _topmost_parent, MwPoint* _point, MwPoint* _relative_mouse_pos, MwPoint* _absolute_pos); /* Recursively dispatch a key event to a widget and its children */ @@ -133,6 +133,9 @@ static void wl_data_device_data_offer(void* data, wl_data_offer_add_listener(offer, &offer_listener, data); + if(self->offer.wl && self->offer.wl != offer) { + wl_data_offer_destroy(self->offer.wl); + } self->offer.wl = offer; }; @@ -320,6 +323,9 @@ static void zwp_primary_selection_device_v1_data_offer(void* data, wl_clipboard_device_context_t* self = data; (void)zwp_primary_selection_device_v1; + if(self->offer.zwp && self->offer.zwp != offer) { + zwp_primary_selection_offer_v1_destroy(self->offer.zwp); + } self->offer.zwp = offer; }; @@ -491,14 +497,15 @@ static wayland_protocol_t* wl_data_device_manager_setup(MwU32 name, struct _MwLL static void wl_data_device_manager_interface_destroy(struct _MwLLWayland* wayland, wayland_protocol_t* data) { (void)data; + destroy_clipboard((MwLL)wayland); + if(wayland->clipboard_source.wl) { + wl_data_source_destroy(wayland->clipboard_source.wl); + wayland->clipboard_source.wl = NULL; + } if(wayland->clipboard_manager.wl != NULL) { wl_data_device_manager_destroy(wayland->clipboard_manager.wl); wayland->clipboard_manager.wl = NULL; } - if(wayland->clipboard_source.wl) { - // wl_data_source_destroy(wayland->clipboard_source.wl); - wayland->clipboard_source.wl = NULL; - } } /* zwp_primary_selection_device_manager_v1 setup function */ @@ -526,8 +533,17 @@ static wayland_protocol_t* zwp_primary_selection_device_manager_v1_setup(MwU32 n } static void zwp_primary_selection_device_manager_v1_interface_destroy(struct _MwLLWayland* wayland, wayland_protocol_t* data) { - (void)wayland; (void)data; + destroy_zwp_clipboard((MwLL)wayland); + if(wayland->clipboard_source.zwp) { + zwp_primary_selection_source_v1_destroy(wayland->clipboard_source.zwp); + wayland->clipboard_source.zwp = NULL; + } + if(wayland->clipboard_manager.zwp != NULL) { + zwp_primary_selection_device_manager_v1_destroy(wayland->clipboard_manager.zwp); + wayland->clipboard_manager.zwp = NULL; + } + wayland->supports_zwp = MwFALSE; } /* zwp_primary_selection_device_manager_v1 setup function */ @@ -538,8 +554,15 @@ static wayland_protocol_t* zwp_pointer_constraints_v1_setup(MwU32 name, struct _ } static void zwp_pointer_constraints_v1_interface_destroy(struct _MwLLWayland* wayland, wayland_protocol_t* data) { - (void)wayland; (void)data; + if(wayland->locked_pointer) { + zwp_locked_pointer_v1_destroy(wayland->locked_pointer); + wayland->locked_pointer = NULL; + } + if(wayland->pointer_constraints) { + zwp_pointer_constraints_v1_destroy(wayland->pointer_constraints); + wayland->pointer_constraints = NULL; + } } static struct wl_surface* curSurface = NULL; @@ -667,8 +690,15 @@ static wayland_protocol_t* zwp_relative_pointer_manager_v1_setup(MwU32 name, str } static void zwp_relative_pointer_manager_v1_interface_destroy(struct _MwLLWayland* wayland, wayland_protocol_t* data) { - (void)wayland; (void)data; + if(wayland->relative_pointer) { + zwp_relative_pointer_v1_destroy(wayland->relative_pointer); + wayland->relative_pointer = NULL; + } + if(wayland->relative_pointer_manager) { + zwp_relative_pointer_manager_v1_destroy(wayland->relative_pointer_manager); + wayland->relative_pointer_manager = NULL; + } } /* `wl_pointer.motion` callback */ @@ -918,9 +948,16 @@ static void keyboard_keymap(void* data, struct xkb_state* xkb_state = xkb_state_new(xkb_keymap); + /* the compositor sends a new keymap whenever the layout changes */ + if(self->wayland.xkb_state) xkb_state_unref(self->wayland.xkb_state); + if(self->wayland.xkb_keymap) xkb_keymap_unref(self->wayland.xkb_keymap); + self->wayland.xkb_keymap = xkb_keymap; self->wayland.xkb_state = xkb_state; } else { + /* we don't read it, but the fd is still ours to close */ + close(fd); + self->wayland.xkb_keymap = self->wayland.parent->wayland.xkb_keymap; self->wayland.xkb_state = self->wayland.parent->wayland.xkb_state; } @@ -1159,6 +1196,31 @@ static void setup_clipboard(MwLL self, struct wl_seat* wl_seat) { device_ctx_wl->accepted_types[5] = "text/uri-list"; }; +static void destroy_zwp_clipboard(MwLL self) { + int i; + for(i = 0; i < arrlen(self->wayland.clipboard_devices_zwp); i++) { + wl_clipboard_device_context_t* ctx = self->wayland.clipboard_devices_zwp[i]; + if(ctx->offer.zwp) zwp_primary_selection_offer_v1_destroy(ctx->offer.zwp); + zwp_primary_selection_device_v1_destroy(ctx->device.zwp); + free(ctx); + } + arrfree(self->wayland.clipboard_devices_zwp); +} +static void destroy_clipboard(MwLL self) { + int i; + for(i = 0; i < arrlen(self->wayland.clipboard_devices_wl); i++) { + wl_clipboard_device_context_t* ctx = self->wayland.clipboard_devices_wl[i]; + if(ctx->offer.wl) wl_data_offer_destroy(ctx->offer.wl); + if(wl_proxy_get_version((struct wl_proxy*)ctx->device.wl) >= WL_DATA_DEVICE_RELEASE_SINCE_VERSION) { + wl_data_device_release(ctx->device.wl); + } else { + wl_data_device_destroy(ctx->device.wl); + } + free(ctx); + } + arrfree(self->wayland.clipboard_devices_wl); +} + static void keyboard_repeat_info(void* data, struct wl_keyboard* wl_keyboard, int32_t rate, @@ -1307,8 +1369,10 @@ static void wl_seat_interface_destroy(struct _MwLLWayland* wayland, wayland_prot wayland->pointer_seat = NULL; } - free(data->listener); - free(data); + if(data) { + free(data->listener); + free(data); + } } /* wl_output setup function */ @@ -1321,8 +1385,11 @@ static wayland_protocol_t* wl_output_setup(MwU32 name, MwLL ll, MwU32 version) { } static void wl_output_interface_destroy(struct _MwLLWayland* wayland, wayland_protocol_t* data) { - (void)wayland; (void)data; + if(wayland->output) { + wl_output_destroy(wayland->output); + wayland->output = NULL; + } } /* wl_compositor setup function */ @@ -1334,8 +1401,11 @@ static wayland_protocol_t* wl_compositor_setup(MwU32 name, struct _MwLLWayland* } static void wl_compositor_interface_destroy(struct _MwLLWayland* wayland, wayland_protocol_t* data) { - (void)wayland; (void)data; + if(wayland->compositor) { + wl_compositor_destroy(wayland->compositor); + wayland->compositor = NULL; + } } /* wl_subcompositor setup function */ @@ -1351,13 +1421,10 @@ static wayland_protocol_t* wl_subcompositor_setup(MwU32 name, struct _MwLLWaylan } static void wl_subcompositor_interface_destroy(struct _MwLLWayland* wayland, wayland_protocol_t* data) { + (void)wayland; (void)data; - if(wayland->type == MwLL_WAYLAND_TOPLEVEL) { - wl_subcompositor_destroy(wayland->toplevel->scompositor); - } - if(wayland->type == MwLL_WAYLAND_SUBSURFACE) { - wl_subcompositor_destroy(wayland->subsurface->subcompositor); - } + /* The subcompositor lives in the toplevel/subsurface struct, which is already freed by the time this runs, + * so destroy_toplevel()/destroy_subsurface() release it instead. */ } /* xdg_wm_base setup function */ @@ -1376,8 +1443,11 @@ static wayland_protocol_t* xdg_wm_base_setup(MwU32 name, struct _MwLLWayland* wa static void xdg_wm_base_interface_destroy(struct _MwLLWayland* wayland, wayland_protocol_t* data) { (void)wayland; - free(data->listener); - free(data); + if(data) { + xdg_wm_base_destroy(data->context); + free(data->listener); + free(data); + } } /* zxdg_decoration_manager_v1 setup function */ @@ -1387,8 +1457,8 @@ static wayland_protocol_t* zxdg_decoration_manager_v1_setup(MwU32 name, struct _ (void)version; proto->listener = NULL; - ctx->manager = wl_registry_bind(wayland->registry, name, &zxdg_decoration_manager_v1_interface, 1); - ; + ctx->manager = wl_registry_bind(wayland->registry, name, &zxdg_decoration_manager_v1_interface, 1); + ctx->decoration = NULL; proto->context = ctx; @@ -1396,8 +1466,15 @@ static wayland_protocol_t* zxdg_decoration_manager_v1_setup(MwU32 name, struct _ } static void zxdg_decoration_manager_v1_interface_destroy(struct _MwLLWayland* wayland, wayland_protocol_t* data) { + zxdg_decoration_manager_v1_context_t* ctx; (void)wayland; - (void)data; + if(!data) return; + + /* the toplevel decoration itself has to go before the xdg_toplevel, so destroy_toplevel() handles that */ + ctx = data->context; + zxdg_decoration_manager_v1_destroy(ctx->manager); + free(ctx); + free(data); } /* wl_shm setup function */ @@ -1411,8 +1488,13 @@ static wayland_protocol_t* wl_shm_setup(MwU32 name, struct _MwLLWayland* wayland } static void wl_shm_interface_destroy(struct _MwLLWayland* wayland, wayland_protocol_t* data) { - (void)wayland; (void)data; + if(wayland->framebuffer.shm) wl_shm_destroy(wayland->framebuffer.shm); + if(wayland->backbuffer.shm) wl_shm_destroy(wayland->backbuffer.shm); + if(wayland->cursor.shm) wl_shm_destroy(wayland->cursor.shm); + wayland->framebuffer.shm = NULL; + wayland->backbuffer.shm = NULL; + wayland->cursor.shm = NULL; } static wayland_protocol_t* xdg_toplevel_icon_manager_v1_setup(MwU32 name, struct _MwLLWayland* wayland, MwU32 version) { @@ -1428,8 +1510,11 @@ static wayland_protocol_t* xdg_toplevel_icon_manager_v1_setup(MwU32 name, struct static void xdg_toplevel_icon_manager_v1_interface_destroy(struct _MwLLWayland* wayland, wayland_protocol_t* data) { (void)wayland; - free(data->listener); - free(data); + if(data) { + xdg_toplevel_icon_manager_v1_destroy(data->context); + free(data->listener); + free(data); + } } static wayland_protocol_t* zwlr_layer_shell_v1_setup(MwU32 name, struct _MwLLWayland* wayland, MwU32 version) { @@ -1443,7 +1528,14 @@ static wayland_protocol_t* zwlr_layer_shell_v1_setup(MwU32 name, struct _MwLLWay static void zwlr_layer_shell_v1_interface_destroy(struct _MwLLWayland* wayland, wayland_protocol_t* data) { (void)wayland; + if(!data) return; + /* the destroy request only exists from version 3, before that the proxy is all there is to free */ + if(wl_proxy_get_version(data->context) >= ZWLR_LAYER_SHELL_V1_DESTROY_SINCE_VERSION) { + zwlr_layer_shell_v1_destroy(data->context); + } else { + wl_proxy_destroy(data->context); + } free(data); } @@ -1458,7 +1550,9 @@ static wayland_protocol_t* wp_fifo_manager_v1_setup(MwU32 name, struct _MwLLWayl static void wp_fifo_manager_v1_interface_destroy(struct _MwLLWayland* wayland, wayland_protocol_t* data) { (void)wayland; + if(!data) return; + wp_fifo_manager_v1_destroy(data->context); free(data); } diff --git a/src/backend/wayland/wayland.c b/src/backend/wayland/wayland.c index fb8cb020..80e0da58 100644 --- a/src/backend/wayland/wayland.c +++ b/src/backend/wayland/wayland.c @@ -342,10 +342,14 @@ static void setup_toplevel(MwLL r, int x, int y) { /* Toplevel destroy function */ static void destroy_toplevel(MwLL r) { - if(shget(r->wayland.wl_protocol_map, zxdg_decoration_manager_v1_interface.name) != NULL) { + /* The toplevel decoration must go before the xdg_toplevel. The manager itself is released in + * zxdg_decoration_manager_v1_interface_destroy along with every other global. */ + if(WAYLAND_GET_INTERFACE(r->wayland, zxdg_decoration_manager_v1) != NULL) { zxdg_decoration_manager_v1_context_t* dec = WAYLAND_GET_INTERFACE(r->wayland, zxdg_decoration_manager_v1)->context; - zxdg_toplevel_decoration_v1_destroy(dec->decoration); - zxdg_decoration_manager_v1_destroy(dec->manager); + if(dec->decoration) { + zxdg_toplevel_decoration_v1_destroy(dec->decoration); + dec->decoration = NULL; + } } MwLLWaylandFramebufferDestroy(&r->wayland); @@ -355,19 +359,34 @@ static void destroy_toplevel(MwLL r) { xdg_surface_destroy(r->wayland.toplevel->xdg_surface); - wl_compositor_destroy(r->wayland.compositor); + xkb_state_unref(r->wayland.xkb_state); xkb_keymap_unref(r->wayland.xkb_keymap); - xkb_state_unref(r->wayland.xkb_state); - xkb_context_unref(r->wayland.xkb_context); + r->wayland.xkb_state = NULL; + r->wayland.xkb_keymap = NULL; + r->wayland.xkb_context = NULL; + /* wl_pointer/wl_keyboard/wl_seat are torn down centrally in wl_seat_interface_destroy, * after this function returns, so every widget type that binds a seat (toplevel, popup, * layer surface) releases them the same way instead of leaking or double-freeing them. */ + /* framebuffer.surface has the wl_subsurface role, so that has to go first or the compositor raises defunct_role_object. */ + wl_subsurface_destroy(r->wayland.toplevel->ssurface); + wl_subcompositor_destroy(r->wayland.toplevel->scompositor); + + if(r->wayland.framebuffer.fifo) { + wp_fifo_v1_destroy(r->wayland.framebuffer.fifo); + r->wayland.framebuffer.fifo = NULL; + } + wl_surface_destroy(r->wayland.framebuffer.surface); + r->wayland.framebuffer.surface = NULL; + + wl_surface_destroy(r->wayland.backbuffer.surface); + r->wayland.backbuffer.surface = NULL; free(r->wayland.toplevel); @@ -422,6 +441,10 @@ static void destroy_sublevel(MwLL r) { MwLLWaylandBackbufferDestroy(&r->wayland); MwLLWaylandFramebufferDestroy(&r->wayland); + /* xkb state is borrowed from the parent, so it is not ours to unref */ + wl_surface_destroy(r->wayland.framebuffer.surface); + r->wayland.framebuffer.surface = NULL; + free(r->wayland.sublevel); r->wayland.configured = MwFALSE; @@ -482,7 +505,14 @@ static void destroy_subsurface(MwLL r) { MwLLWaylandBackbufferDestroy(&r->wayland); MwLLWaylandFramebufferDestroy(&r->wayland); - free(r->wayland.sublevel); + /* role object before the surface */ + wl_subsurface_destroy(r->wayland.subsurface->subsurface); + wl_subcompositor_destroy(r->wayland.subsurface->subcompositor); + + wl_surface_destroy(r->wayland.framebuffer.surface); + r->wayland.framebuffer.surface = NULL; + + free(r->wayland.subsurface); r->wayland.configured = MwFALSE; } @@ -642,8 +672,7 @@ static void destroy_popup(MwLL r) { xdg_positioner_destroy(r->wayland.popup->xdg_positioner); wl_surface_destroy(r->wayland.framebuffer.surface); - - wl_registry_destroy(r->wayland.registry); + r->wayland.framebuffer.surface = NULL; free(r->wayland.popup); @@ -747,24 +776,27 @@ static void setup_layer_surface(MwLL r, int x, int y, int width, int height) { } } -/* Toplevel destroy function */ +/* Layer surface destroy function */ static void destroy_layer_surface(MwLL r) { - if(shget(r->wayland.wl_protocol_map, zxdg_decoration_manager_v1_interface.name) != NULL) { - zxdg_decoration_manager_v1_context_t* dec = WAYLAND_GET_INTERFACE(r->wayland, zxdg_decoration_manager_v1)->context; - zxdg_decoration_manager_v1_destroy(dec->manager); - } + MwLLWaylandFramebufferDestroy(&r->wayland); + + /* The role object has to go before the wl_surface, otherwise the compositor raises defunct_role_object. */ + zwlr_layer_surface_v1_destroy(r->wayland.layer_surface->surface); + + free(r->wayland.layer_surface); wl_surface_destroy(r->wayland.framebuffer.surface); - - wl_compositor_destroy(r->wayland.compositor); - - xkb_keymap_unref(r->wayland.xkb_keymap); + r->wayland.framebuffer.surface = NULL; xkb_state_unref(r->wayland.xkb_state); + xkb_keymap_unref(r->wayland.xkb_keymap); + xkb_context_unref(r->wayland.xkb_context); - wl_registry_destroy(r->wayland.registry); + r->wayland.xkb_state = NULL; + r->wayland.xkb_keymap = NULL; + r->wayland.xkb_context = NULL; r->wayland.configured = MwFALSE; } @@ -793,6 +825,9 @@ static void destroy_widget(MwLL handle) { case MwLL_WAYLAND_LAYER_SURFACE: destroy_layer_surface(handle); break; + case MwLL_WAYLAND_SUBSURFACE: + destroy_subsurface(handle); + break; default: printf("Handle with unknown type(%d) tried to be destroyed (%p)\n", handle->wayland.type, handle); break; @@ -805,18 +840,22 @@ static void destroy_widget(MwLL handle) { } for(i = 0; i < shlen(handle->wayland.wl_protocol_setup_map); i++) { - void* ctx = shget(handle->wayland.wl_protocol_map, handle->wayland.wl_protocol_setup_map[i].key); + const char* key = handle->wayland.wl_protocol_setup_map[i].key; - if(ctx != NULL) { - handle->wayland.wl_protocol_setup_map[i].value->destroy(&handle->wayland, ctx); + /* Many setup functions keep what they bind in the MwLL and store a NULL context, so check + * whether the global was bound at all rather than whether the context is NULL. */ + if(shgeti(handle->wayland.wl_protocol_map, key) != -1) { + handle->wayland.wl_protocol_setup_map[i].value->destroy(&handle->wayland, shget(handle->wayland.wl_protocol_map, key)); } - shdel(handle->wayland.wl_protocol_map, handle->wayland.wl_protocol_setup_map[i].value); free(handle->wayland.wl_protocol_setup_map[i].value); } shfree(handle->wayland.wl_protocol_map); shfree(handle->wayland.wl_protocol_setup_map); + wl_registry_destroy(handle->wayland.registry); + handle->wayland.registry = NULL; + MwLLWaylandFlush(handle); } @@ -880,6 +919,9 @@ static void clip(MwLL handle) { cairo_clip(handle->wayland.cairo.front_cairo_back); } } + + /* parentless handles skip the branch above (arrfree sets ws to NULL, so this is a no-op otherwise) */ + arrfree(ws); } static void wl_logger(const char* fmt, va_list args) { @@ -949,6 +991,10 @@ static void widget_setup(MwLL r, MwLL parent, int x, int y, int width, int heigh } } + /* widget_setup runs again on every state change, drop the regions from the last one */ + if(r->wayland.region) wl_region_destroy(r->wayland.region); + if(r->wayland.o_region) wl_region_destroy(r->wayland.o_region); + r->wayland.region = wl_compositor_create_region(r->wayland.compositor); r->wayland.o_region = wl_compositor_create_region(r->wayland.compositor); MwLLWaylandRegionSetup(r); @@ -1090,6 +1136,9 @@ static void MwLLDestroyImpl(MwLL handle) { MwLLWaylandFlush(handle); MwLLDestroyCommon(handle); + /* destroy_widget() roundtrips, which can still deliver events (keyboard leave etc.) to this handle; + * MwLLDispatch skips a NULL handler table instead of reading the freed one */ + handle->common.handler = NULL; tv.tv_sec = 0; tv.tv_usec = 1000; @@ -1104,24 +1153,39 @@ static void MwLLDestroyImpl(MwLL handle) { #endif if(handle->wayland.valid) { - MwLLWaylandBufferDestroy(&handle->wayland.cursor); - wl_region_destroy(handle->wayland.region); - - if(handle->wayland.supports_zwp) { - zwp_primary_selection_source_v1_destroy(handle->wayland.clipboard_source.zwp); - } else { - wl_data_source_destroy(handle->wayland.clipboard_source.wl); + if(handle->wayland.cursor.setup) { + MwLLWaylandBufferDestroy(&handle->wayland.cursor); + wl_surface_destroy(handle->wayland.cursor.surface); } + wl_region_destroy(handle->wayland.region); + wl_region_destroy(handle->wayland.o_region); + + /* clipboard sources are released with their managers in destroy_widget */ if(handle->wayland.icon != NULL) { - MwLLWaylandBufferDestroy(handle->wayland.icon); - wl_surface_destroy(handle->wayland.icon->surface); + if(handle->wayland.icon->setup) { + MwLLWaylandBufferDestroy(handle->wayland.icon); + wl_surface_destroy(handle->wayland.icon->surface); + } + free(handle->wayland.icon); } destroy_widget(handle); + + /* Only parentless handles connect (children borrow the parent's display), and children are + * always freed before their parent, so nothing else is using it by now. */ + if(!handle->wayland.parent && handle->wayland.display) { + wl_display_disconnect(handle->wayland.display); + handle->wayland.display = NULL; + } } else { printf("widget invalid\n"); } + if(handle->wayland.icon_pixmap) MwLLDestroyPixmap(handle->wayland.icon_pixmap); + if(handle->wayland.snapshot) cairo_surface_destroy(handle->wayland.snapshot); + free(handle->wayland.clipboard_buffer); + arrfree(handle->wayland.currentlyHeldWidgets); + for(i = 0; i < arrlen(topmost_parent->wayland.currentlyHeldWidgets); i++) { if(topmost_parent->wayland.currentlyHeldWidgets[i] == handle) { arrdel(topmost_parent->wayland.currentlyHeldWidgets, i); @@ -1605,8 +1669,9 @@ static void MwLLSetIconImpl(MwLL handle, MwLLPixmap pixmap) { if(handle->wayland.icon == NULL) { handle->wayland.icon = malloc(sizeof(struct _MwLLWaylandShmBuffer)); memset(handle->wayland.icon, 0, sizeof(struct _MwLLWaylandShmBuffer)); - handle->wayland.icon->shm = handle->wayland.framebuffer.shm; } + /* wl_shm is rebound whenever the widget changes state, so don't hold on to an old one */ + handle->wayland.icon->shm = handle->wayland.framebuffer.shm; if(handle->wayland.icon->setup) { MwLLWaylandBufferDestroy(handle->wayland.icon); @@ -1619,11 +1684,17 @@ static void MwLLSetIconImpl(MwLL handle, MwLLPixmap pixmap) { wl_surface_attach(handle->wayland.icon->surface, handle->wayland.icon->shm_buffer, 0, 0); wl_surface_commit(handle->wayland.icon->surface); - for(; i < size; i += 2) { - handle->wayland.icon->buf_back[i] = pixmap->common.raw[i + 2]; - handle->wayland.icon->buf_back[i + 1] = pixmap->common.raw[i + 1]; - handle->wayland.icon->buf_back[i + 2] = pixmap->common.raw[i + 0]; - handle->wayland.icon->buf_back[i + 3] = 255; + /* RGBA -> BGRA, one pixel (4 bytes) at a time. The icon buffer is line x line, so rows + * have to be placed with its stride rather than the pixmap's. */ + for(; i < size; i += 4) { + int x = (i / 4) % pixmap->common.width; + int y = (i / 4) / pixmap->common.width; + unsigned char* dst = &handle->wayland.icon->buf_back[(y * line + x) * 4]; + + dst[0] = pixmap->common.raw[i + 2]; + dst[1] = pixmap->common.raw[i + 1]; + dst[2] = pixmap->common.raw[i + 0]; + dst[3] = 255; } if(handle->wayland.configured) MwLLWaylandBufferUpdate(handle, handle->wayland.icon); @@ -1631,6 +1702,9 @@ static void MwLLSetIconImpl(MwLL handle, MwLLPixmap pixmap) { xdg_toplevel_icon_v1_add_buffer(icon, handle->wayland.icon->shm_buffer_back, 1); xdg_toplevel_icon_manager_v1_set_icon(icon_manager, handle->wayland.toplevel->xdg_top_level, icon); + + /* the icon is immutable once set, destroying it doesn't unset it */ + xdg_toplevel_icon_v1_destroy(icon); } } if(!handle->wayland.has_decorations && handle->wayland.do_csd) { @@ -1794,11 +1868,15 @@ static void MwLLGrabPointerImpl(MwLL handle, int toggle) { while(topmost_parent->wayland.parent) topmost_parent = topmost_parent->wayland.parent; if(handle->wayland.pointer_constraints && handle->wayland.relative_pointer_manager) { if(toggle) { - topmost_parent->wayland.locked_pointer = zwp_pointer_constraints_v1_lock_pointer(topmost_parent->wayland.pointer_constraints, topmost_parent->wayland.backbuffer.surface, topmost_parent->wayland.pointer, topmost_parent->wayland.region, ZWP_POINTER_CONSTRAINTS_V1_LIFETIME_PERSISTENT); - wl_surface_commit(topmost_parent->wayland.backbuffer.surface); + if(!topmost_parent->wayland.locked_pointer) { + topmost_parent->wayland.locked_pointer = zwp_pointer_constraints_v1_lock_pointer(topmost_parent->wayland.pointer_constraints, topmost_parent->wayland.backbuffer.surface, topmost_parent->wayland.pointer, topmost_parent->wayland.region, ZWP_POINTER_CONSTRAINTS_V1_LIFETIME_PERSISTENT); + wl_surface_commit(topmost_parent->wayland.backbuffer.surface); + } zwp_locked_pointer_v1_set_cursor_position_hint(topmost_parent->wayland.locked_pointer, 0, CSD_BORDER_FRAME_TOP); - handle->wayland.relative_pointer = zwp_relative_pointer_manager_v1_get_relative_pointer(handle->wayland.relative_pointer_manager, topmost_parent->wayland.pointer); - zwp_relative_pointer_v1_add_listener(handle->wayland.relative_pointer, &MwLLWaylandRelativePointerListener, topmost_parent); + if(!topmost_parent->wayland.relative_pointer) { + topmost_parent->wayland.relative_pointer = zwp_relative_pointer_manager_v1_get_relative_pointer(handle->wayland.relative_pointer_manager, topmost_parent->wayland.pointer); + zwp_relative_pointer_v1_add_listener(topmost_parent->wayland.relative_pointer, &MwLLWaylandRelativePointerListener, topmost_parent); + } } else { if(topmost_parent->wayland.locked_pointer) { zwp_locked_pointer_v1_destroy(topmost_parent->wayland.locked_pointer); @@ -1821,7 +1899,7 @@ static void MwLLSetClipboardImpl(MwLL handle, const char* text, int clipboard_ty if(handle->wayland.clipboard_buffer != NULL) { free(handle->wayland.clipboard_buffer); } - handle->wayland.clipboard_buffer = malloc(strlen(text)); + handle->wayland.clipboard_buffer = malloc(strlen(text) + 1); strcpy(handle->wayland.clipboard_buffer, text); if(clipboard_type == MwCLIPBOARD_PRIMARY) { diff --git a/src/core.c b/src/core.c index 0618ea3c..a3d8ae15 100644 --- a/src/core.c +++ b/src/core.c @@ -753,7 +753,7 @@ void MwSetString(MwWidget handle, const char* key, const char* value) { void MwSetPointer(MwWidget handle, const char* key, void* value) { if(IsFirstVisible(handle) && strcmp(key, MwNiconPixmap) == 0) { - MwLLSetIcon(handle->lowlevel, value); + if(value != NULL) MwLLSetIcon(handle->lowlevel, ((MwPixmap)value)->lowlevel); } else if(strcmp(key, MwNsizeHints) == 0) { MwSizeHints* sz = value; From f3cb2fe15a284f2f3ecb7b9636614aa95fee39b4 Mon Sep 17 00:00:00 2001 From: IoIxD Date: Mon, 28 Sep 2026 12:12:19 -0700 Subject: [PATCH 2/3] while we're at it we haven't needed the 'valid' checks for awhile --- include/Mw/LowLevel/Wayland.h | 1 - src/backend/wayland/wayland.c | 54 ++++++++++++++--------------------- 2 files changed, 21 insertions(+), 34 deletions(-) diff --git a/include/Mw/LowLevel/Wayland.h b/include/Mw/LowLevel/Wayland.h index 6c4a4e65..239196d3 100644 --- a/include/Mw/LowLevel/Wayland.h +++ b/include/Mw/LowLevel/Wayland.h @@ -381,7 +381,6 @@ struct _MwLLWayland { struct zwp_relative_pointer_v1* relative_pointer; struct zwp_locked_pointer_v1* locked_pointer; MwBool pointer_constrained; - MwBool valid; #ifdef USE_DBUS MwLLDBusContext dbus; diff --git a/src/backend/wayland/wayland.c b/src/backend/wayland/wayland.c index 80e0da58..04bb8aa3 100644 --- a/src/backend/wayland/wayland.c +++ b/src/backend/wayland/wayland.c @@ -951,7 +951,6 @@ static void widget_setup(MwLL r, MwLL parent, int x, int y, int width, int heigh r->wayland.x = x; r->wayland.y = y; r->wayland.parent = parent; - r->wayland.valid = MwTRUE; if(ty == MwLL_WAYLAND_UNKNOWN) { if(parent == NULL) { @@ -1152,33 +1151,29 @@ static void MwLLDestroyImpl(MwLL handle) { } #endif - if(handle->wayland.valid) { - if(handle->wayland.cursor.setup) { - MwLLWaylandBufferDestroy(&handle->wayland.cursor); - wl_surface_destroy(handle->wayland.cursor.surface); - } - wl_region_destroy(handle->wayland.region); - wl_region_destroy(handle->wayland.o_region); + if(handle->wayland.cursor.setup) { + MwLLWaylandBufferDestroy(&handle->wayland.cursor); + wl_surface_destroy(handle->wayland.cursor.surface); + } + wl_region_destroy(handle->wayland.region); + wl_region_destroy(handle->wayland.o_region); - /* clipboard sources are released with their managers in destroy_widget */ - if(handle->wayland.icon != NULL) { - if(handle->wayland.icon->setup) { - MwLLWaylandBufferDestroy(handle->wayland.icon); - wl_surface_destroy(handle->wayland.icon->surface); - } - free(handle->wayland.icon); + /* clipboard sources are released with their managers in destroy_widget */ + if(handle->wayland.icon != NULL) { + if(handle->wayland.icon->setup) { + MwLLWaylandBufferDestroy(handle->wayland.icon); + wl_surface_destroy(handle->wayland.icon->surface); } + free(handle->wayland.icon); + } - destroy_widget(handle); + destroy_widget(handle); - /* Only parentless handles connect (children borrow the parent's display), and children are - * always freed before their parent, so nothing else is using it by now. */ - if(!handle->wayland.parent && handle->wayland.display) { - wl_display_disconnect(handle->wayland.display); - handle->wayland.display = NULL; - } - } else { - printf("widget invalid\n"); + /* Only parentless handles connect (children borrow the parent's display), and children are + * always freed before their parent, so nothing else is using it by now. */ + if(!handle->wayland.parent && handle->wayland.display) { + wl_display_disconnect(handle->wayland.display); + handle->wayland.display = NULL; } if(handle->wayland.icon_pixmap) MwLLDestroyPixmap(handle->wayland.icon_pixmap); @@ -1198,13 +1193,6 @@ static void MwLLDestroyImpl(MwLL handle) { } static void MwLLGetXYWHImpl(MwLL handle, int* x, int* y, unsigned int* w, unsigned int* h) { - if(!handle->wayland.valid) { - *x = 0; - *y = 0; - *w = 1; - *h = 1; - return; - } *x = handle->wayland.x; *y = handle->wayland.y; *w = handle->wayland.ww; @@ -1687,8 +1675,8 @@ static void MwLLSetIconImpl(MwLL handle, MwLLPixmap pixmap) { /* RGBA -> BGRA, one pixel (4 bytes) at a time. The icon buffer is line x line, so rows * have to be placed with its stride rather than the pixmap's. */ for(; i < size; i += 4) { - int x = (i / 4) % pixmap->common.width; - int y = (i / 4) / pixmap->common.width; + int x = (i / 4) % pixmap->common.width; + int y = (i / 4) / pixmap->common.width; unsigned char* dst = &handle->wayland.icon->buf_back[(y * line + x) * 4]; dst[0] = pixmap->common.raw[i + 2]; From 37b1f86fa9f6e2a632389cedffa183563951a138 Mon Sep 17 00:00:00 2001 From: IoIxD Date: Mon, 28 Sep 2026 12:18:47 -0700 Subject: [PATCH 3/3] remove some bad comments --- src/backend/cairo.c | 14 +++++++------- src/backend/wayland/wayland.c | 1 - 2 files changed, 7 insertions(+), 8 deletions(-) diff --git a/src/backend/cairo.c b/src/backend/cairo.c index 4c6a0174..94d806d2 100644 --- a/src/backend/cairo.c +++ b/src/backend/cairo.c @@ -134,7 +134,6 @@ void MwLLCairoBackSetup(struct _MwLLCairo* cairo, MwU8* data, MwU32 width, MwU32 cairo->back_cairo = cairo_create(cairo->back_cs); } -/* Pointers are cleared so that destroying twice (or before any setup) is harmless; cairo ignores NULL. */ void MwLLCairoFrontDestroy(struct _MwLLCairo* cairo) { cairo_destroy(cairo->front_cairo); cairo_destroy(cairo->front_cairo_back); @@ -143,6 +142,7 @@ void MwLLCairoFrontDestroy(struct _MwLLCairo* cairo) { cairo_surface_destroy(cairo->front_cs_back); cairo_surface_destroy(cairo->frontbuffer_cs); + /* set all to null so that cairo ignores future destroys */ cairo->front_cairo = NULL; cairo->front_cairo_back = NULL; cairo->frontbuffer_cairo = NULL; @@ -229,8 +229,8 @@ static void MwLLFreeColorImpl(MwLLColor color) {} static MwBool lmao = MwFALSE; static int MwLLPendingImpl(MwLL handle) { - lmao = !lmao; - return lmao; + lmao = !lmao; + return lmao; } static void MwLLNextEventImpl(MwLL handle) { MwLLDispatch(handle, draw, NULL); @@ -274,10 +274,10 @@ static void MwLLRaiseImpl(MwLL handle) {} static void MwLLClipImpl(MwLL handle, MwRect* rect) {} static void MwLLSetupDragAndDropImpl(MwLL handle) {} static int MwLLCairoCallInitImpl(void) { - if(cairo_load_funcs() != 0) { - return 1; - } - return 0; + if(cairo_load_funcs() != 0) { + return 1; + } + return 0; } #include "call.c" CALL(Cairo); diff --git a/src/backend/wayland/wayland.c b/src/backend/wayland/wayland.c index 04bb8aa3..2cf42049 100644 --- a/src/backend/wayland/wayland.c +++ b/src/backend/wayland/wayland.c @@ -990,7 +990,6 @@ static void widget_setup(MwLL r, MwLL parent, int x, int y, int width, int heigh } } - /* widget_setup runs again on every state change, drop the regions from the last one */ if(r->wayland.region) wl_region_destroy(r->wayland.region); if(r->wayland.o_region) wl_region_destroy(r->wayland.o_region);