GApplication: Add RestartData property, and setter

The RestartData property on the org.gtk.Application interface can be
used by session services to save some data to disk should the
application disappear.

The application would call g_application_set_restart_data() when the
state of the application changes, with enough information for the
application to restart itself in the same "position".

FIXME:
 [ ] API docs
 [ ] note about restart data size
 [ ] have one example implementation of the session side

One idea would be for xdg-desktop-portal to save a stringified
RestartData to ~/.var/app/$ID/config/state.gvariant if none empty when
the application disappears from the bus.

Questions:
- Can we enforce to only do this for unique applications?
- Can we detect applications leaving the bus suddenly, or do we
  want to rely on them clearing RestartData if they're "done" (or should
  they always save state, *some* state)
- Should we implement this via org.gnome.SessionManager.Client and the
  app calling out to gnome-session instead?
- Or is this API fine, and gnome-session can do what xdg-desktop-portal
  does above?
This commit is contained in:
Bastien Nocera 2019-02-20 01:39:10 +01:00 committed by Philip Withnall
parent 741a561c4d
commit 3a113699d7
5 changed files with 331 additions and 5 deletions

View File

@ -3325,6 +3325,10 @@ g_application_set_option_context_description
g_application_set_default
g_application_get_default
<SUBSECTION>
g_application_get_supports_restart_data
g_application_consume_restart_data
g_application_notify_restart_data_changed
<SUBSECTION>
g_application_mark_busy
g_application_unmark_busy
g_application_get_is_busy

View File

