diff --git a/docs/reference/gio/gio-sections-common.txt b/docs/reference/gio/gio-sections-common.txt index f0d1c713d3365cfec23223ce1866a233ed3d6ca7..78b31b42de98e4ef373672e7ed625e6cc2b76b72 100644 --- a/docs/reference/gio/gio-sections-common.txt +++ b/docs/reference/gio/gio-sections-common.txt @@ -3325,6 +3325,10 @@ g_application_set_option_context_description g_application_set_default g_application_get_default +g_application_get_supports_restart_data +g_application_consume_restart_data +g_application_notify_restart_data_changed + g_application_mark_busy g_application_unmark_busy g_application_get_is_busy diff --git a/gio/gapplication.c b/gio/gapplication.c index 200799d53a20fc0ed5513b006230f5bc4af17775..d0bc0a3a5bebc415784737c5d835cab405463495 100644 --- a/gio/gapplication.c +++ b/gio/gapplication.c @@ -42,6 +42,11 @@ #include "glibintl.h" #include "gmarshal-internal.h" +#ifdef G_OS_UNIX +/* For g_unix_signal_source_new() */ +#include +#endif + #include /** @@ -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 application’s state so + * that it can be saved as restart data. Since: 2.78 + * @consume_restart_data: apply the restart data to the application’s 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 it’s 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 application’s state + * next time it’s 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. It’s 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 + * application’s 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 application’s 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: */ diff --git a/gio/gapplication.h b/gio/gapplication.h index cb6b9086721d02d24474f2994e50406df8b18af6..cdd3b799be5d44fa8ad2c4557b4fe93d0d51d06f 100644 --- a/gio/gapplication.h +++ b/gio/gapplication.h @@ -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 application’s 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 application’s 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__ */ diff --git a/gio/gapplicationimpl-dbus.c b/gio/gapplicationimpl-dbus.c index ac6644d888114051fb6353eb853eb71bec638935..a0778a0b187d075db058d9b34680bad9c4198868 100644 --- a/gio/gapplicationimpl-dbus.c +++ b/gio/gapplicationimpl-dbus.c @@ -67,7 +67,8 @@ static const gchar org_gtk_Application_xml[] = "" "" "" - "" + "" + "" "" ""; @@ -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 doesn’t + * 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) { diff --git a/gio/gapplicationimpl.h b/gio/gapplicationimpl.h index 73747893a96349a4754b848e5d52325a6094bf80..46d7817d4ea0eee010951a782313eeb4b07faad2 100644 --- a/gio/gapplicationimpl.h +++ b/gio/gapplicationimpl.h @@ -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);