diff --git a/demos/gtk-demo/application.c b/demos/gtk-demo/application.c index cfb4ca0870c0830ccf1603feaea679bf81a10556..eb454b0f8318ac4738fadb3800a126936056e8f1 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 4d8a88d1ffb0b1d1bfb485edf3986b86465e3d46..ee1081692701ec702db92a8a3c6230ae2612c6ef 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/main.c b/demos/gtk-demo/main.c index 60226a423762e797a55f8b6937136de5ec2e83ae..fba7ef9638c3c32b900fe07848c5c4b3b2f2a930 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 cd085039f3e98519927a39a480cb5a0d51bb0f28..63cc1fd3e4cebf8c1a90f6e0c3fcf1ef23305408 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 3eaa6e8b40da53c1a492fd5688c3c33d859d7401..3abac67d06fa42b6df16ae6af3c3a319fbdfe5f8 100644 --- a/demos/gtk-demo/org.gtk.Demo4.gschema.xml +++ b/demos/gtk-demo/org.gtk.Demo4.gschema.xml @@ -2,24 +2,25 @@ - + - - 'red' - - - (-1, -1) + + '' - - false + + + + + 'red' - - false + + + '' diff --git a/demos/node-editor/meson.build b/demos/node-editor/meson.build index 1d5c181bf5cbe7d63987f68f02fb4f248e27a99d..a2a151ef55103867c01efddea722860e41d668ae 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 be4c0fedc8d80385dfeb3a34acff19a9d75974cb..2779f9ddaf4398b266e598a745c6ce150de00215 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 789128fefd72050232082cae8af790011fbddbb0..e50284b4a1f95aa935a8d4088dccae8d94a08005 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 0000000000000000000000000000000000000000..9bb332b6c1bcef88e113f1d1f59e1b754eb3c09b --- /dev/null +++ b/demos/node-editor/org.gtk.NodeEditor4.gschema.xml @@ -0,0 +1,11 @@ + + + + + + + '' + + + + diff --git a/docs/reference/gtk/running.md b/docs/reference/gtk/running.md index 757bf403d102c5b1bdd72a18e4c25c95bd074685..8b61f97955b7abbb7c6b090545f8d76a2035695c 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 828fd440a0f3016b6cb0295a7eb29fccacaa6a4b..6022c9552e73e4a9bf3b6cbc05a2016762c28969 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 aa04b550786ebb4b7b643a2d77da712ecbb53de0..157039ef00a3f085d02f63886ceff1ad34a608ac 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; diff --git a/gdk/wayland/gdkdisplay-wayland.c b/gdk/wayland/gdkdisplay-wayland.c index 1fecc42df8341c417a69517396675ba75618355e..70e895a562c453f00aa53cca374c268ebbad5c86 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 fc20ac35e2535c9bc14bab76e093ec1d8d5ef6cc..07175bbb63a370c688393c2fbc3551b459947a70 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/gdktoplevel-wayland-private.h b/gdk/wayland/gdktoplevel-wayland-private.h index 9f8e28d937444035432d95f70e0f359746733f2a..d60737dc6fce6f9fa354bf7fd993d0a5d441c929 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 76b3fb65849697d1d68208ef7c9ca492ac6a6ce7..ec2ecd00a495511fb46174b7432c0269f5df90f4 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: */ diff --git a/gdk/wayland/gdkwaylanddisplay.h b/gdk/wayland/gdkwaylanddisplay.h index 406b39c9e458f50e40e76b444a855fe7f7da9473..d00c5807b3fadab408510e1ebe76fa04f835601a 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 diff --git a/gdk/wayland/meson.build b/gdk/wayland/meson.build index 1cc8e99d9313e8bb603a65f139f53c7218719def..cd1529a26d3503267482acc8f634ddd8d6dd7af9 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 0000000000000000000000000000000000000000..e21694424f1db1a0eafd564a74d5d003adaf32b6 --- /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. + + + + + diff --git a/gtk/gtkapplication-wayland.c b/gtk/gtkapplication-wayland.c index fb9dddb92d10b88138aacec27ed43d7f53725aeb..a69a24a84f6d15f62ae71e51fd3aa11ec4102d27 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; } diff --git a/gtk/gtkapplication.c b/gtk/gtkapplication.c index b1f6093c37beb6a27b71b1637f3a06b1b1e3dbf1..75fc45d31d15bc89bfe84aefbd7af63c8575ceaa 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 7260de6dc7ef9c296b6ef322b2dd68ce140988ca..9d0332e2e35f4ab13a22d500ffe45fb526fd6f0b 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 7b5c81729aad831f46d04a4fa10e7a9b743ba5ac..0410777ba4240912d464ab501b148adc66933615 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 2c667e9652293e101f4647bff33b1a88a2d9cce7..e4a0e76212c460bcf61e8f40cbf3976059b1cacb 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 diff --git a/gtk/gtkwindow.c b/gtk/gtkwindow.c index ae300d72e1570d05f9b2090a0ecf2dfb9d036232..4f32a98de7155a34aa3c1865d1fbab9738caa6ee 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 c076bed0103ae1b576f956d030ea685a55522eca..b0b7ffa8bc75ded147df6405906cc7edae8d1980 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) diff --git a/gtk/inspector/general.c b/gtk/inspector/general.c index c2bd6dc3ea21d8f616acda24941c67407ecda636..4941fd3a97ec8654378b42499a4455b022e0f361 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"); }