From 032ec7a7685ee015b368b72529407f783205c5d8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ga=C3=ABl=20Bonithon?= <gael@xfce.org> Date: Sat, 3 Feb 2024 16:58:10 +0100 Subject: [PATCH] wayland: Use ext-session-lock protocol --- .gitignore | 5 + Makefile.am | 1 + configure.ac | 14 ++ protocols/Makefile.am | 65 ++++++ protocols/ext-session-lock-v1.xml | 328 ++++++++++++++++++++++++++++++ src/Makefile.am | 16 ++ src/gs-manager.c | 41 +++- src/gs-session-lock-manager.c | 235 +++++++++++++++++++++ src/gs-session-lock-manager.h | 40 ++++ 9 files changed, 744 insertions(+), 1 deletion(-) create mode 100644 protocols/Makefile.am create mode 100644 protocols/ext-session-lock-v1.xml create mode 100644 src/gs-session-lock-manager.c create mode 100644 src/gs-session-lock-manager.h diff --git a/.gitignore b/.gitignore index af62531..0c008ea 100644 --- a/.gitignore +++ b/.gitignore @@ -14,6 +14,8 @@ xfce4-screensaver-*/ *.desktop *.gmo *.html +*.la +*.lo *.m4 *.o *.pot @@ -23,6 +25,7 @@ xfce4-screensaver-*/ *marshal.h *~ config.* +!config.xsl stamp-* # File Match @@ -56,6 +59,8 @@ po/insert-header.sin po/quot.sed po/remove-potcdate.sed po/remove-potcdate.sin +protocols/*.c +protocols/*.h savers/*.desktop.in savers/floaters savers/popsquares diff --git a/Makefile.am b/Makefile.am index 4814836..f32cd84 100644 --- a/Makefile.am +++ b/Makefile.am @@ -7,6 +7,7 @@ AM_DISTCHECK_CONFIGURE_FLAGS = \ --without-systemd SUBDIRS = \ + protocols \ src \ savers \ data \ diff --git a/configure.ac b/configure.ac index fc1bf88..26ddf08 100644 --- a/configure.ac +++ b/configure.ac @@ -61,6 +61,8 @@ m4_define([libxklavier_min_version], [5.2]) m4_define([libwnck_min_version], [3.20]) m4_define([libwlembed_min_version], [0.0.0]) +m4_define([wayland_min_version], [1.15]) +m4_define([wayland_protocols_min_version], [1.20]) XDT_CHECK_PACKAGE([GLIB], [glib-2.0], [glib_min_version]) XDT_CHECK_PACKAGE([GIO], [gio-2.0], [glib_min_version]) @@ -89,12 +91,23 @@ XDT_CHECK_OPTIONAL_FEATURE([WAYLAND], XDT_FEATURE_DEPENDENCY([GDK_WAYLAND], [gdk-wayland-3.0], [gtk_min_version]) XDT_FEATURE_DEPENDENCY([LIBWLEMBED], [libwlembed-0], [libwlembed_min_version]) XDT_FEATURE_DEPENDENCY([LIBWLEMBED_GTK3], [libwlembed-gtk3-0], [libwlembed_min_version]) + XDT_FEATURE_DEPENDENCY([WAYLAND_CLIENT], [wayland-client], [wayland_min_version]) + XDT_FEATURE_DEPENDENCY([WAYLAND_SCANNER], [wayland-scanner], [wayland_min_version]) + XDT_FEATURE_DEPENDENCY([WAYLAND_PROTOCOLS], [wayland-protocols], [wayland_protocols_min_version]) ], [the Wayland windowing system]) if test x"$ENABLE_X11" != x"yes" -a x"$ENABLE_WAYLAND" != x"yes"; then AC_MSG_ERROR([Either both X11 and Wayland support was disabled, or required dependencies are missing. One of the two must be enabled.]) fi +if test x"$ENABLE_WAYLAND" = x"yes"; then + WAYLAND_PROTOCOLS_PKGDATADIR=`$PKG_CONFIG --variable=pkgdatadir wayland-protocols` + AC_SUBST([WAYLAND_PROTOCOLS_PKGDATADIR]) +fi +dnl FIXME: Bump wayland_protocols_min_version to 1.25 when it is an acceptable requirement, +dnl and remove this and protocols/ext-session-lock-v1.xml +AM_CONDITIONAL([HAVE_SESSION_LOCK], [test -f "$WAYLAND_PROTOCOLS_PKGDATADIR/staging/ext-session-lock/ext-session-lock-v1.xml"]) + if test x"$ENABLE_X11" = x"yes"; then # Check whether to use a xscreensaver hacks configuration directory @@ -902,6 +915,7 @@ AC_SUBST(themesdir) AC_CONFIG_FILES([ Makefile +protocols/Makefile po/Makefile.in src/Makefile src/xfce4-screensaver.desktop.in diff --git a/protocols/Makefile.am b/protocols/Makefile.am new file mode 100644 index 0000000..06041ce --- /dev/null +++ b/protocols/Makefile.am @@ -0,0 +1,65 @@ +NULL = + +if ENABLE_WAYLAND + +AM_CPPFLAGS = \ + -I$(top_srcdir) \ + -DG_LOG_DOMAIN=\"libprotocols\" \ + $(PLATFORM_CPPFLAGS) \ + $(NULL) + +noinst_LTLIBRARIES = \ + libprotocols.la + +libprotocols_built_sources = \ + ext-session-lock-v1.c \ + ext-session-lock-v1-client.h \ + $(NULL) + +nodist_libprotocols_la_SOURCES = \ + $(libprotocols_built_sources) + +libprotocols_la_CFLAGS = \ + $(WAYLAND_CLIENT_CFLAGS) \ + $(PLATFORM_CFLAGS) \ + $(NULL) + +libprotocols_la_LDFLAGS = \ + -no-undefined \ + $(PLATFORM_LDFLAGS) \ + $(NULL) + +libprotocols_la_LIBADD = \ + $(WAYLAND_CLIENT_LIBS) \ + $(NULL) + +if HAVE_SESSION_LOCK +%.c: $(WAYLAND_PROTOCOLS_PKGDATADIR)/staging/ext-session-lock/%.xml + $(AM_V_GEN) wayland-scanner private-code $< $@ + +%-client.h: $(WAYLAND_PROTOCOLS_PKGDATADIR)/staging/ext-session-lock/%.xml + $(AM_V_GEN) wayland-scanner client-header $< $@ +else +%.c: %.xml + $(AM_V_GEN) wayland-scanner private-code $< $@ + +%-client.h: %.xml + $(AM_V_GEN) wayland-scanner client-header $< $@ +endif + +DISTCLEANFILES = \ + $(libprotocols_built_sources) \ + $(NULL) + +BUILT_SOURCES = \ + $(libprotocols_built_sources) \ + $(NULL) + +endif # ENABLE_WAYLAND + +EXTRA_DIST = + +if !HAVE_SESSION_LOCK +EXTRA_DIST += \ + ext-session-lock-v1.xml +endif diff --git a/protocols/ext-session-lock-v1.xml b/protocols/ext-session-lock-v1.xml new file mode 100644 index 0000000..19c12d2 --- /dev/null +++ b/protocols/ext-session-lock-v1.xml @@ -0,0 +1,328 @@ +<?xml version="1.0" encoding="UTF-8"?> +<protocol name="ext_session_lock_v1"> + <copyright> + Copyright 2021 Isaac Freund + + 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. + </copyright> + + <description summary="secure session locking with arbitrary graphics"> + This protocol allows for a privileged Wayland client to lock the session + and display arbitrary graphics while the session is locked. + + The compositor may choose to restrict this protocol to a special client + launched by the compositor itself or expose it to all privileged clients, + this is compositor policy. + + The client is responsible for performing authentication and informing the + compositor when the session should be unlocked. If the client dies while + the session is locked the session remains locked, possibly permanently + depending on compositor policy. + + The key words "must", "must not", "required", "shall", "shall not", + "should", "should not", "recommended", "may", and "optional" in this + document are to be interpreted as described in IETF RFC 2119. + + Warning! The protocol described in this file is currently in the + testing phase. Backward compatible changes may be added together with + the corresponding interface version bump. Backward incompatible changes + can only be done by creating a new major version of the extension. + </description> + + <interface name="ext_session_lock_manager_v1" version="1"> + <description summary="used to lock the session"> + This interface is used to request that the session be locked. + </description> + + <request name="destroy" type="destructor"> + <description summary="destroy the session lock manager object"> + This informs the compositor that the session lock manager object will + no longer be used. Existing objects created through this interface + remain valid. + </description> + </request> + + <request name="lock"> + <description summary="attempt to lock the session"> + This request creates a session lock and asks the compositor to lock the + session. The compositor will send either the ext_session_lock_v1.locked + or ext_session_lock_v1.finished event on the created object in + response to this request. + </description> + <arg name="id" type="new_id" interface="ext_session_lock_v1"/> + </request> + </interface> + + <interface name="ext_session_lock_v1" version="1"> + <description summary="manage lock state and create lock surfaces"> + In response to the creation of this object the compositor must send + either the locked or finished event. + + The locked event indicates that the session is locked. This means + that the compositor must stop rendering and providing input to normal + clients. Instead the compositor must blank all outputs with an opaque + color such that their normal content is fully hidden. + + The only surfaces that should be rendered while the session is locked + are the lock surfaces created through this interface and optionally, + at the compositor's discretion, special privileged surfaces such as + input methods or portions of desktop shell UIs. + + The locked event must not be sent until a new "locked" frame (either + from a session lock surface or the compositor blanking the output) has + been presented on all outputs and no security sensitive normal/unlocked + content is possibly visible. + + The finished event should be sent immediately on creation of this + object if the compositor decides that the locked event will not be sent. + + The compositor may wait for the client to create and render session lock + surfaces before sending the locked event to avoid displaying intermediate + blank frames. However, it must impose a reasonable time limit if + waiting and send the locked event as soon as the hard requirements + described above can be met if the time limit expires. Clients should + immediately create lock surfaces for all outputs on creation of this + object to make this possible. + + This behavior of the locked event is required in order to prevent + possible race conditions with clients that wish to suspend the system + or similar after locking the session. Without these semantics, clients + triggering a suspend after receiving the locked event would race with + the first "locked" frame being presented and normal/unlocked frames + might be briefly visible as the system is resumed if the suspend + operation wins the race. + + If the client dies while the session is locked, the compositor must not + unlock the session in response. It is acceptable for the session to be + permanently locked if this happens. The compositor may choose to continue + to display the lock surfaces the client had mapped before it died or + alternatively fall back to a solid color, this is compositor policy. + + Compositors may also allow a secure way to recover the session, the + details of this are compositor policy. Compositors may allow a new + client to create a ext_session_lock_v1 object and take responsibility + for unlocking the session, they may even start a new lock client + instance automatically. + </description> + + <enum name="error"> + <entry name="invalid_destroy" value="0" + summary="attempted to destroy session lock while locked"/> + <entry name="invalid_unlock" value="1" + summary="unlock requested but locked event was never sent"/> + <entry name="role" value="2" + summary="given wl_surface already has a role"/> + <entry name="duplicate_output" value="3" + summary="given output already has a lock surface"/> + <entry name="already_constructed" value="4" + summary="given wl_surface has a buffer attached or committed"/> + </enum> + + <request name="destroy" type="destructor"> + <description summary="destroy the session lock"> + This informs the compositor that the lock object will no longer be + used. Existing objects created through this interface remain valid. + + After this request is made, lock surfaces created through this object + should be destroyed by the client as they will no longer be used by + the compositor. + + It is a protocol error to make this request if the locked event was + sent, the unlock_and_destroy request must be used instead. + </description> + </request> + + <event name="locked"> + <description summary="session successfully locked"> + This client is now responsible for displaying graphics while the + session is locked and deciding when to unlock the session. + + The locked event must not be sent until a new "locked" frame has been + presented on all outputs and no security sensitive normal/unlocked + content is possibly visible. + + If this event is sent, making the destroy request is a protocol error, + the lock object must be destroyed using the unlock_and_destroy request. + </description> + </event> + + <event name="finished"> + <description summary="the session lock object should be destroyed"> + The compositor has decided that the session lock should be destroyed + as it will no longer be used by the compositor. Exactly when this + event is sent is compositor policy, but it must never be sent more + than once for a given session lock object. + + This might be sent because there is already another ext_session_lock_v1 + object held by a client, or the compositor has decided to deny the + request to lock the session for some other reason. This might also + be sent because the compositor implements some alternative, secure + way to authenticate and unlock the session. + + The finished event should be sent immediately on creation of this + object if the compositor decides that the locked event will not + be sent. + + If the locked event is sent on creation of this object the finished + event may still be sent at some later time in this object's + lifetime. This is compositor policy. + + Upon receiving this event, the client should make either the destroy + request or the unlock_and_destroy request, depending on whether or + not the locked event was received on this object. + </description> + </event> + + <request name="get_lock_surface"> + <description summary="create a lock surface for a given output"> + The client is expected to create lock surfaces for all outputs + currently present and any new outputs as they are advertised. These + won't be displayed by the compositor unless the lock is successful + and the locked event is sent. + + Providing a wl_surface which already has a role or already has a buffer + attached or committed is a protocol error, as is attaching/committing + a buffer before the first ext_session_lock_surface_v1.configure event. + + Attempting to create more than one lock surface for a given output + is a duplicate_output protocol error. + </description> + <arg name="id" type="new_id" interface="ext_session_lock_surface_v1"/> + <arg name="surface" type="object" interface="wl_surface"/> + <arg name="output" type="object" interface="wl_output"/> + </request> + + <request name="unlock_and_destroy" type="destructor"> + <description summary="unlock the session, destroying the object"> + This request indicates that the session should be unlocked, for + example because the user has entered their password and it has been + verified by the client. + + This request also informs the compositor that the lock object will + no longer be used and should be destroyed. Existing objects created + through this interface remain valid. + + After this request is made, lock surfaces created through this object + should be destroyed by the client as they will no longer be used by + the compositor. + + It is a protocol error to make this request if the locked event has + not been sent. In that case, the lock object must be destroyed using + the destroy request. + + Note that a correct client that wishes to exit directly after unlocking + the session must use the wl_display.sync request to ensure the server + receives and processes the unlock_and_destroy request. Otherwise + there is no guarantee that the server has unlocked the session due + to the asynchronous nature of the Wayland protocol. For example, + the server might terminate the client with a protocol error before + it processes the unlock_and_destroy request. + </description> + </request> + </interface> + + <interface name="ext_session_lock_surface_v1" version="1"> + <description summary="a surface displayed while the session is locked"> + The client may use lock surfaces to display a screensaver, render a + dialog to enter a password and unlock the session, or however else it + sees fit. + + On binding this interface the compositor will immediately send the + first configure event. After making the ack_configure request in + response to this event the client should attach and commit the first + buffer. Committing the surface before acking the first configure is a + protocol error. Committing the surface with a null buffer at any time + is a protocol error. + + The compositor is free to handle keyboard/pointer focus for lock + surfaces however it chooses. A reasonable way to do this would be to + give the first lock surface created keyboard focus and change keyboard + focus if the user clicks on other surfaces. + </description> + + <enum name="error"> + <entry name="commit_before_first_ack" value="0" + summary="surface committed before first ack_configure request"/> + <entry name="null_buffer" value="1" + summary="surface committed with a null buffer"/> + <entry name="dimensions_mismatch" value="2" + summary="failed to match ack'd width/height"/> + <entry name="invalid_serial" value="3" + summary="serial provided in ack_configure is invalid"/> + </enum> + + <request name="destroy" type="destructor"> + <description summary="destroy the lock surface object"> + This informs the compositor that the lock surface object will no + longer be used. + + It is recommended for a lock client to destroy lock surfaces if + their corresponding wl_output global is removed. + + If a lock surface on an active output is destroyed before the + ext_session_lock_v1.unlock_and_destroy event is sent, the compositor + must fall back to rendering a solid color. + </description> + </request> + + <request name="ack_configure"> + <description summary="ack a configure event"> + When a configure event is received, if a client commits the surface + in response to the configure event, then the client must make an + ack_configure request sometime before the commit request, passing + along the serial of the configure event. + + If the client receives multiple configure events before it can + respond to one, it only has to ack the last configure event. + + A client is not required to commit immediately after sending an + ack_configure request - it may even ack_configure several times + before its next surface commit. + + A client may send multiple ack_configure requests before committing, + but only the last request sent before a commit indicates which + configure event the client really is responding to. + + Sending an ack_configure request consumes the configure event + referenced by the given serial, as well as all older configure events + sent on this object. + + It is a protocol error to issue multiple ack_configure requests + referencing the same configure event or to issue an ack_configure + request referencing a configure event older than the last configure + event acked for a given lock surface. + </description> + <arg name="serial" type="uint" summary="serial from the configure event"/> + </request> + + <event name="configure"> + <description summary="the client should resize its surface"> + This event is sent once on binding the interface and may be sent again + at the compositor's discretion, for example if output geometry changes. + + The width and height are in surface-local coordinates and are exact + requirements. Failing to match these surface dimensions in the next + commit after acking a configure is a protocol error. + </description> + <arg name="serial" type="uint" summary="serial for use in ack_configure"/> + <arg name="width" type="uint"/> + <arg name="height" type="uint"/> + </event> + </interface> +</protocol> diff --git a/src/Makefile.am b/src/Makefile.am index af3c752..87cc5f6 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -150,14 +150,22 @@ test_window_LDADD += \ endif if ENABLE_WAYLAND +test_window_SOURCES += \ + gs-session-lock-manager.c \ + gs-session-lock-manager.h \ + $(NULL) + test_window_CFLAGS += \ $(LIBWLEMBED_CFLAGS) \ $(LIBWLEMBED_GTK3_CFLAGS) \ + $(WAYLAND_CLIENT_CFLAGS) \ $(NULL) test_window_LDADD += \ + $(top_builddir)/protocols/libprotocols.la \ $(LIBWLEMBED_LIBS) \ $(LIBWLEMBED_GTK3_LIBS) \ + $(WAYLAND_CLIENT_LIBS) \ $(NULL) endif @@ -319,14 +327,22 @@ xfce4_screensaver_LDADD += \ endif if ENABLE_WAYLAND +xfce4_screensaver_SOURCES += \ + gs-session-lock-manager.c \ + gs-session-lock-manager.h \ + $(NULL) + xfce4_screensaver_CFLAGS += \ $(LIBWLEMBED_CFLAGS) \ $(LIBWLEMBED_GTK3_CFLAGS) \ + $(WAYLAND_CLIENT_CFLAGS) \ $(NULL) xfce4_screensaver_LDADD += \ + $(top_builddir)/protocols/libprotocols.la \ $(LIBWLEMBED_LIBS) \ $(LIBWLEMBED_GTK3_LIBS) \ + $(WAYLAND_CLIENT_LIBS) \ $(NULL) endif diff --git a/src/gs-manager.c b/src/gs-manager.c index dae0297..2f66055 100644 --- a/src/gs-manager.c +++ b/src/gs-manager.c @@ -32,6 +32,7 @@ #ifdef ENABLE_WAYLAND #include <gdk/gdkwayland.h> #include <libwlembed-gtk3/libwlembed-gtk3.h> +#include "gs-session-lock-manager.h" #endif #include "gs-debug.h" @@ -67,6 +68,7 @@ struct GSManagerPrivate { #endif #ifdef ENABLE_WAYLAND WleEmbeddedCompositor *compositor; + GSSessionLockManager *lock_manager; #endif }; @@ -461,6 +463,8 @@ gs_manager_init (GSManager *manager) { g_critical ("Failed to create embedded compositor: %s", error->message); g_error_free (error); } + + manager->priv->lock_manager = gs_session_lock_manager_new (); } #endif } @@ -821,6 +825,11 @@ gs_manager_create_window_for_monitor (GSManager *manager, gs_window_set_status_message (window, manager->priv->status_message); gs_window_set_lock_active (window, manager->priv->lock_active); connect_window_signals (manager, window); +#ifdef ENABLE_WAYLAND + if (manager->priv->lock_manager != NULL) { + gs_session_lock_manager_add_window (manager->priv->lock_manager, window); + } +#endif g_hash_table_insert (manager->priv->windows, monitor, window); @@ -831,6 +840,16 @@ gs_manager_create_window_for_monitor (GSManager *manager, return GTK_WIDGET (window); } +#ifdef ENABLE_WAYLAND +static void +remove_window (gpointer monitor, + gpointer window, + gpointer data) { + GSManager *manager = data; + gs_session_lock_manager_remove_window (manager->priv->lock_manager, window); +} +#endif + static gboolean remove_overlays (GtkWidget *window, cairo_t *cr, @@ -866,6 +885,11 @@ recreate_windows (GtkWidget *overlay, gs_debug("Reconfiguring monitors, recreating windows"); g_hash_table_remove_all (manager->priv->jobs); +#ifdef ENABLE_WAYLAND + if (manager->priv->lock_manager != NULL) { + g_hash_table_foreach (manager->priv->windows, remove_window, manager); + } +#endif g_hash_table_remove_all (manager->priv->windows); manager->priv->n_overlay_signal_received = 0; @@ -945,6 +969,11 @@ gs_manager_destroy_windows (GSManager *manager) { on_display_monitor_added, manager); +#ifdef ENABLE_WAYLAND + if (manager->priv->lock_manager != NULL) { + g_hash_table_foreach (manager->priv->windows, remove_window, manager); + } +#endif g_hash_table_remove_all (manager->priv->windows); g_list_free_full (manager->priv->overlays, (GDestroyNotify) gtk_widget_destroy); manager->priv->overlays = NULL; @@ -980,6 +1009,9 @@ gs_manager_finalize (GObject *object) { if (manager->priv->compositor != NULL) { g_object_unref (manager->priv->compositor); } + if (manager->priv->lock_manager != NULL) { + g_object_unref (manager->priv->lock_manager); + } #endif G_OBJECT_CLASS (gs_manager_parent_class)->finalize (object); @@ -1047,7 +1079,9 @@ gs_manager_activate (GSManager *manager) { #ifdef ENABLE_WAYLAND if (GDK_IS_WAYLAND_DISPLAY (gdk_display_get_default ())) { - if (manager->priv->compositor == NULL) { + if (manager->priv->compositor == NULL + || manager->priv->lock_manager == NULL + || !gs_session_lock_manager_lock (manager->priv->lock_manager)) { return FALSE; } } @@ -1080,6 +1114,11 @@ gs_manager_deactivate (GSManager *manager) { #endif g_hash_table_remove_all (manager->priv->jobs); gs_manager_destroy_windows (manager); +#ifdef ENABLE_WAYLAND + if (manager->priv->lock_manager != NULL) { + gs_session_lock_manager_unlock (manager->priv->lock_manager); + } +#endif /* reset state */ manager->priv->active = FALSE; diff --git a/src/gs-session-lock-manager.c b/src/gs-session-lock-manager.c new file mode 100644 index 0000000..b1fea42 --- /dev/null +++ b/src/gs-session-lock-manager.c @@ -0,0 +1,235 @@ +/* + * Copyright (C) 2024 Gaël Bonithon <gael@xfce.org> + * + * 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. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#include "config.h" + +#include <gdk/gdk.h> +#include <gdk/gdkwayland.h> + +#include "gs-session-lock-manager.h" +#include "protocols/ext-session-lock-v1-client.h" +#include "gs-debug.h" + +static void gs_session_lock_manager_finalize (GObject *object); + +static void registry_global (void *data, struct wl_registry *registry, uint32_t id, const char *interface, uint32_t version); +static void registry_global_remove (void *data, struct wl_registry *registry, uint32_t id); +static void lock_locked (void *data, struct ext_session_lock_v1 *lock); +static void lock_finished (void *data, struct ext_session_lock_v1 *lock); +static void surface_configure (void *data, struct ext_session_lock_surface_v1 *surface, uint32_t serial, uint32_t width, uint32_t height); + +struct _GSSessionLockManager { + GObject __parent__; + + struct wl_registry *wl_registry; + struct ext_session_lock_manager_v1 *wl_manager; + struct ext_session_lock_v1 *wl_lock; + GHashTable *lock_surfaces; + gboolean locked; +}; + +static const struct wl_registry_listener registry_listener = { + .global = registry_global, + .global_remove = registry_global_remove, +}; + +static const struct ext_session_lock_v1_listener lock_listener = { + .locked = lock_locked, + .finished = lock_finished, +}; + +static const struct ext_session_lock_surface_v1_listener surface_listener = { + .configure = surface_configure, +}; + + + +G_DEFINE_TYPE (GSSessionLockManager, gs_session_lock_manager, G_TYPE_OBJECT) + + + +static void +gs_session_lock_manager_class_init (GSSessionLockManagerClass *klass) { + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + object_class->finalize = gs_session_lock_manager_finalize; +} + +static void +gs_session_lock_manager_init (GSSessionLockManager *manager) { + struct wl_display *wl_display = gdk_wayland_display_get_wl_display (gdk_display_get_default ()); + + manager->wl_registry = wl_display_get_registry (wl_display); + wl_registry_add_listener (manager->wl_registry, ®istry_listener, manager); + wl_display_roundtrip (wl_display); + if (manager->wl_manager != NULL) { + manager->lock_surfaces = g_hash_table_new_full (g_direct_hash, g_direct_equal, NULL, (GDestroyNotify) ext_session_lock_surface_v1_destroy); + } else { + g_warning ("ext-session-lock-v1 protocol unsupported: xfce4-screensaver will not be able to activate or lock the session"); + } +} + +static void +gs_session_lock_manager_finalize (GObject *object) { + GSSessionLockManager *manager = GS_SESSION_LOCK_MANAGER (object); + + if (manager->wl_manager != NULL) { + if (manager->wl_lock != NULL) { + lock_finished (manager, manager->wl_lock); + } + g_hash_table_destroy (manager->lock_surfaces); + ext_session_lock_manager_v1_destroy (manager->wl_manager); + } + wl_registry_destroy (manager->wl_registry); + + G_OBJECT_CLASS (gs_session_lock_manager_parent_class)->finalize (object); +} + +static void +registry_global (void *data, + struct wl_registry *registry, + uint32_t id, + const char *interface, + uint32_t version) { + GSSessionLockManager *manager = data; + if (g_strcmp0 (ext_session_lock_manager_v1_interface.name, interface) == 0) { + manager->wl_manager = wl_registry_bind (manager->wl_registry, id, &ext_session_lock_manager_v1_interface, + MIN ((uint32_t) ext_session_lock_manager_v1_interface.version, version)); + } +} + +static void +registry_global_remove (void *data, + struct wl_registry *registry, + uint32_t id) { +} + +static void +lock_locked (void *data, + struct ext_session_lock_v1 *lock) { + GSSessionLockManager *manager = data; + gs_debug ("Locked event received from compositor"); + manager->locked = TRUE; +} + +static void +lock_finished (void *data, + struct ext_session_lock_v1 *lock) { + GSSessionLockManager *manager = data; + gs_debug ("Finished event received from compositor"); + gs_session_lock_manager_unlock (manager); +} + +static void +surface_configure (void *data, + struct ext_session_lock_surface_v1 *surface, + uint32_t serial, + uint32_t width, + uint32_t height) { + gs_debug ("Configuring lock surface for monitor %s: width %d, height %d", + gdk_monitor_get_model (gs_window_get_monitor (data)), width, height); + gdk_window_move_resize (gtk_widget_get_window (data), 0, 0, width, height); + ext_session_lock_surface_v1_ack_configure (surface, serial); +} + + + +GSSessionLockManager * +gs_session_lock_manager_new (void) { + GSSessionLockManager *manager = g_object_new (GS_TYPE_SESSION_LOCK_MANAGER, NULL); + if (manager->wl_manager == NULL) { + g_object_unref (manager); + manager = NULL; + } + return manager; +} + +gboolean +gs_session_lock_manager_lock (GSSessionLockManager *manager) { + g_return_val_if_fail (GS_IS_SESSION_LOCK_MANAGER (manager), FALSE); + g_return_val_if_fail (manager->wl_lock == NULL, FALSE); + + gs_debug ("Locking session"); + manager->wl_lock = ext_session_lock_manager_v1_lock (manager->wl_manager); + ext_session_lock_v1_add_listener (manager->wl_lock, &lock_listener, manager); + wl_display_roundtrip (gdk_wayland_display_get_wl_display (gdk_display_get_default ())); + + return manager->wl_lock != NULL; +} + +void +gs_session_lock_manager_unlock (GSSessionLockManager *manager) { + g_return_if_fail (GS_IS_SESSION_LOCK_MANAGER (manager)); + g_return_if_fail (manager->wl_lock != NULL); + + gs_debug ("Unlocking session"); + if (manager->locked) { + ext_session_lock_v1_unlock_and_destroy (manager->wl_lock); + wl_display_roundtrip (gdk_wayland_display_get_wl_display (gdk_display_get_default ())); + } else { + ext_session_lock_v1_destroy (manager->wl_lock); + } + g_hash_table_remove_all (manager->lock_surfaces); + manager->wl_lock = NULL; + manager->locked = FALSE; +} + +static void +window_realized (GtkWidget *window, + GSSessionLockManager *manager) { + struct wl_display *wl_display = gdk_wayland_display_get_wl_display (gdk_display_get_default ()); + struct wl_surface *wl_surface = gdk_wayland_window_get_wl_surface (gtk_widget_get_window (window)); + struct wl_output *wl_output = gdk_wayland_monitor_get_wl_output (gs_window_get_monitor (GS_WINDOW (window))); + struct ext_session_lock_surface_v1 *wl_lock_surface; + + gs_debug ("Creating lock surface for monitor %s", gdk_monitor_get_model (gs_window_get_monitor (GS_WINDOW (window)))); + gdk_wayland_window_set_use_custom_surface (gtk_widget_get_window (window)); + wl_lock_surface = ext_session_lock_v1_get_lock_surface (manager->wl_lock, wl_surface, wl_output); + ext_session_lock_surface_v1_add_listener (wl_lock_surface, &surface_listener, window); + g_hash_table_insert (manager->lock_surfaces, window, wl_lock_surface); + wl_display_roundtrip (wl_display); +} + +void +gs_session_lock_manager_add_window (GSSessionLockManager *manager, + GSWindow *window) { + const gchar *model; + + g_return_if_fail (GS_IS_SESSION_LOCK_MANAGER (manager)); + g_return_if_fail (manager->wl_lock != NULL); + g_return_if_fail (GS_IS_WINDOW (window)); + g_return_if_fail (!gtk_widget_get_realized (GTK_WIDGET (window))); + + model = gdk_monitor_get_model (gs_window_get_monitor (window)); + gs_debug ("Adding window for monitor %s", model); + g_signal_connect (window, "realize", G_CALLBACK (window_realized), manager); + g_object_set_data_full (G_OBJECT (window), "monitor-model", g_strdup (model), g_free); +} + +void +gs_session_lock_manager_remove_window (GSSessionLockManager *manager, + GSWindow *window) { + g_return_if_fail (GS_IS_SESSION_LOCK_MANAGER (manager)); + g_return_if_fail (manager->wl_lock != NULL); + g_return_if_fail (GS_IS_WINDOW (window)); + + gs_debug ("Removing window for monitor %s", g_object_get_data (G_OBJECT (window), "monitor-model")); + /* we can't do this on dispose or destroy or whatever signal unfortunately, it's too + * late and causes a protocol error (surface destroyed before its role) */ + g_hash_table_remove (manager->lock_surfaces, window); +} diff --git a/src/gs-session-lock-manager.h b/src/gs-session-lock-manager.h new file mode 100644 index 0000000..30de8c8 --- /dev/null +++ b/src/gs-session-lock-manager.h @@ -0,0 +1,40 @@ +/* + * Copyright (C) 2024 Gaël Bonithon <gael@xfce.org> + * + * 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. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#ifndef SRC_GS_SESSION_LOCK_MANAGER_H_ +#define SRC_GS_SESSION_LOCK_MANAGER_H_ + +#include <glib-object.h> +#include "gs-window.h" + +G_BEGIN_DECLS + +#define GS_TYPE_SESSION_LOCK_MANAGER (gs_session_lock_manager_get_type ()) +G_DECLARE_FINAL_TYPE (GSSessionLockManager, gs_session_lock_manager, GS, SESSION_LOCK_MANAGER, GObject) + +GSSessionLockManager *gs_session_lock_manager_new (void); +gboolean gs_session_lock_manager_lock (GSSessionLockManager *manager); +void gs_session_lock_manager_unlock (GSSessionLockManager *manager); +void gs_session_lock_manager_add_window (GSSessionLockManager *manager, + GSWindow *window); +void gs_session_lock_manager_remove_window (GSSessionLockManager *manager, + GSWindow *window); + +G_END_DECLS + +#endif /* SRC_GS_SESSION_LOCK_MANAGER_H_ */ -- GitLab