summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMatthias Clasen <mclasen@redhat.com>2021-06-03 08:19:48 -0400
committerMatthias Clasen <mclasen@redhat.com>2021-06-03 10:07:43 -0400
commit9f3525033065197eba5191e1a099b316019134d0 (patch)
treecb71feb79d5ac163b8081c5f677f9e2fb86bde23
parent7e8dfd5bda1b9f20c3bd5d78c1ba3c8fb7ecde6b (diff)
downloadgtk+-9f3525033065197eba5191e1a099b316019134d0.tar.gz
application: Add state saving
Add the necessary plumbing to save and restore application windows and their state.
-rw-r--r--gtk/gtkapplication.c246
-rw-r--r--gtk/gtkapplication.h13
2 files changed, 258 insertions, 1 deletions
diff --git a/gtk/gtkapplication.c b/gtk/gtkapplication.c
index c26e15cadb..648c4fc955 100644
--- a/gtk/gtkapplication.c
+++ b/gtk/gtkapplication.c
@@ -27,6 +27,8 @@
#include <gio/gunixfdlist.h>
#endif
+#include <glib/gstdio.h>
+
#include <stdlib.h>
#ifdef HAVE_UNISTD_H
@@ -119,6 +121,7 @@ enum {
WINDOW_ADDED,
WINDOW_REMOVED,
QUERY_END,
+ CREATE_WINDOW,
LAST_SIGNAL
};
@@ -130,6 +133,7 @@ enum {
PROP_SCREENSAVER_ACTIVE,
PROP_MENUBAR,
PROP_ACTIVE_WINDOW,
+ PROP_SAVE_STATE,
NUM_PROPERTIES
};
@@ -147,6 +151,7 @@ typedef struct
gboolean register_session;
gboolean screensaver_active;
+ gboolean save_state;
GtkActionMuxer *muxer;
GtkBuilder *menus_builder;
char *help_overlay_path;
@@ -271,6 +276,26 @@ gtk_application_startup (GApplication *g_application)
}
static void
+gtk_application_activate (GApplication *g_application)
+{
+ GtkApplication *application = GTK_APPLICATION (g_application);
+ GtkApplicationPrivate *priv = gtk_application_get_instance_private (application);
+ guint activate_id;
+
+ activate_id = g_signal_lookup ("activate", G_TYPE_APPLICATION);
+ if (!g_signal_has_handler_pending (g_application, activate_id, 0, TRUE))
+ {
+ if (!priv->save_state ||
+ !gtk_application_restore (application))
+ {
+ g_signal_emit (application,
+ gtk_application_signals[CREATE_WINDOW], 0, NULL);
+ gtk_window_present (GTK_WINDOW (priv->windows->data));
+ }
+ }
+}
+
+static void
gtk_application_shutdown (GApplication *g_application)
{
GtkApplication *application = GTK_APPLICATION (g_application);
@@ -284,6 +309,9 @@ gtk_application_shutdown (GApplication *g_application)
gtk_action_muxer_remove (priv->muxer, "app");
+ if (priv->save_state)
+ gtk_application_save (application);
+
gtk_main_sync ();
G_APPLICATION_CLASS (gtk_application_parent_class)->shutdown (g_application);
@@ -451,6 +479,10 @@ gtk_application_get_property (GObject *object,
g_value_set_object (value, gtk_application_get_active_window (application));
break;
+ case PROP_SAVE_STATE:
+ g_value_set_boolean (value, priv->save_state);
+ break;
+
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
break;
@@ -476,6 +508,10 @@ gtk_application_set_property (GObject *object,
gtk_application_set_menubar (application, g_value_get_object (value));
break;
+ case PROP_SAVE_STATE:
+ priv->save_state = g_value_get_boolean (value);
+ break;
+
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
break;
@@ -530,6 +566,7 @@ gtk_application_class_init (GtkApplicationClass *class)
application_class->after_emit = gtk_application_after_emit;
application_class->startup = gtk_application_startup;
application_class->shutdown = gtk_application_shutdown;
+ application_class->activate = gtk_application_activate;
application_class->dbus_register = gtk_application_dbus_register;
application_class->dbus_unregister = gtk_application_dbus_unregister;
@@ -587,6 +624,33 @@ gtk_application_class_init (GtkApplicationClass *class)
G_TYPE_NONE, 0);
/**
+ * GtkApplication::create-window:
+ * @application: the `GtkApplication` which emitted the signal
+ * @save_id: (nullable): save-id to set on the new window
+ *
+ * In response to this signal, you should create a new application window,
+ * and add it to @application.
+ *
+ *`GtkApplication` will call [method@Gtk.Window.present] on the window.
+
+ * If @save_id is passed, it should be set as the [property@Gtk.Widget:save-id]
+ * of the newly created window.
+ *
+ * You should handle this signal instead of `::activate` to make automatic
+ * session saving work. See [property@Gtk.Application:save-state] for more
+ * information.
+ *
+ * Since: 4.4
+ */
+ gtk_application_signals[CREATE_WINDOW] =
+ g_signal_new (I_("create-window"), GTK_TYPE_APPLICATION, G_SIGNAL_RUN_FIRST,
+ G_STRUCT_OFFSET (GtkApplicationClass, create_window),
+ NULL, NULL,
+ NULL,
+ G_TYPE_NONE, 1,
+ G_TYPE_STRING);
+
+ /**
* GtkApplication:register-session:
*
* Set this property to `TRUE` to register with the session manager.
@@ -644,6 +708,29 @@ gtk_application_class_init (GtkApplicationClass *class)
GTK_TYPE_WINDOW,
G_PARAM_READABLE|G_PARAM_STATIC_STRINGS);
+ /**
+ * GtkApplication:save-state:
+ *
+ * Whether to save and restore the application state automatically when the
+ * application is closed or the session ends.
+ *
+ * In order to make automatic state restoration work, you should handle
+ * [signal@Gtk.Application::create-window]. The default `::activate` handler
+ * will emit that signal as needed, and call [method@Gtk.Application.restore]
+ * when it finds saved state.
+ *
+ * In order to make state saving work, widgets need to have a
+ * [property@Gtk.Widget:save-id] set.
+ *
+ * Since: 4.4
+ */
+ gtk_application_props[PROP_SAVE_STATE] =
+ g_param_spec_boolean ("save-state",
+ P_("Save state"),
+ P_("Whether to save application state automatically"),
+ FALSE,
+ G_PARAM_READWRITE|G_PARAM_STATIC_STRINGS);
+
g_object_class_install_properties (object_class, NUM_PROPERTIES, gtk_application_props);
}
@@ -1230,3 +1317,162 @@ gtk_application_set_screensaver_active (GtkApplication *application,
g_object_notify (G_OBJECT (application), "screensaver-active");
}
}
+
+static char *
+get_state_file (GtkApplication *application)
+{
+ const char *app_id;
+ const char *dir;
+
+ app_id = g_application_get_application_id (G_APPLICATION (application));
+ dir = g_get_user_data_dir ();
+ return g_strconcat (dir, "/", app_id, ".state", NULL);
+}
+
+/**
+ * gtk_application_save:
+ * @application: a `GtkApplication`
+ *
+ * Saves the state of application.
+ *
+ * See [method@Gtk.Application.restore] for a way to restore the state.
+ *
+ * If [property@Gtk.Application:save-state] is set, `GtkApplication` calls this
+ * function automatically when the application is closed or the session ends.
+ *
+ * Since: 4.4
+ */
+void
+gtk_application_save (GtkApplication *application)
+{
+ GtkApplicationPrivate *priv = gtk_application_get_instance_private (application);
+ GList *l;
+ GVariantBuilder builder;
+ GVariant *state;
+
+ g_variant_builder_init (&builder, G_VARIANT_TYPE_VARDICT);
+
+ for (l = priv->windows; l != NULL; l = l->next)
+ {
+ const char *id;
+ GVariant *v;
+
+ id = gtk_widget_get_save_id (GTK_WIDGET (l->data));
+ if (!id)
+ continue;
+
+ v = gtk_widget_save_state (GTK_WIDGET (l->data));
+ g_variant_builder_add (&builder, "{sv}", id, v);
+ }
+
+ state = g_variant_builder_end (&builder);
+
+ if (g_variant_n_children (state) > 0)
+ {
+ char *file = get_state_file (application);
+
+ g_file_set_contents (file,
+ g_variant_get_data (state),
+ g_variant_get_size (state),
+ NULL);
+
+ g_free (file);
+ }
+
+ g_variant_unref (state);
+}
+
+/**
+ * gtk_application_forget:
+ * @application: a `GtkApplication`
+ *
+ * Forget state that has been previously saved.
+ *
+ * Note that `GtkApplication` will save state automatically
+ * as long as [property@Gtk.Application:save-state] is set,
+ * so you probably want to unset that property as well, if
+ * your goal is to implement a “factory reset”.
+ *
+ * See [method@Gtk.Application.save] for more information.
+ *
+ * Since: 4.4
+ */
+void
+gtk_application_forget (GtkApplication *application)
+{
+ char *file = get_state_file (application);
+
+ g_remove (file);
+
+ g_free (file);
+}
+
+/**
+ * gtk_application_restore:
+ * @application: a `GtkApplication`
+ *
+ * Restores previously saved state.
+ *
+ * See [method@Gtk.Application.save] for a way to save application state.
+ *
+ * If [property@Gtk.Application:save-state] is set, `GtkApplication` calls this
+ * function automatically in the default `::activate` handler. Note that you
+ * need to handle the [signal@Gtk.Application::create-window] to make restoring
+ * state work.
+ *
+ * Returns: %TRUE if at least one window has been restored from saved state
+ *
+ * Since: 4.4
+ */
+gboolean
+gtk_application_restore (GtkApplication *application)
+{
+ GtkApplicationPrivate *priv = gtk_application_get_instance_private (application);
+ char *file = get_state_file (application);
+ char *contents;
+ gsize len;
+ GVariant *state;
+ GVariantIter iter;
+ const char *key;
+ gboolean restored;
+
+ if (!g_file_get_contents (file, &contents, &len, NULL))
+ {
+ g_free (file);
+ return FALSE;
+ }
+
+ state = g_variant_new_from_data (G_VARIANT_TYPE_VARDICT, contents, len, FALSE, NULL, NULL);
+ g_variant_iter_init (&iter, state);
+ while (g_variant_iter_next (&iter, "{sv}", &key, NULL))
+ g_signal_emit (application, gtk_application_signals[CREATE_WINDOW], 0, key);
+
+ restored = FALSE;
+
+ for (GList *l = priv->windows; l; l = l->next)
+ {
+ GtkWidget *window = GTK_WIDGET (l->data);
+ const char *id;
+ GVariant *v;
+
+ id = gtk_widget_get_save_id (window);
+ if (id &&
+ (v = g_variant_lookup_value (state, id, NULL)) != NULL)
+ {
+ restored = TRUE;
+ gtk_widget_restore_state (window, v);
+ g_variant_unref (v);
+ }
+ else
+ {
+ g_warning ("::create-window did not set save-id");
+ }
+
+ gtk_window_present (GTK_WINDOW (window));
+ }
+
+ g_free (file);
+ g_free (contents);
+
+ return restored;
+}
diff --git a/gtk/gtkapplication.h b/gtk/gtkapplication.h
index ae354388e5..a5ddced061 100644
--- a/gtk/gtkapplication.h
+++ b/gtk/gtkapplication.h
@@ -64,8 +64,11 @@ struct _GtkApplicationClass
void (*window_removed) (GtkApplication *application,
GtkWindow *window);
+ void (*create_window) (GtkApplication *application,
+ const char *save_id);
+
/*< private >*/
- gpointer padding[8];
+ gpointer padding[7];
};
GDK_AVAILABLE_IN_ALL
@@ -135,6 +138,14 @@ GDK_AVAILABLE_IN_ALL
GMenu * gtk_application_get_menu_by_id (GtkApplication *application,
const char *id);
+GDK_AVAILABLE_IN_4_4
+void gtk_application_save (GtkApplication *application);
+GDK_AVAILABLE_IN_4_4
+void gtk_application_forget (GtkApplication *application);
+GDK_AVAILABLE_IN_4_4
+gboolean gtk_application_restore (GtkApplication *application);
+
+
G_DEFINE_AUTOPTR_CLEANUP_FUNC(GtkApplication, g_object_unref)
G_END_DECLS