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