evolution-3.6.4/smclient/eggsmclient.c

No issues found

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