hythmbox-2.98/lib/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 #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 (global_client_mode != EGG_SM_CLIENT_MODE_DISABLED &&
206       EGG_SM_CLIENT_GET_CLASS (client)->startup)
207     EGG_SM_CLIENT_GET_CLASS (client)->startup (client, sm_client_id);
208   return TRUE;
209 }
210 
211 /**
212  * egg_sm_client_get_option_group:
213  *
214  * Creates a %GOptionGroup containing the session-management-related
215  * options. You should add this group to the application's
216  * %GOptionContext if you want to use #EggSMClient.
217  *
218  * Return value: the %GOptionGroup
219  **/
220 GOptionGroup *
221 egg_sm_client_get_option_group (void)
222 {
223   const GOptionEntry entries[] = {
224     { "sm-client-disable", 0, 0,
225       G_OPTION_ARG_NONE, &sm_client_disable,
226       N_("Disable connection to session manager"), NULL },
227     { "sm-client-state-file", 0, 0,
228       G_OPTION_ARG_FILENAME, &sm_client_state_file,
229       N_("Specify file containing saved configuration"), N_("FILE") },
230     { "sm-client-id", 0, 0,
231       G_OPTION_ARG_STRING, &sm_client_id,
232       N_("Specify session management ID"), N_("ID") },
233     /* GnomeClient compatibility option */
234     { "sm-disable", 0, G_OPTION_FLAG_HIDDEN,
235       G_OPTION_ARG_NONE, &sm_client_disable,
236       NULL, NULL },
237     /* GnomeClient compatibility option. This is a dummy option that only
238      * exists so that sessions saved by apps with GnomeClient can be restored
239      * later when they've switched to EggSMClient. See bug #575308.
240      */
241     { "sm-config-prefix", 0, G_OPTION_FLAG_HIDDEN,
242       G_OPTION_ARG_STRING, &sm_config_prefix,
243       NULL, NULL },
244     { NULL }
245   };
246   GOptionGroup *group;
247 
248   /* Use our own debug handler for the "EggSMClient" domain. */
249   g_log_set_handler (G_LOG_DOMAIN, G_LOG_LEVEL_DEBUG,
250 		     egg_sm_client_debug_handler, NULL);
251 
252   group = g_option_group_new ("sm-client",
253 			      _("Session management options:"),
254 			      _("Show session management options"),
255 			      NULL, NULL);
256   g_option_group_add_entries (group, entries);
257   g_option_group_set_parse_hooks (group, NULL, sm_client_post_parse_func);
258 
259   return group;
260 }
261 
262 /**
263  * egg_sm_client_set_mode:
264  * @mode: an #EggSMClient mode
265  *
266  * Sets the "mode" of #EggSMClient as follows:
267  *
268  *    %EGG_SM_CLIENT_MODE_DISABLED: Session management is completely
269  *    disabled, until the mode is changed again. The application will
270  *    not even connect to the session manager. (egg_sm_client_get()
271  *    will still return an #EggSMClient object.)
272  *
273  *    %EGG_SM_CLIENT_MODE_NO_RESTART: The application will connect to
274  *    the session manager (and thus will receive notification when the
275  *    user is logging out, etc), but will request to not be
276  *    automatically restarted with saved state in future sessions.
277  *
278  *    %EGG_SM_CLIENT_MODE_NORMAL: The default. #EggSMCLient will
279  *    function normally.
280  *
281  * This must be called before the application's main loop begins and
282  * before any call to egg_sm_client_get(), unless the mode was set
283  * earlier to %EGG_SM_CLIENT_MODE_DISABLED and this call enables
284  * session management. Note that option parsing will call
285  * egg_sm_client_get().
286  **/
287 void
288 egg_sm_client_set_mode (EggSMClientMode mode)
289 {
290   EggSMClientMode old_mode = global_client_mode;
291 
292   g_return_if_fail (global_client == NULL || global_client_mode == EGG_SM_CLIENT_MODE_DISABLED);
293   g_return_if_fail (!(global_client != NULL && mode == EGG_SM_CLIENT_MODE_DISABLED));
294 
295   global_client_mode = mode;
296 
297   if (global_client != NULL && old_mode == EGG_SM_CLIENT_MODE_DISABLED)
298     {
299       if (EGG_SM_CLIENT_GET_CLASS (global_client)->startup)
300         EGG_SM_CLIENT_GET_CLASS (global_client)->startup (global_client, sm_client_id);
301     }
302 }
303 
304 /**
305  * egg_sm_client_get_mode:
306  *
307  * Gets the global #EggSMClientMode. See egg_sm_client_set_mode()
308  * for details.
309  *
310  * Return value: the global #EggSMClientMode
311  **/
312 EggSMClientMode
313 egg_sm_client_get_mode (void)
314 {
315   return global_client_mode;
316 }
317 
318 /**
319  * egg_sm_client_get:
320  *
321  * Returns the master #EggSMClient for the application.
322  *
323  * On platforms that support saved sessions (ie, POSIX/X11), the
324  * application will only request to be restarted by the session
325  * manager if you call egg_set_desktop_file() to set an application
326  * desktop file. In particular, if the desktop file contains the key
327  * "X
328  *
329  * Return value: the master #EggSMClient.
330  **/
331 EggSMClient *
332 egg_sm_client_get (void)
333 {
334   if (!global_client)
335     {
336       if (!sm_client_disable)
337 	{
338 #if defined (GDK_WINDOWING_WIN32)
339 	  global_client = egg_sm_client_win32_new ();
340 #elif defined (GDK_WINDOWING_QUARTZ)
341 	  global_client = egg_sm_client_osx_new ();
342 #else
343 	  /* If both D-Bus and XSMP are compiled in, try XSMP first
344 	   * (since it supports state saving) and fall back to D-Bus
345 	   * if XSMP isn't available.
346 	   */
347 # ifdef EGG_SM_CLIENT_BACKEND_XSMP
348 	  global_client = egg_sm_client_xsmp_new ();
349 # endif
350 # ifdef EGG_SM_CLIENT_BACKEND_DBUS
351 	  if (!global_client)
352 	    global_client = egg_sm_client_dbus_new ();
353 # endif
354 #endif
355 	}
356 
357       /* Fallback: create a dummy client, so that callers don't have
358        * to worry about a %NULL return value.
359        */
360       if (!global_client)
361 	global_client = g_object_new (EGG_TYPE_SM_CLIENT, NULL);
362     }
363 
364   return global_client;
365 }
366 
367 /**
368  * egg_sm_client_is_resumed:
369  * @client: the client
370  *
371  * Checks whether or not the current session has been resumed from
372  * a previous saved session. If so, the application should call
373  * egg_sm_client_get_state_file() and restore its state from the
374  * returned #GKeyFile.
375  *
376  * Return value: %TRUE if the session has been resumed
377  **/
378 gboolean
379 egg_sm_client_is_resumed (EggSMClient *client)
380 {
381   g_return_val_if_fail (client == global_client, FALSE);
382 
383   return sm_client_state_file != NULL;
384 }
385 
386 /**
387  * egg_sm_client_get_state_file:
388  * @client: the client
389  *
390  * If the application was resumed by the session manager, this will
391  * return the #GKeyFile containing its state from the previous
392  * session.
393  *
394  * Note that other libraries and #EggSMClient itself may also store
395  * state in the key file, so if you call egg_sm_client_get_groups(),
396  * on it, the return value will likely include groups that you did not
397  * put there yourself. (It is also not guaranteed that the first
398  * group created by the application will still be the "start group"
399  * when it is resumed.)
400  *
401  * Return value: the #GKeyFile containing the application's earlier
402  * state, or %NULL on error. You should not free this key file; it
403  * is owned by @client.
404  **/
405 GKeyFile *
406 egg_sm_client_get_state_file (EggSMClient *client)
407 {
408   EggSMClientPrivate *priv = EGG_SM_CLIENT_GET_PRIVATE (client);
409   char *state_file_path;
410   GError *err = NULL;
411 
412   g_return_val_if_fail (client == global_client, NULL);
413 
414   if (!sm_client_state_file)
415     return NULL;
416   if (priv->state_file)
417     return priv->state_file;
418 
419   if (!strncmp (sm_client_state_file, "file://", 7))
420     state_file_path = g_filename_from_uri (sm_client_state_file, NULL, NULL);
421   else
422     state_file_path = g_strdup (sm_client_state_file);
423 
424   priv->state_file = g_key_file_new ();
425   if (!g_key_file_load_from_file (priv->state_file, state_file_path, 0, &err))
426     {
427       g_warning ("Could not load SM state file '%s': %s",
428 		 sm_client_state_file, err->message);
429       g_clear_error (&err);
430       g_key_file_free (priv->state_file);
431       priv->state_file = NULL;
432     }
433 
434   g_free (state_file_path);
435   return priv->state_file;
436 }
437 
438 /**
439  * egg_sm_client_set_restart_command:
440  * @client: the client
441  * @argc: the length of @argv
442  * @argv: argument vector
443  *
444  * Sets the command used to restart @client if it does not have a
445  * .desktop file that can be used to find its restart command.
446  *
447  * This can also be used when handling the ::save_state signal, to
448  * save the current state via an updated command line. (Eg, providing
449  * a list of filenames to open when the application is resumed.)
450  **/
451 void
452 egg_sm_client_set_restart_command (EggSMClient  *client,
453 				   int           argc,
454 				   const char  **argv)
455 {
456   g_return_if_fail (EGG_IS_SM_CLIENT (client));
457 
458   if (EGG_SM_CLIENT_GET_CLASS (client)->set_restart_command)
459     EGG_SM_CLIENT_GET_CLASS (client)->set_restart_command (client, argc, argv);
460 }
461 
462 /**
463  * egg_sm_client_will_quit:
464  * @client: the client
465  * @will_quit: whether or not the application is willing to quit
466  *
467  * This MUST be called in response to the ::quit_requested signal, to
468  * indicate whether or not the application is willing to quit. The
469  * application may call it either directly from the signal handler, or
470  * at some later point (eg, after asynchronously interacting with the
471  * user).
472  *
473  * If the application does not connect to ::quit_requested,
474  * #EggSMClient will call this method on its behalf (passing %TRUE
475  * for @will_quit).
476  *
477  * After calling this method, the application should wait to receive
478  * either ::quit_cancelled or ::quit.
479  **/
480 void
481 egg_sm_client_will_quit (EggSMClient *client,
482 			 gboolean     will_quit)
483 {
484   g_return_if_fail (EGG_IS_SM_CLIENT (client));
485 
486   if (EGG_SM_CLIENT_GET_CLASS (client)->will_quit)
487     EGG_SM_CLIENT_GET_CLASS (client)->will_quit (client, will_quit);
488 }
489 
490 /**
491  * egg_sm_client_end_session:
492  * @style: a hint at how to end the session
493  * @request_confirmation: whether or not the user should get a chance
494  * to confirm the action
495  *
496  * Requests that the session manager end the current session. @style
497  * indicates how the session should be ended, and
498  * @request_confirmation indicates whether or not the user should be
499  * given a chance to confirm the logout/reboot/shutdown. Both of these
500  * flags are merely hints though; the session manager may choose to
501  * ignore them.
502  *
503  * Return value: %TRUE if the request was sent; %FALSE if it could not
504  * be (eg, because it could not connect to the session manager).
505  **/
506 gboolean
507 egg_sm_client_end_session (EggSMClientEndStyle  style,
508 			   gboolean             request_confirmation)
509 {
510   EggSMClient *client = egg_sm_client_get ();
511 
512   g_return_val_if_fail (EGG_IS_SM_CLIENT (client), FALSE);
513 
514   if (EGG_SM_CLIENT_GET_CLASS (client)->end_session)
515     {
516       return EGG_SM_CLIENT_GET_CLASS (client)->end_session (client, style,
517 							    request_confirmation);
518     }
519   else
520     return FALSE;
521 }
522 
523 /* Signal-emitting callbacks from platform-specific code */
524 
525 GKeyFile *
526 egg_sm_client_save_state (EggSMClient *client)
527 {
528   GKeyFile *state_file;
529   char *group;
530 
531   g_return_val_if_fail (client == global_client, NULL);
532 
533   state_file = g_key_file_new ();
534 
535   g_debug ("Emitting save_state");
536   g_signal_emit (client, signals[SAVE_STATE], 0, state_file);
537   g_debug ("Done emitting save_state");
538 
539   group = g_key_file_get_start_group (state_file);
540   if (group)
541     {
542       g_free (group);
543       return state_file;
544     }
545   else
546     {
547       g_key_file_free (state_file);
548       return NULL;
549     }
550 }
551 
552 void
553 egg_sm_client_quit_requested (EggSMClient *client)
554 {
555   g_return_if_fail (client == global_client);
556 
557   if (!g_signal_has_handler_pending (client, signals[QUIT_REQUESTED], 0, FALSE))
558     {
559       g_debug ("Not emitting quit_requested because no one is listening");
560       egg_sm_client_will_quit (client, TRUE);
561       return;
562     }
563 
564   g_debug ("Emitting quit_requested");
565   g_signal_emit (client, signals[QUIT_REQUESTED], 0);
566   g_debug ("Done emitting quit_requested");
567 }
568 
569 void
570 egg_sm_client_quit_cancelled (EggSMClient *client)
571 {
572   g_return_if_fail (client == global_client);
573 
574   g_debug ("Emitting quit_cancelled");
575   g_signal_emit (client, signals[QUIT_CANCELLED], 0);
576   g_debug ("Done emitting quit_cancelled");
577 }
578 
579 void
580 egg_sm_client_quit (EggSMClient *client)
581 {
582   g_return_if_fail (client == global_client);
583 
584   g_debug ("Emitting quit");
585   g_signal_emit (client, signals[QUIT], 0);
586   g_debug ("Done emitting quit");
587 
588   /* FIXME: should we just call gtk_main_quit() here? */
589 }
590 
591 static void
592 egg_sm_client_debug_handler (const char *log_domain,
593 			     GLogLevelFlags log_level,
594 			     const char *message,
595 			     gpointer user_data)
596 {
597   static int debug = -1;
598 
599   if (debug < 0)
600     debug = (g_getenv ("EGG_SM_CLIENT_DEBUG") != NULL);
601 
602   if (debug)
603     g_log_default_handler (log_domain, log_level, message, NULL);
604 }