From fe2e770e9249095616262db0f27286287b26a2c1 Mon Sep 17 00:00:00 2001 From: Clement Lefebvre Date: Sat, 20 Jun 2026 16:35:18 +0200 Subject: [PATCH] nemo-desktop: Don't crash/quit in Wayland when the monitor is removed Hold the application alive in Wayland mode when monitors disappear, so it can recover when the monitor returns, rather than exiting. Handle GdkDisplay::monitor-removed immediately, by destroying windows cleanly before gtk-layer-shell's closed handler fires, so there's no dangling surface for the frame clock to hit. --- src/nemo-desktop-manager.c | 80 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 80 insertions(+) diff --git a/src/nemo-desktop-manager.c b/src/nemo-desktop-manager.c index a508987b5..2e2cc8372 100644 --- a/src/nemo-desktop-manager.c +++ b/src/nemo-desktop-manager.c @@ -58,6 +58,10 @@ typedef struct { gulong name_owner_changed_id; gulong proxy_signals_id; gulong fallback_size_changed_id; + gulong monitor_added_id; + gulong monitor_removed_id; + + gboolean has_wayland_app_hold; } NemoDesktopManagerPrivate; struct _NemoDesktopManager @@ -572,6 +576,37 @@ on_failsafe_timeout (NemoDesktopManager *manager) return G_SOURCE_REMOVE; } +static void +on_monitor_removed_wayland (GdkDisplay *display, + GdkMonitor *monitor, + NemoDesktopManager *manager) +{ + FETCH_PRIV (manager); + + if (priv->current_run_state < RUN_STATE_RUNNING) { + return; + } + + /* The compositor sent zwlr_layer_surface_v1.closed for the layer surface + * associated with the removed output. gtk-layer-shell will destroy the + * backing wl_surface in response. We must destroy our GtkWindow first, + * before that happens, or a pending frame clock event will call + * gdk_window_begin_draw_frame() on the dead wl_surface and crash. */ + DEBUG ("Wayland output removed - closing desktop windows immediately"); + + close_all_windows (manager); + queue_update_layout (manager); +} + +static void +on_monitor_added_wayland (GdkDisplay *display, + GdkMonitor *monitor, + NemoDesktopManager *manager) +{ + DEBUG ("Wayland output added - queuing layout update"); + queue_update_layout (manager); +} + static void connect_fallback_signals (NemoDesktopManager *manager) { @@ -583,6 +618,20 @@ connect_fallback_signals (NemoDesktopManager *manager) "size_changed", G_CALLBACK (queue_update_layout), manager); + + if (eel_check_is_wayland ()) { + GdkDisplay *display = gdk_display_get_default (); + + priv->monitor_removed_id = g_signal_connect (display, + "monitor-removed", + G_CALLBACK (on_monitor_removed_wayland), + manager); + + priv->monitor_added_id = g_signal_connect (display, + "monitor-added", + G_CALLBACK (on_monitor_added_wayland), + manager); + } } static void @@ -671,6 +720,25 @@ nemo_desktop_manager_dispose (GObject *object) priv->fallback_size_changed_id = 0; } + if (priv->monitor_removed_id > 0 || priv->monitor_added_id > 0) { + GdkDisplay *display = gdk_display_get_default (); + + if (priv->monitor_removed_id > 0) { + g_signal_handler_disconnect (display, priv->monitor_removed_id); + priv->monitor_removed_id = 0; + } + + if (priv->monitor_added_id > 0) { + g_signal_handler_disconnect (display, priv->monitor_added_id); + priv->monitor_added_id = 0; + } + } + + if (priv->has_wayland_app_hold) { + g_application_release (G_APPLICATION (nemo_application_get_singleton ())); + priv->has_wayland_app_hold = FALSE; + } + G_OBJECT_CLASS (nemo_desktop_manager_parent_class)->dispose (object); } @@ -764,6 +832,18 @@ nemo_desktop_manager_init (NemoDesktopManager *manager) } connect_fallback_signals (manager); + + if (eel_check_is_wayland ()) { + /* Hold the application alive independently of any windows so that + * nemo-desktop survives monitor removal (e.g. KVM switch). Without + * this, gtk-layer-shell destroys our windows when the compositor + * removes an output and GtkApplication exits immediately with no + * windows left. The hold is released in nemo_desktop_manager_dispose + * when the application is genuinely quitting. */ + g_application_hold (G_APPLICATION (nemo_application_get_singleton ())); + priv->has_wayland_app_hold = TRUE; + } + g_timeout_add (250, (GSourceFunc) fallback_startup_idle_cb, manager); }