diff --git a/debian/libxapp1.symbols b/debian/libxapp1.symbols index 58ef70a..2805fc0 100644 --- a/debian/libxapp1.symbols +++ b/debian/libxapp1.symbols @@ -1,6 +1,14 @@ libxapp.so.1 libxapp1 #MINVER# _favorite_vfs_file_new_for_info@Base 2.0.7 _xapp_favorites_get_display_names@Base 2.0.7 + (optional)_xapp_wayland_update_icon_name@Base 3.3.1 + (optional)_xapp_wayland_update_progress@Base 3.3.1 + (optional)_xapp_wayland_window_unrealized@Base 3.3.1 + _xapp_x11_set_xid_icon_name@Base 3.3.1 + _xapp_x11_set_xid_progress@Base 3.3.1 + _xapp_x11_set_xid_progress_pulse@Base 3.3.1 + _xapp_x11_update_icon_name@Base 3.3.1 + _xapp_x11_update_progress@Base 3.3.1 create_blanking_window@Base 1.4.9 debug_flag_to_string@Base 2.4.2 fav_uri_to_display_name@Base 2.0.7 diff --git a/libxapp/meson.build b/libxapp/meson.build index e20e899..c0a6079 100644 --- a/libxapp/meson.build +++ b/libxapp/meson.build @@ -23,6 +23,39 @@ if gtk_layer_shell_dep.found() libxapp_c_args += '-DHAVE_GTK_LAYER_SHELL=1' endif +# XAppGtkWindow display-server backends. The X11 backend (the _NET_WM_XAPP_* +# window properties) is always built; the Wayland backend (the private +# xapp-shell protocol) is built when the wayland tooling is available. Kept out +# of the introspected sources below - they expose no public API. +xapp_backend_sources = [ 'xapp-gtk-window-x11.c' ] +xapp_wayland_generated = [] + +wayland_scanner_dep = dependency('wayland-scanner', required: false, native: true) +wayland_client_dep = dependency('wayland-client', required: false) +gdk_wayland_dep = dependency('gdk-wayland-3.0', required: false) + +if wayland_scanner_dep.found() and wayland_client_dep.found() and gdk_wayland_dep.found() + wayland_scanner = find_program(wayland_scanner_dep.get_variable(pkgconfig: 'wayland_scanner'), + native: true) + + xapp_shell_client_header = custom_target('xapp-shell client header', + input: 'xapp-shell.xml', + output: 'xapp-shell-client-protocol.h', + command: [wayland_scanner, 'client-header', '@INPUT@', '@OUTPUT@']) + + xapp_shell_code = custom_target('xapp-shell code', + input: 'xapp-shell.xml', + output: 'xapp-shell-protocol.c', + command: [wayland_scanner, 'private-code', '@INPUT@', '@OUTPUT@']) + + xapp_wayland_generated = [ xapp_shell_client_header, xapp_shell_code ] + xapp_backend_sources += 'xapp-gtk-window-wayland.c' + + libdeps += wayland_client_dep + libdeps += gdk_wayland_dep + libxapp_c_args += '-DHAVE_WAYLAND=1' +endif + favorite_vfs_sources = [ 'favorite-vfs-file.c', 'favorite-vfs-file-enumerator.c', @@ -105,7 +138,7 @@ xapp_enums = gnome.mkenums_simple('xapp-enums', ) libxapp = library('xapp', - sources : xapp_headers + xapp_sources + xapp_enums + dbus_headers + favorite_vfs_sources + xapp_debug, + sources : xapp_headers + xapp_sources + xapp_enums + dbus_headers + favorite_vfs_sources + xapp_debug + xapp_backend_sources + xapp_wayland_generated, include_directories: [top_inc], version: meson.project_version(), soversion: '1', diff --git a/libxapp/xapp-gtk-window-backend.h b/libxapp/xapp-gtk-window-backend.h new file mode 100644 index 0000000..2f3387e --- /dev/null +++ b/libxapp/xapp-gtk-window-backend.h @@ -0,0 +1,36 @@ +#ifndef __XAPP_GTK_WINDOW_BACKEND_H__ +#define __XAPP_GTK_WINDOW_BACKEND_H__ + +#include + +G_BEGIN_DECLS + +/* X11 backend - always built. + * Each setter is a no-op when the default display is not an X11 display. */ +void _xapp_x11_update_icon_name (GtkWindow *window, + const gchar *icon_str); +void _xapp_x11_update_progress (GtkWindow *window, + guint progress, + gboolean pulse); +void _xapp_x11_set_xid_icon_name (gulong xid, + const gchar *icon_str); +void _xapp_x11_set_xid_progress (gulong xid, + gint progress); +void _xapp_x11_set_xid_progress_pulse (gulong xid, + gboolean pulse); + +#ifdef HAVE_WAYLAND +/* Wayland backend - drives the private xapp-shell protocol. + * Each setter is a no-op when the window is not a Wayland window or the + * compositor does not implement xapp-shell. */ +void _xapp_wayland_update_icon_name (GtkWindow *window, + const gchar *icon_str); +void _xapp_wayland_update_progress (GtkWindow *window, + guint progress, + gboolean pulse); +void _xapp_wayland_window_unrealized (GtkWindow *window); +#endif + +G_END_DECLS + +#endif /* __XAPP_GTK_WINDOW_BACKEND_H__ */ diff --git a/libxapp/xapp-gtk-window-wayland.c b/libxapp/xapp-gtk-window-wayland.c new file mode 100644 index 0000000..8973168 --- /dev/null +++ b/libxapp/xapp-gtk-window-wayland.c @@ -0,0 +1,202 @@ +#include + +#include +#include +#include + +#include "xapp-gtk-window-backend.h" +#include "xapp-shell-client-protocol.h" + +#define DISPLAY_DATA_KEY "xapp-wayland-shell-data" +#define SURFACE_DATA_KEY "xapp-wayland-surface" + +typedef struct +{ + struct xapp_shell1 *shell; + uint32_t capabilities; +} XAppWaylandShell; + +static void +shell_handle_capabilities (void *data, + struct xapp_shell1 *shell, + uint32_t capabilities) +{ + XAppWaylandShell *wd = data; + + wd->capabilities = capabilities; +} + +static const struct xapp_shell1_listener shell_listener = { + shell_handle_capabilities, +}; + +static void +registry_handle_global (void *data, + struct wl_registry *registry, + uint32_t id, + const char *interface, + uint32_t version) +{ + XAppWaylandShell *wd = data; + + if (g_strcmp0 (interface, xapp_shell1_interface.name) == 0) + { + wd->shell = wl_registry_bind (registry, id, &xapp_shell1_interface, + MIN (version, 1u)); + xapp_shell1_add_listener (wd->shell, &shell_listener, wd); + } +} + +static void +registry_handle_global_remove (void *data, + struct wl_registry *registry, + uint32_t id) +{ +} + +static const struct wl_registry_listener registry_listener = { + registry_handle_global, + registry_handle_global_remove, +}; + +static void +free_shell_data (gpointer data) +{ + XAppWaylandShell *wd = data; + + if (wd->shell) + xapp_shell1_destroy (wd->shell); + + g_free (wd); +} + +/* Bind (once per display) the xapp_shell1 global from the registry, using a + * private event queue so our blocking roundtrips don't dispatch GTK's events. */ +static XAppWaylandShell * +ensure_shell (GdkDisplay *display) +{ + XAppWaylandShell *wd; + struct wl_display *wl_display; + struct wl_event_queue *queue; + struct wl_registry *registry; + + if (!GDK_IS_WAYLAND_DISPLAY (display)) + return NULL; + + wd = g_object_get_data (G_OBJECT (display), DISPLAY_DATA_KEY); + if (wd) + return wd; + + wd = g_new0 (XAppWaylandShell, 1); + + wl_display = gdk_wayland_display_get_wl_display (GDK_WAYLAND_DISPLAY (display)); + queue = wl_display_create_queue (wl_display); + registry = wl_display_get_registry (wl_display); + wl_proxy_set_queue ((struct wl_proxy *) registry, queue); + wl_registry_add_listener (registry, ®istry_listener, wd); + + /* First roundtrip processes the globals (binding the shell, if present). */ + if (wl_display_roundtrip_queue (wl_display, queue) < 0) + g_warning ("XAppGtkWindow: Wayland roundtrip failed while querying for the xapp-shell global"); + /* Second roundtrip processes the shell's 'capabilities' event. */ + if (wd->shell && wl_display_roundtrip_queue (wl_display, queue) < 0) + g_warning ("XAppGtkWindow: Wayland roundtrip failed while querying xapp-shell capabilities"); + + wl_registry_destroy (registry); + + /* The shell proxy must outlive the temporary queue, so move it back to the + * default queue. It receives no further events. */ + if (wd->shell) + wl_proxy_set_queue ((struct wl_proxy *) wd->shell, NULL); + + wl_event_queue_destroy (queue); + + g_object_set_data_full (G_OBJECT (display), DISPLAY_DATA_KEY, + wd, free_shell_data); + return wd; +} + +static struct xapp_surface1 * +ensure_xapp_surface (GtkWindow *window, + XAppWaylandShell *wd) +{ + GdkWindow *gdk_window; + struct wl_surface *wl_surface; + struct xapp_surface1 *xapp_surface; + + xapp_surface = g_object_get_data (G_OBJECT (window), SURFACE_DATA_KEY); + if (xapp_surface) + return xapp_surface; + + gdk_window = gtk_widget_get_window (GTK_WIDGET (window)); + if (gdk_window == NULL || !GDK_IS_WAYLAND_WINDOW (gdk_window)) + return NULL; + + wl_surface = gdk_wayland_window_get_wl_surface (gdk_window); + if (wl_surface == NULL) + return NULL; + + xapp_surface = xapp_shell1_get_xapp_surface (wd->shell, wl_surface); + g_object_set_data_full (G_OBJECT (window), SURFACE_DATA_KEY, + xapp_surface, + (GDestroyNotify) xapp_surface1_destroy); + return xapp_surface; +} + +static void +flush_display (GdkDisplay *display) +{ + wl_display_flush (gdk_wayland_display_get_wl_display (GDK_WAYLAND_DISPLAY (display))); +} + +void +_xapp_wayland_update_icon_name (GtkWindow *window, + const gchar *icon_str) +{ + GdkDisplay *display = gtk_widget_get_display (GTK_WIDGET (window)); + XAppWaylandShell *wd; + struct xapp_surface1 *xapp_surface; + + wd = ensure_shell (display); + if (wd == NULL || wd->shell == NULL || + !(wd->capabilities & XAPP_SHELL1_CAPABILITY_ICON_NAME)) + return; + + xapp_surface = ensure_xapp_surface (window, wd); + if (xapp_surface == NULL) + return; + + xapp_surface1_set_icon_name (xapp_surface, icon_str); + flush_display (display); +} + +void +_xapp_wayland_update_progress (GtkWindow *window, + guint progress, + gboolean pulse) +{ + GdkDisplay *display = gtk_widget_get_display (GTK_WIDGET (window)); + XAppWaylandShell *wd; + struct xapp_surface1 *xapp_surface; + + wd = ensure_shell (display); + if (wd == NULL || wd->shell == NULL || + !(wd->capabilities & XAPP_SHELL1_CAPABILITY_PROGRESS)) + return; + + xapp_surface = ensure_xapp_surface (window, wd); + if (xapp_surface == NULL) + return; + + xapp_surface1_set_progress (xapp_surface, (int32_t) progress); + xapp_surface1_set_progress_pulse (xapp_surface, pulse ? 1 : 0); + flush_display (display); +} + +void +_xapp_wayland_window_unrealized (GtkWindow *window) +{ + /* Drops the cached xapp_surface1 (destroying it via its GDestroyNotify) so + * a fresh one is created against the new wl_surface on the next realize. */ + g_object_set_data (G_OBJECT (window), SURFACE_DATA_KEY, NULL); +} diff --git a/libxapp/xapp-gtk-window-x11.c b/libxapp/xapp-gtk-window-x11.c new file mode 100644 index 0000000..7c4dc92 --- /dev/null +++ b/libxapp/xapp-gtk-window-x11.c @@ -0,0 +1,148 @@ +#include + +#include + +#include +#include + +#include +#include + +#include "xapp-gtk-window-backend.h" + +#define ICON_NAME_HINT "_NET_WM_XAPP_ICON_NAME" +#define PROGRESS_HINT "_NET_WM_XAPP_PROGRESS" +#define PROGRESS_PULSE_HINT "_NET_WM_XAPP_PROGRESS_PULSE" + +static gboolean +ensure_x11 (GdkDisplay **display_out) +{ + GdkDisplay *display = gdk_display_get_default (); + + if (!GDK_IS_X11_DISPLAY (display)) + return FALSE; + + *display_out = display; + return TRUE; +} + +static void +set_window_hint_utf8 (Window xid, + const gchar *atom_name, + const gchar *str) +{ + GdkDisplay *display; + + if (!ensure_x11 (&display)) + return; + + if (str != NULL) + { + XChangeProperty (GDK_DISPLAY_XDISPLAY (display), + xid, + gdk_x11_get_xatom_by_name_for_display (display, atom_name), + gdk_x11_get_xatom_by_name_for_display (display, "UTF8_STRING"), 8, + PropModeReplace, (guchar *) str, strlen (str)); + } + else + { + XDeleteProperty (GDK_DISPLAY_XDISPLAY (display), + xid, + gdk_x11_get_xatom_by_name_for_display (display, atom_name)); + } +} + +static void +set_window_hint_cardinal (Window xid, + const gchar *atom_name, + gulong cardinal) +{ + GdkDisplay *display; + + if (!ensure_x11 (&display)) + return; + + gdk_error_trap_push (); + + if (cardinal > 0) + { + XChangeProperty (GDK_DISPLAY_XDISPLAY (display), + xid, + gdk_x11_get_xatom_by_name_for_display (display, atom_name), + XA_CARDINAL, 32, + PropModeReplace, + (guchar *) &cardinal, 1); + } + else + { + XDeleteProperty (GDK_DISPLAY_XDISPLAY (display), + xid, + gdk_x11_get_xatom_by_name_for_display (display, atom_name)); + } + + gdk_error_trap_pop_ignored (); +} + +static Window +get_window_xid (GtkWindow *window) +{ + GdkWindow *gdk_window; + + gdk_window = gtk_widget_get_window (GTK_WIDGET (window)); + + if (gdk_window_get_effective_toplevel (gdk_window) != gdk_window) + { + g_warning ("Window is not toplevel"); + return 0; + } + + return GDK_WINDOW_XID (gdk_window); +} + +void +_xapp_x11_update_icon_name (GtkWindow *window, + const gchar *icon_str) +{ + Window xid = get_window_xid (window); + + if (xid == 0) + return; + + set_window_hint_utf8 (xid, ICON_NAME_HINT, icon_str); +} + +void +_xapp_x11_update_progress (GtkWindow *window, + guint progress, + gboolean pulse) +{ + Window xid = get_window_xid (window); + + if (xid == 0) + return; + + set_window_hint_cardinal (xid, PROGRESS_HINT, (gulong) progress); + set_window_hint_cardinal (xid, PROGRESS_PULSE_HINT, (gulong) (pulse ? 1 : 0)); +} + +void +_xapp_x11_set_xid_icon_name (gulong xid, + const gchar *icon_str) +{ + set_window_hint_utf8 (xid, ICON_NAME_HINT, icon_str); +} + +void +_xapp_x11_set_xid_progress (gulong xid, + gint progress) +{ + set_window_hint_cardinal (xid, PROGRESS_HINT, (gulong) (CLAMP (progress, 0, 100))); + set_window_hint_cardinal (xid, PROGRESS_PULSE_HINT, (gulong) 0); +} + +void +_xapp_x11_set_xid_progress_pulse (gulong xid, + gboolean pulse) +{ + set_window_hint_cardinal (xid, PROGRESS_PULSE_HINT, (gulong) (pulse ? 1 : 0)); +} diff --git a/libxapp/xapp-gtk-window.c b/libxapp/xapp-gtk-window.c index b8a968c..71efa89 100644 --- a/libxapp/xapp-gtk-window.c +++ b/libxapp/xapp-gtk-window.c @@ -4,20 +4,15 @@ #include #include #include -#include -#include #include #include #include "xapp-gtk-window.h" +#include "xapp-gtk-window-backend.h" #define DEBUG_FLAG XAPP_DEBUG_WINDOW #include "xapp-debug.h" -#define ICON_NAME_HINT "_NET_WM_XAPP_ICON_NAME" -#define PROGRESS_HINT "_NET_WM_XAPP_PROGRESS" -#define PROGRESS_PULSE_HINT "_NET_WM_XAPP_PROGRESS_PULSE" - /** * SECTION:xapp-gtk-window * @Short_description: A subclass of %GtkWindow that allows additional @@ -90,122 +85,39 @@ clear_icon_strings (XAppGtkWindowPrivate *priv) g_clear_pointer (&priv->icon_path, g_free); } -static void -set_window_hint_utf8 (Window xid, - const gchar *atom_name, - const gchar *str) -{ - GdkDisplay *display; - - display = gdk_display_get_default (); - - if (str != NULL) - { - XChangeProperty (GDK_DISPLAY_XDISPLAY (display), - xid, - gdk_x11_get_xatom_by_name_for_display (display, atom_name), - gdk_x11_get_xatom_by_name_for_display (display, "UTF8_STRING"), 8, - PropModeReplace, (guchar *) str, strlen (str)); - } - else - { - XDeleteProperty (GDK_DISPLAY_XDISPLAY (display), - xid, - gdk_x11_get_xatom_by_name_for_display (display, atom_name)); - } -} - -static void -set_window_hint_cardinal (Window xid, - const gchar *atom_name, - gulong cardinal) -{ - GdkDisplay *display; - - display = gdk_display_get_default (); - - gdk_error_trap_push (); - - if (cardinal > 0) - { - XChangeProperty (GDK_DISPLAY_XDISPLAY (display), - xid, - gdk_x11_get_xatom_by_name_for_display (display, atom_name), - XA_CARDINAL, 32, - PropModeReplace, - (guchar *) &cardinal, 1); - } - else - { - XDeleteProperty (GDK_DISPLAY_XDISPLAY (display), - xid, - gdk_x11_get_xatom_by_name_for_display (display, atom_name)); - } - - gdk_error_trap_pop_ignored (); -} - -static Window -get_window_xid (GtkWindow *window) -{ - GdkWindow *gdk_window; - - gdk_window = gtk_widget_get_window (GTK_WIDGET (window)); - - if (gdk_window_get_effective_toplevel (gdk_window) != gdk_window) - { - g_warning ("Window is not toplevel"); - return 0; - } - - return GDK_WINDOW_XID (gdk_window); -} - static void update_window_icon (GtkWindow *window, XAppGtkWindowPrivate *priv) { - if (!is_x11_session ()) { - return; - } + /* Either an icon name or an icon path may be set; never both. */ + const gchar *icon_str = priv->icon_name ? priv->icon_name : priv->icon_path; - /* Icon name/path */ - if (priv->icon_name != NULL) + if (is_x11_session ()) { - set_window_hint_utf8 (get_window_xid (window), - ICON_NAME_HINT, - priv->icon_name); - } - else if (priv->icon_path != NULL) - { - set_window_hint_utf8 (get_window_xid (window), - ICON_NAME_HINT, - priv->icon_path); + _xapp_x11_update_icon_name (window, icon_str); } +#ifdef HAVE_WAYLAND else { - set_window_hint_utf8 (get_window_xid (window), - ICON_NAME_HINT, - NULL); + _xapp_wayland_update_icon_name (window, icon_str); } +#endif } static void update_window_progress (GtkWindow *window, XAppGtkWindowPrivate *priv) { - if (!is_x11_session ()) { - return; + if (is_x11_session ()) + { + _xapp_x11_update_progress (window, priv->progress, priv->progress_pulse); } - - /* Progress: 0 - 100 */ - set_window_hint_cardinal (get_window_xid (window), - PROGRESS_HINT, - (gulong) priv->progress); - - set_window_hint_cardinal (get_window_xid (window), - PROGRESS_PULSE_HINT, - (gulong) (priv->progress_pulse ? 1 : 0)); +#ifdef HAVE_WAYLAND + else + { + _xapp_wayland_update_progress (window, priv->progress, priv->progress_pulse); + } +#endif } static void @@ -347,6 +259,14 @@ xapp_gtk_window_realize (GtkWidget *widget) static void xapp_gtk_window_unrealize (GtkWidget *widget) { + /* Drops the Wayland surface for XAppGtkWindow instances. Plain GtkWindows + * decorated via the xapp_set_window_* helpers are covered separately by + * on_gtk_window_unrealized(); a window driven by both paths gets called + * twice, which is harmless. */ +#ifdef HAVE_WAYLAND + _xapp_wayland_window_unrealized (GTK_WINDOW (widget)); +#endif + GTK_WIDGET_CLASS (xapp_gtk_window_parent_class)->unrealize (widget); } @@ -512,6 +432,15 @@ on_gtk_window_realized (GtkWidget *widget, update_window_progress (GTK_WINDOW (widget), priv); } +static void +on_gtk_window_unrealized (GtkWidget *widget, + gpointer user_data) +{ +#ifdef HAVE_WAYLAND + _xapp_wayland_window_unrealized (GTK_WINDOW (widget)); +#endif +} + static void destroy_xapp_struct (gpointer user_data) { @@ -548,6 +477,11 @@ get_xapp_struct (GtkWindow *window) G_CALLBACK (on_gtk_window_realized), priv); + g_signal_connect (GTK_WIDGET (window), + "unrealize", + G_CALLBACK (on_gtk_window_unrealized), + priv); + return priv; } @@ -700,7 +634,7 @@ xapp_set_xid_icon_name (gulong xid, { g_return_if_fail (xid > 0); - set_window_hint_utf8 (xid, ICON_NAME_HINT, icon_name); + _xapp_x11_set_xid_icon_name (xid, icon_name); } /** @@ -722,7 +656,7 @@ xapp_set_xid_icon_from_file (gulong xid, g_return_if_fail (xid > 0); - set_window_hint_utf8 (xid, ICON_NAME_HINT, file_name); + _xapp_x11_set_xid_icon_name (xid, file_name); } /** @@ -751,8 +685,7 @@ xapp_set_xid_progress (gulong xid, { g_return_if_fail (xid > 0); - set_window_hint_cardinal (xid, PROGRESS_HINT, (gulong) (CLAMP (progress, 0, 100))); - set_window_hint_cardinal (xid, PROGRESS_PULSE_HINT, (gulong) 0); + _xapp_x11_set_xid_progress (xid, progress); } /** @@ -776,5 +709,5 @@ xapp_set_xid_progress_pulse (gulong xid, { g_return_if_fail (xid > 0); - set_window_hint_cardinal (xid, PROGRESS_PULSE_HINT, (gulong) (pulse ? 1 : 0)); + _xapp_x11_set_xid_progress_pulse (xid, pulse); } diff --git a/libxapp/xapp-shell.xml b/libxapp/xapp-shell.xml new file mode 100644 index 0000000..11ea354 --- /dev/null +++ b/libxapp/xapp-shell.xml @@ -0,0 +1,109 @@ + + + + + Copyright © 2026 Linux Mint + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + DEALINGS IN THE SOFTWARE. + + + + + xapp_shell is a private protocol extension that lets clients attach + XApp-specific window-manager hints to their toplevel surfaces. It is the + Wayland counterpart of the X11 _NET_WM_XAPP_* window properties used by + the Cinnamon desktop: a themed icon name (resolved crisply by the + compositor at any scale) and task-manager progress information. + + The compositor advertises which hints it understands through the + 'capabilities' event, allowing clients to negotiate features instead of + relying on the interface version alone. + + + + + + + + + + This event is sent once when the client binds the global, describing the + set of hints the compositor supports. + + + + + + + Create an xapp_surface1 object associated with the given wl_surface, + through which XApp hints can be set on the corresponding window. + + + + + + + + Destroy the xapp_shell1 object. This does not affect any xapp_surface1 + objects created with it. + + + + + + + An xapp_surface1 carries XApp hints for the wl_surface it was created for. + Setting a hint takes effect immediately. The hints persist until changed + or unset; destroying the object does not reset them. + + + + + Set a themed icon name, or an absolute path to an icon file, that the + compositor and its desktop shell should use for this window in titlebars, + task lists and window switchers. Pass a null name to unset it. + + + + + + + Set a progress value, from 0 to 100, to be displayed by a task manager + for this window. A value of 0 clears the progress. Setting an explicit + progress value also clears the pulsing state. + + + + + + + Set whether the window should show indeterminate ('pulsing') progress. + A non-zero value enables pulsing. + + + + + + + Destroy the xapp_surface1 object. Hints already set on the window are + left untouched. + + + + diff --git a/test-scripts/xapp-gtk-window b/test-scripts/xapp-gtk-window index bb83e87..0cc69e3 100755 --- a/test-scripts/xapp-gtk-window +++ b/test-scripts/xapp-gtk-window @@ -1,7 +1,17 @@ #! /usr/bin/python3 """ -A demo/test script for the XAppAppGtkWindow class +A demo/test script for the XAppAppGtkWindow class. + +XAppGtkWindow forwards its icon-name/icon-path and progress hints to the window +manager. Under X11 this is done via the _NET_WM_XAPP_* window properties (watch +them with 'xprop -spy'). Under Wayland it is done via the private xapp-shell +protocol (run this script with WAYLAND_DEBUG=1 to watch the xapp_shell1 and +xapp_surface1 requests on the wire). + +Use the "New window" button to open additional windows - each one gets its own +Wayland surface (and its own xapp_surface1), so you can confirm that setting an +icon or progress on one window doesn't affect the others. """ import sys, os import signal @@ -12,17 +22,74 @@ import gi gi.require_version('Gtk', '3.0') gi.require_version('XApp', '1.0') -from gi.repository import GLib, Gtk, XApp, GObject +from gi.repository import GLib, Gtk, Gdk, XApp, GObject signal.signal(signal.SIGINT, signal.SIG_DFL) -class Main: +windows = [] + +def display_backend(): + display = Gdk.Display.get_default() + + if display is None: + return "none", "(no display)" + + gtype_name = display.__gtype__.name + + if "X11" in gtype_name: + return "X11", gtype_name + if "Wayland" in gtype_name: + return "Wayland", gtype_name + + return gtype_name, gtype_name + +def window_surface_info(gtk_window): + gdk_window = gtk_window.get_window() + + if gdk_window is None: + return "not realized yet" + + backend, _ = display_backend() + win_type = gdk_window.__gtype__.name + + if backend == "X11": + try: + gi.require_version('GdkX11', '3.0') + from gi.repository import GdkX11 + return "%s (xid 0x%x)" % (win_type, GdkX11.X11Window.get_xid(gdk_window)) + except Exception: + return win_type + + return win_type + +def print_environment_summary(): + backend, gtype_name = display_backend() + print("--- xapp-gtk-window test ---") + print(" XApp backend in use : %s (%s)" % (backend, gtype_name)) + print(" XDG_SESSION_TYPE : %s" % os.environ.get("XDG_SESSION_TYPE", "(unset)")) + print(" WAYLAND_DISPLAY : %s" % os.environ.get("WAYLAND_DISPLAY", "(unset)")) + print(" DISPLAY : %s" % os.environ.get("DISPLAY", "(unset)")) + if backend == "Wayland": + print(" Tip: re-run with WAYLAND_DEBUG=1 to watch xapp_shell1 / " + "xapp_surface1 traffic.") + elif backend == "X11": + print(" Tip: use 'xprop -spy' on a window to watch the _NET_WM_XAPP_* " + "properties.") + print("----------------------------") + + +class TestWindow: + count = 0 + def __init__(self): - self.win = XApp.GtkWindow() + TestWindow.count += 1 + self.index = TestWindow.count self._animate_progress = 0 - self.win.set_default_size(320, 200) + self.win = XApp.GtkWindow() + self.win.set_title("XAppGtkWindow test #%d" % self.index) + self.win.set_default_size(360, 280) frame = Gtk.Frame() frame.set_margin_start(2) @@ -40,13 +107,30 @@ class Main: frame.add(box) + backend, _ = display_backend() + if backend == "X11": + tip = ("Use 'xprop -spy' " + "to monitor _NET_WM_XAPP_* changes") + elif backend == "Wayland": + tip = ("Run with 'WAYLAND_DEBUG=1' " + "to watch xapp-shell traffic") + else: + tip = "Set icon name/path and progress below" + heading = Gtk.Label() - heading.set_markup("Use 'xprop -spy' to monitor changes") - box.pack_start(heading, True, True, 4) + heading.set_markup(tip) + heading.set_line_wrap(True) + box.pack_start(heading, False, False, 4) + + # Backend / surface debug info, refreshed once the window is realized. + self.info_label = Gtk.Label(xalign=0.0) + self.info_label.set_selectable(True) + self.info_label.set_line_wrap(True) + box.pack_start(self.info_label, False, False, 4) hbox = Gtk.HBox() self.icon_name_entry = Gtk.Entry() - self.icon_name_setter = Gtk.Button("Set icon name") + self.icon_name_setter = Gtk.Button(label="Set icon name") self.icon_name_setter.connect("clicked", self.on_icon_name_setter_clicked) hbox.pack_start(self.icon_name_entry, True, True, 4) hbox.pack_start(self.icon_name_setter, False, False, 4) @@ -54,14 +138,14 @@ class Main: hbox = Gtk.HBox() self.icon_path_entry = Gtk.Entry() - self.icon_path_setter = Gtk.Button("Set icon path") + self.icon_path_setter = Gtk.Button(label="Set icon path") self.icon_path_setter.connect("clicked", self.on_icon_path_setter_clicked) hbox.pack_start(self.icon_path_entry, True, True, 4) hbox.pack_start(self.icon_path_setter, False, False, 4) box.pack_start(hbox, True, True, 4) hbox = Gtk.HBox() - self.progress_label = Gtk.Label("Progress:") + self.progress_label = Gtk.Label(label="Progress:") self.progress = Gtk.Scale() self.progress.connect("value-changed", self.on_progress_value_changed) self.progress.set_draw_value(True) @@ -73,7 +157,7 @@ class Main: box.pack_start(hbox, True, True, 4) hbox = Gtk.HBox() - self.pulse_label = Gtk.Label("Progress pulse:") + self.pulse_label = Gtk.Label(label="Progress pulse:") self.pulse_switch = Gtk.Switch() self.pulse_switch.set_halign(Gtk.Align.CENTER) self.pulse_switch.connect("notify::active", self.on_pulse_switch_changed) @@ -82,17 +166,56 @@ class Main: box.pack_start(hbox, True, True, 4) hbox = Gtk.HBox() - self.animate_button = Gtk.Button("Simulate progress over time") + self.animate_button = Gtk.Button(label="Simulate progress over time") self.animate_button.connect("clicked", self.on_animate_progress_clicked) hbox.pack_start(self.animate_button, True, True, 4) + box.pack_start(hbox, True, True, 4) + hbox = Gtk.HBox() + self.new_window_button = Gtk.Button(label="New window") + self.new_window_button.connect("clicked", self.on_new_window_clicked) + self.debug_button = Gtk.Button(label="Print debug info") + self.debug_button.connect("clicked", self.on_debug_clicked) + hbox.pack_start(self.new_window_button, True, True, 4) + hbox.pack_start(self.debug_button, True, True, 4) box.pack_start(hbox, True, True, 4) frame.show_all() - self.win.connect("delete-event", lambda w, e: Gtk.main_quit()) + + windows.append(self) + self.win.connect("delete-event", self.on_delete_event) + self.win.connect("realize", lambda w: self.refresh_info()) self.win.present() - Gtk.main() + self.refresh_info() + + def refresh_info(self): + backend, gtype_name = display_backend() + surface = window_surface_info(self.win) + self.info_label.set_markup( + "Window #%d\n" + "Backend: %s (%s)\n" + "Surface: %s" + % (self.index, backend, gtype_name, GLib.markup_escape_text(surface))) + + def on_delete_event(self, window, event): + if self in windows: + windows.remove(self) + if not windows: + Gtk.main_quit() + return False + + def on_new_window_clicked(self, button, data=None): + TestWindow() + + def on_debug_clicked(self, button, data=None): + backend, gtype_name = display_backend() + print("[window #%d] backend=%s (%s) surface=%s icon_name=%r progress=%d pulse=%s" + % (self.index, backend, gtype_name, + window_surface_info(self.win), + self.icon_name_entry.get_text(), + int(self.progress.get_value()), + self.pulse_switch.get_active())) def on_animate_progress_clicked(self, button, data=None): self.progress.set_sensitive(False) @@ -134,5 +257,13 @@ class Main: def on_pulse_switch_changed(self, switch, pspec, data=None): self.win.set_progress_pulse(self.pulse_switch.get_active()) + +class Main: + def __init__(self): + print_environment_summary() + TestWindow() + Gtk.main() + + if __name__ == "__main__": main = Main()