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 }