@ -42,6 +42,11 @@
#include "glibintl.h"
#include "gmarshal-internal.h"
#ifdef G_OS_UNIX
/* For g_unix_signal_source_new() */
#include <glib-unix.h>
#endif
#include <string.h>
/**
@ -108,6 +113,20 @@
* g_application_register()). Unfortunately, this means that you cannot use
* g_application_get_is_remote() to decide if you want to register object paths.
*
* #GApplication supports restart data if it is supported by the platform.
* This is a way of saving application state at key times, so that the
* application can be restored to a similar state the next time it is run. The
* state would typically be saved before the user logs out, before the app is
* stopped to save resources, or whenever the app significantly changes state
* (such as opening a new document). In order to trigger saving state, call
* g_application_notify_restart_data_changed(). This function is automatically
* called when the process receives a `SIGTERM` signal (on Unix).
*
* In order to use restart data support, your application must fulfil a few
* criteria see g_application_get_supports_restart_data(). Some #GApplication
* subclasses, such as #GtkApplication, may support this transparently by
* default.
*
* GApplication also implements the #GActionGroup and #GActionMap
* interfaces and lets you easily export actions by adding them with
* g_action_map_add_action(). When invoking an action by calling
@ -220,6 +239,10 @@
* @handle_local_options: invoked locally after the parsing of the commandline
* options has occurred. Since: 2.40
* @name_lost: invoked when another instance is taking over the name. Since: 2.60
* @build_restart_data: serialize the key parts of the applications state so
* that it can be saved as restart data. Since: 2.78
* @consume_restart_data: apply the restart data to the applications state, to
* restore the saved state. Since: 2.78
*
* Virtual function table for #GApplication.
*
@ -261,6 +284,10 @@ struct _GApplicationPrivate
/* Allocated option strings, from g_application_add_main_option() */
GSList *option_strings;
#ifdef G_OS_UNIX
GSource *sigterm_source; /* (nullable) (owned) */
#endif
};
enum
@ -1386,6 +1413,14 @@ g_application_finalize (GObject *object)
{
GApplication *application = G_APPLICATION (object);
#ifdef G_OS_UNIX
if (application->priv->sigterm_source != NULL)
{
g_source_destroy (application->priv->sigterm_source);
g_clear_pointer (&application->priv->sigterm_source, g_source_unref);
}
#endif /* G_OS_UNIX */
if (application->priv->inactivity_timeout_id)
g_source_remove (application->priv->inactivity_timeout_id);
@ -2361,6 +2396,20 @@ g_application_open (GApplication *application,
0, files, n_files, hint);
}
#ifdef G_OS_UNIX
static gboolean
sigterm_cb (gpointer user_data)
{
GApplication *application = G_APPLICATION (user_data);
/* Give the application a chance to save its restart data before the SIGTERM
* likely causes the application to exit. */
g_application_notify_restart_data_changed (application);
return G_SOURCE_CONTINUE;
}
#endif /* G_OS_UNIX */
/* Run {{{1 */
/**
* g_application_run:
@ -2569,6 +2618,18 @@ g_application_run (GApplication *application,
g_timeout_add (10000, inactivity_timeout_expired, application);
}
#ifdef G_OS_UNIX
if (application->priv->is_registered &&
!application->priv->is_remote &&
g_application_get_supports_restart_data (application))
{
application->priv->sigterm_source = g_unix_signal_source_new (SIGTERM);
g_source_set_callback (application->priv->sigterm_source, sigterm_cb, application, NULL);
g_source_attach (application->priv->sigterm_source, context);
}
#endif /* G_OS_UNIX */
/* Run until the application is told to quit. */
while (application->priv->use_count || application->priv->inactivity_timeout_id)
{
if (application->priv->must_quit_now)
@ -2578,8 +2639,17 @@ g_application_run (GApplication *application,
status = 0;
}
/* Shutdown */
if (application->priv->is_registered && !application->priv->is_remote)
{
#ifdef G_OS_UNIX
if (application->priv->sigterm_source != NULL)
{
g_source_destroy (application->priv->sigterm_source);
g_clear_pointer (&application->priv->sigterm_source, g_source_unref);
}
#endif /* G_OS_UNIX */
g_signal_emit (application, g_application_signals[SIGNAL_SHUTDOWN], 0);
if (!application->priv->did_shutdown)
@ -3140,5 +3210,137 @@ g_application_unbind_busy_property (GApplication *application,
g_signal_handler_disconnect (object, handler_id);
}
/* Session handling {{{1 */
/**
* g_application_get_supports_restart_data:
* @application: a #GApplication
*
* Get whether the @application supports saving and loading restart data to
* restore its state after being restarted.
*
* As well as @application supporting restart data, the platform its running on
* must also support it for the feature to work.
*
* A #GApplication supports restart data if it
* - does not have the %G_APPLICATION_NON_UNIQUE flag,
* - implements #GApplicationClass.build_restart_data, and
* - implements #GApplicationClass.consume_restart_data.
*
* Returns: %TRUE if @application supports restart data, %FALSE otherwise
* Since: 2.78
*/
gboolean
g_application_get_supports_restart_data (GApplication *application)
{
GApplicationClass *klass;
g_return_val_if_fail (G_IS_APPLICATION (application), FALSE);
klass = G_APPLICATION_GET_CLASS (application);
return (!(application->priv->flags & G_APPLICATION_NON_UNIQUE) &&
klass->build_restart_data != NULL &&
klass->consume_restart_data != NULL);
}
/**
* g_application_consume_restart_data:
* @application: a #GApplication
* @tag: (nullable): tag for versioning the data, or %NULL to not version it
* @data: (nullable): data to store for the next restart, or %NULL to clear it
*
* Set the restart data which will be used to restore the applications state
* next time its restarted.
*
* Applications may be restarted after being interrupted by the system to save
* resources; or after they crash; or when restarting the computer. This list is
* not exhaustive.
*
* Calling g_application_consume_restart_data() will cause a notification to be
* sent to the session manager, which will cause the data to be saved at some
* point in the near future. Its not guaranteed that the data has been saved by
* the time this function returns. Depending on system configuration, the data
* may not be saved at all. If g_application_consume_restart_data() is called
* multiple times, multiple notifications will be sent this can be used to
* request that the restart data is saved after key interactions in your
* applications interface (such as the user changing the set of open documents
* or tabs).
*
* It is an error to call this function if @application does not support restart
* data (see g_application_get_supports_restart_data()).
*
* If @tag is specified, it will be stored alongside the data and returned when
* the data is restored. It can be used to detect incompatibilities between the
* software and the data. For example, by setting it to the software version, a
* restart of an upgraded software version using data stored by an older version
* can be detected and handled. No format is mandated for @tag, other than that,
* if non-%NULL, it must be non-empty and valid UTF-8.
*
* If @data is %NULL, any stored restart data will be cleared. @tag will be
* ignored.
*
* If @data is non-%NULL, it may have any type. No type checks are performed on
* the data when it is loaded or saved. Applications are responsible for doing
* this if needed.
*
* The size of @data should be at most a few hundred kilobytes. If your
* application needs to store more state than this, such as unsaved and
* in-progress documents, they should be stored externally (in an autosave
* folder, for example), and a path to them stored in @data.
*
* If @data is a floating #GVariant, it will be consumed.
*
* Since: 2.78
*/
void
g_application_consume_restart_data (GApplication *application,
const char *tag,
GVariant *data)
{
GApplicationClass *klass;
g_return_if_fail (G_IS_APPLICATION (application));
g_return_if_fail (g_application_get_supports_restart_data (application));
g_return_if_fail (tag == NULL || *tag != '\0');
g_return_if_fail (data != NULL);
/* Ensure the @data passed to consume_restart_data() is not floating. */
g_variant_ref_sink (data);
klass = G_APPLICATION_GET_CLASS (application);
klass->consume_restart_data (application, tag, data);
g_variant_unref (data);
}
/**
* g_application_notify_restart_data_changed:
* @application: a #GApplication
*
* Notify the platform that this applications restart data has changed
* significantly.
*
* This should be used to notify the platform of significant changes in the
* state of the application, such as completing a setup process or changing the
* set of open documents.
*
* The platform may then query the application for its updated restart data and
* save it so that it can be loaded again when the application is next started.
*
* It is an error to call this function if @application does not support restart
* data (see g_application_get_supports_restart_data()).
*
* Since: 2.78
*/
void
g_application_notify_restart_data_changed (GApplication *application)
{
g_return_if_fail (G_IS_APPLICATION (application));
g_return_if_fail (g_application_get_supports_restart_data (application));
g_application_impl_notify_restart_data_changed (application->priv->impl);
}
/* Epilogue {{{1 */
/* vim:set foldmethod=marker: */

View File

@ -119,8 +119,55 @@ struct _GApplicationClass
GVariantDict *options);
gboolean (* name_lost) (GApplication *application);
/**
* GApplicationClass::build_restart_data:
* @application: a #GApplication
* @out_tag: (optional) (nullable) (out) (transfer full): return location for
* a tag to version the restart data, or %NULL to ignore; the returned tag
* may be %NULL if no restart data is being returned
*
* Serialize the key parts of the applications state so that it can be saved
* as restart data.
*
* The returned #GVariant can have any type which is valid to send over D-Bus.
*
* Returns: (transfer full) (nullable): restart data for @application; this
* may be a floating #GVariant
*
* Since: 2.78
*/
GVariant *(* build_restart_data) (GApplication *application,
char **out_tag);
/**
* GApplicationClass::consume_restart_data:
* @application: a #GApplication
* @tag: (nullable): a tag to version the restart data, or %NULL if none was
* set
* @data: (not nullable): the restart data; this is guaranteed to not be
* floating
*
* Apply the restart data to the applications state, to restore the saved
* state.
*
* This will typically be called early in the lifetime of @application, so
* that it can initialize its state correctly according to @data.
*
* It will not be called if there is no restart data to load.
*
* Implementations should check that @tag matches what they expect (typically
* the current application version), and may discard the @data if there is a
* mismatch. This will typically mean that @data dates from a previous version
* of the application, and may not be compatible.
*
* Since: 2.78
*/
void (* consume_restart_data)(GApplication *application,
const char *tag,
GVariant *data);
/*< private >*/
gpointer padding[7];
gpointer padding[5];
};
GIO_AVAILABLE_IN_ALL
@ -252,6 +299,15 @@ void g_application_unbind_busy_property (GApplic
gpointer object,
const gchar *property);
GIO_AVAILABLE_IN_2_78
gboolean g_application_get_supports_restart_data (GApplication *application);
GIO_AVAILABLE_IN_2_78
void g_application_consume_restart_data (GApplication *application,
const char *tag,
GVariant *data);
GIO_AVAILABLE_IN_2_78
void g_application_notify_restart_data_changed (GApplication *application);
G_END_DECLS
#endif /* __G_APPLICATION_H__ */

