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