From 9ef26ef91ac244cc926bd0798a67fa865a8cc1c3 Mon Sep 17 00:00:00 2001 From: Yevgen Muntyan <17531749+muntyan@users.noreply.github.com> Date: Sat, 5 Mar 2011 15:19:56 -0800 Subject: [PATCH] Latest eggsmclient --- moo/eggsmclient/eggdesktopfile.c | 296 +++++++++++++++------ moo/eggsmclient/eggdesktopfile.h | 47 +++- moo/eggsmclient/eggsmclient-dbus.c | 294 +++++++++++++++++++++ moo/eggsmclient/eggsmclient-osx.c | 235 +++++++++++++++++ moo/eggsmclient/eggsmclient-private.h | 16 +- moo/eggsmclient/eggsmclient-win32.c | 163 +++++++++--- moo/eggsmclient/eggsmclient-xsmp.c | 357 ++++++++++++-------------- moo/eggsmclient/eggsmclient.c | 118 ++++++--- 8 files changed, 1171 insertions(+), 355 deletions(-) create mode 100644 moo/eggsmclient/eggsmclient-dbus.c create mode 100644 moo/eggsmclient/eggsmclient-osx.c diff --git a/moo/eggsmclient/eggdesktopfile.c b/moo/eggsmclient/eggdesktopfile.c index f687dbbb..c524ed35 100644 --- a/moo/eggsmclient/eggdesktopfile.c +++ b/moo/eggsmclient/eggdesktopfile.c @@ -27,15 +27,12 @@ #include "eggdesktopfile.h" -#include - #include #include #include -#include -#include #include +#include struct EggDesktopFile { GKeyFile *key_file; @@ -58,7 +55,6 @@ struct EggDesktopFile { EggDesktopFile * egg_desktop_file_new (const char *desktop_file_path, GError **error) { - EggDesktopFile *desktop_file; GKeyFile *key_file; key_file = g_key_file_new (); @@ -68,13 +64,8 @@ egg_desktop_file_new (const char *desktop_file_path, GError **error) return NULL; } - desktop_file = egg_desktop_file_new_from_key_file (key_file, - desktop_file_path, - error); - if (!desktop_file) - g_key_file_free (key_file); - - return desktop_file; + return egg_desktop_file_new_from_key_file (key_file, desktop_file_path, + error); } /** @@ -108,9 +99,42 @@ egg_desktop_file_new_from_data_dirs (const char *desktop_file_path, full_path, error); g_free (full_path); - if (!desktop_file) - g_key_file_free (key_file); + return desktop_file; +} +/** + * egg_desktop_file_new_from_dirs: + * @desktop_file_path: relative path to a Freedesktop-style Desktop file + * @search_dirs: NULL-terminated array of directories to search + * @error: error pointer + * + * Looks for @desktop_file_path in the paths returned from + * g_get_user_data_dir() and g_get_system_data_dirs(), and creates + * a new #EggDesktopFile from it. + * + * Return value: the new #EggDesktopFile, or %NULL on error. + **/ +EggDesktopFile * +egg_desktop_file_new_from_dirs (const char *desktop_file_path, + const char **search_dirs, + GError **error) +{ + EggDesktopFile *desktop_file; + GKeyFile *key_file; + char *full_path; + + key_file = g_key_file_new (); + if (!g_key_file_load_from_dirs (key_file, desktop_file_path, search_dirs, + &full_path, 0, error)) + { + g_key_file_free (key_file); + return NULL; + } + + desktop_file = egg_desktop_file_new_from_key_file (key_file, + full_path, + error); + g_free (full_path); return desktop_file; } @@ -121,8 +145,8 @@ egg_desktop_file_new_from_data_dirs (const char *desktop_file_path, * @error: error pointer * * Creates a new #EggDesktopFile for @key_file. Assumes ownership of - * @key_file on success (meaning it will be freed when the desktop_file - * is freed). + * @key_file (on success or failure); you should consider @key_file to + * be freed after calling this function. * * Return value: the new #EggDesktopFile, or %NULL on error. **/ @@ -139,6 +163,7 @@ egg_desktop_file_new_from_key_file (GKeyFile *key_file, g_set_error (error, EGG_DESKTOP_FILE_ERROR, EGG_DESKTOP_FILE_ERROR_INVALID, _("File is not a valid .desktop file")); + g_key_file_free (key_file); return NULL; } @@ -162,13 +187,14 @@ egg_desktop_file_new_from_key_file (GKeyFile *key_file, EGG_DESKTOP_FILE_ERROR_INVALID, _("Unrecognized desktop file Version '%s'"), version); g_free (version); + g_key_file_free (key_file); return NULL; } - else g_free (version); } desktop_file = g_new0 (EggDesktopFile, 1); + desktop_file->key_file = key_file; if (g_path_is_absolute (source)) desktop_file->source = g_filename_to_uri (source, NULL, NULL); @@ -204,6 +230,7 @@ egg_desktop_file_new_from_key_file (GKeyFile *key_file, if (!exec) { egg_desktop_file_free (desktop_file); + g_free (type); return NULL; } @@ -236,6 +263,7 @@ egg_desktop_file_new_from_key_file (GKeyFile *key_file, if (!url) { egg_desktop_file_free (desktop_file); + g_free (type); return NULL; } g_free (url); @@ -245,6 +273,8 @@ egg_desktop_file_new_from_key_file (GKeyFile *key_file, else desktop_file->type = EGG_DESKTOP_FILE_TYPE_UNRECOGNIZED; + g_free (type); + /* Check the Icon key */ desktop_file->icon = g_key_file_get_string (key_file, EGG_DESKTOP_FILE_GROUP, @@ -268,7 +298,6 @@ egg_desktop_file_new_from_key_file (GKeyFile *key_file, } } - desktop_file->key_file = key_file; return desktop_file; } @@ -288,22 +317,6 @@ egg_desktop_file_free (EggDesktopFile *desktop_file) g_free (desktop_file); } -/** - * egg_desktop_file_get_key_file: - * @desktop_file: an #EggDesktopFile - * - * Gets the #GKeyFile associated with @desktop_file. You must not free - * this value, and changes made to it will not be reflected by - * @desktop_file. - * - * Return value: the #GKeyFile associated with @desktop_file. - **/ -GKeyFile * -egg_desktop_file_get_key_file (EggDesktopFile *desktop_file) -{ - return desktop_file->key_file; -} - /** * egg_desktop_file_get_source: * @desktop_file: an #EggDesktopFile @@ -367,6 +380,91 @@ egg_desktop_file_get_icon (EggDesktopFile *desktop_file) return desktop_file->icon; } +gboolean +egg_desktop_file_has_key (EggDesktopFile *desktop_file, + const char *key, + GError **error) +{ + return g_key_file_has_key (desktop_file->key_file, + EGG_DESKTOP_FILE_GROUP, key, + error); +} + +char * +egg_desktop_file_get_string (EggDesktopFile *desktop_file, + const char *key, + GError **error) +{ + return g_key_file_get_string (desktop_file->key_file, + EGG_DESKTOP_FILE_GROUP, key, + error); +} + +char * +egg_desktop_file_get_locale_string (EggDesktopFile *desktop_file, + const char *key, + const char *locale, + GError **error) +{ + return g_key_file_get_locale_string (desktop_file->key_file, + EGG_DESKTOP_FILE_GROUP, key, locale, + error); +} + +gboolean +egg_desktop_file_get_boolean (EggDesktopFile *desktop_file, + const char *key, + GError **error) +{ + return g_key_file_get_boolean (desktop_file->key_file, + EGG_DESKTOP_FILE_GROUP, key, + error); +} + +double +egg_desktop_file_get_numeric (EggDesktopFile *desktop_file, + const char *key, + GError **error) +{ + return g_key_file_get_double (desktop_file->key_file, + EGG_DESKTOP_FILE_GROUP, key, + error); +} + +int +egg_desktop_file_get_integer (EggDesktopFile *desktop_file, + const char *key, + GError **error) +{ + return g_key_file_get_integer (desktop_file->key_file, + EGG_DESKTOP_FILE_GROUP, key, + error); +} + +char ** +egg_desktop_file_get_string_list (EggDesktopFile *desktop_file, + const char *key, + gsize *length, + GError **error) +{ + return g_key_file_get_string_list (desktop_file->key_file, + EGG_DESKTOP_FILE_GROUP, key, length, + error); +} + +char ** +egg_desktop_file_get_locale_string_list (EggDesktopFile *desktop_file, + const char *key, + const char *locale, + gsize *length, + GError **error) +{ + return g_key_file_get_locale_string_list (desktop_file->key_file, + EGG_DESKTOP_FILE_GROUP, key, + locale, length, + error); +} + /** * egg_desktop_file_can_launch: * @desktop_file: an #EggDesktopFile @@ -821,7 +919,7 @@ parse_link (EggDesktopFile *desktop_file, return TRUE; } -#ifdef HAVE_GDK_X11_DISPLAY_BROADCAST_STARTUP_MESSAGE +#if GTK_CHECK_VERSION (2, 12, 0) static char * start_startup_notification (GdkDisplay *display, EggDesktopFile *desktop_file, @@ -899,7 +997,7 @@ end_startup_notification (GdkDisplay *display, NULL); } -#define EGG_DESKTOP_FILE_SN_TIMEOUT_LENGTH (30 /* seconds */ * 1000) +#define EGG_DESKTOP_FILE_SN_TIMEOUT_LENGTH (30 /* seconds */) typedef struct { GdkDisplay *display; @@ -929,10 +1027,10 @@ set_startup_notification_timeout (GdkDisplay *display, sn_data->display = g_object_ref (display); sn_data->startup_id = g_strdup (startup_id); - g_timeout_add (EGG_DESKTOP_FILE_SN_TIMEOUT_LENGTH, - startup_notification_timeout, sn_data); + g_timeout_add_seconds (EGG_DESKTOP_FILE_SN_TIMEOUT_LENGTH, + startup_notification_timeout, sn_data); } -#endif /* HAVE_GDK_X11_DISPLAY_BROADCAST_STARTUP_MESSAGE */ +#endif /* GTK 2.12 */ static GPtrArray * array_putenv (GPtrArray *env, char *variable) @@ -941,12 +1039,20 @@ array_putenv (GPtrArray *env, char *variable) if (!env) { - char **environ_ptr = environ; + char **envp; env = g_ptr_array_new (); - for (i = 0; environ_ptr[i]; i++) - g_ptr_array_add (env, g_strdup (environ_ptr[i])); + envp = g_listenv (); + for (i = 0; envp[i]; i++) + { + const char *value; + + value = g_getenv (envp[i]); + g_ptr_array_add (env, g_strdup_printf ("%s=%s", envp[i], + value ? value : "")); + } + g_strfreev (envp); } keylen = strcspn (variable, "="); @@ -976,7 +1082,7 @@ egg_desktop_file_launchv (EggDesktopFile *desktop_file, GError **error) { EggDesktopFileLaunchOption option; - GSList *translated_documents = NULL, *docs; + GSList *translated_documents = NULL, *docs = NULL; char *command, **argv; int argc, i, screen_num; gboolean success, current_success; @@ -1111,7 +1217,7 @@ egg_desktop_file_launchv (EggDesktopFile *desktop_file, } g_free (command); -#ifdef HAVE_GDK_X11_DISPLAY_BROADCAST_STARTUP_MESSAGE +#if GTK_CHECK_VERSION (2, 12, 0) startup_id = start_startup_notification (display, desktop_file, argv[0], screen_num, workspace, launch_time); @@ -1124,7 +1230,10 @@ egg_desktop_file_launchv (EggDesktopFile *desktop_file, } #else startup_id = NULL; -#endif /* HAVE_GDK_X11_DISPLAY_BROADCAST_STARTUP_MESSAGE */ +#endif /* GTK 2.12 */ + + if (env != NULL) + g_ptr_array_add (env, NULL); current_success = g_spawn_async_with_pipes (directory, @@ -1139,7 +1248,7 @@ egg_desktop_file_launchv (EggDesktopFile *desktop_file, if (startup_id) { -#ifdef HAVE_GDK_X11_DISPLAY_BROADCAST_STARTUP_MESSAGE +#if GTK_CHECK_VERSION (2, 12, 0) if (current_success) { set_startup_notification_timeout (display, startup_id); @@ -1150,7 +1259,7 @@ egg_desktop_file_launchv (EggDesktopFile *desktop_file, g_free (startup_id); } else -#endif /* HAVE_GDK_X11_DISPLAY_BROADCAST_STARTUP_MESSAGE */ +#endif /* GTK 2.12 */ g_free (startup_id); } else if (ret_startup_id) @@ -1175,8 +1284,8 @@ egg_desktop_file_launchv (EggDesktopFile *desktop_file, out: if (env) { - g_strfreev ((char **)env->pdata); - g_ptr_array_free (env, FALSE); + g_ptr_array_foreach (env, (GFunc)g_free, NULL); + g_ptr_array_free (env, TRUE); } free_document_list (translated_documents); @@ -1287,6 +1396,8 @@ egg_desktop_file_launch (EggDesktopFile *desktop_file, free_document_list (documents); break; + case EGG_DESKTOP_FILE_TYPE_UNRECOGNIZED: + case EGG_DESKTOP_FILE_TYPE_DIRECTORY: default: g_set_error (error, EGG_DESKTOP_FILE_ERROR, EGG_DESKTOP_FILE_ERROR_NOT_LAUNCHABLE, @@ -1309,23 +1420,9 @@ egg_desktop_file_error_quark (void) G_LOCK_DEFINE_STATIC (egg_desktop_file); static EggDesktopFile *egg_desktop_file; -/** - * egg_set_desktop_file: - * @desktop_file_path: path to the application's desktop file - * - * Creates an #EggDesktopFile for the application from the data at - * @desktop_file_path. This will also call g_set_application_name() - * with the localized application name from the desktop file, and - * gtk_window_set_default_icon_name() or - * gtk_window_set_default_icon_from_file() with the application's - * icon. Other code may use additional information from the desktop - * file. - * - * Note that for thread safety reasons, this function can only - * be called once. - **/ -void -egg_set_desktop_file (const char *desktop_file_path) +static void +egg_set_desktop_file_internal (const char *desktop_file_path, + gboolean set_defaults) { GError *error = NULL; @@ -1341,20 +1438,67 @@ egg_set_desktop_file (const char *desktop_file_path) g_error_free (error); } - /* Set localized application name and default window icon */ - if (egg_desktop_file->name) - g_set_application_name (egg_desktop_file->name); - if (egg_desktop_file->icon) - { - if (g_path_is_absolute (egg_desktop_file->icon)) - gtk_window_set_default_icon_from_file (egg_desktop_file->icon, NULL); - else - gtk_window_set_default_icon_name (egg_desktop_file->icon); - } + if (set_defaults && egg_desktop_file != NULL) { + /* Set localized application name and default window icon */ + if (egg_desktop_file->name) + g_set_application_name (egg_desktop_file->name); + if (egg_desktop_file->icon) + { + if (g_path_is_absolute (egg_desktop_file->icon)) + gtk_window_set_default_icon_from_file (egg_desktop_file->icon, NULL); + else + gtk_window_set_default_icon_name (egg_desktop_file->icon); + } + } G_UNLOCK (egg_desktop_file); } +/** + * egg_set_desktop_file: + * @desktop_file_path: path to the application's desktop file + * + * Creates an #EggDesktopFile for the application from the data at + * @desktop_file_path. This will also call g_set_application_name() + * with the localized application name from the desktop file, and + * gtk_window_set_default_icon_name() or + * gtk_window_set_default_icon_from_file() with the application's + * icon. Other code may use additional information from the desktop + * file. + * See egg_set_desktop_file_without_defaults() for a variant of this + * function that does not set the application name and default window + * icon. + * + * Note that for thread safety reasons, this function can only + * be called once, and is mutually exclusive with calling + * egg_set_desktop_file_without_defaults(). + **/ +void +egg_set_desktop_file (const char *desktop_file_path) +{ + egg_set_desktop_file_internal (desktop_file_path, TRUE); +} + +/** + * egg_set_desktop_file_without_defaults: + * @desktop_file_path: path to the application's desktop file + * + * Creates an #EggDesktopFile for the application from the data at + * @desktop_file_path. + * See egg_set_desktop_file() for a variant of this function that + * sets the application name and default window icon from the information + * in the desktop file. + * + * Note that for thread safety reasons, this function can only + * be called once, and is mutually exclusive with calling + * egg_set_desktop_file(). + **/ +void +egg_set_desktop_file_without_defaults (const char *desktop_file_path) +{ + egg_set_desktop_file_internal (desktop_file_path, FALSE); +} + /** * egg_get_desktop_file: * diff --git a/moo/eggsmclient/eggdesktopfile.h b/moo/eggsmclient/eggdesktopfile.h index 711bfd43..16c5426c 100644 --- a/moo/eggsmclient/eggdesktopfile.h +++ b/moo/eggsmclient/eggdesktopfile.h @@ -37,16 +37,17 @@ typedef enum { EggDesktopFile *egg_desktop_file_new (const char *desktop_file_path, GError **error); -EggDesktopFile *egg_desktop_file_new_from_data_dirs (const char *desktop_file_path, - GError **error); -EggDesktopFile *egg_desktop_file_new_from_key_file (GKeyFile *desktop, +EggDesktopFile *egg_desktop_file_new_from_data_dirs (const char *desktop_file_path, + GError **error); +EggDesktopFile *egg_desktop_file_new_from_dirs (const char *desktop_file_path, + const char **search_dirs, + GError **error); +EggDesktopFile *egg_desktop_file_new_from_key_file (GKeyFile *key_file, const char *source, GError **error); void egg_desktop_file_free (EggDesktopFile *desktop_file); -GKeyFile *egg_desktop_file_get_key_file (EggDesktopFile *desktop_file); - const char *egg_desktop_file_get_source (EggDesktopFile *desktop_file); EggDesktopFileType egg_desktop_file_get_desktop_file_type (EggDesktopFile *desktop_file); @@ -109,6 +110,37 @@ typedef enum { #define EGG_DESKTOP_FILE_KEY_STARTUP_WM_CLASS "StartupWMClass" #define EGG_DESKTOP_FILE_KEY_URL "URL" +/* Accessors */ +gboolean egg_desktop_file_has_key (EggDesktopFile *desktop_file, + const char *key, + GError **error); +char *egg_desktop_file_get_string (EggDesktopFile *desktop_file, + const char *key, + GError **error) G_GNUC_MALLOC; +char *egg_desktop_file_get_locale_string (EggDesktopFile *desktop_file, + const char *key, + const char *locale, + GError **error) G_GNUC_MALLOC; +gboolean egg_desktop_file_get_boolean (EggDesktopFile *desktop_file, + const char *key, + GError **error); +double egg_desktop_file_get_numeric (EggDesktopFile *desktop_file, + const char *key, + GError **error); +int egg_desktop_file_get_integer (EggDesktopFile *desktop_file, + const char *key, + GError **error); +char **egg_desktop_file_get_string_list (EggDesktopFile *desktop_file, + const char *key, + gsize *length, + GError **error) G_GNUC_MALLOC; +char **egg_desktop_file_get_locale_string_list (EggDesktopFile *desktop_file, + const char *key, + const char *locale, + gsize *length, + GError **error) G_GNUC_MALLOC; + + /* Errors */ #define EGG_DESKTOP_FILE_ERROR egg_desktop_file_error_quark() @@ -121,8 +153,9 @@ typedef enum { } EggDesktopFileError; /* Global application desktop file */ -void egg_set_desktop_file (const char *desktop_file_path); -EggDesktopFile *egg_get_desktop_file (void); +void egg_set_desktop_file (const char *desktop_file_path); +void egg_set_desktop_file_without_defaults (const char *desktop_file_path); +EggDesktopFile *egg_get_desktop_file (void); G_END_DECLS diff --git a/moo/eggsmclient/eggsmclient-dbus.c b/moo/eggsmclient/eggsmclient-dbus.c new file mode 100644 index 00000000..21d9199b --- /dev/null +++ b/moo/eggsmclient/eggsmclient-dbus.c @@ -0,0 +1,294 @@ +/* + * Copyright (C) 2008 Red Hat, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#include "config.h" + +#include "eggsmclient.h" +#include "eggsmclient-private.h" + +#include "eggdesktopfile.h" + +#include +#include +#include +#include +#include + +#include +#include + +#define GSM_DBUS_NAME "org.gnome.SessionManager" +#define GSM_DBUS_PATH "/org/gnome/SessionManager" +#define GSM_DBUS_INTERFACE "org.gnome.SessionManager" + +#define GSM_CLIENT_PRIVATE_DBUS_INTERFACE "org.gnome.SessionManager.ClientPrivate" +#define GSM_CLIENT_DBUS_INTERFACE "org.gnome.SessionManager.Client" + +#define EGG_TYPE_SM_CLIENT_DBUS (egg_sm_client_dbus_get_type ()) +#define EGG_SM_CLIENT_DBUS(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), EGG_TYPE_SM_CLIENT_DBUS, EggSMClientDBus)) +#define EGG_SM_CLIENT_DBUS_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), EGG_TYPE_SM_CLIENT_DBUS, EggSMClientDBusClass)) +#define EGG_IS_SM_CLIENT_DBUS(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), EGG_TYPE_SM_CLIENT_DBUS)) +#define EGG_IS_SM_CLIENT_DBUS_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), EGG_TYPE_SM_CLIENT_DBUS)) +#define EGG_SM_CLIENT_DBUS_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), EGG_TYPE_SM_CLIENT_DBUS, EggSMClientDBusClass)) + +typedef struct _EggSMClientDBus EggSMClientDBus; +typedef struct _EggSMClientDBusClass EggSMClientDBusClass; + +struct _EggSMClientDBus +{ + EggSMClient parent; + + DBusGConnection *conn; + DBusGProxy *sm_proxy, *client_proxy; + char *client_path; +}; + +struct _EggSMClientDBusClass +{ + EggSMClientClass parent_class; + +}; + +static void sm_client_dbus_startup (EggSMClient *client, + const char *client_id); +static void sm_client_dbus_will_quit (EggSMClient *client, + gboolean will_quit); +static gboolean sm_client_dbus_end_session (EggSMClient *client, + EggSMClientEndStyle style, + gboolean request_confirmation); + +static void dbus_client_query_end_session (DBusGProxy *proxy, + guint flags, + gpointer smclient); +static void dbus_client_end_session (DBusGProxy *proxy, + guint flags, + gpointer smclient); +static void dbus_client_cancel_end_session (DBusGProxy *proxy, + gpointer smclient); +static void dbus_client_stop (DBusGProxy *proxy, + gpointer smclient); + +G_DEFINE_TYPE (EggSMClientDBus, egg_sm_client_dbus, EGG_TYPE_SM_CLIENT) + +static void +egg_sm_client_dbus_init (EggSMClientDBus *dbus) +{ + ; +} + +static void +egg_sm_client_dbus_class_init (EggSMClientDBusClass *klass) +{ + EggSMClientClass *sm_client_class = EGG_SM_CLIENT_CLASS (klass); + + sm_client_class->startup = sm_client_dbus_startup; + sm_client_class->will_quit = sm_client_dbus_will_quit; + sm_client_class->end_session = sm_client_dbus_end_session; +} + +EggSMClient * +egg_sm_client_dbus_new (void) +{ + DBusGConnection *conn; + DBusGProxy *proxy; + EggSMClientDBus *dbus; + + conn = dbus_g_bus_get (DBUS_BUS_SESSION, NULL); + if (!conn) + return NULL; + + proxy = dbus_g_proxy_new_for_name (conn, GSM_DBUS_NAME, GSM_DBUS_PATH, + GSM_DBUS_INTERFACE); + if (!proxy) + { + g_object_unref (conn); + return NULL; + } + + dbus = g_object_new (EGG_TYPE_SM_CLIENT_DBUS, NULL); + dbus->conn = conn; + dbus->sm_proxy = proxy; + return (EggSMClient *)dbus; +} + +static void +sm_client_dbus_startup (EggSMClient *client, + const char *client_id) +{ + EggSMClientDBus *dbus = (EggSMClientDBus *)client; + GError *error = NULL; + char *client_path, *ret_client_id; + DBusGProxy *client_public; + + if (!dbus_g_proxy_call (dbus->sm_proxy, "RegisterClient", &error, + G_TYPE_STRING, g_get_prgname (), + G_TYPE_STRING, client_id, + G_TYPE_INVALID, + DBUS_TYPE_G_OBJECT_PATH, &client_path, + G_TYPE_INVALID)) + { + g_warning ("Failed to register client: %s", error->message); + g_error_free (error); + return; + } + + g_debug ("Client registered with session manager: %s", client_path); + dbus->client_proxy = dbus_g_proxy_new_for_name (dbus->conn, GSM_DBUS_NAME, + client_path, + GSM_CLIENT_PRIVATE_DBUS_INTERFACE); + dbus_g_proxy_add_signal (dbus->client_proxy, "QueryEndSession", + G_TYPE_UINT, + G_TYPE_INVALID); + dbus_g_proxy_connect_signal (dbus->client_proxy, "QueryEndSession", + G_CALLBACK (dbus_client_query_end_session), + dbus, NULL); + dbus_g_proxy_add_signal (dbus->client_proxy, "EndSession", + G_TYPE_UINT, + G_TYPE_INVALID); + dbus_g_proxy_connect_signal (dbus->client_proxy, "EndSession", + G_CALLBACK (dbus_client_end_session), + dbus, NULL); + dbus_g_proxy_add_signal (dbus->client_proxy, "CancelEndSession", + G_TYPE_UINT, + G_TYPE_INVALID); + dbus_g_proxy_connect_signal (dbus->client_proxy, "CancelEndSession", + G_CALLBACK (dbus_client_cancel_end_session), + dbus, NULL); + dbus_g_proxy_add_signal (dbus->client_proxy, "Stop", + G_TYPE_INVALID); + dbus_g_proxy_connect_signal (dbus->client_proxy, "Stop", + G_CALLBACK (dbus_client_stop), + dbus, NULL); + + client_public = dbus_g_proxy_new_for_name (dbus->conn, GSM_DBUS_NAME, + client_path, + GSM_CLIENT_DBUS_INTERFACE); + if (dbus_g_proxy_call (client_public, "GetStartupId", &error, + G_TYPE_INVALID, + G_TYPE_STRING, &ret_client_id, + G_TYPE_INVALID)) + { + gdk_threads_enter (); + gdk_set_sm_client_id (ret_client_id); + gdk_threads_leave (); + + g_debug ("Got client ID \"%s\"", ret_client_id); + g_free (ret_client_id); + } + else + { + g_warning ("Could not get client id: %s", error->message); + g_error_free (error); + } + g_object_unref (client_public); +} + +static void +sm_client_dbus_shutdown (EggSMClient *client) +{ + EggSMClientDBus *dbus = EGG_SM_CLIENT_DBUS (client); + GError *error = NULL; + + if (!dbus_g_proxy_call (dbus->sm_proxy, "UnregisterClient", &error, + DBUS_TYPE_G_OBJECT_PATH, dbus->client_path, + G_TYPE_INVALID, + G_TYPE_INVALID)) + { + g_warning ("Failed to unregister client: %s", error->message); + g_error_free (error); + return; + } + + g_free (dbus->client_path); + dbus->client_path = NULL; + + g_object_unref (dbus->client_proxy); + dbus->client_proxy = NULL; +} + +static void +dbus_client_query_end_session (DBusGProxy *proxy, + guint flags, + gpointer smclient) +{ + egg_sm_client_quit_requested (smclient); +} + +static void +sm_client_dbus_will_quit (EggSMClient *client, + gboolean will_quit) +{ + EggSMClientDBus *dbus = (EggSMClientDBus *)client; + + g_return_if_fail (dbus->client_proxy != NULL); + + dbus_g_proxy_call (dbus->client_proxy, "EndSessionResponse", NULL, + G_TYPE_BOOLEAN, will_quit, + G_TYPE_STRING, NULL, + G_TYPE_INVALID, + G_TYPE_INVALID); +} + +static void +dbus_client_end_session (DBusGProxy *proxy, + guint flags, + gpointer smclient) +{ + sm_client_dbus_will_quit (smclient, TRUE); + sm_client_dbus_shutdown (smclient); + egg_sm_client_quit (smclient); +} + +static void +dbus_client_cancel_end_session (DBusGProxy *proxy, + gpointer smclient) +{ + egg_sm_client_quit_cancelled (smclient); +} + +static void +dbus_client_stop (DBusGProxy *proxy, + gpointer smclient) +{ + sm_client_dbus_shutdown (smclient); + egg_sm_client_quit (smclient); +} + +static gboolean +sm_client_dbus_end_session (EggSMClient *client, + EggSMClientEndStyle style, + gboolean request_confirmation) +{ + EggSMClientDBus *dbus = (EggSMClientDBus *)client; + + if (style == EGG_SM_CLIENT_END_SESSION_DEFAULT || + style == EGG_SM_CLIENT_LOGOUT) + { + return dbus_g_proxy_call (dbus->sm_proxy, "Logout", NULL, + G_TYPE_UINT, request_confirmation ? 0 : 1, + G_TYPE_INVALID, + G_TYPE_INVALID); + } + else + { + return dbus_g_proxy_call (dbus->sm_proxy, "Shutdown", NULL, + G_TYPE_INVALID, + G_TYPE_INVALID); + } +} diff --git a/moo/eggsmclient/eggsmclient-osx.c b/moo/eggsmclient/eggsmclient-osx.c new file mode 100644 index 00000000..7d3ff4b6 --- /dev/null +++ b/moo/eggsmclient/eggsmclient-osx.c @@ -0,0 +1,235 @@ +/* + * Copyright (C) 2007 Novell, Inc. + * Copyright (C) 2008 Red Hat, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +/* EggSMClientOSX + * + * For details on the OS X logout process, see: + * http://developer.apple.com/documentation/MacOSX/Conceptual/BPSystemStartup/Articles/BootProcess.html#//apple_ref/doc/uid/20002130-114618 + * + * EggSMClientOSX registers for the kAEQuitApplication AppleEvent; the + * handler we register (quit_requested()) will be invoked from inside + * the quartz event-handling code (specifically, from inside + * [NSApplication nextEventMatchingMask]) when an AppleEvent arrives. + * We use AESuspendTheCurrentEvent() and AEResumeTheCurrentEvent() to + * allow asynchronous / non-main-loop-reentering processing of the + * quit request. (These are part of the Carbon framework; it doesn't + * seem to be possible to handle AppleEvents asynchronously from + * Cocoa.) + */ + +#include "config.h" + +#include "eggsmclient-private.h" +#include +#include +#include + +#define EGG_TYPE_SM_CLIENT_OSX (egg_sm_client_osx_get_type ()) +#define EGG_SM_CLIENT_OSX(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), EGG_TYPE_SM_CLIENT_OSX, EggSMClientOSX)) +#define EGG_SM_CLIENT_OSX_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), EGG_TYPE_SM_CLIENT_OSX, EggSMClientOSXClass)) +#define EGG_IS_SM_CLIENT_OSX(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), EGG_TYPE_SM_CLIENT_OSX)) +#define EGG_IS_SM_CLIENT_OSX_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), EGG_TYPE_SM_CLIENT_OSX)) +#define EGG_SM_CLIENT_OSX_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), EGG_TYPE_SM_CLIENT_OSX, EggSMClientOSXClass)) + +typedef struct _EggSMClientOSX EggSMClientOSX; +typedef struct _EggSMClientOSXClass EggSMClientOSXClass; + +struct _EggSMClientOSX { + EggSMClient parent; + + AppleEvent quit_event, quit_reply; + gboolean quit_requested, quitting; +}; + +struct _EggSMClientOSXClass +{ + EggSMClientClass parent_class; + +}; + +static void sm_client_osx_startup (EggSMClient *client, + const char *client_id); +static void sm_client_osx_will_quit (EggSMClient *client, + gboolean will_quit); +static gboolean sm_client_osx_end_session (EggSMClient *client, + EggSMClientEndStyle style, + gboolean request_confirmation); + +static pascal OSErr quit_requested (const AppleEvent *, AppleEvent *, long); + +G_DEFINE_TYPE (EggSMClientOSX, egg_sm_client_osx, EGG_TYPE_SM_CLIENT) + +static void +egg_sm_client_osx_init (EggSMClientOSX *osx) +{ + ; +} + +static void +egg_sm_client_osx_class_init (EggSMClientOSXClass *klass) +{ + EggSMClientClass *sm_client_class = EGG_SM_CLIENT_CLASS (klass); + + sm_client_class->startup = sm_client_osx_startup; + sm_client_class->will_quit = sm_client_osx_will_quit; + sm_client_class->end_session = sm_client_osx_end_session; +} + +EggSMClient * +egg_sm_client_osx_new (void) +{ + return g_object_new (EGG_TYPE_SM_CLIENT_OSX, NULL); +} + +static void +sm_client_osx_startup (EggSMClient *client, + const char *client_id) +{ + AEInstallEventHandler (kCoreEventClass, kAEQuitApplication, + NewAEEventHandlerUPP (quit_requested), + (long)GPOINTER_TO_SIZE (client), false); +} + +static gboolean +idle_quit_requested (gpointer client) +{ + egg_sm_client_quit_requested (client); + return FALSE; +} + +static pascal OSErr +quit_requested (const AppleEvent *aevt, AppleEvent *reply, long refcon) +{ + EggSMClient *client = GSIZE_TO_POINTER ((gsize)refcon); + EggSMClientOSX *osx = GSIZE_TO_POINTER ((gsize)refcon); + + g_return_val_if_fail (!osx->quit_requested, userCanceledErr); + + /* FIXME AEInteractWithUser? */ + + osx->quit_requested = TRUE; + AEDuplicateDesc (aevt, &osx->quit_event); + AEDuplicateDesc (reply, &osx->quit_reply); + AESuspendTheCurrentEvent (aevt); + + /* Don't emit the "quit_requested" signal immediately, since we're + * called from a weird point in the guts of gdkeventloop-quartz.c + */ + g_idle_add (idle_quit_requested, client); + return noErr; +} + +static pascal OSErr +quit_requested_resumed (const AppleEvent *aevt, AppleEvent *reply, long refcon) +{ + EggSMClientOSX *osx = GSIZE_TO_POINTER ((gsize)refcon); + + osx->quit_requested = FALSE; + return osx->quitting ? noErr : userCanceledErr; +} + +static gboolean +idle_will_quit (gpointer client) +{ + EggSMClientOSX *osx = (EggSMClientOSX *)client; + + /* Resume the event with a new handler that will return a value to + * the system. + */ + AEResumeTheCurrentEvent (&osx->quit_event, &osx->quit_reply, + NewAEEventHandlerUPP (quit_requested_resumed), + (long)GPOINTER_TO_SIZE (client)); + AEDisposeDesc (&osx->quit_event); + AEDisposeDesc (&osx->quit_reply); + + if (osx->quitting) + egg_sm_client_quit (client); + return FALSE; +} + +static void +sm_client_osx_will_quit (EggSMClient *client, + gboolean will_quit) +{ + EggSMClientOSX *osx = (EggSMClientOSX *)client; + + g_return_if_fail (osx->quit_requested); + + osx->quitting = will_quit; + + /* Finish in an idle handler since the caller might have called + * egg_sm_client_will_quit() from inside the "quit_requested" signal + * handler, but may not expect the "quit" signal to arrive during + * the _will_quit() call. + */ + g_idle_add (idle_will_quit, client); +} + +static gboolean +sm_client_osx_end_session (EggSMClient *client, + EggSMClientEndStyle style, + gboolean request_confirmation) +{ + static const ProcessSerialNumber loginwindow_psn = { 0, kSystemProcess }; + AppleEvent event = { typeNull, NULL }, reply = { typeNull, NULL }; + AEAddressDesc target; + AEEventID id; + OSErr err; + + switch (style) + { + case EGG_SM_CLIENT_END_SESSION_DEFAULT: + case EGG_SM_CLIENT_LOGOUT: + id = request_confirmation ? kAELogOut : kAEReallyLogOut; + break; + case EGG_SM_CLIENT_REBOOT: + id = request_confirmation ? kAEShowRestartDialog : kAERestart; + break; + case EGG_SM_CLIENT_SHUTDOWN: + id = request_confirmation ? kAEShowShutdownDialog : kAEShutDown; + break; + } + + err = AECreateDesc (typeProcessSerialNumber, &loginwindow_psn, + sizeof (loginwindow_psn), &target); + if (err != noErr) + { + g_warning ("Could not create descriptor for loginwindow: %d", err); + return FALSE; + } + + err = AECreateAppleEvent (kCoreEventClass, id, &target, + kAutoGenerateReturnID, kAnyTransactionID, + &event); + AEDisposeDesc (&target); + if (err != noErr) + { + g_warning ("Could not create logout AppleEvent: %d", err); + return FALSE; + } + + err = AESend (&event, &reply, kAENoReply, kAENormalPriority, + kAEDefaultTimeout, NULL, NULL); + AEDisposeDesc (&event); + if (err == noErr) + AEDisposeDesc (&reply); + + return err == noErr; +} diff --git a/moo/eggsmclient/eggsmclient-private.h b/moo/eggsmclient/eggsmclient-private.h index d3535e55..57f23071 100644 --- a/moo/eggsmclient/eggsmclient-private.h +++ b/moo/eggsmclient/eggsmclient-private.h @@ -20,13 +20,18 @@ #ifndef __EGG_SM_CLIENT_PRIVATE_H__ #define __EGG_SM_CLIENT_PRIVATE_H__ -#ifdef __GNUC__ -#pragma GCC diagnostic ignored "-Wunused-parameter" +#include + +#if !GTK_CHECK_VERSION(2,91,7) && !GTK_CHECK_VERSION(3,0,0) +/* GTK+ 3 includes this automatically */ +#include #endif -#include #include "eggsmclient.h" +#undef G_LOG_DOMAIN +#define G_LOG_DOMAIN "EggSMClient" + G_BEGIN_DECLS GKeyFile *egg_sm_client_save_state (EggSMClient *client); @@ -46,12 +51,9 @@ EggSMClient *egg_sm_client_dbus_new (void); #elif defined (GDK_WINDOWING_WIN32) GType egg_sm_client_win32_get_type (void); EggSMClient *egg_sm_client_win32_new (void); -#elif defined (GDK_WINDOWING_QUARTZ) && 0 +#elif defined (GDK_WINDOWING_QUARTZ) GType egg_sm_client_osx_get_type (void); EggSMClient *egg_sm_client_osx_new (void); -#else -GType egg_sm_client_dummy_get_type (void); -EggSMClient *egg_sm_client_dummy_new (void); #endif G_END_DECLS diff --git a/moo/eggsmclient/eggsmclient-win32.c b/moo/eggsmclient/eggsmclient-win32.c index a5731de0..d3d8d9e5 100644 --- a/moo/eggsmclient/eggsmclient-win32.c +++ b/moo/eggsmclient/eggsmclient-win32.c @@ -17,12 +17,42 @@ * Boston, MA 02111-1307, USA. */ +/* EggSMClientWin32 + * + * For details on the Windows XP logout process, see: + * http://msdn.microsoft.com/en-us/library/aa376876.aspx. + * + * Vista adds some new APIs which EggSMClient does not make use of; see + * http://msdn.microsoft.com/en-us/library/ms700677(VS.85).aspx + * + * When shutting down, Windows sends every top-level window a + * WM_QUERYENDSESSION event, which the application must respond to + * synchronously, saying whether or not it will quit. To avoid main + * loop re-entrancy problems (and to avoid having to muck about too + * much with the guts of the gdk-win32 main loop), we watch for this + * event in a separate thread, which then signals the main thread and + * waits for the main thread to handle the event. Since we don't want + * to require g_thread_init() to be called, we do this all using + * Windows-specific thread methods. + * + * After the application handles the WM_QUERYENDSESSION event, + * Windows then sends it a WM_ENDSESSION event with a TRUE or FALSE + * parameter indicating whether the session is or is not actually + * going to end now. We handle this from the other thread as well. + * + * As mentioned above, Vista introduces several additional new APIs + * that don't fit into the (current) EggSMClient API. Windows also has + * an entirely separate shutdown-notification scheme for non-GUI apps, + * which we also don't handle here. + */ + #include "config.h" #include "eggsmclient-private.h" #include #include +#include #define EGG_TYPE_SM_CLIENT_WIN32 (egg_sm_client_win32_get_type ()) #define EGG_SM_CLIENT_WIN32(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), EGG_TYPE_SM_CLIENT_WIN32, EggSMClientWin32)) @@ -37,7 +67,10 @@ typedef struct _EggSMClientWin32Class EggSMClientWin32Class; struct _EggSMClientWin32 { EggSMClient parent; - GAsyncQueue *msg_queue; + HANDLE message_event, response_event; + + volatile GSourceFunc event; + volatile gboolean will_quit; }; struct _EggSMClientWin32Class @@ -54,13 +87,17 @@ static gboolean sm_client_win32_end_session (EggSMClient *client, EggSMClientEndStyle style, gboolean request_confirmation); -static gpointer sm_client_thread (gpointer data); +static GSource *g_win32_handle_source_add (HANDLE handle, GSourceFunc callback, + gpointer user_data); +static gboolean got_message (gpointer user_data); +static void sm_client_thread (gpointer data); G_DEFINE_TYPE (EggSMClientWin32, egg_sm_client_win32, EGG_TYPE_SM_CLIENT) static void -egg_sm_client_win32_init (EggSMClientWin32 *win32) +egg_sm_client_win32_init (G_GNUC_UNUSED EggSMClientWin32 *win32) { + ; } static void @@ -81,13 +118,14 @@ egg_sm_client_win32_new (void) static void sm_client_win32_startup (EggSMClient *client, - const char *client_id) + G_GNUC_UNUSED const char *client_id) { EggSMClientWin32 *win32 = (EggSMClientWin32 *)client; - /* spawn another thread to listen for logout signals on */ - win32->msg_queue = g_async_queue_new (); - g_thread_create (sm_client_thread, client, FALSE, NULL); + win32->message_event = CreateEvent (NULL, FALSE, FALSE, NULL); + win32->response_event = CreateEvent (NULL, FALSE, FALSE, NULL); + g_win32_handle_source_add (win32->message_event, got_message, win32); + _beginthread (sm_client_thread, 0, client); } static void @@ -96,14 +134,14 @@ sm_client_win32_will_quit (EggSMClient *client, { EggSMClientWin32 *win32 = (EggSMClientWin32 *)client; - /* Can't push NULL onto a GAsyncQueue, so we add 1 to the value... */ - g_async_queue_push (win32->msg_queue, GINT_TO_POINTER (will_quit + 1)); + win32->will_quit = will_quit; + SetEvent (win32->response_event); } static gboolean -sm_client_win32_end_session (EggSMClient *client, +sm_client_win32_end_session (G_GNUC_UNUSED EggSMClient *client, EggSMClientEndStyle style, - gboolean request_confirmation) + G_GNUC_UNUSED gboolean request_confirmation) { UINT uFlags = EWX_LOGOFF; @@ -156,7 +194,7 @@ emit_quit (gpointer smclient) egg_sm_client_quit (smclient); gdk_threads_leave (); - g_async_queue_push (win32->msg_queue, GINT_TO_POINTER (1)); + SetEvent (win32->response_event); return FALSE; } @@ -169,25 +207,80 @@ emit_quit_cancelled (gpointer smclient) egg_sm_client_quit_cancelled (smclient); gdk_threads_leave (); - g_async_queue_push (win32->msg_queue, GINT_TO_POINTER (1)); + SetEvent (win32->response_event); return FALSE; } +static gboolean +got_message (gpointer smclient) +{ + EggSMClientWin32 *win32 = smclient; + + win32->event (win32); + return TRUE; +} + +/* Windows HANDLE GSource */ + +typedef struct { + GSource source; + GPollFD pollfd; +} GWin32HandleSource; + +static gboolean +g_win32_handle_source_prepare (G_GNUC_UNUSED GSource *source, gint *timeout) +{ + *timeout = -1; + return FALSE; +} + +static gboolean +g_win32_handle_source_check (GSource *source) +{ + GWin32HandleSource *hsource = (GWin32HandleSource *)source; + + return hsource->pollfd.revents; +} + +static gboolean +g_win32_handle_source_dispatch (G_GNUC_UNUSED GSource *source, GSourceFunc callback, gpointer user_data) +{ + return (*callback) (user_data); +} + +static void +g_win32_handle_source_finalize (G_GNUC_UNUSED GSource *source) +{ + ; +} + +GSourceFuncs g_win32_handle_source_funcs = { + g_win32_handle_source_prepare, + g_win32_handle_source_check, + g_win32_handle_source_dispatch, + g_win32_handle_source_finalize +}; + +static GSource * +g_win32_handle_source_add (HANDLE handle, GSourceFunc callback, gpointer user_data) +{ + GWin32HandleSource *hsource; + GSource *source; + + source = g_source_new (&g_win32_handle_source_funcs, sizeof (GWin32HandleSource)); + hsource = (GWin32HandleSource *)source; + hsource->pollfd.fd = (int)handle; + hsource->pollfd.events = G_IO_IN; + hsource->pollfd.revents = 0; + g_source_add_poll (source, &hsource->pollfd); + + g_source_set_callback (source, callback, user_data, NULL); + g_source_attach (source, NULL); + return source; +} /* logout-listener thread */ -static int -async_emit (EggSMClientWin32 *win32, GSourceFunc emitter) -{ - /* ensure message queue is empty */ - while (g_async_queue_try_pop (win32->msg_queue)) - ; - - /* Emit signal in the main thread and wait for a response */ - g_idle_add (emitter, win32); - return GPOINTER_TO_INT (g_async_queue_pop (win32->msg_queue)) - 1; -} - static LRESULT CALLBACK sm_client_win32_window_procedure (HWND hwnd, UINT message, @@ -200,19 +293,27 @@ sm_client_win32_window_procedure (HWND hwnd, switch (message) { case WM_QUERYENDSESSION: - return async_emit (win32, emit_quit_requested); + win32->event = emit_quit_requested; + SetEvent (win32->message_event); + + WaitForSingleObject (win32->response_event, INFINITE); + return win32->will_quit; case WM_ENDSESSION: if (wParam) { /* The session is ending */ - async_emit (win32, emit_quit); + win32->event = emit_quit; } else { /* Nope, the session *isn't* ending */ - async_emit (win32, emit_quit_cancelled); + win32->event = emit_quit_cancelled; } + + SetEvent (win32->message_event); + WaitForSingleObject (win32->response_event, INFINITE); + return 0; default: @@ -220,11 +321,11 @@ sm_client_win32_window_procedure (HWND hwnd, } } -static gpointer +static void sm_client_thread (gpointer smclient) { HINSTANCE instance; - WNDCLASSEXW wcl; + WNDCLASSEXW wcl; ATOM klass; HWND window; MSG msg; @@ -247,6 +348,4 @@ sm_client_thread (gpointer smclient) /* main loop */ while (GetMessage (&msg, NULL, 0, 0)) DispatchMessage (&msg); - - return NULL; } diff --git a/moo/eggsmclient/eggsmclient-xsmp.c b/moo/eggsmclient/eggsmclient-xsmp.c index 3a4acc4f..b3b075d7 100644 --- a/moo/eggsmclient/eggsmclient-xsmp.c +++ b/moo/eggsmclient/eggsmclient-xsmp.c @@ -33,10 +33,10 @@ #include #include #include -#include #include #include +#include #define EGG_TYPE_SM_CLIENT_XSMP (egg_sm_client_xsmp_get_type ()) #define EGG_SM_CLIENT_XSMP(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), EGG_TYPE_SM_CLIENT_XSMP, EggSMClientXSMP)) @@ -57,7 +57,6 @@ typedef struct _EggSMClientXSMPClass EggSMClientXSMPClass; */ typedef enum { - XSMP_STATE_START, XSMP_STATE_IDLE, XSMP_STATE_SAVE_YOURSELF, XSMP_STATE_INTERACT_REQUEST, @@ -68,7 +67,6 @@ typedef enum } EggSMClientXSMPState; static const char *state_names[] = { - "start", "idle", "save-yourself", "interact-request", @@ -102,6 +100,7 @@ struct _EggSMClientXSMP guint shutting_down : 1; /* Todo list */ + guint waiting_to_set_initial_properties : 1; guint waiting_to_emit_quit : 1; guint waiting_to_emit_quit_cancelled : 1; guint waiting_to_save_myself : 1; @@ -180,22 +179,11 @@ egg_sm_client_xsmp_init (EggSMClientXSMP *xsmp) xsmp->restart_style = SmRestartIfRunning; } -static void -sm_client_xsmp_finalize (GObject *object) -{ - EggSMClientXSMP *xsmp = EGG_SM_CLIENT_XSMP (object); - g_free (xsmp->client_id); - g_strfreev (xsmp->restart_command); - G_OBJECT_CLASS (egg_sm_client_xsmp_parent_class)->finalize (object); -} - static void egg_sm_client_xsmp_class_init (EggSMClientXSMPClass *klass) { EggSMClientClass *sm_client_class = EGG_SM_CLIENT_CLASS (klass); - G_OBJECT_CLASS (sm_client_class)->finalize = sm_client_xsmp_finalize; - sm_client_class->startup = sm_client_xsmp_startup; sm_client_class->set_restart_command = sm_client_xsmp_set_restart_command; sm_client_class->will_quit = sm_client_xsmp_will_quit; @@ -212,74 +200,19 @@ egg_sm_client_xsmp_new (void) } static gboolean -sm_client_xsmp_connect (gpointer user_data) +sm_client_xsmp_set_initial_properties (gpointer user_data) { EggSMClientXSMP *xsmp = user_data; - SmcCallbacks callbacks; - char *client_id; - char error_string_ret[256]; - char pid_str[64]; EggDesktopFile *desktop_file; GPtrArray *clone, *restart; + char pid_str[64]; - g_source_remove (xsmp->idle); - xsmp->idle = 0; - - ice_init (); - SmcSetErrorHandler (smc_error_handler); - - callbacks.save_yourself.callback = xsmp_save_yourself; - callbacks.die.callback = xsmp_die; - callbacks.save_complete.callback = xsmp_save_complete; - callbacks.shutdown_cancelled.callback = xsmp_shutdown_cancelled; - - callbacks.save_yourself.client_data = xsmp; - callbacks.die.client_data = xsmp; - callbacks.save_complete.client_data = xsmp; - callbacks.shutdown_cancelled.client_data = xsmp; - - client_id = NULL; - error_string_ret[0] = '\0'; - xsmp->connection = - SmcOpenConnection (NULL, xsmp, SmProtoMajor, SmProtoMinor, - SmcSaveYourselfProcMask | SmcDieProcMask | - SmcSaveCompleteProcMask | - SmcShutdownCancelledProcMask, - &callbacks, - xsmp->client_id, &client_id, - sizeof (error_string_ret), error_string_ret); - - if (!xsmp->connection) + if (xsmp->idle) { - g_warning ("Failed to connect to the session manager: %s\n", - error_string_ret[0] ? - error_string_ret : "no error message given"); - xsmp->state = XSMP_STATE_CONNECTION_CLOSED; - return FALSE; - } - - /* We expect a pointless initial SaveYourself if either (a) we - * didn't have an initial client ID, or (b) we DID have an initial - * client ID, but the server rejected it and gave us a new one. - */ - if (!xsmp->client_id || - (client_id && strcmp (xsmp->client_id, client_id) != 0)) - xsmp->expecting_initial_save_yourself = TRUE; - - if (client_id) - { - g_free (xsmp->client_id); - xsmp->client_id = g_strdup (client_id); - free (client_id); - - gdk_threads_enter (); - gdk_set_sm_client_id (xsmp->client_id); - gdk_threads_leave (); - -#if 0 - g_debug ("Got client ID \"%s\"", xsmp->client_id); -#endif + g_source_remove (xsmp->idle); + xsmp->idle = 0; } + xsmp->waiting_to_set_initial_properties = FALSE; if (egg_sm_client_get_mode () == EGG_SM_CLIENT_MODE_NO_RESTART) xsmp->restart_style = SmRestartNever; @@ -288,19 +221,14 @@ sm_client_xsmp_connect (gpointer user_data) desktop_file = egg_get_desktop_file (); if (desktop_file) { - GKeyFile *key_file; GError *err = NULL; char *cmdline, **argv; int argc; - key_file = egg_desktop_file_get_key_file (desktop_file); - if (xsmp->restart_style == SmRestartIfRunning) { - if (g_key_file_has_key (key_file, EGG_DESKTOP_FILE_GROUP, - "X-GNOME-AutoRestart", NULL) && - g_key_file_get_boolean (key_file, EGG_DESKTOP_FILE_GROUP, - "X-GNOME-AutoRestart", NULL)) + if (egg_desktop_file_get_boolean (desktop_file, + "X-GNOME-AutoRestart", NULL)) xsmp->restart_style = SmRestartImmediately; } @@ -319,6 +247,7 @@ sm_client_xsmp_connect (gpointer user_data) err->message); g_error_free (err); } + g_free (cmdline); } } @@ -328,9 +257,7 @@ sm_client_xsmp_connect (gpointer user_data) clone = generate_command (xsmp->restart_command, NULL, NULL); restart = generate_command (xsmp->restart_command, xsmp->client_id, NULL); -#if 0 g_debug ("Setting initial properties"); -#endif /* Program, CloneCommand, RestartCommand, and UserID are required. * ProcessID isn't required, but the SM may be able to do something @@ -355,7 +282,7 @@ sm_client_xsmp_connect (gpointer user_data) NULL); } - xsmp->state = XSMP_STATE_IDLE; + update_pending_events (xsmp); return FALSE; } @@ -371,9 +298,7 @@ sm_client_xsmp_disconnect (EggSMClientXSMP *xsmp) if (!xsmp->connection) return; -#if 0 g_debug ("Disconnecting"); -#endif connection = xsmp->connection; xsmp->connection = NULL; @@ -389,19 +314,78 @@ sm_client_xsmp_startup (EggSMClient *client, const char *client_id) { EggSMClientXSMP *xsmp = (EggSMClientXSMP *)client; + SmcCallbacks callbacks; + char *ret_client_id; + char error_string_ret[256]; - xsmp->state = XSMP_STATE_START; - if (xsmp->client_id) - g_free (xsmp->client_id); xsmp->client_id = g_strdup (client_id); - /* Don't connect to the session manager until we reach the main - * loop, since the session manager may assume we're fully up and - * running once we connect. (This also gives the application a - * chance to call egg_set_desktop_file() before we set the initial - * properties.) + ice_init (); + SmcSetErrorHandler (smc_error_handler); + + callbacks.save_yourself.callback = xsmp_save_yourself; + callbacks.die.callback = xsmp_die; + callbacks.save_complete.callback = xsmp_save_complete; + callbacks.shutdown_cancelled.callback = xsmp_shutdown_cancelled; + + callbacks.save_yourself.client_data = xsmp; + callbacks.die.client_data = xsmp; + callbacks.save_complete.client_data = xsmp; + callbacks.shutdown_cancelled.client_data = xsmp; + + client_id = NULL; + error_string_ret[0] = '\0'; + xsmp->connection = + SmcOpenConnection (NULL, xsmp, SmProtoMajor, SmProtoMinor, + SmcSaveYourselfProcMask | SmcDieProcMask | + SmcSaveCompleteProcMask | + SmcShutdownCancelledProcMask, + &callbacks, + xsmp->client_id, &ret_client_id, + sizeof (error_string_ret), error_string_ret); + + if (!xsmp->connection) + { + g_warning ("Failed to connect to the session manager: %s\n", + error_string_ret[0] ? + error_string_ret : "no error message given"); + xsmp->state = XSMP_STATE_CONNECTION_CLOSED; + return; + } + + /* We expect a pointless initial SaveYourself if either (a) we + * didn't have an initial client ID, or (b) we DID have an initial + * client ID, but the server rejected it and gave us a new one. */ - xsmp->idle = g_idle_add (sm_client_xsmp_connect, client); + if (!xsmp->client_id || + (ret_client_id && strcmp (xsmp->client_id, ret_client_id) != 0)) + xsmp->expecting_initial_save_yourself = TRUE; + + if (ret_client_id) + { + g_free (xsmp->client_id); + xsmp->client_id = g_strdup (ret_client_id); + free (ret_client_id); + +#if !GTK_CHECK_VERSION(2,91,7) && !GTK_CHECK_VERSION(3,0,0) + gdk_set_sm_client_id (xsmp->client_id); +#else + gdk_x11_set_sm_client_id (xsmp->client_id); +#endif + + g_debug ("Got client ID \"%s\"", xsmp->client_id); + } + + xsmp->state = XSMP_STATE_IDLE; + + /* Do not set the initial properties until we reach the main loop, + * so that the application has a chance to call + * egg_set_desktop_file(). (This may also help the session manager + * have a better idea of when the application is fully up and + * running.) + */ + xsmp->waiting_to_set_initial_properties = TRUE; + xsmp->idle = g_idle_add (sm_client_xsmp_set_initial_properties, client); } static void @@ -449,24 +433,20 @@ sm_client_xsmp_will_quit (EggSMClient *client, g_return_if_fail (xsmp->state == XSMP_STATE_INTERACT); -#if 0 g_debug ("Sending InteractDone(%s)", will_quit ? "False" : "True"); -#endif SmcInteractDone (xsmp->connection, !will_quit); if (will_quit && xsmp->need_save_state) save_state (xsmp); -#if 0 g_debug ("Sending SaveYourselfDone(%s)", will_quit ? "True" : "False"); -#endif SmcSaveYourselfDone (xsmp->connection, will_quit); xsmp->state = XSMP_STATE_SAVE_YOURSELF_DONE; } static gboolean sm_client_xsmp_end_session (EggSMClient *client, - EggSMClientEndStyle style, + G_GNUC_UNUSED EggSMClientEndStyle style, gboolean request_confirmation) { EggSMClientXSMP *xsmp = (EggSMClientXSMP *)client; @@ -495,11 +475,6 @@ sm_client_xsmp_end_session (EggSMClient *client, switch (xsmp->state) { - case XSMP_STATE_START: - /* Force the connection to complete (or fail) now. */ - sm_client_xsmp_connect (xsmp); - break; - case XSMP_STATE_CONNECTION_CLOSED: return FALSE; @@ -520,6 +495,9 @@ sm_client_xsmp_end_session (EggSMClient *client, return TRUE; case XSMP_STATE_IDLE: + if (xsmp->waiting_to_set_initial_properties) + sm_client_xsmp_set_initial_properties (xsmp); + if (!xsmp->expecting_initial_save_yourself) break; /* else fall through */ @@ -545,9 +523,7 @@ sm_client_xsmp_end_session (EggSMClient *client, else save_type = SmSaveGlobal; -#if 0 g_debug ("Sending SaveYourselfRequest(SmSaveGlobal, Shutdown, SmInteractStyleAny, %sFast)", request_confirmation ? "!" : ""); -#endif SmcRequestSaveYourself (xsmp->connection, save_type, True, /* shutdown */ @@ -636,7 +612,7 @@ fix_broken_state (EggSMClientXSMP *xsmp, const char *message, /* SM callbacks */ static void -xsmp_save_yourself (SmcConn smc_conn, +xsmp_save_yourself (G_GNUC_UNUSED SmcConn smc_conn, SmPointer client_data, int save_type, Bool shutdown, @@ -646,7 +622,6 @@ xsmp_save_yourself (SmcConn smc_conn, EggSMClientXSMP *xsmp = client_data; gboolean wants_quit_requested; -#if 0 g_debug ("Received SaveYourself(%s, %s, %s, %s) in state %s", save_type == SmSaveLocal ? "SmSaveLocal" : save_type == SmSaveGlobal ? "SmSaveGlobal" : "SmSaveBoth", @@ -655,7 +630,6 @@ xsmp_save_yourself (SmcConn smc_conn, interact_style == SmInteractStyleErrors ? "SmInteractStyleErrors" : "SmInteractStyleNone", fast ? "Fast" : "!Fast", EGG_SM_CLIENT_XSMP_STATE (xsmp)); -#endif if (xsmp->state != XSMP_STATE_IDLE && xsmp->state != XSMP_STATE_SHUTDOWN_CANCELLED) @@ -664,6 +638,9 @@ xsmp_save_yourself (SmcConn smc_conn, return; } + if (xsmp->waiting_to_set_initial_properties) + sm_client_xsmp_set_initial_properties (xsmp); + /* If this is the initial SaveYourself, ignore it; we've already set * properties and there's no reason to actually save state too. */ @@ -675,9 +652,7 @@ xsmp_save_yourself (SmcConn smc_conn, interact_style == SmInteractStyleNone && !shutdown && !fast) { -#if 0 g_debug ("Sending SaveYourselfDone(True) for initial SaveYourself"); -#endif SmcSaveYourselfDone (xsmp->connection, True); /* As explained in the comment at the end of * do_save_yourself(), SAVE_YOURSELF_DONE is the correct @@ -762,10 +737,8 @@ do_save_yourself (EggSMClientXSMP *xsmp) { xsmp->state = XSMP_STATE_INTERACT_REQUEST; -#if 0 g_debug ("Sending InteractRequest(%s)", xsmp->interact_errors ? "Error" : "Normal"); -#endif SmcInteractRequest (xsmp->connection, xsmp->interact_errors ? SmDialogError : SmDialogNormal, xsmp_interact, @@ -784,9 +757,7 @@ do_save_yourself (EggSMClientXSMP *xsmp) return; } -#if 0 g_debug ("Sending SaveYourselfDone(True)"); -#endif SmcSaveYourselfDone (xsmp->connection, True); /* The client state diagram in the XSMP spec says that after a @@ -797,30 +768,6 @@ do_save_yourself (EggSMClientXSMP *xsmp) xsmp->state = XSMP_STATE_SAVE_YOURSELF_DONE; } -static void -merge_keyfiles (GKeyFile *dest, GKeyFile *source) -{ - int g, k; - char **groups, **keys, *value; - - groups = g_key_file_get_groups (source, NULL); - for (g = 0; groups[g]; g++) - { - keys = g_key_file_get_keys (source, groups[g], NULL, NULL); - for (k = 0; keys[k]; k++) - { - value = g_key_file_get_value (source, groups[g], keys[k], NULL); - if (value) - { - g_key_file_set_value (dest, groups[g], keys[k], value); - g_free (value); - } - } - g_strfreev (keys); - } - g_strfreev (groups); -} - static void save_state (EggSMClientXSMP *xsmp) { @@ -851,30 +798,61 @@ save_state (EggSMClientXSMP *xsmp) if (desktop_file) { GKeyFile *merged_file; - char *exec; - guint i; + char *desktop_file_path; merged_file = g_key_file_new (); - merge_keyfiles (merged_file, egg_desktop_file_get_key_file (desktop_file)); - merge_keyfiles (merged_file, state_file); + desktop_file_path = + g_filename_from_uri (egg_desktop_file_get_source (desktop_file), + NULL, NULL); + if (desktop_file_path && + g_key_file_load_from_file (merged_file, desktop_file_path, + G_KEY_FILE_KEEP_COMMENTS | + G_KEY_FILE_KEEP_TRANSLATIONS, NULL)) + { + guint g, k, i; + char **groups, **keys, *value, *exec; - g_key_file_free (state_file); - state_file = merged_file; + groups = g_key_file_get_groups (state_file, NULL); + for (g = 0; groups[g]; g++) + { + keys = g_key_file_get_keys (state_file, groups[g], NULL, NULL); + for (k = 0; keys[k]; k++) + { + value = g_key_file_get_value (state_file, groups[g], + keys[k], NULL); + if (value) + { + g_key_file_set_value (merged_file, groups[g], + keys[k], value); + g_free (value); + } + } + g_strfreev (keys); + } + g_strfreev (groups); - /* Update Exec key using "--sm-client-state-file %k" */ - restart = generate_command (xsmp->restart_command, - NULL, "%k"); - for (i = 0; i < restart->len; i++) - restart->pdata[i] = g_shell_quote (restart->pdata[i]); - g_ptr_array_add (restart, NULL); - exec = g_strjoinv (" ", (char **)restart->pdata); - g_strfreev ((char **)restart->pdata); - g_ptr_array_free (restart, FALSE); + g_key_file_free (state_file); + state_file = merged_file; - g_key_file_set_string (state_file, EGG_DESKTOP_FILE_GROUP, - EGG_DESKTOP_FILE_KEY_EXEC, - exec); - g_free (exec); + /* Update Exec key using "--sm-client-state-file %k" */ + restart = generate_command (xsmp->restart_command, + NULL, "%k"); + for (i = 0; i < restart->len; i++) + restart->pdata[i] = g_shell_quote (restart->pdata[i]); + g_ptr_array_add (restart, NULL); + exec = g_strjoinv (" ", (char **)restart->pdata); + g_strfreev ((char **)restart->pdata); + g_ptr_array_free (restart, FALSE); + + g_key_file_set_string (state_file, EGG_DESKTOP_FILE_GROUP, + EGG_DESKTOP_FILE_KEY_EXEC, + exec); + g_free (exec); + } + else + desktop_file = NULL; + + g_free (desktop_file_path); } /* Now write state_file to disk. (We can't use mktemp(), because @@ -953,16 +931,14 @@ save_state (EggSMClientXSMP *xsmp) } static void -xsmp_interact (SmcConn smc_conn, +xsmp_interact (G_GNUC_UNUSED SmcConn smc_conn, SmPointer client_data) { EggSMClientXSMP *xsmp = client_data; EggSMClient *client = client_data; -#if 0 g_debug ("Received Interact message in state %s", EGG_SM_CLIENT_XSMP_STATE (xsmp)); -#endif if (xsmp->state != XSMP_STATE_INTERACT_REQUEST) { @@ -975,31 +951,27 @@ xsmp_interact (SmcConn smc_conn, } static void -xsmp_die (SmcConn smc_conn, +xsmp_die (G_GNUC_UNUSED SmcConn smc_conn, SmPointer client_data) { EggSMClientXSMP *xsmp = client_data; EggSMClient *client = client_data; -#if 0 g_debug ("Received Die message in state %s", EGG_SM_CLIENT_XSMP_STATE (xsmp)); -#endif sm_client_xsmp_disconnect (xsmp); egg_sm_client_quit (client); } static void -xsmp_save_complete (SmcConn smc_conn, +xsmp_save_complete (G_GNUC_UNUSED SmcConn smc_conn, SmPointer client_data) { EggSMClientXSMP *xsmp = client_data; -#if 0 g_debug ("Received SaveComplete message in state %s", EGG_SM_CLIENT_XSMP_STATE (xsmp)); -#endif if (xsmp->state == XSMP_STATE_SAVE_YOURSELF_DONE) xsmp->state = XSMP_STATE_IDLE; @@ -1008,16 +980,14 @@ xsmp_save_complete (SmcConn smc_conn, } static void -xsmp_shutdown_cancelled (SmcConn smc_conn, +xsmp_shutdown_cancelled (G_GNUC_UNUSED SmcConn smc_conn, SmPointer client_data) { EggSMClientXSMP *xsmp = client_data; EggSMClient *client = client_data; -#if 0 g_debug ("Received ShutdownCancelled message in state %s", EGG_SM_CLIENT_XSMP_STATE (xsmp)); -#endif xsmp->shutting_down = FALSE; @@ -1042,9 +1012,7 @@ xsmp_shutdown_cancelled (SmcConn smc_conn, } else { -#if 0 g_debug ("Sending SaveYourselfDone(False)"); -#endif SmcSaveYourselfDone (xsmp->connection, False); if (xsmp->state == XSMP_STATE_INTERACT) @@ -1332,14 +1300,15 @@ process_ice_messages (IceConn ice_conn) case IceProcessMessagesConnectionClosed: return FALSE; - } - g_return_val_if_reached (FALSE); + default: + g_assert_not_reached (); + } } static gboolean -ice_iochannel_watch (GIOChannel *channel, - GIOCondition condition, +ice_iochannel_watch (G_GNUC_UNUSED GIOChannel *channel, + G_GNUC_UNUSED GIOCondition condition, gpointer client_data) { return process_ice_messages (client_data); @@ -1347,7 +1316,7 @@ ice_iochannel_watch (GIOChannel *channel, static void ice_connection_watch (IceConn ice_conn, - IcePointer client_data, + G_GNUC_UNUSED IcePointer client_data, Bool opening, IcePointer *watch_data) { @@ -1374,31 +1343,31 @@ ice_connection_watch (IceConn ice_conn, } static void -ice_error_handler (IceConn ice_conn, - Bool swap, - int offending_minor_opcode, - unsigned long offending_sequence, - int error_class, - int severity, - IcePointer values) +ice_error_handler (G_GNUC_UNUSED IceConn ice_conn, + G_GNUC_UNUSED Bool swap, + G_GNUC_UNUSED int offending_minor_opcode, + G_GNUC_UNUSED unsigned long offending_sequence, + G_GNUC_UNUSED int error_class, + G_GNUC_UNUSED int severity, + G_GNUC_UNUSED IcePointer values) { /* Do nothing */ } static void -ice_io_error_handler (IceConn ice_conn) +ice_io_error_handler (G_GNUC_UNUSED IceConn ice_conn) { /* Do nothing */ } static void -smc_error_handler (SmcConn smc_conn, - Bool swap, - int offending_minor_opcode, - unsigned long offending_sequence, - int error_class, - int severity, - SmPointer values) +smc_error_handler (G_GNUC_UNUSED SmcConn smc_conn, + G_GNUC_UNUSED Bool swap, + G_GNUC_UNUSED int offending_minor_opcode, + G_GNUC_UNUSED unsigned long offending_sequence, + G_GNUC_UNUSED int error_class, + G_GNUC_UNUSED int severity, + G_GNUC_UNUSED SmPointer values) { /* Do nothing */ } diff --git a/moo/eggsmclient/eggsmclient.c b/moo/eggsmclient/eggsmclient.c index 740a7af6..831c1876 100644 --- a/moo/eggsmclient/eggsmclient.c +++ b/moo/eggsmclient/eggsmclient.c @@ -20,7 +20,7 @@ #include "config.h" #include -#include +#include #include "eggsmclient.h" #include "eggsmclient-private.h" @@ -38,7 +38,7 @@ enum { LAST_SIGNAL }; -static guint signals[LAST_SIGNAL] = { 0 }; +static guint signals[LAST_SIGNAL]; struct _EggSMClientPrivate { GKeyFile *state_file; @@ -52,7 +52,7 @@ static EggSMClient *global_client; static EggSMClientMode global_client_mode = EGG_SM_CLIENT_MODE_NORMAL; static void -egg_sm_client_init (EggSMClient *client) +egg_sm_client_init (G_GNUC_UNUSED EggSMClient *client) { ; } @@ -116,7 +116,7 @@ egg_sm_client_class_init (EggSMClientClass *klass) * handling this signal; if the user has requested that the session * be saved when logging out, then ::save_state will be emitted * separately. - * + * * If the application agrees to quit, it should then wait for either * the ::quit_cancelled or ::quit signals to be emitted. **/ @@ -178,29 +178,32 @@ egg_sm_client_class_init (EggSMClientClass *klass) static gboolean sm_client_disable = FALSE; static char *sm_client_state_file = NULL; static char *sm_client_id = NULL; - -static GOptionEntry entries[] = { - { "sm-client-disable", 0, 0, - G_OPTION_ARG_NONE, &sm_client_disable, - N_("Disable connection to session manager"), NULL }, - { "sm-client-state-file", 0, 0, - G_OPTION_ARG_STRING, &sm_client_state_file, - N_("Specify file containing saved configuration"), N_("FILE") }, - { "sm-client-id", 0, 0, - G_OPTION_ARG_STRING, &sm_client_id, - N_("Specify session management ID"), N_("ID") }, - { NULL } -}; +static char *sm_config_prefix = NULL; static gboolean -sm_client_post_parse_func (GOptionContext *context, - GOptionGroup *group, - gpointer data, - GError **error) +sm_client_post_parse_func (G_GNUC_UNUSED GOptionContext *context, + G_GNUC_UNUSED GOptionGroup *group, + G_GNUC_UNUSED gpointer data, + G_GNUC_UNUSED GError **error) { EggSMClient *client = egg_sm_client_get (); - if (EGG_SM_CLIENT_GET_CLASS (client)->startup) + if (sm_client_id == NULL) + { + const gchar *desktop_autostart_id; + + desktop_autostart_id = g_getenv ("DESKTOP_AUTOSTART_ID"); + + if (desktop_autostart_id != NULL) + sm_client_id = g_strdup (desktop_autostart_id); + } + + /* Unset DESKTOP_AUTOSTART_ID in order to avoid child processes to + * use the same client id. */ + g_unsetenv ("DESKTOP_AUTOSTART_ID"); + + if (global_client_mode != EGG_SM_CLIENT_MODE_DISABLED && + EGG_SM_CLIENT_GET_CLASS (client)->startup) EGG_SM_CLIENT_GET_CLASS (client)->startup (client, sm_client_id); return TRUE; } @@ -217,6 +220,29 @@ sm_client_post_parse_func (GOptionContext *context, GOptionGroup * egg_sm_client_get_option_group (void) { + const GOptionEntry entries[] = { + { "sm-client-disable", 0, 0, + G_OPTION_ARG_NONE, &sm_client_disable, + N_("Disable connection to session manager"), NULL }, + { "sm-client-state-file", 0, 0, + G_OPTION_ARG_FILENAME, &sm_client_state_file, + N_("Specify file containing saved configuration"), N_("FILE") }, + { "sm-client-id", 0, 0, + G_OPTION_ARG_STRING, &sm_client_id, + N_("Specify session management ID"), N_("ID") }, + /* GnomeClient compatibility option */ + { "sm-disable", 0, G_OPTION_FLAG_HIDDEN, + G_OPTION_ARG_NONE, &sm_client_disable, + NULL, NULL }, + /* GnomeClient compatibility option. This is a dummy option that only + * exists so that sessions saved by apps with GnomeClient can be restored + * later when they've switched to EggSMClient. See bug #575308. + */ + { "sm-config-prefix", 0, G_OPTION_FLAG_HIDDEN, + G_OPTION_ARG_STRING, &sm_config_prefix, + NULL, NULL }, + { NULL } + }; GOptionGroup *group; /* Use our own debug handler for the "EggSMClient" domain. */ @@ -224,8 +250,8 @@ egg_sm_client_get_option_group (void) egg_sm_client_debug_handler, NULL); group = g_option_group_new ("sm-client", - _("Session Management Options"), - _("Show Session Management options"), + _("Session management options:"), + _("Show session management options"), NULL, NULL); g_option_group_add_entries (group, entries); g_option_group_set_parse_hooks (group, NULL, sm_client_post_parse_func); @@ -240,9 +266,9 @@ egg_sm_client_get_option_group (void) * Sets the "mode" of #EggSMClient as follows: * * %EGG_SM_CLIENT_MODE_DISABLED: Session management is completely - * disabled. The application will not even connect to the session - * manager. (egg_sm_client_get() will still return an #EggSMClient, - * but it will just be a dummy object.) + * disabled, until the mode is changed again. The application will + * not even connect to the session manager. (egg_sm_client_get() + * will still return an #EggSMClient object.) * * %EGG_SM_CLIENT_MODE_NO_RESTART: The application will connect to * the session manager (and thus will receive notification when the @@ -252,12 +278,27 @@ egg_sm_client_get_option_group (void) * %EGG_SM_CLIENT_MODE_NORMAL: The default. #EggSMCLient will * function normally. * - * This must be called before the application's main loop begins. + * This must be called before the application's main loop begins and + * before any call to egg_sm_client_get(), unless the mode was set + * earlier to %EGG_SM_CLIENT_MODE_DISABLED and this call enables + * session management. Note that option parsing will call + * egg_sm_client_get(). **/ void egg_sm_client_set_mode (EggSMClientMode mode) { + EggSMClientMode old_mode = global_client_mode; + + g_return_if_fail (global_client == NULL || global_client_mode == EGG_SM_CLIENT_MODE_DISABLED); + g_return_if_fail (!(global_client != NULL && mode == EGG_SM_CLIENT_MODE_DISABLED)); + global_client_mode = mode; + + if (global_client != NULL && old_mode == EGG_SM_CLIENT_MODE_DISABLED) + { + if (EGG_SM_CLIENT_GET_CLASS (global_client)->startup) + EGG_SM_CLIENT_GET_CLASS (global_client)->startup (global_client, sm_client_id); + } } /** @@ -292,24 +333,23 @@ egg_sm_client_get (void) { if (!global_client) { - if (global_client_mode != EGG_SM_CLIENT_MODE_DISABLED && - !sm_client_disable) + if (!sm_client_disable) { #if defined (GDK_WINDOWING_WIN32) global_client = egg_sm_client_win32_new (); #elif defined (GDK_WINDOWING_QUARTZ) - global_client = egg_sm_client_dummy_new (); + global_client = egg_sm_client_osx_new (); #else - /* If both D-Bus and XSMP are compiled in, try D-Bus first - * and fall back to XSMP if D-Bus session management isn't - * available. + /* If both D-Bus and XSMP are compiled in, try XSMP first + * (since it supports state saving) and fall back to D-Bus + * if XSMP isn't available. */ -# ifdef EGG_SM_CLIENT_BACKEND_DBUS - global_client = egg_sm_client_dbus_new (); -# endif # ifdef EGG_SM_CLIENT_BACKEND_XSMP + global_client = egg_sm_client_xsmp_new (); +# endif +# ifdef EGG_SM_CLIENT_BACKEND_DBUS if (!global_client) - global_client = egg_sm_client_xsmp_new (); + global_client = egg_sm_client_dbus_new (); # endif #endif } @@ -552,7 +592,7 @@ static void egg_sm_client_debug_handler (const char *log_domain, GLogLevelFlags log_level, const char *message, - gpointer user_data) + G_GNUC_UNUSED gpointer user_data) { static int debug = -1;