1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*-
2 *
3 * Copyright (C) 2010 Red Hat, Inc
4 *
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 2, or (at your option)
8 * any later version.
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License
16 * along with this program; if not, see <http://www.gnu.org/licenses/>.
17 */
18
19 #include "config.h"
20
21 #include <errno.h>
22 #include <string.h>
23 #include <unistd.h>
24
25 #include <glib.h>
26 #include <glib-object.h>
27 #include <glib/gi18n.h>
28 #include <gio/gio.h>
29
30 #include "gsm-inhibitor.h"
31 #include "gsm-shell.h"
32
33 #define SHELL_NAME "org.gnome.Shell"
34 #define SHELL_PATH "/org/gnome/Shell"
35 #define SHELL_INTERFACE "org.gnome.Shell"
36
37 #define SHELL_END_SESSION_DIALOG_PATH "/org/gnome/SessionManager/EndSessionDialog"
38 #define SHELL_END_SESSION_DIALOG_INTERFACE "org.gnome.SessionManager.EndSessionDialog"
39
40 #define AUTOMATIC_ACTION_TIMEOUT 60
41
42 #define GSM_SHELL_GET_PRIVATE(o) \
43 (G_TYPE_INSTANCE_GET_PRIVATE ((o), GSM_TYPE_SHELL, GsmShellPrivate))
44
45 struct _GsmShellPrivate
46 {
47 GDBusProxy *end_session_dialog_proxy;
48 GsmStore *inhibitors;
49
50 guint32 is_running : 1;
51
52 gboolean dialog_is_open;
53 GsmShellEndSessionDialogType end_session_dialog_type;
54
55 guint update_idle_id;
56 guint watch_id;
57 };
58
59 enum {
60 PROP_0,
61 PROP_IS_RUNNING
62 };
63
64 enum {
65 END_SESSION_DIALOG_OPENED = 0,
66 END_SESSION_DIALOG_OPEN_FAILED,
67 END_SESSION_DIALOG_CLOSED,
68 END_SESSION_DIALOG_CANCELED,
69 END_SESSION_DIALOG_CONFIRMED_LOGOUT,
70 END_SESSION_DIALOG_CONFIRMED_SHUTDOWN,
71 END_SESSION_DIALOG_CONFIRMED_REBOOT,
72 NUMBER_OF_SIGNALS
73 };
74
75 static guint signals[NUMBER_OF_SIGNALS] = { 0 };
76
77 static void gsm_shell_class_init (GsmShellClass *klass);
78 static void gsm_shell_init (GsmShell *ck);
79 static void gsm_shell_finalize (GObject *object);
80
81 static void queue_end_session_dialog_update (GsmShell *shell);
82
83 G_DEFINE_TYPE (GsmShell, gsm_shell, G_TYPE_OBJECT);
84
85 static void
gsm_shell_get_property(GObject * object,guint prop_id,GValue * value,GParamSpec * pspec)86 gsm_shell_get_property (GObject *object,
87 guint prop_id,
88 GValue *value,
89 GParamSpec *pspec)
90 {
91 GsmShell *shell = GSM_SHELL (object);
92
93 switch (prop_id) {
94 case PROP_IS_RUNNING:
95 g_value_set_boolean (value,
96 shell->priv->is_running);
97 break;
98
99 default:
100 G_OBJECT_WARN_INVALID_PROPERTY_ID (object,
101 prop_id,
102 pspec);
103 }
104 }
105
106 static void
gsm_shell_class_init(GsmShellClass * shell_class)107 gsm_shell_class_init (GsmShellClass *shell_class)
108 {
109 GObjectClass *object_class;
110 GParamSpec *param_spec;
111
112 object_class = G_OBJECT_CLASS (shell_class);
113
114 object_class->finalize = gsm_shell_finalize;
115 object_class->get_property = gsm_shell_get_property;
116
117 param_spec = g_param_spec_boolean ("is-running",
118 "Is running",
119 "Whether GNOME Shell is running in the session",
120 FALSE,
121 G_PARAM_READABLE);
122
123 g_object_class_install_property (object_class, PROP_IS_RUNNING,
124 param_spec);
125
126 signals [END_SESSION_DIALOG_OPENED] =
127 g_signal_new ("end-session-dialog-opened",
128 G_OBJECT_CLASS_TYPE (object_class),
129 G_SIGNAL_RUN_LAST,
130 G_STRUCT_OFFSET (GsmShellClass, end_session_dialog_opened),
131 NULL, NULL, NULL,
132 G_TYPE_NONE, 0);
133
134 signals [END_SESSION_DIALOG_OPEN_FAILED] =
135 g_signal_new ("end-session-dialog-open-failed",
136 G_OBJECT_CLASS_TYPE (object_class),
137 G_SIGNAL_RUN_LAST,
138 G_STRUCT_OFFSET (GsmShellClass, end_session_dialog_open_failed),
139 NULL, NULL, NULL,
140 G_TYPE_NONE, 0);
141
142 signals [END_SESSION_DIALOG_CLOSED] =
143 g_signal_new ("end-session-dialog-closed",
144 G_OBJECT_CLASS_TYPE (object_class),
145 G_SIGNAL_RUN_LAST,
146 G_STRUCT_OFFSET (GsmShellClass, end_session_dialog_closed),
147 NULL, NULL, NULL,
148 G_TYPE_NONE, 0);
149
150 signals [END_SESSION_DIALOG_CANCELED] =
151 g_signal_new ("end-session-dialog-canceled",
152 G_OBJECT_CLASS_TYPE (object_class),
153 G_SIGNAL_RUN_LAST,
154 G_STRUCT_OFFSET (GsmShellClass, end_session_dialog_canceled),
155 NULL, NULL, NULL,
156 G_TYPE_NONE, 0);
157
158 signals [END_SESSION_DIALOG_CONFIRMED_LOGOUT] =
159 g_signal_new ("end-session-dialog-confirmed-logout",
160 G_OBJECT_CLASS_TYPE (object_class),
161 G_SIGNAL_RUN_LAST,
162 G_STRUCT_OFFSET (GsmShellClass, end_session_dialog_confirmed_logout),
163 NULL, NULL, NULL,
164 G_TYPE_NONE, 0);
165
166 signals [END_SESSION_DIALOG_CONFIRMED_SHUTDOWN] =
167 g_signal_new ("end-session-dialog-confirmed-shutdown",
168 G_OBJECT_CLASS_TYPE (object_class),
169 G_SIGNAL_RUN_LAST,
170 G_STRUCT_OFFSET (GsmShellClass, end_session_dialog_confirmed_shutdown),
171 NULL, NULL, NULL,
172 G_TYPE_NONE, 0);
173
174 signals [END_SESSION_DIALOG_CONFIRMED_REBOOT] =
175 g_signal_new ("end-session-dialog-confirmed-reboot",
176 G_OBJECT_CLASS_TYPE (object_class),
177 G_SIGNAL_RUN_LAST,
178 G_STRUCT_OFFSET (GsmShellClass, end_session_dialog_confirmed_reboot),
179 NULL, NULL, NULL,
180 G_TYPE_NONE, 0);
181
182 g_type_class_add_private (shell_class, sizeof (GsmShellPrivate));
183 }
184
185 static void
on_shell_name_vanished(GDBusConnection * connection,const gchar * name,gpointer user_data)186 on_shell_name_vanished (GDBusConnection *connection,
187 const gchar *name,
188 gpointer user_data)
189 {
190 GsmShell *shell = user_data;
191 shell->priv->is_running = FALSE;
192 }
193
194 static void
on_shell_name_appeared(GDBusConnection * connection,const gchar * name,const gchar * name_owner,gpointer user_data)195 on_shell_name_appeared (GDBusConnection *connection,
196 const gchar *name,
197 const gchar *name_owner,
198 gpointer user_data)
199 {
200 GsmShell *shell = user_data;
201 shell->priv->is_running = TRUE;
202 }
203
204 static void
gsm_shell_ensure_connection(GsmShell * shell)205 gsm_shell_ensure_connection (GsmShell *shell)
206 {
207 if (shell->priv->watch_id != 0) {
208 return;
209 }
210
211 shell->priv->watch_id = g_bus_watch_name (G_BUS_TYPE_SESSION,
212 SHELL_NAME,
213 G_BUS_NAME_WATCHER_FLAGS_NONE,
214 on_shell_name_appeared,
215 on_shell_name_vanished,
216 shell, NULL);
217 }
218
219 static void
gsm_shell_init(GsmShell * shell)220 gsm_shell_init (GsmShell *shell)
221 {
222 shell->priv = GSM_SHELL_GET_PRIVATE (shell);
223
224 gsm_shell_ensure_connection (shell);
225 }
226
227 static void
gsm_shell_finalize(GObject * object)228 gsm_shell_finalize (GObject *object)
229 {
230 GsmShell *shell;
231 GObjectClass *parent_class;
232
233 shell = GSM_SHELL (object);
234
235 parent_class = G_OBJECT_CLASS (gsm_shell_parent_class);
236
237 g_object_unref (shell->priv->inhibitors);
238
239 if (shell->priv->watch_id != 0) {
240 g_bus_unwatch_name (shell->priv->watch_id);
241 shell->priv->watch_id = 0;
242 }
243
244 if (parent_class->finalize != NULL) {
245 parent_class->finalize (object);
246 }
247 }
248
249 GsmShell *
gsm_shell_new(void)250 gsm_shell_new (void)
251 {
252 GsmShell *shell;
253
254 shell = g_object_new (GSM_TYPE_SHELL, NULL);
255
256 return shell;
257 }
258
259 GsmShell *
gsm_get_shell(void)260 gsm_get_shell (void)
261 {
262 static GsmShell *shell = NULL;
263
264 if (shell == NULL) {
265 shell = gsm_shell_new ();
266 }
267
268 return g_object_ref (shell);
269 }
270
271 gboolean
gsm_shell_is_running(GsmShell * shell)272 gsm_shell_is_running (GsmShell *shell)
273 {
274 gsm_shell_ensure_connection (shell);
275
276 return shell->priv->is_running;
277 }
278
279 static gboolean
add_inhibitor_to_array(const char * id,GsmInhibitor * inhibitor,GVariantBuilder * builder)280 add_inhibitor_to_array (const char *id,
281 GsmInhibitor *inhibitor,
282 GVariantBuilder *builder)
283 {
284 g_variant_builder_add (builder, "o", gsm_inhibitor_peek_id (inhibitor));
285 return FALSE;
286 }
287
288 static GVariant *
get_array_from_store(GsmStore * inhibitors)289 get_array_from_store (GsmStore *inhibitors)
290 {
291 GVariantBuilder builder;
292
293 g_variant_builder_init (&builder, G_VARIANT_TYPE ("ao"));
294 gsm_store_foreach (inhibitors,
295 (GsmStoreFunc) add_inhibitor_to_array,
296 &builder);
297
298 return g_variant_builder_end (&builder);
299 }
300
301 static void
on_open_finished(GObject * source,GAsyncResult * result,gpointer user_data)302 on_open_finished (GObject *source,
303 GAsyncResult *result,
304 gpointer user_data)
305 {
306 GsmShell *shell = user_data;
307 GError *error;
308
309 if (shell->priv->update_idle_id != 0) {
310 g_source_remove (shell->priv->update_idle_id);
311 shell->priv->update_idle_id = 0;
312 }
313
314 shell->priv->dialog_is_open = FALSE;
315
316 error = NULL;
317 g_dbus_proxy_call_finish (G_DBUS_PROXY (source), result, &error);
318
319 if (error != NULL) {
320 g_warning ("Unable to open shell end session dialog: %s", error->message);
321 g_error_free (error);
322
323 g_signal_emit (G_OBJECT (shell), signals[END_SESSION_DIALOG_OPEN_FAILED], 0);
324 return;
325 }
326
327 g_signal_emit (G_OBJECT (shell), signals[END_SESSION_DIALOG_OPENED], 0);
328 }
329
330 static void
on_end_session_dialog_dbus_signal(GDBusProxy * proxy,gchar * sender_name,gchar * signal_name,GVariant * parameters,GsmShell * shell)331 on_end_session_dialog_dbus_signal (GDBusProxy *proxy,
332 gchar *sender_name,
333 gchar *signal_name,
334 GVariant *parameters,
335 GsmShell *shell)
336 {
337 struct {
338 const char *name;
339 int index;
340 } signal_map[] = {
341 { "Closed", END_SESSION_DIALOG_CLOSED },
342 { "Canceled", END_SESSION_DIALOG_CANCELED },
343 { "ConfirmedLogout", END_SESSION_DIALOG_CONFIRMED_LOGOUT },
344 { "ConfirmedReboot", END_SESSION_DIALOG_CONFIRMED_REBOOT },
345 { "ConfirmedShutdown", END_SESSION_DIALOG_CONFIRMED_SHUTDOWN },
346 { NULL, -1 }
347 };
348 int signal_index = -1;
349 int i;
350
351 for (i = 0; signal_map[i].name != NULL; i++) {
352 if (g_strcmp0 (signal_map[i].name, signal_name) == 0) {
353 signal_index = signal_map[i].index;
354 break;
355 }
356 }
357
358 if (signal_index == -1)
359 return;
360
361 shell->priv->dialog_is_open = FALSE;
362
363 if (shell->priv->update_idle_id != 0) {
364 g_source_remove (shell->priv->update_idle_id);
365 shell->priv->update_idle_id = 0;
366 }
367
368 g_signal_handlers_disconnect_by_func (shell->priv->inhibitors,
369 G_CALLBACK (queue_end_session_dialog_update),
370 shell);
371
372 g_signal_emit (G_OBJECT (shell), signals[signal_index], 0);
373 }
374
375 static void
on_end_session_dialog_name_owner_changed(GDBusProxy * proxy,GParamSpec * pspec,GsmShell * shell)376 on_end_session_dialog_name_owner_changed (GDBusProxy *proxy,
377 GParamSpec *pspec,
378 GsmShell *shell)
379 {
380 gchar *name_owner;
381
382 name_owner = g_dbus_proxy_get_name_owner (proxy);
383 if (name_owner == NULL) {
384 g_clear_object (&shell->priv->end_session_dialog_proxy);
385 }
386
387 g_free (name_owner);
388 }
389
390 static gboolean
on_need_end_session_dialog_update(GsmShell * shell)391 on_need_end_session_dialog_update (GsmShell *shell)
392 {
393 /* No longer need an update */
394 if (shell->priv->update_idle_id == 0)
395 return FALSE;
396
397 shell->priv->update_idle_id = 0;
398
399 gsm_shell_open_end_session_dialog (shell,
400 shell->priv->end_session_dialog_type,
401 shell->priv->inhibitors);
402 return FALSE;
403 }
404
405 static void
queue_end_session_dialog_update(GsmShell * shell)406 queue_end_session_dialog_update (GsmShell *shell)
407 {
408 if (shell->priv->update_idle_id != 0)
409 return;
410
411 shell->priv->update_idle_id = g_idle_add ((GSourceFunc) on_need_end_session_dialog_update,
412 shell);
413 }
414
415 gboolean
gsm_shell_open_end_session_dialog(GsmShell * shell,GsmShellEndSessionDialogType type,GsmStore * inhibitors)416 gsm_shell_open_end_session_dialog (GsmShell *shell,
417 GsmShellEndSessionDialogType type,
418 GsmStore *inhibitors)
419 {
420 GDBusProxy *proxy;
421 GError *error;
422
423 error = NULL;
424
425 if (shell->priv->dialog_is_open) {
426 g_return_val_if_fail (shell->priv->end_session_dialog_type == type,
427 FALSE);
428
429 return TRUE;
430 }
431
432 if (shell->priv->end_session_dialog_proxy == NULL) {
433 proxy = g_dbus_proxy_new_for_bus_sync (G_BUS_TYPE_SESSION,
434 G_DBUS_PROXY_FLAGS_NONE,
435 NULL,
436 SHELL_NAME,
437 SHELL_END_SESSION_DIALOG_PATH,
438 SHELL_END_SESSION_DIALOG_INTERFACE,
439 NULL, &error);
440
441 if (error != NULL) {
442 g_critical ("Could not connect to the shell: %s",
443 error->message);
444 g_error_free (error);
445 return FALSE;
446 }
447
448 shell->priv->end_session_dialog_proxy = proxy;
449
450 g_signal_connect (proxy, "notify::g-name-owner",
451 G_CALLBACK (on_end_session_dialog_name_owner_changed),
452 shell);
453 g_signal_connect (proxy, "g-signal",
454 G_CALLBACK (on_end_session_dialog_dbus_signal),
455 shell);
456 }
457
458 g_dbus_proxy_call (shell->priv->end_session_dialog_proxy,
459 "Open",
460 g_variant_new ("(uuu@ao)",
461 type,
462 0,
463 AUTOMATIC_ACTION_TIMEOUT,
464 get_array_from_store (inhibitors)),
465 G_DBUS_CALL_FLAGS_NONE,
466 G_MAXINT, NULL,
467 on_open_finished, shell);
468
469 g_object_ref (inhibitors);
470
471 if (shell->priv->inhibitors != NULL) {
472 g_signal_handlers_disconnect_by_func (shell->priv->inhibitors,
473 G_CALLBACK (queue_end_session_dialog_update),
474 shell);
475 g_object_unref (shell->priv->inhibitors);
476 }
477
478 shell->priv->inhibitors = inhibitors;
479
480 g_signal_connect_swapped (inhibitors, "added",
481 G_CALLBACK (queue_end_session_dialog_update),
482 shell);
483
484 g_signal_connect_swapped (inhibitors, "removed",
485 G_CALLBACK (queue_end_session_dialog_update),
486 shell);
487
488 shell->priv->dialog_is_open = TRUE;
489 shell->priv->end_session_dialog_type = type;
490
491 return TRUE;
492 }
493
494 void
gsm_shell_close_end_session_dialog(GsmShell * shell)495 gsm_shell_close_end_session_dialog (GsmShell *shell)
496 {
497 if (!shell->priv->end_session_dialog_proxy)
498 return;
499
500 shell->priv->dialog_is_open = FALSE;
501
502 g_dbus_proxy_call (shell->priv->end_session_dialog_proxy,
503 "Close",
504 NULL,
505 G_DBUS_CALL_FLAGS_NONE,
506 -1, NULL, NULL, NULL);
507 }
508