View File

@ -67,7 +67,8 @@ static const gchar org_gtk_Application_xml[] =
"<arg type='a{sv}' name='platform-data' direction='in'/>"
"<arg type='i' name='exit-status' direction='out'/>"
"</method>"
"<property name='Busy' type='b' access='read'/>"
"<property name='Busy' type='b' access='read'/>"
"<property name='RestartData' type='sv' access='read'/>"
"</interface>"
"</node>";
@ -130,6 +131,8 @@ struct _GApplicationImpl
static GApplicationCommandLine *
g_dbus_command_line_new (GDBusMethodInvocation *invocation);
static GVariant *format_restart_data (const char *restart_data_tag,
GVariant *restart_data);
static GVariant *
g_application_impl_get_property (GDBusConnection *connection,
@ -141,9 +144,32 @@ g_application_impl_get_property (GDBusConnection *connection,
gpointer user_data)
{
GApplicationImpl *impl = user_data;
GApplicationClass *class;
class = G_APPLICATION_GET_CLASS (impl->app);
if (strcmp (property_name, "Busy") == 0)
return g_variant_new_boolean (impl->busy);
else if (strcmp (property_name, "RestartData") == 0)
{
GVariant *restart_data = NULL;
char *restart_data_tag = NULL;
GVariant *out = NULL;
/* Interpret a o.fdo.DBus.Properties.Get() call on the property as an
* explicit request to update the restart data. */
if (class->build_restart_data != NULL)
restart_data = class->build_restart_data (impl->app, &restart_data_tag);
if (restart_data != NULL)
g_variant_take_ref (restart_data);
out = format_restart_data (restart_data_tag, restart_data);
g_clear_pointer (&restart_data, g_variant_unref);
g_clear_pointer (&restart_data_tag, g_free);
return g_steal_pointer (&out);
}
g_assert_not_reached ();
@ -151,14 +177,16 @@ g_application_impl_get_property (GDBusConnection *connection,
}
static void
send_property_change (GApplicationImpl *impl)
send_property_change (GApplicationImpl *impl,
const char *property_name,
GVariant *value)
{
GVariantBuilder builder;
g_variant_builder_init (&builder, G_VARIANT_TYPE_ARRAY);
g_variant_builder_add (&builder,
"{sv}",
"Busy", g_variant_new_boolean (impl->busy));
property_name, value);
g_dbus_connection_emit_signal (impl->session_bus,
NULL,
@ -594,10 +622,45 @@ g_application_impl_set_busy_state (GApplicationImpl *impl,
if (impl->busy != busy)
{
impl->busy = busy;
send_property_change (impl);
send_property_change (impl, "Busy", g_variant_new_boolean (impl->busy));
}
}
static GVariant *
format_restart_data (const char *restart_data_tag,
GVariant *restart_data)
{
/* The wire format of no restart data is `("", <"">)`, since D-Bus doesnt
* currently support maybe types. */
gboolean no_data = (restart_data == NULL);
return g_variant_new ("(sv)",
(no_data || restart_data_tag == NULL) ? "" : restart_data_tag,
no_data ? g_variant_new_string ("") : restart_data);
}
void
g_application_impl_notify_restart_data_changed (GApplicationImpl *impl)
{
GApplicationClass *app_class = G_APPLICATION_GET_CLASS (impl->app);
GVariant *restart_data = NULL;
char *restart_data_tag = NULL;
GVariant *out = NULL;
if (app_class->build_restart_data != NULL)
restart_data = app_class->build_restart_data (impl->app, &restart_data_tag);
if (restart_data != NULL)
g_variant_take_ref (restart_data);
out = format_restart_data (restart_data_tag, restart_data);
g_clear_pointer (&restart_data, g_variant_unref);
g_clear_pointer (&restart_data_tag, g_free);
send_property_change (impl, "RestartData", out);
g_variant_unref (out);
}
void
g_application_impl_destroy (GApplicationImpl *impl)
{

View File

@ -63,3 +63,4 @@ const gchar * g_application_impl_get_dbus_object_path (GApplic
void g_application_impl_set_busy_state (GApplicationImpl *impl,
gboolean busy);
void g_application_impl_notify_restart_data_changed (GApplicationImpl *impl);