GtkApplication: Add logout notification

This is fairly basic, allowing applications to learn when
the session manager is about to end the session, and possibly
block this. The only implementation at this point is using the
org.gnome.SessionManager D-Bus interface of gnome-session. It should
be straightforward to port the EggSMClient implementations for
Windows and OS X.
This commit is contained in:
Matthias Clasen 2012-01-03 14:52:29 -05:00
parent b40d16972f
commit 3133791302
3 changed files with 276 additions and 1 deletions

View File

@ -224,6 +224,7 @@ gtk_application_get_menubar
gtk_application_get_type gtk_application_get_type
gtk_application_get_windows gtk_application_get_windows
gtk_application_new gtk_application_new
gtk_application_quit_response
gtk_application_remove_accelerator gtk_application_remove_accelerator
gtk_application_remove_window gtk_application_remove_window
gtk_application_set_app_menu gtk_application_set_app_menu

View File

@ -34,6 +34,7 @@
#include "gtkmain.h" #include "gtkmain.h"
#include "gtkaccelmapprivate.h" #include "gtkaccelmapprivate.h"
#include "gactionmuxer.h" #include "gactionmuxer.h"
#include "gtkintl.h"
#ifdef GDK_WINDOWING_QUARTZ #ifdef GDK_WINDOWING_QUARTZ
#include "gtkquartz-menu.h" #include "gtkquartz-menu.h"
@ -103,21 +104,36 @@
enum { enum {
WINDOW_ADDED, WINDOW_ADDED,
WINDOW_REMOVED, WINDOW_REMOVED,
QUIT_REQUESTED,
QUIT_CANCELLED,
QUIT,
LAST_SIGNAL LAST_SIGNAL
}; };
static guint gtk_application_signals[LAST_SIGNAL]; static guint gtk_application_signals[LAST_SIGNAL];
enum {
PROP_ZERO,
PROP_REGISTER_SESSION
};
G_DEFINE_TYPE (GtkApplication, gtk_application, G_TYPE_APPLICATION) G_DEFINE_TYPE (GtkApplication, gtk_application, G_TYPE_APPLICATION)
struct _GtkApplicationPrivate struct _GtkApplicationPrivate
{ {
GList *windows; GList *windows;
gboolean register_session;
#ifdef GDK_WINDOWING_X11 #ifdef GDK_WINDOWING_X11
GDBusConnection *session_bus; GDBusConnection *session_bus;
gchar *window_prefix; gchar *window_prefix;
guint next_id; guint next_id;
GDBusProxy *sm_proxy;
GDBusProxy *client_proxy;
gchar *app_id;
gchar *client_path;
#endif #endif
#ifdef GDK_WINDOWING_QUARTZ #ifdef GDK_WINDOWING_QUARTZ
@ -187,6 +203,9 @@ window_prefix_from_appid (const gchar *appid)
return appid_path; return appid_path;
} }
static void gtk_application_startup_session_dbus (GtkApplication *app);
static void static void
gtk_application_startup_x11 (GtkApplication *application) gtk_application_startup_x11 (GtkApplication *application)
{ {
@ -195,6 +214,8 @@ gtk_application_startup_x11 (GtkApplication *application)
application_id = g_application_get_application_id (G_APPLICATION (application)); application_id = g_application_get_application_id (G_APPLICATION (application));
application->priv->session_bus = g_bus_get_sync (G_BUS_TYPE_SESSION, NULL, NULL); application->priv->session_bus = g_bus_get_sync (G_BUS_TYPE_SESSION, NULL, NULL);
application->priv->window_prefix = window_prefix_from_appid (application_id); application->priv->window_prefix = window_prefix_from_appid (application_id);
gtk_application_startup_session_dbus (GTK_APPLICATION (application));
} }
static void static void
@ -203,6 +224,11 @@ gtk_application_shutdown_x11 (GtkApplication *application)
g_free (application->priv->window_prefix); g_free (application->priv->window_prefix);
application->priv->window_prefix = NULL; application->priv->window_prefix = NULL;
g_clear_object (&application->priv->session_bus); g_clear_object (&application->priv->session_bus);
g_clear_object (&application->priv->sm_proxy);
g_clear_object (&application->priv->client_proxy);
g_free (application->priv->app_id);
g_free (application->priv->client_path);
} }
#endif #endif
@ -481,12 +507,54 @@ gtk_application_notify (GObject *object,
G_OBJECT_CLASS (gtk_application_parent_class)->notify (object, pspec); G_OBJECT_CLASS (gtk_application_parent_class)->notify (object, pspec);
} }
static void
gtk_application_get_property (GObject *object,
guint prop_id,
GValue *value,
GParamSpec *pspec)
{
GtkApplication *application = GTK_APPLICATION (object);
switch (prop_id)
{
case PROP_REGISTER_SESSION:
g_value_set_boolean (value, application->priv->register_session);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
break;
}
}
static void
gtk_application_set_property (GObject *object,
guint prop_id,
const GValue *value,
GParamSpec *pspec)
{
GtkApplication *application = GTK_APPLICATION (object);
switch (prop_id)
{
case PROP_REGISTER_SESSION:
application->priv->register_session = g_value_get_boolean (value);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
break;
}
}
static void static void
gtk_application_class_init (GtkApplicationClass *class) gtk_application_class_init (GtkApplicationClass *class)
{ {
GObjectClass *object_class = G_OBJECT_CLASS (class); GObjectClass *object_class = G_OBJECT_CLASS (class);
GApplicationClass *application_class = G_APPLICATION_CLASS (class); GApplicationClass *application_class = G_APPLICATION_CLASS (class);
object_class->get_property = gtk_application_get_property;
object_class->set_property = gtk_application_set_property;
object_class->notify = gtk_application_notify; object_class->notify = gtk_application_notify;
application_class->add_platform_data = gtk_application_add_platform_data; application_class->add_platform_data = gtk_application_add_platform_data;
@ -534,6 +602,27 @@ gtk_application_class_init (GtkApplicationClass *class)
NULL, NULL, NULL, NULL,
g_cclosure_marshal_VOID__OBJECT, g_cclosure_marshal_VOID__OBJECT,
G_TYPE_NONE, 1, GTK_TYPE_WINDOW); G_TYPE_NONE, 1, GTK_TYPE_WINDOW);
gtk_application_signals[QUIT_REQUESTED] =
g_signal_new ("quit-requested", GTK_TYPE_APPLICATION, G_SIGNAL_RUN_LAST,
G_STRUCT_OFFSET (GtkApplicationClass, quit_requested),
NULL, NULL, g_cclosure_marshal_VOID__VOID, G_TYPE_NONE, 0);
gtk_application_signals[QUIT_CANCELLED] =
g_signal_new ("quit-cancelled", GTK_TYPE_APPLICATION, G_SIGNAL_RUN_LAST,
G_STRUCT_OFFSET (GtkApplicationClass, quit_cancelled),
NULL, NULL, g_cclosure_marshal_VOID__VOID, G_TYPE_NONE, 0);
gtk_application_signals[QUIT] =
g_signal_new ("quit", GTK_TYPE_APPLICATION, G_SIGNAL_RUN_LAST,
G_STRUCT_OFFSET (GtkApplicationClass, quit),
NULL, NULL, g_cclosure_marshal_VOID__VOID, G_TYPE_NONE, 0);
g_object_class_install_property (object_class, PROP_REGISTER_SESSION,
g_param_spec_boolean ("register-session",
P_("Register session"),
P_("Register with the session manager"),
FALSE, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
} }
/** /**
@ -847,3 +936,180 @@ gtk_application_get_menubar (GtkApplication *application)
return menubar; return menubar;
} }
/* D-Bus Session Management
*
* The protocol and the D-Bus API are described here:
* http://live.gnome.org/SessionManagement/GnomeSession
* http://people.gnome.org/~mccann/gnome-session/docs/gnome-session.html
*/
#ifdef GDK_WINDOWING_X11
static void
unregister_client (GtkApplication *app)
{
GError *error = NULL;
g_debug ("Unregistering client");
g_dbus_proxy_call_sync (app->priv->sm_proxy,
"UnregisterClient",
g_variant_new ("(o)", app->priv->client_path),
G_DBUS_CALL_FLAGS_NONE,
G_MAXINT,
NULL,
&error);
if (error)
{
g_warning ("Failed to unregister client: %s", error->message);
g_error_free (error);
}
g_clear_object (&app->priv->client_proxy);
g_free (app->priv->client_path);
app->priv->client_path = NULL;
}
static void
client_proxy_signal (GDBusProxy *proxy,
const gchar *sender_name,
const gchar *signal_name,
GVariant *parameters,
GtkApplication *app)
{
if (strcmp (signal_name, "QueryEndSession") == 0)
{
g_debug ("Received QueryEndSession");
g_signal_emit (app, gtk_application_signals[QUIT_REQUESTED], 0);
}
else if (strcmp (signal_name, "EndSession") == 0)
{
g_debug ("Received EndSession");
gtk_application_quit_response (app, TRUE, NULL);
unregister_client (app);
g_signal_emit (app, gtk_application_signals[QUIT], 0);
}
else if (strcmp (signal_name, "CancelEndSession") == 0)
{
g_debug ("Received CancelEndSession");
g_signal_emit (app, gtk_application_signals[QUIT_CANCELLED], 0);
}
else if (strcmp (signal_name, "Stop") == 0)
{
g_debug ("Received Stop");
unregister_client (app);
g_signal_emit (app, gtk_application_signals[QUIT], 0);
}
}
static void
gtk_application_startup_session_dbus (GtkApplication *app)
{
static gchar *client_id;
GError *error = NULL;
GVariant *res;
if (app->priv->session_bus == NULL)
return;
if (client_id == NULL)
{
const gchar *desktop_autostart_id;
desktop_autostart_id = g_getenv ("DESKTOP_AUTOSTART_ID");
/* Unset DESKTOP_AUTOSTART_ID in order to avoid child processes to
* use the same client id.
*/
g_unsetenv ("DESKTOP_AUTOSTART_ID");
client_id = g_strdup (desktop_autostart_id ? desktop_autostart_id : "");
}
g_debug ("Connecting to session manager");
app->priv->sm_proxy = g_dbus_proxy_new_sync (app->priv->session_bus,
G_DBUS_PROXY_FLAGS_DO_NOT_LOAD_PROPERTIES |
G_DBUS_PROXY_FLAGS_DO_NOT_CONNECT_SIGNALS,
NULL,
"org.gnome.SessionManager",
"/org/gnome/SessionManager",
"org.gnome.SessionManager",
NULL,
&error);
if (error)
{
g_warning ("Failed to get a session proxy: %s", error->message);
g_error_free (error);
return;
}
/* FIXME: should we reuse the D-Bus application id here ? */
app->priv->app_id = g_strdup (g_get_prgname ());
if (!app->priv->register_session)
return;
g_debug ("Registering client '%s' '%s'", app->priv->app_id, client_id);
res = g_dbus_proxy_call_sync (app->priv->sm_proxy,
"RegisterClient",
g_variant_new ("(ss)", app->priv->app_id, client_id),
G_DBUS_CALL_FLAGS_NONE,
G_MAXINT,
NULL,
&error);
if (error)
{
g_warning ("Failed to register client: %s", error->message);
g_error_free (error);
g_clear_object (&app->priv->sm_proxy);
return;
}
g_variant_get (res, "(o)", &app->priv->client_path);
g_variant_unref (res);
g_debug ("Registered client at '%s'", app->priv->client_path);
app->priv->client_proxy = g_dbus_proxy_new_sync (app->priv->session_bus, 0,
NULL,
"org.gnome.SessionManager",
app->priv->client_path,
"org.gnome.SessionManager.ClientPrivate",
NULL,
&error);
if (error)
{
g_warning ("Failed to get client proxy: %s", error->message);
g_error_free (error);
g_clear_object (&app->priv->sm_proxy);
g_free (app->priv->client_path);
app->priv->client_path = NULL;
return;
}
g_signal_connect (app->priv->client_proxy, "g-signal", G_CALLBACK (client_proxy_signal), app);
}
void
gtk_application_quit_response (GtkApplication *application,
gboolean will_quit,
const gchar *reason)
{
g_return_if_fail (GTK_IS_APPLICATION (application));
g_return_if_fail (!g_application_get_is_remote (G_APPLICATION (application)));
g_return_if_fail (application->priv->client_proxy != NULL);
g_debug ("Calling EndSessionResponse %d '%s'", will_quit, reason);
g_dbus_proxy_call (application->priv->client_proxy,
"EndSessionResponse",
g_variant_new ("(bs)", will_quit, reason ? reason : ""),
G_DBUS_CALL_FLAGS_NONE,
G_MAXINT,
NULL, NULL, NULL);
}
#endif

View File

@ -59,8 +59,12 @@ struct _GtkApplicationClass
void (*window_removed) (GtkApplication *application, void (*window_removed) (GtkApplication *application,
GtkWindow *window); GtkWindow *window);
void (*quit_requested) (GtkApplication *application);
void (*quit_cancelled) (GtkApplication *application);
void (*quit) (GtkApplication *application);
/*< private >*/ /*< private >*/
gpointer padding[14]; gpointer padding[11];
}; };
GType gtk_application_get_type (void) G_GNUC_CONST; GType gtk_application_get_type (void) G_GNUC_CONST;
@ -91,6 +95,10 @@ void gtk_application_remove_accelerator (GtkApplication *application
const gchar *action_name, const gchar *action_name,
GVariant *parameter); GVariant *parameter);
void gtk_application_quit_response (GtkApplication *application,
gboolean will_quit,
const gchar *reason);
G_END_DECLS G_END_DECLS
#endif /* __GTK_APPLICATION_H__ */ #endif /* __GTK_APPLICATION_H__ */