From 2dbe5df2d8a7590213d76aa4ce112024602eb57d Mon Sep 17 00:00:00 2001 From: Wes Brown Date: Fri, 14 Aug 2026 13:11:51 -0500 Subject: [PATCH] Wayland: fix crash when an output global is removed surfaceHandleEnter() and surfaceHandleLeave() passed the event's output argument straight to wl_output_get_user_data(). Wayland delivers object arguments as NULL when the object was destroyed client-side before the event was dispatched, even for arguments declared non-nullable, so this segfaults whenever a wl_output global goes away while a window is open. Easily reproduced with a hardware KVM or a monitor input switch. Also fixes two pre-existing problems in surfaceHandleLeave(): - The removal loop stopped at monitorsCount - 1, so it never compared the last entry, and the trailing unconditional window->wl.monitors[--window->wl.monitorsCount] = NULL dropped the last monitor whether or not it was the one that was left. A leave for a monitor not in the list therefore evicted an unrelated one. - That same statement ran even when monitorsCount was 0, writing to monitors[-1] and leaving the count negative. Both are replaced with the remove_i_from_array() loop already used for this array in registryHandleGlobalRemove(). Co-Authored-By: Claude Opus 5 --- glfw/wl_window.c | 22 ++++++++++++++++------ 1 file changed, 16 insertions(+), 6 deletions(-) diff --git a/glfw/wl_window.c b/glfw/wl_window.c index cc1074ba7..d33046716 100644 --- a/glfw/wl_window.c +++ b/glfw/wl_window.c @@ -549,7 +549,15 @@ static const struct zxdg_toplevel_decoration_v1_listener xdgDecorationListener = static void surfaceHandleEnter(void *data, struct wl_surface *surface UNUSED, struct wl_output *output) { _GLFWwindow *window = data; + // output is NULL when the wl_output global it refers to was removed before + // this event was dispatched. Wayland delivers destroyed objects as NULL even + // for arguments declared non-nullable, so this must be handled. + // See https://wayland.freedesktop.org/docs/html/apc.html + if (!output) return; _GLFWmonitor *monitor = wl_output_get_user_data(output); + // never store NULL in window->wl.monitors as checkScaleChange() dereferences + // every entry unconditionally + if (!monitor) return; if (window->wl.monitorsCount + 1 > window->wl.monitorsSize) { ++window->wl.monitorsSize; @@ -567,15 +575,17 @@ surfaceHandleEnter(void *data, struct wl_surface *surface UNUSED, struct wl_outp static void surfaceHandleLeave(void *data, struct wl_surface *surface UNUSED, struct wl_output *output) { _GLFWwindow *window = data; + // output is NULL when the wl_output global it refers to was removed before + // this event was dispatched, see the comment in surfaceHandleEnter(). In that + // case registryHandleGlobalRemove() has already dropped the monitor from + // window->wl.monitors, so there is nothing left to do here. + if (!output) return; _GLFWmonitor *monitor = wl_output_get_user_data(output); - bool found; - int i; + if (!monitor) return; - for (i = 0, found = false; i < window->wl.monitorsCount - 1; ++i) { - if (monitor == window->wl.monitors[i]) found = true; - if (found) window->wl.monitors[i] = window->wl.monitors[i + 1]; + for (int i = window->wl.monitorsCount - 1; i >= 0; i--) { + if (window->wl.monitors[i] == monitor) { remove_i_from_array(window->wl.monitors, i, window->wl.monitorsCount); } } - window->wl.monitors[--window->wl.monitorsCount] = NULL; if (checkScaleChange(window)) { debug("Scale changed to %.3f for window %llu in surfaceHandleLeave\n", _glfwWaylandWindowScale(window), window->id);