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