From 0eaf48dca933535be25f781582069e140779bd3d Mon Sep 17 00:00:00 2001 From: Matthias Clasen Date: Mon, 2 Sep 2024 12:22:54 -0400 Subject: [PATCH 01/11] gdk: Add a debug key to disable session management Add GDK_DISABLE=session-mgmt to stop GDK from using the Wayland session management protocol. --- docs/reference/gtk/running.md | 3 +++ gdk/gdk.c | 1 + gdk/gdkdebugprivate.h | 3 ++- 3 files changed, 6 insertions(+), 1 deletion(-) diff --git a/docs/reference/gtk/running.md b/docs/reference/gtk/running.md index 757bf403d10..8b61f97955b 100644 --- a/docs/reference/gtk/running.md +++ b/docs/reference/gtk/running.md @@ -366,6 +366,9 @@ disable certain features. `threads` : Disabled the use of threads where possible +`session-mgmt` +: Disable session management + ### `GDK_GL_DISABLE` This variable can be set to a list of values, which cause GDK to diff --git a/gdk/gdk.c b/gdk/gdk.c index 828fd440a0f..6022c9552e7 100644 --- a/gdk/gdk.c +++ b/gdk/gdk.c @@ -158,6 +158,7 @@ static const GdkDebugKey gdk_feature_keys[] = { { "dmabuf", GDK_FEATURE_DMABUF, "Disable dmabuf support" }, { "offload", GDK_FEATURE_OFFLOAD, "Disable graphics offload" }, { "threads", GDK_FEATURE_THREADS, "Disable threads where possible" }, + { "session-mgmt", GDK_FEATURE_SESSION_MANAGEMENT, "Disable session management" }, }; static GdkFeatures gdk_features; diff --git a/gdk/gdkdebugprivate.h b/gdk/gdkdebugprivate.h index aa04b550786..157039ef00a 100644 --- a/gdk/gdkdebugprivate.h +++ b/gdk/gdkdebugprivate.h @@ -64,9 +64,10 @@ typedef enum { GDK_FEATURE_DMABUF = 1 << 7, GDK_FEATURE_OFFLOAD = 1 << 8, GDK_FEATURE_THREADS = 1 << 9, + GDK_FEATURE_SESSION_MANAGEMENT = 1 << 10, } GdkFeatures; -#define GDK_ALL_FEATURES ((1 << 10) - 1) +#define GDK_ALL_FEATURES ((1 << 11) - 1) extern guint _gdk_debug_flags; -- GitLab From 9cdcbdcc8ad16218aefb9de3da7706072096f82f Mon Sep 17 00:00:00 2001 From: Carlos Garnacho Date: Wed, 19 Jun 2024 13:18:13 +0200 Subject: [PATCH 02/11] wayland: Require the xdg-session-management protocol This is not upstreamed yet, so use an internal xx-session-management protocol that we can eventually move to use the upstream protocol. --- gdk/wayland/meson.build | 5 + .../protocol/xx-session-management-v1.xml | 264 ++++++++++++++++++ 2 files changed, 269 insertions(+) create mode 100644 gdk/wayland/protocol/xx-session-management-v1.xml diff --git a/gdk/wayland/meson.build b/gdk/wayland/meson.build index 1cc8e99d931..cd1529a26d3 100644 --- a/gdk/wayland/meson.build +++ b/gdk/wayland/meson.build @@ -166,6 +166,11 @@ proto_sources = [ 'stability': 'staging', 'version': 1, }, + { + 'name': 'xx-session-management', + 'stability': 'private', + 'version': 1, + }, ] gdk_wayland_gen_headers = [] diff --git a/gdk/wayland/protocol/xx-session-management-v1.xml b/gdk/wayland/protocol/xx-session-management-v1.xml new file mode 100644 index 00000000000..e21694424f1 --- /dev/null +++ b/gdk/wayland/protocol/xx-session-management-v1.xml @@ -0,0 +1,264 @@ + + + + Copyright 2018 Mike Blumenkrantz + Copyright 2018 Samsung Electronics Co., Ltd + Copyright 2018 Red Hat Inc. + + 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 (including the next + paragraph) 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. + + + + This description provides a high-level overview of the interplay between + the interfaces defined this protocol. For details, see the protocol + specification. + + The xx_session_manager protocol declares interfaces necessary to + allow clients to restore toplevel state from previous executions. The + xx_session_manager_v1.get_session request can be used to obtain a + xx_session_v1 resource representing the state of a set of toplevels. + + Clients may obtain the session string to use in future calls through + the xx_session_v1.created event. Compositors will use this string + as an identifiable token for future runs, possibly storing data about + the related toplevels in persistent storage. + + Toplevels are managed through the xx_session_v1.add_toplevel and + xx_session_toplevel_v1.remove pair of requests. Clients will explicitly + request a toplevel to be restored according to prior state through the + xx_session_v1.restore_toplevel request before the toplevel is mapped. + + 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. + + + + + The xx_session_manager interface defines base requests for creating and + managing a session for an application. Sessions persist across application + and compositor restarts unless explicitly destroyed. A session is created + for the purpose of maintaining an application's xdg_toplevel surfaces + across compositor or application restarts. The compositor should remember + as many states as possible for surfaces in a given session, but there is + no requirement for which states must be remembered. + + + + + + + + + The reason may determine in what way a session restores the window + management state of associated toplevels. + + For example newly launched applications might be launched on the active + workspace with restored size and position, while a recovered + applications might restore additional state such as active workspace and + stacking order. + + + + A new app instance is launched, for example from an app launcher. + + + + + A app instance is recovering from for example a compositor or app crash. + + + + + A app instance is restored, for example part of a restored session, or + restored from having been temporarily terminated due to resource + constraints. + + + + + + + This has no effect other than to destroy the xx_session_manager object. + + + + + + Create a session object corresponding to either an existing session + identified by the given session identifier string or a new session. + While the session object exists, the session is considered to be "in + use". + + If a identifier string represents a session that is currently actively + in use by the the same client, an 'in_use' error is raised. If some + other client is currently using the same session, the new session will + replace managing the associated state. + + NULL is passed to initiate a new session. If an id is passed which does + not represent a valid session, the compositor treats it as if NULL had + been passed. + + A client is allowed to have any number of in use sessions at the same + time. + + + + + + + + + + A xx_session_v1 object represents a session for an application. While the + object exists, all surfaces which have been added to the session will + have states stored by the compositor which can be reapplied at a later + time. Two sessions cannot exist for the same identifier string. + + States for surfaces added to a session are automatically updated by the + compositor when they are changed. + + Surfaces which have been added to a session are automatically removed from + the session if xdg_toplevel.destroy is called for the surface. + + + + + + + + + + + Destroy a session object, preserving the current state but not continuing + to make further updates if state changes occur. This makes the associated + xx_toplevel_session_v1 objects inert. + + + + + + Remove the session, making it no longer available for restoration. A + compositor should in response to this request remove the data related to + this session from its storage. + + + + + + Attempt to add a given surface to the session. The passed name is used + to identify what window is being restored, and may be used store window + specific state within the session. + + Calling this with a toplevel that is already managed by the session with + the same associated will raise an in_use error. + + + + + + + + + Inform the compositor that the toplevel associated with the passed name + should have its window management state restored. + + Calling this with a toplevel that is already managed by the session with + the same associated will raise an in_use error. + + This request must be called prior to the first commit on the associated + wl_surface, otherwise an already_mapped error is raised. + + As part of the initial configure sequence, if the toplevel was + successfully restored, a xx_toplevel_session_v1.restored event is + emitted. See the xx_toplevel_session_v1.restored event for further + details. + + + + + + + + + Emitted at most once some time after getting a new session object. It + means that no previous state was restored, and a new session was created. + The passed id can be used to restore previous sessions. + + + + + + + Emitted at most once some time after getting a new session object. It + means that previous state was at least partially restored. The same id + can again be used to restore previous sessions. + + + + + + Emitted at most once, if the session was taken over by some other + client. When this happens, the session and all its toplevel session + objects become inert, and should be destroyed. + + + + + + + + Destroy the object. This has no effect window management of the + associated toplevel. + + + + + + Remove a specified surface from the session and render any corresponding + xx_toplevel_session_v1 object inert. The compositor should remove any + data related to the toplevel in the corresponding session from its internal + storage. + + + + + + The "restored" event is emitted prior to the first + xdg_toplevel.configure for the toplevel. It will only be emitted after + xx_session_v1.restore_toplevel, and the initial empty surface state has + been applied, and it indicates that the surface's session is being + restored with this configure event. + + + + + -- GitLab From b579595564e491f04e58c7dcc7f85d2a0d3344e5 Mon Sep 17 00:00:00 2001 From: Carlos Garnacho Date: Wed, 19 Jun 2024 13:20:38 +0200 Subject: [PATCH 03/11] wayland: Get/initialize a xdg_session_v1 object Add the internal API to let the upper layers define a session ID (e.g. saved from previous runs), and retrieve the session ID eventually used (e.g. in case the session is created from scratch). --- gdk/wayland/gdkdisplay-wayland.c | 105 ++++++++++++++++++++++++++++++- gdk/wayland/gdkdisplay-wayland.h | 6 ++ gdk/wayland/gdkwaylanddisplay.h | 7 +++ 3 files changed, 117 insertions(+), 1 deletion(-) diff --git a/gdk/wayland/gdkdisplay-wayland.c b/gdk/wayland/gdkdisplay-wayland.c index 1fecc42df83..70e895a562c 100644 --- a/gdk/wayland/gdkdisplay-wayland.c +++ b/gdk/wayland/gdkdisplay-wayland.c @@ -62,6 +62,9 @@ #include "linux-dmabuf-unstable-v1-client-protocol.h" #include "presentation-time-client-protocol.h" #include "color-management-v1-client-protocol.h" +#include "xx-session-management-v1-client-protocol.h" + +#include "wm-button-layout-translation.h" #include "gdk/gdkprivate.h" @@ -454,6 +457,49 @@ static const struct org_kde_kwin_server_decoration_manager_listener server_decor .default_mode = server_decoration_manager_default_mode }; +/* }}} */ +/* {{{ session listener */ + +static void +session_listener_created (void *data, + struct xx_session_v1 *xx_session_v1, + const char *id) +{ + GdkWaylandDisplay *display_wayland = data; + + GDK_DEBUG (MISC, "session created: %s", id); + + if (g_strcmp0 (display_wayland->session_id, id) != 0) + { + g_clear_pointer (&display_wayland->session_id, g_free); + display_wayland->session_id = g_strdup (id); + } +} + +static void +session_listener_restored (void *data, + struct xx_session_v1 *xx_session_v1) +{ + GdkWaylandDisplay *display_wayland = data; + + GDK_DEBUG (MISC, "session restored: %s", display_wayland->session_id); +} + +static void +session_listener_replaced (void *data, + struct xx_session_v1 *xx_session_v1) +{ + GdkWaylandDisplay *display_wayland = data; + + GDK_DEBUG (MISC, "session replaced: %s", display_wayland->session_id); +} + +static const struct xx_session_v1_listener xx_session_listener = { + .created = session_listener_created, + .restored = session_listener_restored, + .replaced = session_listener_replaced, +}; + /* }}} */ /* {{{ wl_registry listener */ @@ -514,6 +560,14 @@ gdk_registry_handle_global (void *data, &xdg_wm_dialog_v1_interface, MIN (version, XDG_WM_DIALOG_VERSION)); } + else if (match_global (display_wayland, interface, version, xx_session_manager_v1_interface.name, 0) && + gdk_has_feature (GDK_FEATURE_SESSION_MANAGEMENT)) + { + display_wayland->xx_session_manager = + wl_registry_bind (display_wayland->wl_registry, id, + &xx_session_manager_v1_interface, + version); + } else if (match_global (display_wayland, interface, version, gtk_shell1_interface.name, 0)) { display_wayland->gtk_shell = @@ -873,6 +927,8 @@ gdk_wayland_display_dispose (GObject *object) g_clear_pointer (&display_wayland->color, gdk_wayland_color_free); g_clear_pointer (&display_wayland->system_bell, xdg_system_bell_v1_destroy); g_clear_pointer (&display_wayland->toplevel_icon, xdg_toplevel_icon_manager_v1_destroy); + g_clear_pointer (&display_wayland->xx_session, xx_session_v1_destroy); + g_clear_pointer (&display_wayland->xx_session_manager, xx_session_manager_v1_destroy); g_clear_pointer (&display_wayland->shm, wl_shm_destroy); g_clear_pointer (&display_wayland->wl_registry, wl_registry_destroy); @@ -1318,6 +1374,53 @@ gdk_wayland_display_query_registry (GdkDisplay *display, return FALSE; } -/* }}} */ +void +gdk_wayland_display_register_session (GdkDisplay *display, + const char *name) +{ + GdkWaylandDisplay *display_wayland = GDK_WAYLAND_DISPLAY (display); + + GDK_DEBUG (MISC, "register session %s", name); + + if (!display_wayland->xx_session_manager) + return; + + g_clear_pointer (&display_wayland->session_id, g_free); + display_wayland->session_id = g_strdup (name); + + display_wayland->xx_session = + xx_session_manager_v1_get_session (display_wayland->xx_session_manager, + XX_SESSION_MANAGER_V1_REASON_LAUNCH, + name); + xx_session_v1_add_listener (display_wayland->xx_session, + &xx_session_listener, + display_wayland); + + wl_display_roundtrip (display_wayland->wl_display); +} + +void +gdk_wayland_display_unregister_session (GdkDisplay *display) +{ + GdkWaylandDisplay *display_wayland = GDK_WAYLAND_DISPLAY (display); + + if (!display_wayland->xx_session_manager) + return; + + if (display_wayland->xx_session) + xx_session_v1_remove (display_wayland->xx_session); + + g_clear_pointer (&display_wayland->session_id, g_free); + g_clear_pointer (&display_wayland->xx_session, xx_session_v1_destroy); +} +const char * +gdk_wayland_display_get_current_session_id (GdkDisplay *display) +{ + GdkWaylandDisplay *display_wayland = GDK_WAYLAND_DISPLAY (display); + + return display_wayland->session_id; +} + +/* }}} */ /* vim:set foldmethod=marker: */ diff --git a/gdk/wayland/gdkdisplay-wayland.h b/gdk/wayland/gdkdisplay-wayland.h index fc20ac35e25..a0ddbbb6529 100644 --- a/gdk/wayland/gdkdisplay-wayland.h +++ b/gdk/wayland/gdkdisplay-wayland.h @@ -45,6 +45,7 @@ #include #include #include +#include #include #include @@ -131,6 +132,9 @@ struct _GdkWaylandDisplay struct wp_single_pixel_buffer_manager_v1 *single_pixel_buffer; struct wp_cursor_shape_manager_v1 *cursor_shape; struct xdg_toplevel_icon_manager_v1 *toplevel_icon; + struct xx_session_manager_v1 *xx_session_manager; + struct xx_session_v1 *xx_session; + GdkWaylandColor *color; GList *async_roundtrips; @@ -158,6 +162,8 @@ struct _GdkWaylandDisplay GListStore *monitors; + char *session_id; + gint64 last_bell_time_ms; }; diff --git a/gdk/wayland/gdkwaylanddisplay.h b/gdk/wayland/gdkwaylanddisplay.h index 406b39c9e45..d00c5807b3f 100644 --- a/gdk/wayland/gdkwaylanddisplay.h +++ b/gdk/wayland/gdkwaylanddisplay.h @@ -65,5 +65,12 @@ gboolean gdk_wayland_display_query_registry (GdkDisplay *di GDK_AVAILABLE_IN_4_4 gpointer gdk_wayland_display_get_egl_display (GdkDisplay *display); +void gdk_wayland_display_register_session (GdkDisplay *display, + const char *name); + +void gdk_wayland_display_unregister_session (GdkDisplay *display); + +const char * gdk_wayland_display_get_current_session_id (GdkDisplay *display); + G_END_DECLS -- GitLab From 30bc64a6fbe74509c133c4ca367bb353b9d0554b Mon Sep 17 00:00:00 2001 From: Carlos Garnacho Date: Wed, 19 Jun 2024 13:25:51 +0200 Subject: [PATCH 04/11] wayland: Add API to restore GdkToplevels Any toplevel may be referred by an ID, that will be used by the compositor to restore window state for it. Add the glue so that the upper layers may restore a toplevel from one of these IDs. We keep the api private, for now at least. --- gdk/wayland/gdkdisplay-wayland.h | 2 +- gdk/wayland/gdktoplevel-wayland-private.h | 8 +++ gdk/wayland/gdktoplevel-wayland.c | 64 +++++++++++++++++++++++ 3 files changed, 73 insertions(+), 1 deletion(-) diff --git a/gdk/wayland/gdkdisplay-wayland.h b/gdk/wayland/gdkdisplay-wayland.h index a0ddbbb6529..07175bbb63a 100644 --- a/gdk/wayland/gdkdisplay-wayland.h +++ b/gdk/wayland/gdkdisplay-wayland.h @@ -45,7 +45,7 @@ #include #include #include -#include +#include #include #include diff --git a/gdk/wayland/gdktoplevel-wayland-private.h b/gdk/wayland/gdktoplevel-wayland-private.h index 9f8e28d9374..d60737dc6fc 100644 --- a/gdk/wayland/gdktoplevel-wayland-private.h +++ b/gdk/wayland/gdktoplevel-wayland-private.h @@ -38,3 +38,11 @@ gboolean gdk_wayland_toplevel_inhibit_idle (GdkToplevel *toplevel); void gdk_wayland_toplevel_uninhibit_idle (GdkToplevel *toplevel); void gdk_wayland_toplevel_destroy (GdkToplevel *toplevel); + +void gdk_wayland_toplevel_set_session_id (GdkToplevel *toplevel, + const char *session_id); + +void gdk_wayland_toplevel_restore_from_session + (GdkToplevel *toplevel); + +void gdk_wayland_toplevel_remove_from_session (GdkToplevel *toplevel); diff --git a/gdk/wayland/gdktoplevel-wayland.c b/gdk/wayland/gdktoplevel-wayland.c index 76b3fb65849..ec2ecd00a49 100644 --- a/gdk/wayland/gdktoplevel-wayland.c +++ b/gdk/wayland/gdktoplevel-wayland.c @@ -37,6 +37,7 @@ #include #include #include +#include #include #include @@ -129,6 +130,7 @@ struct _GdkWaylandToplevel struct wl_output *initial_fullscreen_output; struct wp_presentation_feedback *feedback; + struct xx_toplevel_session_v1 *toplevel_session; struct { GdkToplevelState unset_flags; @@ -149,6 +151,7 @@ struct _GdkWaylandToplevel char *title; gboolean decorated; + char *session_id; GdkGeometry geometry_hints; GdkSurfaceHints geometry_mask; @@ -942,6 +945,23 @@ zxdg_toplevel_v6_create_resources (gpointer unused, GdkWaylandToplevel *toplevel toplevel); } +static void +attempt_restore_toplevel (GdkWaylandToplevel *wayland_toplevel) +{ + GdkDisplay *display = gdk_surface_get_display (GDK_SURFACE (wayland_toplevel)); + GdkWaylandDisplay *display_wayland = GDK_WAYLAND_DISPLAY (display); + + if (display_wayland->xx_session && + wayland_toplevel->session_id && + wayland_toplevel->display_server.xdg_toplevel) + { + wayland_toplevel->toplevel_session = + xx_session_v1_restore_toplevel (display_wayland->xx_session, + wayland_toplevel->display_server.xdg_toplevel, + wayland_toplevel->session_id); + } +} + static void gdk_wayland_surface_create_xdg_toplevel (GdkWaylandToplevel *wayland_toplevel) { @@ -980,6 +1000,7 @@ gdk_wayland_surface_create_xdg_toplevel (GdkWaylandToplevel *wayland_toplevel) maybe_set_gtk_surface_modal (wayland_toplevel); maybe_set_xdg_toplevel_icon (wayland_toplevel); + attempt_restore_toplevel (wayland_toplevel); gdk_profiler_add_mark (GDK_PROFILER_CURRENT_TIME, 0, "Wayland surface commit", NULL); wl_surface_commit (wayland_surface->display_server.wl_surface); @@ -1560,6 +1581,9 @@ gdk_wayland_toplevel_finalize (GObject *object) g_free (self->application.application_object_path); g_free (self->application.unique_bus_name); + g_free (self->session_id); + g_clear_pointer (&self->toplevel_session, xx_toplevel_session_v1_destroy); + g_free (self->title); g_clear_pointer (&self->shortcuts_inhibitors, g_hash_table_unref); @@ -2801,5 +2825,45 @@ gdk_wayland_toplevel_set_transient_for_exported (GdkToplevel *toplevel, return TRUE; } +void +gdk_wayland_toplevel_set_session_id (GdkToplevel *toplevel, + const char *session_id) +{ + GdkWaylandToplevel *wayland_toplevel = GDK_WAYLAND_TOPLEVEL (toplevel); + + g_clear_pointer (&wayland_toplevel->session_id, g_free); + wayland_toplevel->session_id = g_strdup (session_id); +} + +void +gdk_wayland_toplevel_restore_from_session (GdkToplevel *toplevel) +{ + GdkWaylandToplevel *wayland_toplevel = GDK_WAYLAND_TOPLEVEL (toplevel); + GdkDisplay *display = gdk_surface_get_display (GDK_SURFACE (toplevel)); + GdkWaylandDisplay *display_wayland = GDK_WAYLAND_DISPLAY (display); + + if (display_wayland->xx_session && wayland_toplevel->display_server.xdg_toplevel) + { + wayland_toplevel->toplevel_session = + xx_session_v1_restore_toplevel (display_wayland->xx_session, + wayland_toplevel->display_server.xdg_toplevel, + wayland_toplevel->session_id); + } +} + +void +gdk_wayland_toplevel_remove_from_session (GdkToplevel *toplevel) +{ + GdkWaylandToplevel *wayland_toplevel = GDK_WAYLAND_TOPLEVEL (toplevel); + GdkDisplay *display = gdk_surface_get_display (GDK_SURFACE (toplevel)); + GdkWaylandDisplay *display_wayland = GDK_WAYLAND_DISPLAY (display); + + if (display_wayland->xx_session && wayland_toplevel->toplevel_session) + { + xx_toplevel_session_v1_remove (wayland_toplevel->toplevel_session); + wayland_toplevel->toplevel_session = NULL; + } +} + /* }}} */ /* vim:set foldmethod=marker: */ -- GitLab From 9c6a07832f7d8615a2c50381a95553d2b8db3e50 Mon Sep 17 00:00:00 2001 From: Carlos Garnacho Date: Wed, 19 Jun 2024 13:36:18 +0200 Subject: [PATCH 05/11] gtk: Add GtkWindow property for session identifiers This identifier will be used for session management purposes, and is meant to be stable across executions. --- gtk/gtkwindow.c | 77 +++++++++++++++++++++++++++++++++++++++++++++++++ gtk/gtkwindow.h | 7 +++++ 2 files changed, 84 insertions(+) diff --git a/gtk/gtkwindow.c b/gtk/gtkwindow.c index ae300d72e15..4f32a98de71 100644 --- a/gtk/gtkwindow.c +++ b/gtk/gtkwindow.c @@ -291,6 +291,8 @@ typedef struct GdkCursor *resize_cursor; + char *session_id; + GtkEventController *menubar_controller; } GtkWindowPrivate; @@ -341,6 +343,8 @@ enum { PROP_MAXIMIZED, PROP_FULLSCREENED, + PROP_SESSION_ID, + LAST_ARG }; @@ -1175,6 +1179,24 @@ gtk_window_class_init (GtkWindowClass *klass) GTK_WINDOW_GRAVITY_TOP_START, GTK_PARAM_READWRITE|G_PARAM_STATIC_STRINGS|G_PARAM_EXPLICIT_NOTIFY); + /** + * GtkWindow:session-id: (attributes org.gtk.Property.get=gtk_window_get_session_id org.gtk.Property.set=gtk_window_set_session_id) + * + * The identifier of this toplevel in the session. + * + * In windowing environments that allow it, this identifier will be + * used to identify windows in a persistent manner across runs, and + * restore window state (e.g. position, size) for them. + * + * Currently, this is only implemented for the Wayland backend. + * + * Since: 4.2020 + */ + window_props[PROP_SESSION_ID] = + g_param_spec_string ("session-id", NULL, NULL, + NULL, + GTK_PARAM_READWRITE|G_PARAM_EXPLICIT_NOTIFY); + g_object_class_install_properties (gobject_class, LAST_ARG, window_props); /** @@ -2029,6 +2051,9 @@ gtk_window_set_property (GObject *object, case PROP_GRAVITY: gtk_window_set_gravity (window, g_value_get_enum (value)); break; + case PROP_SESSION_ID: + gtk_window_set_session_id (window, g_value_get_string (value)); + break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; @@ -2121,6 +2146,9 @@ gtk_window_get_property (GObject *object, case PROP_GRAVITY: g_value_set_enum (value, gtk_window_get_gravity (window)); break; + case PROP_SESSION_ID: + g_value_set_string (value, gtk_window_get_session_id (window)); + break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; @@ -7284,3 +7312,52 @@ gtk_window_set_gravity (GtkWindow *window, g_object_notify_by_pspec (G_OBJECT (window), window_props[PROP_GRAVITY]); } + +/** + * gtk_window_set_session_id: + * @window: a `GtkWindow` + * @session_id: (nullable): A persistent identifier for this window + * + * Sets the identifier to be used for session management purposes. + * + * State of this window may be restored from prior executions, by using this + * identifier to match the related state. This identifier should be constant, + * or at least stable between executions, and unique among the windows of the + * application. + * + * Different backends may have different requirements for this string, therefore + * it is best to be conservative and keep the string relatively short and stick + * to ASCII without leading or trailing whitespace. + * + * Since: 4.20 + */ +void +gtk_window_set_session_id (GtkWindow *window, + const char *session_id) +{ + GtkWindowPrivate *priv = gtk_window_get_instance_private (window); + + if (g_set_str (&priv->session_id, session_id)) + g_object_notify_by_pspec (G_OBJECT (window), window_props[PROP_SESSION_ID]); +} + +/** + * gtk_window_get_session_id: + * @window: a `GtkWindow` + * + * Gets the window identifier to be used for session management purposes. + * + * See [method@Gtk.Window.set_session_id] for more details about window + * identifiers for session management. + * + * Returns: (nullable): the session identifier + * + * Since: 4.20 + */ +const char * +gtk_window_get_session_id (GtkWindow *window) +{ + GtkWindowPrivate *priv = gtk_window_get_instance_private (window); + + return priv->session_id; +} diff --git a/gtk/gtkwindow.h b/gtk/gtkwindow.h index c076bed0103..b0b7ffa8bc7 100644 --- a/gtk/gtkwindow.h +++ b/gtk/gtkwindow.h @@ -320,6 +320,13 @@ GDK_AVAILABLE_IN_4_20 void gtk_window_set_gravity (GtkWindow *window, GtkWindowGravity gravity); +GDK_AVAILABLE_IN_4_20 +void gtk_window_set_session_id (GtkWindow *window, + const char *session_id); + +GDK_AVAILABLE_IN_4_20 +const char * gtk_window_get_session_id (GtkWindow *window); + G_DEFINE_AUTOPTR_CLEANUP_FUNC(GtkWindow, g_object_unref) G_DEFINE_AUTOPTR_CLEANUP_FUNC(GtkWindowGroup, g_object_unref) -- GitLab From 4daf05a6ed7014ae826b87cd467216a0696ee789 Mon Sep 17 00:00:00 2001 From: Carlos Garnacho Date: Wed, 19 Jun 2024 14:00:37 +0200 Subject: [PATCH 06/11] gtk: Add GtkApplication property for session IDs This API has two "phases": - An initialization call, calling gtk_application_set_session_id() to set a session identifier from previous runs. - A post-initialization call, to retrieve the session identifier in use for this session. This may be the same identifier than previously specified, if the session could be recovered, or a brand new one if the session is created from scratch. The storage of this session identifier is left up to the applications, and could be material for later GNOME session integration with persistent save states. --- gtk/gtkapplication.c | 154 +++++++++++++++++++++++++++++++++++- gtk/gtkapplication.h | 10 +++ gtk/gtkapplicationimpl.c | 9 +++ gtk/gtkapplicationprivate.h | 4 +- 4 files changed, 175 insertions(+), 2 deletions(-) diff --git a/gtk/gtkapplication.c b/gtk/gtkapplication.c index b1f6093c37b..75fc45d31d1 100644 --- a/gtk/gtkapplication.c +++ b/gtk/gtkapplication.c @@ -122,6 +122,34 @@ * inform the user about the negative consequences of ending the * session while inhibitors are present. * + * ## Restoring window state + * + * In windowing environments that support it, `GtkApplication` supports + * restoring window state (this is up to the environment, but typically + * includes the position, size, and states such as maximized or fullscreen). + * + * In order to restore state from a previous execution, the application needs + * to specify a session identifier through [method@Gtk.Application.set_session_id] + * obtained from a previous session. In order to start a new session, `NULL` + * should be used. + * + * The session will be registered during [signal@Gio.Application::startup], + * at which point [method@Gtk.Application.get_current_session_id] will return + * the session identifier for the current session. This is the session identifier + * that needs to be preserved for future runs. If the session is being restored + * from a prior run, this identifier will match the one given through + * [method@Gtk.Application.set_session_id], otherwise it will be a new identifier. + * + * Individual windows are identified through [method@Gtk.Window.set_session_id], + * so application toplevel windows can be matched with the previously saved + * session state. Windows that whose state was saved will be restored to their + * previous position when mapped. + * + * The window state is saved automatically in the background, thus no explicit + * save() call is necessary. Other state, such as the active tab in a notebook + * or scroll positions, is not preserved automatically and needs to be saved by + * the application, if that is desired. + * * ## See Also * * - [Using GtkApplication](https://developer.gnome.org/documentation/tutorials/application.html) @@ -143,6 +171,7 @@ enum { PROP_SCREENSAVER_ACTIVE, PROP_MENUBAR, PROP_ACTIVE_WINDOW, + PROP_SESSION_ID, NUM_PROPERTIES }; @@ -163,6 +192,7 @@ typedef struct GtkActionMuxer *muxer; GtkBuilder *menus_builder; char *help_overlay_path; + char *session_id; } GtkApplicationPrivate; G_DEFINE_TYPE_WITH_PRIVATE (GtkApplication, gtk_application, G_TYPE_APPLICATION) @@ -526,6 +556,10 @@ gtk_application_get_property (GObject *object, g_value_set_object (value, gtk_application_get_active_window (application)); break; + case PROP_SESSION_ID: + g_value_set_string (value, gtk_application_get_session_id (application)); + break; + default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; @@ -551,6 +585,10 @@ gtk_application_set_property (GObject *object, gtk_application_set_menubar (application, g_value_get_object (value)); break; + case PROP_SESSION_ID: + gtk_application_set_session_id (application, g_value_get_string (value)); + break; + default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; @@ -712,6 +750,21 @@ gtk_application_class_init (GtkApplicationClass *class) GTK_TYPE_WINDOW, G_PARAM_READABLE|G_PARAM_STATIC_STRINGS); + /** + * GtkApplication:session-id: (attributes org.gtk.Property.get=gtk_application_get_session_id org.gtk.Property.set=gtk_application_set_session_id) + * + * The identifier of the session used to restore window state. + * + * This property should be set to the ID of a previously saved session + * before ::startup (if available). + * + * Since: 4.20 + */ + gtk_application_props[PROP_SESSION_ID] = + g_param_spec_string ("session-id", NULL, NULL, + NULL, + G_PARAM_READWRITE|G_PARAM_STATIC_STRINGS); + g_object_class_install_properties (object_class, NUM_PROPERTIES, gtk_application_props); } @@ -762,7 +815,7 @@ gtk_application_new (const char *application_id, * * This call can only happen after the application has started; * typically, you should add new application windows in response - * to the emission of the [signal@GIO.Application::activate] signal. + * to the emission of the [signal@Gio.Application::activate] signal. * * This call is equivalent to setting the [property@Gtk.Window:application] * property of the window to @application. @@ -1300,3 +1353,102 @@ gtk_application_set_screensaver_active (GtkApplication *application, g_object_notify (G_OBJECT (application), "screensaver-active"); } } + +/** + * gtk_application_set_session_id: + * @application: a `GtkApplication` + * @session_id: (nullable): The identifier used for session management purposes + * + * Sets the identifier used for session management purposes. + * + * This identifier would have been typically retrieved in prior runs + * through [method@Gtk.Application.get_current_session_id]. + * + * This session identifier must be set before `GApplication::startup` happens, + * as it will be applied there. + * + * In the case `NULL` was passed, or the request resulted in a new session being + * created from scratch, [method@Gtk.Application.get_current_session_id] may + * return a different identifier than the one passed here. + * + * Since: 4.20 + */ +void +gtk_application_set_session_id (GtkApplication *application, + const char *session_id) +{ + GtkApplicationPrivate *priv = + gtk_application_get_instance_private (application); + + g_return_if_fail (GTK_IS_APPLICATION (application)); + + if (g_set_str (&priv->session_id, session_id)) + g_object_notify (G_OBJECT (application), "session-id"); +} + +/** + * gtk_application_get_session_id: + * @application: a `GtkApplication` + * + * Gets the session identifier that this `GtkApplication` will use + * for session management registration. + * + * See [method@Gtk.Application.set_session_id] for more information about + * session management. + * + * Returns: (nullable): The session management ID + * + * Since: 4.20 + */ +const char * +gtk_application_get_session_id (GtkApplication *application) +{ + GtkApplicationPrivate *priv = + gtk_application_get_instance_private (application); + + return priv->session_id; +} + +/** + * gtk_application_get_current_session_id: + * @application: a `GtkApplication` + * + * Retrieves the session management identifier that the application + * is using during execution. + * + * This identifier may be saved for future executions of the application + * to have their window state recovered. + * + * This function may be used during or after [signal@Gio.Application::startup] + * to retrieve a session identifier string in environments where session + * management is supported. + * + * In order to restore window state in future runs of the same application, + * this string should be stored, so it can be passed through + * [method@Gtk.Application.set_session_id] in these future executions. + * + * Note that this identifier may end up being different from the one + * initially specified through [method@Gtk.Application.set_session_id], + * e.g. in the case the specified session ID could not be recovered and a + * new session was created. + * + * If called on an environment/backend that does not support session + * management, or prior to application startup, this method will return `NULL`. + * + * Currently, session management is only supported in the Wayland backend. + * + * Returns: (nullable): The session identifier to preserve for future runs + * + * Since: 4.20 + */ +const char * +gtk_application_get_current_session_id (GtkApplication *application) +{ + GtkApplicationPrivate *priv = + gtk_application_get_instance_private (application); + + if (!priv->impl) + return NULL; + + return gtk_application_impl_get_current_session_id (priv->impl); +} diff --git a/gtk/gtkapplication.h b/gtk/gtkapplication.h index 7260de6dc7e..9d0332e2e35 100644 --- a/gtk/gtkapplication.h +++ b/gtk/gtkapplication.h @@ -134,6 +134,16 @@ GDK_AVAILABLE_IN_ALL GMenu * gtk_application_get_menu_by_id (GtkApplication *application, const char *id); +GDK_AVAILABLE_IN_4_18 +void gtk_application_set_session_id (GtkApplication *application, + const char *session_id); + +GDK_AVAILABLE_IN_4_18 +const char * gtk_application_get_session_id (GtkApplication *application); + +GDK_AVAILABLE_IN_4_18 +const char * gtk_application_get_current_session_id (GtkApplication *application); + G_DEFINE_AUTOPTR_CLEANUP_FUNC(GtkApplication, g_object_unref) G_END_DECLS diff --git a/gtk/gtkapplicationimpl.c b/gtk/gtkapplicationimpl.c index 7b5c81729aa..0410777ba42 100644 --- a/gtk/gtkapplicationimpl.c +++ b/gtk/gtkapplicationimpl.c @@ -157,6 +157,15 @@ gtk_application_impl_prefers_app_menu (GtkApplicationImpl *impl) return GTK_APPLICATION_IMPL_GET_CLASS (impl)->prefers_app_menu (impl); } +const char * +gtk_application_impl_get_current_session_id (GtkApplicationImpl *impl) +{ + if (!GTK_APPLICATION_IMPL_GET_CLASS (impl)->get_current_session_id) + return NULL; + + return GTK_APPLICATION_IMPL_GET_CLASS (impl)->get_current_session_id (impl); +} + GtkApplicationImpl * gtk_application_impl_new (GtkApplication *application, GdkDisplay *display) diff --git a/gtk/gtkapplicationprivate.h b/gtk/gtkapplicationprivate.h index 2c667e96522..e4a0e76212c 100644 --- a/gtk/gtkapplicationprivate.h +++ b/gtk/gtkapplicationprivate.h @@ -100,7 +100,7 @@ typedef struct gboolean (* prefers_app_menu) (GtkApplicationImpl *impl); - + const char * (* get_current_session_id) (GtkApplicationImpl *impl); } GtkApplicationImplClass; #define GTK_TYPE_APPLICATION_IMPL_DBUS (gtk_application_impl_dbus_get_type ()) @@ -196,5 +196,7 @@ gboolean gtk_application_impl_prefers_app_menu (GtkAppl void gtk_application_impl_quartz_setup_menu (GMenuModel *model, GtkActionMuxer *muxer); +const char * gtk_application_impl_get_current_session_id (GtkApplicationImpl *impl); + G_END_DECLS -- GitLab From 153690e5db4797a9acdb5535a88a570c51ff704d Mon Sep 17 00:00:00 2001 From: Carlos Garnacho Date: Wed, 19 Jun 2024 14:04:01 +0200 Subject: [PATCH 07/11] gtk: Hook up session management in wayland's GtkApplication impl Make this application impl object handle the integration with the private GDK/wayland API, so this is hooked up to the xdg-session-management-v1 protocol. As a nicety, use GtkWindow::hide-on-close to determine whether a window should be remembered, or immediately forgotten by the session after being closed. --- gtk/gtkapplication-wayland.c | 60 ++++++++++++++++++++++++++++++++++-- 1 file changed, 58 insertions(+), 2 deletions(-) diff --git a/gtk/gtkapplication-wayland.c b/gtk/gtkapplication-wayland.c index fb9dddb92d1..a69a24a84f6 100644 --- a/gtk/gtkapplication-wayland.c +++ b/gtk/gtkapplication-wayland.c @@ -75,6 +75,7 @@ gtk_application_impl_wayland_handle_window_realize (GtkApplicationImpl *impl, GtkApplicationImplDBus *dbus = (GtkApplicationImplDBus *) impl; GdkSurface *gdk_surface; char *window_path; + const char *session_id; gdk_surface = gtk_native_get_surface (GTK_NATIVE (window)); @@ -93,6 +94,14 @@ gtk_application_impl_wayland_handle_window_realize (GtkApplicationImpl *impl, g_free (window_path); + session_id = gtk_window_get_session_id (window); + if (session_id) + { + gdk_wayland_toplevel_set_session_id (GDK_TOPLEVEL (gdk_surface), + session_id); + gdk_wayland_toplevel_restore_from_session (GDK_TOPLEVEL (gdk_surface)); + } + impl_class->handle_window_realize (impl, window); } @@ -111,6 +120,22 @@ G_GNUC_BEGIN_IGNORE_DEPRECATIONS G_GNUC_END_IGNORE_DEPRECATIONS } +static gboolean +should_remove_from_session (GtkApplicationImpl *impl, + GtkWindow *window) +{ + if (gtk_window_get_transient_for (window)) + return TRUE; + + if (gtk_window_get_hide_on_close (window)) + return FALSE; + + if (g_list_length (gtk_application_get_windows (impl->application)) == 1) + return FALSE; + + return TRUE; +} + static void gtk_application_impl_wayland_window_removed (GtkApplicationImpl *impl, GtkWindow *window) @@ -133,6 +158,14 @@ gtk_application_impl_wayland_window_removed (GtkApplicationImpl *impl, } } } + + if (should_remove_from_session (impl, window)) + { + GdkSurface *surface; + + surface = gtk_native_get_surface (GTK_NATIVE (window)); + gdk_wayland_toplevel_remove_from_session (GDK_TOPLEVEL (surface)); + } } static guint @@ -200,6 +233,25 @@ gtk_application_impl_wayland_uninhibit (GtkApplicationImpl *impl, g_warning ("Invalid inhibitor cookie"); } +static void +gtk_application_impl_wayland_startup (GtkApplicationImpl *impl, + gboolean register_session) +{ + const char *session_id; + + GTK_APPLICATION_IMPL_CLASS (gtk_application_impl_wayland_parent_class)->startup (impl, register_session); + + /* Note: This is independent of the register_session argument */ + session_id = gtk_application_get_session_id (impl->application); + gdk_wayland_display_register_session (gdk_display_get_default (), session_id); +} + +static const char * +gtk_application_impl_wayland_get_current_session_id (GtkApplicationImpl *impl) +{ + return gdk_wayland_display_get_current_session_id (gdk_display_get_default ()); +} + static void gtk_application_impl_wayland_init (GtkApplicationImplWayland *wayland) { @@ -213,14 +265,18 @@ gtk_application_impl_wayland_class_init (GtkApplicationImplWaylandClass *class) class->dbus_inhibit = impl_class->inhibit; class->dbus_uninhibit = impl_class->uninhibit; + impl_class->startup = + gtk_application_impl_wayland_startup; impl_class->handle_window_realize = gtk_application_impl_wayland_handle_window_realize; impl_class->before_emit = gtk_application_impl_wayland_before_emit; - impl_class->window_removed = - gtk_application_impl_wayland_window_removed; impl_class->inhibit = gtk_application_impl_wayland_inhibit; impl_class->uninhibit = gtk_application_impl_wayland_uninhibit; + impl_class->window_removed = + gtk_application_impl_wayland_window_removed; + impl_class->get_current_session_id = + gtk_application_impl_wayland_get_current_session_id; } -- GitLab From 62a1f341f1f77c9373b3ab97071cef596d5d0a10 Mon Sep 17 00:00:00 2001 From: Carlos Garnacho Date: Wed, 19 Jun 2024 14:06:47 +0200 Subject: [PATCH 08/11] demo: Make gtk4-demo use session management Use it as an example of what is to be expected to make your application windows restore-able between executions, the session ID is preserved in a setting between runs, so that it's fed to the GtkApplication on startup. The actual session ID used is likewise always stored in this setting, for later runs. The main window gets an identifier within the session, so that its state may be restored when first mapped. --- demos/gtk-demo/main.c | 23 +++++++++++++++++++++++ demos/gtk-demo/main.ui | 1 + demos/gtk-demo/org.gtk.Demo4.gschema.xml | 4 ++++ 3 files changed, 28 insertions(+) diff --git a/demos/gtk-demo/main.c b/demos/gtk-demo/main.c index 60226a42376..fba7ef9638c 100644 --- a/demos/gtk-demo/main.c +++ b/demos/gtk-demo/main.c @@ -1108,6 +1108,19 @@ out: return 0; } +static void +startup (GApplication *app, + gpointer data) +{ + GSettings *settings = data; + const char *session_id; + + session_id = gtk_application_get_current_session_id (GTK_APPLICATION (app)); + + if (session_id) + g_settings_set_string (settings, "session-id", session_id); +} + G_MODULE_EXPORT int main (int argc, char **argv) @@ -1125,6 +1138,8 @@ main (int argc, char **argv) { "app.about", { "F1", NULL } }, { "app.quit", { "q", NULL } }, }; + GSettings *settings; + char *session_id; int i; char version[80]; @@ -1143,6 +1158,11 @@ main (int argc, char **argv) app_entries, G_N_ELEMENTS (app_entries), app); + settings = g_settings_new ("org.gtk.Demo4"); + session_id = g_settings_get_string (settings, "session-id"); + gtk_application_set_session_id (app, session_id); + g_free (session_id); + for (i = 0; i < G_N_ELEMENTS (accels); i++) gtk_application_set_accels_for_action (app, accels[i].action_and_target, accels[i].accelerators); @@ -1152,8 +1172,11 @@ main (int argc, char **argv) g_signal_connect (app, "activate", G_CALLBACK (activate), NULL); g_signal_connect (app, "command-line", G_CALLBACK (command_line), NULL); + g_signal_connect (app, "startup", G_CALLBACK (startup), settings); g_application_run (G_APPLICATION (app), argc, argv); + g_object_unref (settings); + return 0; } diff --git a/demos/gtk-demo/main.ui b/demos/gtk-demo/main.ui index cd085039f3e..63cc1fd3e4c 100644 --- a/demos/gtk-demo/main.ui +++ b/demos/gtk-demo/main.ui @@ -19,6 +19,7 @@ 800 600 + main-window 1 diff --git a/demos/gtk-demo/org.gtk.Demo4.gschema.xml b/demos/gtk-demo/org.gtk.Demo4.gschema.xml index 3eaa6e8b40d..0332824dcd3 100644 --- a/demos/gtk-demo/org.gtk.Demo4.gschema.xml +++ b/demos/gtk-demo/org.gtk.Demo4.gschema.xml @@ -21,6 +21,10 @@ false + + + '' + -- GitLab From 59cc378bf5c4c736e7948bc3d2f7a8d487edaaf7 Mon Sep 17 00:00:00 2001 From: Matthias Clasen Date: Mon, 2 Sep 2024 11:33:50 -0400 Subject: [PATCH 09/11] demo: Redo the application demo A big point of this demo was to show state saving. So make it use the new session saving protocol, which can take care of the window size and state. We still save the color on the client side. --- demos/gtk-demo/application.c | 127 ++++------------------- demos/gtk-demo/application.ui | 1 + demos/gtk-demo/org.gtk.Demo4.gschema.xml | 19 ++-- 3 files changed, 30 insertions(+), 117 deletions(-) diff --git a/demos/gtk-demo/application.c b/demos/gtk-demo/application.c index cfb4ca0870c..eb454b0f831 100644 --- a/demos/gtk-demo/application.c +++ b/demos/gtk-demo/application.c @@ -17,11 +17,6 @@ typedef struct { GtkWidget *menubutton; GMenuModel *toolmenu; GtkTextBuffer *buffer; - - int width; - int height; - gboolean maximized; - gboolean fullscreen; } DemoApplicationWindow; typedef GtkApplicationWindowClass DemoApplicationWindowClass; @@ -337,6 +332,7 @@ static void startup (GApplication *app) { GtkBuilder *builder; + const char *session_id; G_APPLICATION_CLASS (demo_application_parent_class)->startup (app); @@ -347,6 +343,17 @@ startup (GApplication *app) G_MENU_MODEL (gtk_builder_get_object (builder, "menubar"))); g_object_unref (builder); + + session_id = gtk_application_get_current_session_id (GTK_APPLICATION (app)); + + if (session_id) + { + GSettings *settings = g_settings_new ("org.gtk.Demo4.Application"); + + g_settings_set_string (settings, "session-id", session_id); + + g_object_unref (settings); + } } static void @@ -377,7 +384,7 @@ demo_application_init (DemoApplication *app) GSettings *settings; GAction *action; - settings = g_settings_new ("org.gtk.Demo4"); + settings = g_settings_new ("org.gtk.Demo4.Application"); g_action_map_add_action_entries (G_ACTION_MAP (app), app_entries, G_N_ELEMENTS (app_entries), @@ -399,40 +406,11 @@ demo_application_class_init (DemoApplicationClass *class) app_class->activate = activate; } -static void -demo_application_window_store_state (DemoApplicationWindow *win) -{ - GSettings *settings; - - settings = g_settings_new ("org.gtk.Demo4"); - g_settings_set (settings, "window-size", "(ii)", win->width, win->height); - g_settings_set_boolean (settings, "maximized", win->maximized); - g_settings_set_boolean (settings, "fullscreen", win->fullscreen); - g_object_unref (settings); -} - -static void -demo_application_window_load_state (DemoApplicationWindow *win) -{ - GSettings *settings; - - settings = g_settings_new ("org.gtk.Demo4"); - g_settings_get (settings, "window-size", "(ii)", &win->width, &win->height); - win->maximized = g_settings_get_boolean (settings, "maximized"); - win->fullscreen = g_settings_get_boolean (settings, "fullscreen"); - g_object_unref (settings); -} - static void demo_application_window_init (DemoApplicationWindow *window) { GtkWidget *popover; - window->width = -1; - window->height = -1; - window->maximized = FALSE; - window->fullscreen = FALSE; - gtk_widget_init_template (GTK_WIDGET (window)); popover = gtk_popover_menu_new_from_model (window->toolmenu); @@ -443,77 +421,11 @@ demo_application_window_init (DemoApplicationWindow *window) window); } -static void -demo_application_window_constructed (GObject *object) -{ - DemoApplicationWindow *window = (DemoApplicationWindow *)object; - - demo_application_window_load_state (window); - - gtk_window_set_default_size (GTK_WINDOW (window), window->width, window->height); - - if (window->maximized) - gtk_window_maximize (GTK_WINDOW (window)); - - if (window->fullscreen) - gtk_window_fullscreen (GTK_WINDOW (window)); - - G_OBJECT_CLASS (demo_application_window_parent_class)->constructed (object); -} - -static void -demo_application_window_size_allocate (GtkWidget *widget, - int width, - int height, - int baseline) -{ - DemoApplicationWindow *window = (DemoApplicationWindow *)widget; - - GTK_WIDGET_CLASS (demo_application_window_parent_class)->size_allocate (widget, - width, - height, - baseline); - - if (!window->maximized && !window->fullscreen) - gtk_window_get_default_size (GTK_WINDOW (window), &window->width, &window->height); -} - -static void -surface_state_changed (GtkWidget *widget) -{ - DemoApplicationWindow *window = (DemoApplicationWindow *)widget; - GdkToplevelState new_state; - - new_state = gdk_toplevel_get_state (GDK_TOPLEVEL (gtk_native_get_surface (GTK_NATIVE (widget)))); - window->maximized = (new_state & GDK_TOPLEVEL_STATE_MAXIMIZED) != 0; - window->fullscreen = (new_state & GDK_TOPLEVEL_STATE_FULLSCREEN) != 0; -} - -static void -demo_application_window_realize (GtkWidget *widget) -{ - GTK_WIDGET_CLASS (demo_application_window_parent_class)->realize (widget); - - g_signal_connect_swapped (gtk_native_get_surface (GTK_NATIVE (widget)), "notify::state", - G_CALLBACK (surface_state_changed), widget); -} - -static void -demo_application_window_unrealize (GtkWidget *widget) -{ - g_signal_handlers_disconnect_by_func (gtk_native_get_surface (GTK_NATIVE (widget)), - surface_state_changed, widget); - - GTK_WIDGET_CLASS (demo_application_window_parent_class)->unrealize (widget); -} - static void demo_application_window_dispose (GObject *object) { DemoApplicationWindow *window = (DemoApplicationWindow *)object; - demo_application_window_store_state (window); - gtk_widget_dispose_template (GTK_WIDGET (window), demo_application_window_get_type ()); G_OBJECT_CLASS (demo_application_window_parent_class)->dispose (object); @@ -525,13 +437,8 @@ demo_application_window_class_init (DemoApplicationWindowClass *class) GObjectClass *object_class = G_OBJECT_CLASS (class); GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (class); - object_class->constructed = demo_application_window_constructed; object_class->dispose = demo_application_window_dispose; - widget_class->size_allocate = demo_application_window_size_allocate; - widget_class->realize = demo_application_window_realize; - widget_class->unrealize = demo_application_window_unrealize; - gtk_widget_class_set_template_from_resource (widget_class, "/application_demo/application.ui"); gtk_widget_class_bind_template_child (widget_class, DemoApplicationWindow, message); gtk_widget_class_bind_template_child (widget_class, DemoApplicationWindow, infobar); @@ -548,11 +455,19 @@ int main (int argc, char *argv[]) { GtkApplication *app; + GSettings *settings; + char *session_id; app = GTK_APPLICATION (g_object_new (demo_application_get_type (), "application-id", "org.gtk.Demo4.App", "flags", G_APPLICATION_HANDLES_OPEN, NULL)); + settings = g_settings_new ("org.gtk.Demo4.Application"); + session_id = g_settings_get_string (settings, "session-id"); + gtk_application_set_session_id (app, session_id); + g_free (session_id); + g_object_unref (settings); + return g_application_run (G_APPLICATION (app), 0, NULL); } diff --git a/demos/gtk-demo/application.ui b/demos/gtk-demo/application.ui index 4d8a88d1ffb..ee108169270 100644 --- a/demos/gtk-demo/application.ui +++ b/demos/gtk-demo/application.ui @@ -5,6 +5,7 @@ 200 200 document-open + application-main diff --git a/demos/gtk-demo/org.gtk.Demo4.gschema.xml b/demos/gtk-demo/org.gtk.Demo4.gschema.xml index 0332824dcd3..3abac67d06f 100644 --- a/demos/gtk-demo/org.gtk.Demo4.gschema.xml +++ b/demos/gtk-demo/org.gtk.Demo4.gschema.xml @@ -2,24 +2,21 @@ - + - - 'red' - - - (-1, -1) - - - false + + '' - - false + + + + + 'red' -- GitLab From 2aeb5c7b271f9ef89f895163b9593bdccac9e177 Mon Sep 17 00:00:00 2001 From: Carlos Garnacho Date: Mon, 29 Jul 2024 18:13:31 +0200 Subject: [PATCH 10/11] demo: Restore Node Editor window state Save the session id, and restore the main window position from previous runs. --- demos/node-editor/meson.build | 3 +++ demos/node-editor/node-editor-application.c | 13 +++++++++++++ demos/node-editor/node-editor-window.ui | 1 + demos/node-editor/org.gtk.NodeEditor4.gschema.xml | 11 +++++++++++ 4 files changed, 28 insertions(+) create mode 100644 demos/node-editor/org.gtk.NodeEditor4.gschema.xml diff --git a/demos/node-editor/meson.build b/demos/node-editor/meson.build index 1d5c181bf5c..a2a151ef551 100644 --- a/demos/node-editor/meson.build +++ b/demos/node-editor/meson.build @@ -30,6 +30,9 @@ endforeach # desktop file install_data('org.gtk.gtk4.NodeEditor.desktop', install_dir: gtk_applicationsdir) +install_data('org.gtk.NodeEditor4.gschema.xml', install_dir: gtk_schemasdir) +gnome.compile_schemas() + # appdata configure_file( input: 'org.gtk.gtk4.NodeEditor.appdata.xml.in', diff --git a/demos/node-editor/node-editor-application.c b/demos/node-editor/node-editor-application.c index be4c0fedc8d..2779f9ddaf4 100644 --- a/demos/node-editor/node-editor-application.c +++ b/demos/node-editor/node-editor-application.c @@ -195,6 +195,16 @@ node_editor_application_startup (GApplication *app) const char *quit_accels[2] = { "Q", NULL }; const char *open_accels[2] = { "O", NULL }; GtkCssProvider *provider; + GSettings *settings; + gchar *session_id; + + settings = g_settings_new ("org.gtk.NodeEditor4"); + session_id = g_settings_get_string (settings, "session-id"); + if (session_id) + { + gtk_application_set_session_id (GTK_APPLICATION (app), session_id); + g_free (session_id); + } G_APPLICATION_CLASS (node_editor_application_parent_class)->startup (app); @@ -211,6 +221,9 @@ node_editor_application_startup (GApplication *app) gtk_style_context_add_provider_for_display (gdk_display_get_default (), GTK_STYLE_PROVIDER (provider), GTK_STYLE_PROVIDER_PRIORITY_APPLICATION); + + g_settings_set_string (settings, "session-id", + gtk_application_get_current_session_id (GTK_APPLICATION (app))); } static void diff --git a/demos/node-editor/node-editor-window.ui b/demos/node-editor/node-editor-window.ui index 789128fefd7..e50284b4a1f 100644 --- a/demos/node-editor/node-editor-window.ui +++ b/demos/node-editor/node-editor-window.ui @@ -94,6 +94,7 @@ 1024 768 text_view + main-window diff --git a/demos/node-editor/org.gtk.NodeEditor4.gschema.xml b/demos/node-editor/org.gtk.NodeEditor4.gschema.xml new file mode 100644 index 00000000000..9bb332b6c1b --- /dev/null +++ b/demos/node-editor/org.gtk.NodeEditor4.gschema.xml @@ -0,0 +1,11 @@ + + + + + + + '' + + + + -- GitLab From c4d5c8bf59100ec6941ff5f67e185af9e039acef Mon Sep 17 00:00:00 2001 From: Matthias Clasen Date: Mon, 2 Sep 2024 08:10:01 -0400 Subject: [PATCH 11/11] inspector: Show the session management protocol We want the inspector to show all the protocols that we use. --- gtk/inspector/general.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/gtk/inspector/general.c b/gtk/inspector/general.c index c2bd6dc3ea2..4941fd3a97e 100644 --- a/gtk/inspector/general.c +++ b/gtk/inspector/general.c @@ -1310,6 +1310,7 @@ add_wayland_protocols (GdkDisplay *display, append_wayland_protocol_row (gen, (struct wl_proxy *)d->system_bell); append_wayland_protocol_row (gen, (struct wl_proxy *)d->cursor_shape); append_wayland_protocol_row (gen, (struct wl_proxy *)d->toplevel_icon); + append_wayland_protocol_row (gen, (struct wl_proxy *)d->xx_session_manager); } } @@ -1352,6 +1353,7 @@ dump_wayland_protocols (GdkDisplay *display, append_wayland_protocol (string, (struct wl_proxy *)d->system_bell, &count); append_wayland_protocol (string, (struct wl_proxy *)d->cursor_shape, &count); append_wayland_protocol (string, (struct wl_proxy *)d->toplevel_icon, &count); + append_wayland_protocol (string, (struct wl_proxy *)d->xx_session_manager, &count); g_string_append (string, " |\n"); } -- GitLab