From d1b873463468dd0c21cc83baf568388c1ca5ba5a Mon Sep 17 00:00:00 2001 From: Michael Webster Date: Mon, 2 Mar 2026 12:21:25 -0500 Subject: [PATCH] csd-background: Run as a wayland client if gtk-layer-shell is available. Current csd-background runs with xwayland and requires special handling built in to muffin to 'make it work'. Benefits: - Allows running as a wayland client - Enables free re-use of background surfaces by Cinnamon consumers. - Eliminates need for special handling in wayland sessions by muffin. --- debian/control | 2 + debian/rules | 1 + meson.build | 11 +++ meson_options.txt | 6 ++ plugins/background/csd-background-manager.c | 65 +++++++++----- plugins/background/main.c | 33 +++++++- plugins/background/meson.build | 4 + plugins/background/monitor-background.c | 94 ++++++++------------- plugins/background/monitor-background.h | 2 - plugins/common/daemon-skeleton-gtk.h | 6 ++ plugins/common/meson.build | 5 ++ plugins/common/wayland-utils.c | 87 +++++++++++++++++++ plugins/common/wayland-utils.h | 22 +++++ 13 files changed, 256 insertions(+), 82 deletions(-) create mode 100644 plugins/common/wayland-utils.c create mode 100644 plugins/common/wayland-utils.h diff --git a/debian/control b/debian/control index 66ebf267..91e977e7 100644 --- a/debian/control +++ b/debian/control @@ -14,6 +14,7 @@ Build-Depends: libfontconfig1-dev, libglib2.0-dev (>= 2.37.3), libgtk-3-dev (>= 3.9.10), + libgtk-layer-shell-dev, libgudev-1.0-dev [linux-any], liblcms2-dev, libnotify-dev (>= 0.7.0), @@ -24,6 +25,7 @@ Build-Depends: libsystemd-dev [linux-any], libupower-glib-dev (>= 0.99.11), libwacom-dev (>= 0.4) [!s390x !hurd-any !kfreebsd-any], + libwayland-dev, libx11-dev, libxext-dev, libxi-dev, diff --git a/debian/rules b/debian/rules index 254ccf58..6b37eb38 100755 --- a/debian/rules +++ b/debian/rules @@ -15,6 +15,7 @@ DEB_HOST_MULTIARCH ?= $(shell dpkg-architecture -qDEB_HOST_MULTIARCH) override_dh_auto_configure: dh_auto_configure -- \ --libexecdir=/usr/libexec \ + -D gtk_layer_shell=true \ $(CONFFLAGS) diff --git a/meson.build b/meson.build index c48e8c1d..6df09be7 100644 --- a/meson.build +++ b/meson.build @@ -89,6 +89,14 @@ if not get_option('use_logind').disabled() endif endif +gtk_layer_shell_enabled = get_option('gtk_layer_shell') +gtk_layer_shell = dependency('', required: false) +wayland_client = dependency('', required: false) +if gtk_layer_shell_enabled + gtk_layer_shell = dependency('gtk-layer-shell-0', version: '>= 0.8', required: true) + wayland_client = dependency('wayland-client', required: true) +endif + cc = meson.get_compiler('c') math = cc.find_library('m', required: false) @@ -105,6 +113,9 @@ csd_conf.set_quoted('LIBEXECDIR', join_paths(prefix, libexecdir)) csd_conf.set_quoted('SYSCONFDIR', sysconfdir) csd_conf.set_quoted('LIBDIR', libdir) csd_conf.set10('HAVE_TIMERFD', has_timerfd_create) +if gtk_layer_shell_enabled + csd_conf.set('HAVE_GTK_LAYER_SHELL', 1) +endif if gudev.found() cargs += '-DHAVE_GUDEV' diff --git a/meson_options.txt b/meson_options.txt index 81db2ba5..c79db786 100644 --- a/meson_options.txt +++ b/meson_options.txt @@ -70,3 +70,9 @@ option( value: '/usr/share/zoneinfo/zone.tab', description: 'Path to tzdata zone.tab or zone1970.tab' ) +option( + 'gtk_layer_shell', + type: 'boolean', + value: false, + description: 'Use gtk-layer-shell for native Wayland background windows' +) diff --git a/plugins/background/csd-background-manager.c b/plugins/background/csd-background-manager.c index 94b48d89..b2c11527 100644 --- a/plugins/background/csd-background-manager.c +++ b/plugins/background/csd-background-manager.c @@ -37,6 +37,9 @@ #include #include #include +#ifdef GDK_WINDOWING_WAYLAND +#include +#endif #define GNOME_DESKTOP_USE_UNSTABLE_API #include @@ -59,11 +62,14 @@ struct CsdBackgroundManagerPrivate guint proxy_signal_id; GPtrArray *mbs; + + guint screen_changed_id; }; static void csd_background_manager_finalize (GObject *object); static void setup_bg (CsdBackgroundManager *manager); +static void setup_monitors (CsdBackgroundManager *manager); static void connect_screen_signals (CsdBackgroundManager *manager); G_DEFINE_TYPE (CsdBackgroundManager, csd_background_manager, G_TYPE_OBJECT) @@ -77,6 +83,13 @@ draw_background_wayland_session (CsdBackgroundManager *manager) cinnamon_settings_profile_start (NULL); + if (!manager->priv->mbs || manager->priv->mbs->len == 0 || + manager->priv->screen_changed_id != 0) + { + cinnamon_settings_profile_end (NULL); + return; + } + for (i = 0; i < manager->priv->mbs->len; i++) { GtkImage *image; @@ -127,29 +140,19 @@ draw_background_x11_session (CsdBackgroundManager *manager) } static gboolean -session_is_wayland (void) +using_wayland_backend (void) { - static gboolean session_is_wayland = FALSE; - static gsize once_init = 0; - - if (g_once_init_enter (&once_init)) { - const gchar *env = g_getenv ("XDG_SESSION_TYPE"); - if (env && g_strcmp0 (env, "wayland") == 0) { - session_is_wayland = TRUE; - } - - g_debug ("Session is Wayland? %d", session_is_wayland); - - g_once_init_leave (&once_init, 1); - } - - return session_is_wayland; +#ifdef GDK_WINDOWING_WAYLAND + return GDK_IS_WAYLAND_DISPLAY (gdk_display_get_default ()); +#else + return FALSE; +#endif } static void draw_background (CsdBackgroundManager *manager) { - if (session_is_wayland ()) { + if (using_wayland_backend ()) { draw_background_wayland_session (manager); } else { draw_background_x11_session (manager); @@ -177,12 +180,31 @@ settings_change_event_cb (GSettings *settings, return FALSE; } +static gboolean +on_screen_changed_idle (gpointer user_data) +{ + CsdBackgroundManager *manager = CSD_BACKGROUND_MANAGER (user_data); + + manager->priv->screen_changed_id = 0; + + if (using_wayland_backend ()) { + setup_monitors (manager); + } + + draw_background (manager); + + return G_SOURCE_REMOVE; +} + static void on_screen_size_changed (GdkScreen *screen, CsdBackgroundManager *manager) { + if (manager->priv->screen_changed_id != 0) + return; - draw_background (manager); + manager->priv->screen_changed_id = + g_timeout_add (250, on_screen_changed_idle, manager); } static void @@ -193,6 +215,9 @@ setup_monitors (CsdBackgroundManager *manager) display = gdk_display_get_default (); + if (!display) + return; + g_clear_pointer (&manager->priv->mbs, g_ptr_array_unref); manager->priv->mbs = g_ptr_array_new_with_free_func ((GDestroyNotify) g_object_unref); @@ -251,7 +276,7 @@ setup_bg_and_draw_background (CsdBackgroundManager *manager) { setup_bg (manager); - if (session_is_wayland ()) { + if (using_wayland_backend ()) { setup_monitors (manager); } @@ -396,6 +421,8 @@ csd_background_manager_stop (CsdBackgroundManager *manager) g_debug ("Stopping background manager"); + g_clear_handle_id (&manager->priv->screen_changed_id, g_source_remove); + disconnect_screen_signals (manager); if (manager->priv->proxy) { diff --git a/plugins/background/main.c b/plugins/background/main.c index 270410fa..d9c39bcd 100644 --- a/plugins/background/main.c +++ b/plugins/background/main.c @@ -1,3 +1,8 @@ +#include "config.h" + +#include +#include + #define NEW csd_background_manager_new #define START csd_background_manager_start #define STOP csd_background_manager_stop @@ -12,8 +17,34 @@ // Setting this to TRUE makes the plugin force GDK_SCALE=1 #define FORCE_GDK_SCALE TRUE -// This plugin must run under x11/xwayland +// When gtk-layer-shell is available, check at runtime if the compositor +// supports wlr-layer-shell. If not, fall back to X11/XWayland. +#ifdef HAVE_GTK_LAYER_SHELL +#include "wayland-utils.h" + +static void +pre_gtk_init (void) +{ + if (csd_check_layer_shell_support ()) { + g_message ("csd-background: using Wayland backend, gtk-layer-shell supported"); + } else { + g_message ("csd-background: not a Wayland session or wlr-layer-shell protocol not supported, using X11 backend"); + gdk_set_allowed_backends ("x11"); + } +} + +#define PRE_GTK_INIT pre_gtk_init +#define FORCE_X11_BACKEND FALSE +#else +static void +pre_gtk_init (void) +{ + g_message ("csd-background: gtk-layer-shell not built, using X11 backend"); +} + +#define PRE_GTK_INIT pre_gtk_init #define FORCE_X11_BACKEND TRUE +#endif #include "csd-background-manager.h" diff --git a/plugins/background/meson.build b/plugins/background/meson.build index f722207c..08203be3 100644 --- a/plugins/background/meson.build +++ b/plugins/background/meson.build @@ -13,6 +13,10 @@ background_deps = [ libnotify, ] +if gtk_layer_shell_enabled + background_deps += gtk_layer_shell +endif + executable( 'csd-background', background_sources, diff --git a/plugins/background/monitor-background.c b/plugins/background/monitor-background.c index ba0853ed..298dee09 100644 --- a/plugins/background/monitor-background.c +++ b/plugins/background/monitor-background.c @@ -11,40 +11,14 @@ #include #include +#ifdef HAVE_GTK_LAYER_SHELL +#include +#endif + #include "monitor-background.h" G_DEFINE_TYPE (MonitorBackground, monitor_background, G_TYPE_OBJECT) -enum { - INVALIDATED, - N_SIGNALS -}; - -static guint signals[N_SIGNALS] = { 0 }; - -static void -invalidate_mb (MonitorBackground *mb) -{ - mb->valid = FALSE; - - g_signal_emit (mb, signals[INVALIDATED], 0); -} - -static void -on_gdk_monitor_dispose (gpointer data, - GObject *monitor) -{ - MonitorBackground *mb = MONITOR_BACKGROUND (data); - invalidate_mb (mb); -} - -static void -on_gdk_monitor_invalidate (gpointer data) -{ - MonitorBackground *mb = MONITOR_BACKGROUND (data); - invalidate_mb (mb); -} - static void on_window_realized (GtkWidget *widget, gpointer user_data) @@ -60,14 +34,33 @@ static void build_monitor_background (MonitorBackground *mb) { GdkRectangle geometry; +#ifdef HAVE_GTK_LAYER_SHELL + gboolean use_layer_shell = gtk_layer_is_supported (); +#else + gboolean use_layer_shell = FALSE; +#endif mb->window = gtk_window_new (GTK_WINDOW_TOPLEVEL); - gtk_window_set_type_hint (GTK_WINDOW (mb->window), GDK_WINDOW_TYPE_HINT_DESKTOP); gtk_window_set_decorated (GTK_WINDOW (mb->window), FALSE); - // Set keep below so muffin recognizes the backgrounds and keeps them - // at the bottom of the bottom window layer (under file managers, etc..) - gtk_window_set_keep_below (GTK_WINDOW (mb->window), TRUE); +#ifdef HAVE_GTK_LAYER_SHELL + if (use_layer_shell) { + gtk_layer_init_for_window (GTK_WINDOW (mb->window)); + gtk_layer_set_layer (GTK_WINDOW (mb->window), GTK_LAYER_SHELL_LAYER_BACKGROUND); + gtk_layer_set_namespace (GTK_WINDOW (mb->window), "csd-background"); + gtk_layer_set_keyboard_mode (GTK_WINDOW (mb->window), GTK_LAYER_SHELL_KEYBOARD_MODE_NONE); + gtk_layer_set_exclusive_zone (GTK_WINDOW (mb->window), -1); + gtk_layer_set_anchor (GTK_WINDOW (mb->window), GTK_LAYER_SHELL_EDGE_TOP, TRUE); + gtk_layer_set_anchor (GTK_WINDOW (mb->window), GTK_LAYER_SHELL_EDGE_BOTTOM, TRUE); + gtk_layer_set_anchor (GTK_WINDOW (mb->window), GTK_LAYER_SHELL_EDGE_LEFT, TRUE); + gtk_layer_set_anchor (GTK_WINDOW (mb->window), GTK_LAYER_SHELL_EDGE_RIGHT, TRUE); + gtk_layer_set_monitor (GTK_WINDOW (mb->window), mb->monitor); + } else +#endif + { + gtk_window_set_type_hint (GTK_WINDOW (mb->window), GDK_WINDOW_TYPE_HINT_DESKTOP); + gtk_window_set_keep_below (GTK_WINDOW (mb->window), TRUE); + } mb->stack = gtk_stack_new (); g_object_set (mb->stack, @@ -81,22 +74,25 @@ build_monitor_background (MonitorBackground *mb) mb->width = geometry.width; mb->height = geometry.height; - gtk_window_set_default_size (GTK_WINDOW (mb->window), geometry.width, geometry.height); + if (!use_layer_shell) { + gtk_window_set_default_size (GTK_WINDOW (mb->window), geometry.width, geometry.height); + g_signal_connect (mb->window, "realize", G_CALLBACK (on_window_realized), mb); + } - g_signal_connect (mb->window, "realize", G_CALLBACK (on_window_realized), mb); gtk_widget_show_all (mb->window); } static void monitor_background_init (MonitorBackground *mb) { - mb->valid = TRUE; } static void monitor_background_dispose (GObject *object) { - g_debug ("MonitorBackground dispose (%p)", object); + MonitorBackground *mb = MONITOR_BACKGROUND (object); + + g_clear_pointer (&mb->window, gtk_widget_destroy); G_OBJECT_CLASS (monitor_background_parent_class)->dispose (object); } @@ -117,13 +113,6 @@ monitor_background_class_init (MonitorBackgroundClass *klass) gobject_class->dispose = monitor_background_dispose; gobject_class->finalize = monitor_background_finalize; - - signals[INVALIDATED] = g_signal_new ("invalidated", - G_OBJECT_CLASS_TYPE (gobject_class), - G_SIGNAL_RUN_LAST, - 0, - NULL, NULL, NULL, - G_TYPE_NONE, 0); } MonitorBackground * @@ -133,9 +122,6 @@ monitor_background_new (gint index, GdkMonitor *monitor) mb->monitor_index = index; mb->monitor = monitor; - g_object_weak_ref (G_OBJECT (monitor), (GWeakNotify) on_gdk_monitor_dispose, mb); - g_signal_connect_swapped (monitor, "invalidate", G_CALLBACK (on_gdk_monitor_invalidate), mb); - build_monitor_background (mb); return mb; @@ -144,12 +130,6 @@ monitor_background_new (gint index, GdkMonitor *monitor) GtkImage * monitor_background_get_pending_image (MonitorBackground *mb) { - if (!mb->valid) - { - g_warning ("Asked for pending image when monitor is no longer valid"); - return NULL; - } - if (!mb->pending) { mb->pending = gtk_image_new (); @@ -165,12 +145,6 @@ monitor_background_show_next_image (MonitorBackground *mb) { g_return_if_fail (mb->pending != NULL); - if (!mb->valid) - { - g_warning ("Asked for pending image when monitor is no longer valid"); - return; - } - GtkWidget *tmp; tmp = mb->current; diff --git a/plugins/background/monitor-background.h b/plugins/background/monitor-background.h index 23a191ec..fe73955b 100644 --- a/plugins/background/monitor-background.h +++ b/plugins/background/monitor-background.h @@ -20,8 +20,6 @@ struct _MonitorBackground GtkWidget *current; GtkWidget *pending; - gboolean valid; - gint monitor_index; gint width; gint height; diff --git a/plugins/common/daemon-skeleton-gtk.h b/plugins/common/daemon-skeleton-gtk.h index b563cea7..2e6f266f 100644 --- a/plugins/common/daemon-skeleton-gtk.h +++ b/plugins/common/daemon-skeleton-gtk.h @@ -31,6 +31,10 @@ #define INIT_LIBNOTIFY FALSE #endif +#ifndef PRE_GTK_INIT +#define PRE_GTK_INIT() +#endif + #define GNOME_SESSION_DBUS_NAME "org.gnome.SessionManager" #define GNOME_SESSION_DBUS_PATH "/org/gnome/SessionManager" #define GNOME_SESSION_CLIENT_PRIVATE_NAME "org.gnome.SessionManager.ClientPrivate" @@ -201,6 +205,8 @@ main (int argc, char **argv) g_type_ensure (G_TYPE_DBUS_CONNECTION); g_type_ensure (G_TYPE_DBUS_PROXY); + PRE_GTK_INIT(); + if (FORCE_X11_BACKEND) { const gchar *setup_display = getenv ("GNOME_SETUP_DISPLAY"); if (setup_display && *setup_display != '\0') diff --git a/plugins/common/meson.build b/plugins/common/meson.build index b7eddba6..a021bbdf 100644 --- a/plugins/common/meson.build +++ b/plugins/common/meson.build @@ -12,6 +12,11 @@ common_deps = [ xi, ] +if gtk_layer_shell_enabled + common_sources += 'wayland-utils.c' + common_deps += wayland_client +endif + common_inc = include_directories('.') common_lib = static_library( diff --git a/plugins/common/wayland-utils.c b/plugins/common/wayland-utils.c new file mode 100644 index 00000000..5e6ccb0d --- /dev/null +++ b/plugins/common/wayland-utils.c @@ -0,0 +1,87 @@ +/* wayland-utils.c - Wayland utility functions for CSD plugins + * + * Copyright (C) 2026 Linux Mint + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + */ + +#include "config.h" +#include "wayland-utils.h" + +#include + +static void +registry_handle_global (void *data, + struct wl_registry *registry, + uint32_t name, + const char *interface, + uint32_t version) +{ + if (g_strcmp0 (interface, "zwlr_layer_shell_v1") == 0) + *(gboolean *) data = TRUE; +} + +static void +registry_handle_global_remove (void *data, + struct wl_registry *registry, + uint32_t name) +{ +} + +static const struct wl_registry_listener registry_listener = { + registry_handle_global, + registry_handle_global_remove, +}; + +/** + * csd_check_layer_shell_support: + * + * Check if wlr-layer-shell protocol is supported by connecting to the + * Wayland display and checking the registry. This can be called before + * gtk_init() unlike gtk_layer_is_supported(). + * + * Returns: %TRUE if layer shell is available, %FALSE otherwise + */ +gboolean +csd_check_layer_shell_support (void) +{ + struct wl_display *display; + struct wl_registry *registry; + gboolean found = FALSE; + + display = wl_display_connect (NULL); + if (!display) + { + g_debug ("csd_check_layer_shell_support: could not connect to Wayland display"); + return FALSE; + } + + registry = wl_display_get_registry (display); + if (!registry) + { + g_debug ("csd_check_layer_shell_support: failed to get Wayland registry"); + wl_display_disconnect (display); + return FALSE; + } + + wl_registry_add_listener (registry, ®istry_listener, &found); + + if (wl_display_roundtrip (display) < 0) + { + g_debug ("csd_check_layer_shell_support: Wayland roundtrip failed"); + wl_registry_destroy (registry); + wl_display_disconnect (display); + return FALSE; + } + + wl_registry_destroy (registry); + wl_display_disconnect (display); + + if (!found) + g_debug ("csd_check_layer_shell_support: wlr-layer-shell protocol not advertised by compositor"); + + return found; +} diff --git a/plugins/common/wayland-utils.h b/plugins/common/wayland-utils.h new file mode 100644 index 00000000..ba668dce --- /dev/null +++ b/plugins/common/wayland-utils.h @@ -0,0 +1,22 @@ +/* wayland-utils.h - Wayland utility functions for CSD plugins + * + * Copyright (C) 2026 Linux Mint + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + */ + +#ifndef __WAYLAND_UTILS_H +#define __WAYLAND_UTILS_H + +#include + +G_BEGIN_DECLS + +gboolean csd_check_layer_shell_support (void); + +G_END_DECLS + +#endif /* __WAYLAND_UTILS_H */