]> www.fi.muni.cz Git - evince.git/blob - cut-n-paste/smclient/eggsmclient.c
Update smclient from libegg master.
[evince.git] / cut-n-paste / smclient / eggsmclient.c
1 /*
2  * Copyright (C) 2007 Novell, Inc.
3  *
4  * This library is free software; you can redistribute it and/or
5  * modify it under the terms of the GNU Library General Public
6  * License as published by the Free Software Foundation; either
7  * version 2 of the License, or (at your option) any later version.
8  *
9  * This library is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
12  * Library General Public License for more details.
13  *
14  * You should have received a copy of the GNU Library General Public
15  * License along with this library; if not, write to the
16  * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
17  * Boston, MA 02111-1307, USA.
18  */
19
20 #include "config.h"
21
22 #include <string.h>
23 #include <glib/gi18n.h>
24
25 #include "eggsmclient.h"
26 #include "eggsmclient-private.h"
27
28 static void egg_sm_client_debug_handler (const char *log_domain,
29                                          GLogLevelFlags log_level,
30                                          const char *message,
31                                          gpointer user_data);
32
33 enum {
34   SAVE_STATE,
35   QUIT_REQUESTED,
36   QUIT_CANCELLED,
37   QUIT,
38   LAST_SIGNAL
39 };
40
41 static guint signals[LAST_SIGNAL];
42
43 struct _EggSMClientPrivate {
44   GKeyFile *state_file;
45 };
46
47 #define EGG_SM_CLIENT_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), EGG_TYPE_SM_CLIENT, EggSMClientPrivate))
48
49 G_DEFINE_TYPE (EggSMClient, egg_sm_client, G_TYPE_OBJECT)
50
51 static EggSMClient *global_client;
52 static EggSMClientMode global_client_mode = EGG_SM_CLIENT_MODE_NORMAL;
53
54 static void
55 egg_sm_client_init (EggSMClient *client)
56 {
57   ;
58 }
59
60 static void
61 egg_sm_client_class_init (EggSMClientClass *klass)
62 {
63   GObjectClass *object_class = G_OBJECT_CLASS (klass);
64
65   g_type_class_add_private (klass, sizeof (EggSMClientPrivate));
66
67   /**
68    * EggSMClient::save_state:
69    * @client: the client
70    * @state_file: a #GKeyFile to save state information into
71    *
72    * Emitted when the session manager has requested that the
73    * application save information about its current state. The
74    * application should save its state into @state_file, and then the
75    * session manager may then restart the application in a future
76    * session and tell it to initialize itself from that state.
77    *
78    * You should not save any data into @state_file's "start group"
79    * (ie, the %NULL group). Instead, applications should save their
80    * data into groups with names that start with the application name,
81    * and libraries that connect to this signal should save their data
82    * into groups with names that start with the library name.
83    *
84    * Alternatively, rather than (or in addition to) using @state_file,
85    * the application can save its state by calling
86    * egg_sm_client_set_restart_command() during the processing of this
87    * signal (eg, to include a list of files to open).
88    **/
89   signals[SAVE_STATE] =
90     g_signal_new ("save_state",
91                   G_OBJECT_CLASS_TYPE (object_class),
92                   G_SIGNAL_RUN_LAST,
93                   G_STRUCT_OFFSET (EggSMClientClass, save_state),
94                   NULL, NULL,
95                   g_cclosure_marshal_VOID__POINTER,
96                   G_TYPE_NONE,
97                   1, G_TYPE_POINTER);
98
99   /**
100    * EggSMClient::quit_requested:
101    * @client: the client
102    *
103    * Emitted when the session manager requests that the application
104    * exit (generally because the user is logging out). The application
105    * should decide whether or not it is willing to quit (perhaps after
106    * asking the user what to do with documents that have unsaved
107    * changes) and then call egg_sm_client_will_quit(), passing %TRUE
108    * or %FALSE to give its answer to the session manager. (It does not
109    * need to give an answer before returning from the signal handler;
110    * it can interact with the user asynchronously and then give its
111    * answer later on.) If the application does not connect to this
112    * signal, then #EggSMClient will automatically return %TRUE on its
113    * behalf.
114    *
115    * The application should not save its session state as part of
116    * handling this signal; if the user has requested that the session
117    * be saved when logging out, then ::save_state will be emitted
118    * separately.
119    * 
120    * If the application agrees to quit, it should then wait for either
121    * the ::quit_cancelled or ::quit signals to be emitted.
122    **/
123   signals[QUIT_REQUESTED] =
124     g_signal_new ("quit_requested",
125                   G_OBJECT_CLASS_TYPE (object_class),
126                   G_SIGNAL_RUN_LAST,
127                   G_STRUCT_OFFSET (EggSMClientClass, quit_requested),
128                   NULL, NULL,
129                   g_cclosure_marshal_VOID__VOID,
130                   G_TYPE_NONE,
131                   0);
132
133   /**
134    * EggSMClient::quit_cancelled:
135    * @client: the client
136    *
137    * Emitted when the session manager decides to cancel a logout after
138    * the application has already agreed to quit. After receiving this
139    * signal, the application can go back to what it was doing before
140    * receiving the ::quit_requested signal.
141    **/
142   signals[QUIT_CANCELLED] =
143     g_signal_new ("quit_cancelled",
144                   G_OBJECT_CLASS_TYPE (object_class),
145                   G_SIGNAL_RUN_LAST,
146                   G_STRUCT_OFFSET (EggSMClientClass, quit_cancelled),
147                   NULL, NULL,
148                   g_cclosure_marshal_VOID__VOID,
149                   G_TYPE_NONE,
150                   0);
151
152   /**
153    * EggSMClient::quit:
154    * @client: the client
155    *
156    * Emitted when the session manager wants the application to quit
157    * (generally because the user is logging out). The application
158    * should exit as soon as possible after receiving this signal; if
159    * it does not, the session manager may choose to forcibly kill it.
160    *
161    * Normally a GUI application would only be sent a ::quit if it
162    * agreed to quit in response to a ::quit_requested signal. However,
163    * this is not guaranteed; in some situations the session manager
164    * may decide to end the session without giving applications a
165    * chance to object.
166    **/
167   signals[QUIT] =
168     g_signal_new ("quit",
169                   G_OBJECT_CLASS_TYPE (object_class),
170                   G_SIGNAL_RUN_LAST,
171                   G_STRUCT_OFFSET (EggSMClientClass, quit),
172                   NULL, NULL,
173                   g_cclosure_marshal_VOID__VOID,
174                   G_TYPE_NONE,
175                   0);
176 }
177
178 static gboolean sm_client_disable = FALSE;
179 static char *sm_client_state_file = NULL;
180 static char *sm_client_id = NULL;
181 static char *sm_config_prefix = NULL;
182
183 static gboolean
184 sm_client_post_parse_func (GOptionContext  *context,
185                            GOptionGroup    *group,
186                            gpointer         data,
187                            GError         **error)
188 {
189   EggSMClient *client = egg_sm_client_get ();
190
191   if (sm_client_id == NULL)
192     {
193       const gchar *desktop_autostart_id;
194
195       desktop_autostart_id = g_getenv ("DESKTOP_AUTOSTART_ID");
196
197       if (desktop_autostart_id != NULL)
198         sm_client_id = g_strdup (desktop_autostart_id);
199     }
200
201   /* Unset DESKTOP_AUTOSTART_ID in order to avoid child processes to
202    * use the same client id. */
203   g_unsetenv ("DESKTOP_AUTOSTART_ID");
204
205   if (EGG_SM_CLIENT_GET_CLASS (client)->startup)
206     EGG_SM_CLIENT_GET_CLASS (client)->startup (client, sm_client_id);
207   return TRUE;
208 }
209
210 /**
211  * egg_sm_client_get_option_group:
212  *
213  * Creates a %GOptionGroup containing the session-management-related
214  * options. You should add this group to the application's
215  * %GOptionContext if you want to use #EggSMClient.
216  *
217  * Return value: the %GOptionGroup
218  **/
219 GOptionGroup *
220 egg_sm_client_get_option_group (void)
221 {
222   const GOptionEntry entries[] = {
223     { "sm-client-disable", 0, 0,
224       G_OPTION_ARG_NONE, &sm_client_disable,
225       N_("Disable connection to session manager"), NULL },
226     { "sm-client-state-file", 0, 0,
227       G_OPTION_ARG_FILENAME, &sm_client_state_file,
228       N_("Specify file containing saved configuration"), N_("FILE") },
229     { "sm-client-id", 0, 0,
230       G_OPTION_ARG_STRING, &sm_client_id,
231       N_("Specify session management ID"), N_("ID") },
232     /* GnomeClient compatibility option */
233     { "sm-disable", 0, G_OPTION_FLAG_HIDDEN,
234       G_OPTION_ARG_NONE, &sm_client_disable,
235       NULL, NULL },
236     /* GnomeClient compatibility option. This is a dummy option that only
237      * exists so that sessions saved by apps with GnomeClient can be restored
238      * later when they've switched to EggSMClient. See bug #575308.
239      */
240     { "sm-config-prefix", 0, G_OPTION_FLAG_HIDDEN,
241       G_OPTION_ARG_STRING, &sm_config_prefix,
242       NULL, NULL },
243     { NULL }
244   };
245   GOptionGroup *group;
246
247   /* Use our own debug handler for the "EggSMClient" domain. */
248   g_log_set_handler (G_LOG_DOMAIN, G_LOG_LEVEL_DEBUG,
249                      egg_sm_client_debug_handler, NULL);
250
251   group = g_option_group_new ("sm-client",
252                               _("Session management options:"),
253                               _("Show session management options"),
254                               NULL, NULL);
255   g_option_group_add_entries (group, entries);
256   g_option_group_set_parse_hooks (group, NULL, sm_client_post_parse_func);
257
258   return group;
259 }
260
261 /**
262  * egg_sm_client_set_mode:
263  * @mode: an #EggSMClient mode
264  *
265  * Sets the "mode" of #EggSMClient as follows:
266  *
267  *    %EGG_SM_CLIENT_MODE_DISABLED: Session management is completely
268  *    disabled. The application will not even connect to the session
269  *    manager. (egg_sm_client_get() will still return an #EggSMClient,
270  *    but it will just be a dummy object.)
271  *
272  *    %EGG_SM_CLIENT_MODE_NO_RESTART: The application will connect to
273  *    the session manager (and thus will receive notification when the
274  *    user is logging out, etc), but will request to not be
275  *    automatically restarted with saved state in future sessions.
276  *
277  *    %EGG_SM_CLIENT_MODE_NORMAL: The default. #EggSMCLient will
278  *    function normally.
279  *
280  * This must be called before the application's main loop begins.
281  **/
282 void
283 egg_sm_client_set_mode (EggSMClientMode mode)
284 {
285   global_client_mode = mode;
286 }
287
288 /**
289  * egg_sm_client_get_mode:
290  *
291  * Gets the global #EggSMClientMode. See egg_sm_client_set_mode()
292  * for details.
293  *
294  * Return value: the global #EggSMClientMode
295  **/
296 EggSMClientMode
297 egg_sm_client_get_mode (void)
298 {
299   return global_client_mode;
300 }
301
302 /**
303  * egg_sm_client_get:
304  *
305  * Returns the master #EggSMClient for the application.
306  *
307  * On platforms that support saved sessions (ie, POSIX/X11), the
308  * application will only request to be restarted by the session
309  * manager if you call egg_set_desktop_file() to set an application
310  * desktop file. In particular, if the desktop file contains the key
311  * "X
312  *
313  * Return value: the master #EggSMClient.
314  **/
315 EggSMClient *
316 egg_sm_client_get (void)
317 {
318   if (!global_client)
319     {
320       if (global_client_mode != EGG_SM_CLIENT_MODE_DISABLED &&
321           !sm_client_disable)
322         {
323 #if defined (GDK_WINDOWING_WIN32)
324           global_client = egg_sm_client_win32_new ();
325 #elif defined (GDK_WINDOWING_QUARTZ)
326           global_client = egg_sm_client_osx_new ();
327 #else
328           /* If both D-Bus and XSMP are compiled in, try XSMP first
329            * (since it supports state saving) and fall back to D-Bus
330            * if XSMP isn't available.
331            */
332 # ifdef EGG_SM_CLIENT_BACKEND_XSMP
333           global_client = egg_sm_client_xsmp_new ();
334 # endif
335 # ifdef EGG_SM_CLIENT_BACKEND_DBUS
336           if (!global_client)
337             global_client = egg_sm_client_dbus_new ();
338 # endif
339 #endif
340         }
341
342       /* Fallback: create a dummy client, so that callers don't have
343        * to worry about a %NULL return value.
344        */
345       if (!global_client)
346         global_client = g_object_new (EGG_TYPE_SM_CLIENT, NULL);
347     }
348
349   return global_client;
350 }
351
352 /**
353  * egg_sm_client_is_resumed:
354  * @client: the client
355  *
356  * Checks whether or not the current session has been resumed from
357  * a previous saved session. If so, the application should call
358  * egg_sm_client_get_state_file() and restore its state from the
359  * returned #GKeyFile.
360  *
361  * Return value: %TRUE if the session has been resumed
362  **/
363 gboolean
364 egg_sm_client_is_resumed (EggSMClient *client)
365 {
366   g_return_val_if_fail (client == global_client, FALSE);
367
368   return sm_client_state_file != NULL;
369 }
370
371 /**
372  * egg_sm_client_get_state_file:
373  * @client: the client
374  *
375  * If the application was resumed by the session manager, this will
376  * return the #GKeyFile containing its state from the previous
377  * session.
378  *
379  * Note that other libraries and #EggSMClient itself may also store
380  * state in the key file, so if you call egg_sm_client_get_groups(),
381  * on it, the return value will likely include groups that you did not
382  * put there yourself. (It is also not guaranteed that the first
383  * group created by the application will still be the "start group"
384  * when it is resumed.)
385  *
386  * Return value: the #GKeyFile containing the application's earlier
387  * state, or %NULL on error. You should not free this key file; it
388  * is owned by @client.
389  **/
390 GKeyFile *
391 egg_sm_client_get_state_file (EggSMClient *client)
392 {
393   EggSMClientPrivate *priv = EGG_SM_CLIENT_GET_PRIVATE (client);
394   char *state_file_path;
395   GError *err = NULL;
396
397   g_return_val_if_fail (client == global_client, NULL);
398
399   if (!sm_client_state_file)
400     return NULL;
401   if (priv->state_file)
402     return priv->state_file;
403
404   if (!strncmp (sm_client_state_file, "file://", 7))
405     state_file_path = g_filename_from_uri (sm_client_state_file, NULL, NULL);
406   else
407     state_file_path = g_strdup (sm_client_state_file);
408
409   priv->state_file = g_key_file_new ();
410   if (!g_key_file_load_from_file (priv->state_file, state_file_path, 0, &err))
411     {
412       g_warning ("Could not load SM state file '%s': %s",
413                  sm_client_state_file, err->message);
414       g_clear_error (&err);
415       g_key_file_free (priv->state_file);
416       priv->state_file = NULL;
417     }
418
419   g_free (state_file_path);
420   return priv->state_file;
421 }
422
423 /**
424  * egg_sm_client_set_restart_command:
425  * @client: the client
426  * @argc: the length of @argv
427  * @argv: argument vector
428  *
429  * Sets the command used to restart @client if it does not have a
430  * .desktop file that can be used to find its restart command.
431  *
432  * This can also be used when handling the ::save_state signal, to
433  * save the current state via an updated command line. (Eg, providing
434  * a list of filenames to open when the application is resumed.)
435  **/
436 void
437 egg_sm_client_set_restart_command (EggSMClient  *client,
438                                    int           argc,
439                                    const char  **argv)
440 {
441   g_return_if_fail (EGG_IS_SM_CLIENT (client));
442
443   if (EGG_SM_CLIENT_GET_CLASS (client)->set_restart_command)
444     EGG_SM_CLIENT_GET_CLASS (client)->set_restart_command (client, argc, argv);
445 }
446
447 /**
448  * egg_sm_client_will_quit:
449  * @client: the client
450  * @will_quit: whether or not the application is willing to quit
451  *
452  * This MUST be called in response to the ::quit_requested signal, to
453  * indicate whether or not the application is willing to quit. The
454  * application may call it either directly from the signal handler, or
455  * at some later point (eg, after asynchronously interacting with the
456  * user).
457  *
458  * If the application does not connect to ::quit_requested,
459  * #EggSMClient will call this method on its behalf (passing %TRUE
460  * for @will_quit).
461  *
462  * After calling this method, the application should wait to receive
463  * either ::quit_cancelled or ::quit.
464  **/
465 void
466 egg_sm_client_will_quit (EggSMClient *client,
467                          gboolean     will_quit)
468 {
469   g_return_if_fail (EGG_IS_SM_CLIENT (client));
470
471   if (EGG_SM_CLIENT_GET_CLASS (client)->will_quit)
472     EGG_SM_CLIENT_GET_CLASS (client)->will_quit (client, will_quit);
473 }
474
475 /**
476  * egg_sm_client_end_session:
477  * @style: a hint at how to end the session
478  * @request_confirmation: whether or not the user should get a chance
479  * to confirm the action
480  *
481  * Requests that the session manager end the current session. @style
482  * indicates how the session should be ended, and
483  * @request_confirmation indicates whether or not the user should be
484  * given a chance to confirm the logout/reboot/shutdown. Both of these
485  * flags are merely hints though; the session manager may choose to
486  * ignore them.
487  *
488  * Return value: %TRUE if the request was sent; %FALSE if it could not
489  * be (eg, because it could not connect to the session manager).
490  **/
491 gboolean
492 egg_sm_client_end_session (EggSMClientEndStyle  style,
493                            gboolean             request_confirmation)
494 {
495   EggSMClient *client = egg_sm_client_get ();
496
497   g_return_val_if_fail (EGG_IS_SM_CLIENT (client), FALSE);
498
499   if (EGG_SM_CLIENT_GET_CLASS (client)->end_session)
500     {
501       return EGG_SM_CLIENT_GET_CLASS (client)->end_session (client, style,
502                                                             request_confirmation);
503     }
504   else
505     return FALSE;
506 }
507
508 /* Signal-emitting callbacks from platform-specific code */
509
510 GKeyFile *
511 egg_sm_client_save_state (EggSMClient *client)
512 {
513   GKeyFile *state_file;
514   char *group;
515
516   g_return_val_if_fail (client == global_client, NULL);
517
518   state_file = g_key_file_new ();
519
520   g_debug ("Emitting save_state");
521   g_signal_emit (client, signals[SAVE_STATE], 0, state_file);
522   g_debug ("Done emitting save_state");
523
524   group = g_key_file_get_start_group (state_file);
525   if (group)
526     {
527       g_free (group);
528       return state_file;
529     }
530   else
531     {
532       g_key_file_free (state_file);
533       return NULL;
534     }
535 }
536
537 void
538 egg_sm_client_quit_requested (EggSMClient *client)
539 {
540   g_return_if_fail (client == global_client);
541
542   if (!g_signal_has_handler_pending (client, signals[QUIT_REQUESTED], 0, FALSE))
543     {
544       g_debug ("Not emitting quit_requested because no one is listening");
545       egg_sm_client_will_quit (client, TRUE);
546       return;
547     }
548
549   g_debug ("Emitting quit_requested");
550   g_signal_emit (client, signals[QUIT_REQUESTED], 0);
551   g_debug ("Done emitting quit_requested");
552 }
553
554 void
555 egg_sm_client_quit_cancelled (EggSMClient *client)
556 {
557   g_return_if_fail (client == global_client);
558
559   g_debug ("Emitting quit_cancelled");
560   g_signal_emit (client, signals[QUIT_CANCELLED], 0);
561   g_debug ("Done emitting quit_cancelled");
562 }
563
564 void
565 egg_sm_client_quit (EggSMClient *client)
566 {
567   g_return_if_fail (client == global_client);
568
569   g_debug ("Emitting quit");
570   g_signal_emit (client, signals[QUIT], 0);
571   g_debug ("Done emitting quit");
572
573   /* FIXME: should we just call gtk_main_quit() here? */
574 }
575
576 static void
577 egg_sm_client_debug_handler (const char *log_domain,
578                              GLogLevelFlags log_level,
579                              const char *message,
580                              gpointer user_data)
581 {
582   static int debug = -1;
583
584   if (debug < 0)
585     debug = (g_getenv ("EGG_SM_CLIENT_DEBUG") != NULL);
586
587   if (debug)
588     g_log_default_handler (log_domain, log_level, message, NULL);
589 }