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 }