1 /* -*- Mode: C; indent-tabs-mode: nil; tab-width: 4 -*-
2  *
3  * Copyright (C) 2010-2011 Robert Ancell.
4  * Author: Robert Ancell <robert.ancell@canonical.com>
5  *
6  * This program is free software: you can redistribute it and/or modify it under
7  * the terms of the GNU General Public License as published by the Free Software
8  * Foundation, either version 3 of the License, or (at your option) any later
9  * version. See http://www.gnu.org/copyleft/gpl.html the full text of the
10  * license.
11  */
12 
13 #include <string.h>
14 #include <gio/gio.h>
15 
16 #include "login1.h"
17 
18 #define LOGIN1_SERVICE_NAME "org.freedesktop.login1"
19 #define LOGIN1_OBJECT_NAME "/org/freedesktop/login1"
20 #define LOGIN1_MANAGER_INTERFACE_NAME "org.freedesktop.login1.Manager"
21 
22 enum {
23     SEAT_ADDED,
24     SEAT_REMOVED,
25     LAST_SERVICE_SIGNAL
26 };
27 static guint service_signals[LAST_SERVICE_SIGNAL] = { 0 };
28 
29 typedef struct
30 {
31     /* Connection to bus service is running on */
32     GDBusConnection *connection;
33 
34     /* TRUE if have connected to service */
35     gboolean connected;
36 
37     /* Seats the service is reporting */
38     GList *seats;
39 
40     /* Handle to signal subscription */
41     guint signal_id;
42 } Login1ServicePrivate;
43 
44 enum {
45     CAN_GRAPHICAL_CHANGED,
46     ACTIVE_SESSION_CHANGED,
47     LAST_SEAT_SIGNAL
48 };
49 static guint seat_signals[LAST_SEAT_SIGNAL] = { 0 };
50 
51 typedef struct
52 {
53     /* Connection to bus seat is running on */
54     GDBusConnection *connection;
55 
56     /* Seat Id */
57     gchar *id;
58 
59     /* D-Bus path for this seat */
60     gchar *path;
61 
62     /* Handle to signal subscription */
63     guint signal_id;
64 
65     /* TRUE if can run a graphical display on this seat */
66     gboolean can_graphical;
67 
68     /* TRUE if can do session switching */
69     gboolean can_multi_session;
70 } Login1SeatPrivate;
71 
72 G_DEFINE_TYPE_WITH_PRIVATE (Login1Service, login1_service, G_TYPE_OBJECT)
73 G_DEFINE_TYPE_WITH_PRIVATE (Login1Seat, login1_seat, G_TYPE_OBJECT)
74 
75 G_DEFINE_AUTOPTR_CLEANUP_FUNC (Login1Seat, g_object_unref)
76 
77 static Login1Service *singleton = NULL;
78 
79 Login1Service *
login1_service_get_instance(void)80 login1_service_get_instance (void)
81 {
82     if (!singleton)
83         singleton = g_object_new (LOGIN1_SERVICE_TYPE, NULL);
84     return singleton;
85 }
86 
87 static void
update_property(Login1Seat * seat,const gchar * name,GVariant * value)88 update_property (Login1Seat *seat, const gchar *name, GVariant *value)
89 {
90     Login1SeatPrivate *priv = login1_seat_get_instance_private (seat);
91 
92     if (strcmp (name, "CanGraphical") == 0 && g_variant_is_of_type (value, G_VARIANT_TYPE_BOOLEAN))
93     {
94         priv->can_graphical = g_variant_get_boolean (value);
95         g_signal_emit (seat, seat_signals[CAN_GRAPHICAL_CHANGED], 0);
96     }
97     else if (strcmp (name, "ActiveSession") == 0 && g_variant_is_of_type (value, G_VARIANT_TYPE ("(so)")))
98     {
99         const gchar *login1_session_id;
100         g_variant_get (value, "(&so)", &login1_session_id, NULL);
101         g_signal_emit (seat, seat_signals[ACTIVE_SESSION_CHANGED], 0, login1_session_id);
102     }
103 }
104 
105 static void
seat_properties_changed_cb(GDBusConnection * connection,const gchar * sender_name,const gchar * object_path,const gchar * interface_name,const gchar * signal_name,GVariant * parameters,gpointer user_data)106 seat_properties_changed_cb (GDBusConnection *connection,
107                             const gchar *sender_name,
108                             const gchar *object_path,
109                             const gchar *interface_name,
110                             const gchar *signal_name,
111                             GVariant *parameters,
112                             gpointer user_data)
113 {
114     Login1Seat *seat = user_data;
115     Login1SeatPrivate *priv = login1_seat_get_instance_private (seat);
116 
117     g_autoptr(GVariantIter) iter = NULL;
118     g_autoptr(GVariantIter) invalidated_properties = NULL;
119     g_variant_get (parameters, "(sa{sv}as)", NULL, &iter, &invalidated_properties);
120 
121     const gchar *name;
122     GVariant *value;
123     while (g_variant_iter_loop (iter, "{&sv}", &name, &value))
124         update_property (seat, name, value);
125 
126     while (g_variant_iter_loop (invalidated_properties, "&s", &name))
127     {
128         g_autoptr(GError) error = NULL;
129         g_autoptr(GVariant) result = g_dbus_connection_call_sync (connection,
130                                                                   LOGIN1_SERVICE_NAME,
131                                                                   priv->path,
132                                                                   "org.freedesktop.DBus.Properties",
133                                                                   "Get",
134                                                                   g_variant_new ("(ss)", "org.freedesktop.login1.Seat", name),
135                                                                   G_VARIANT_TYPE ("(v)"),
136                                                                   G_DBUS_CALL_FLAGS_NONE,
137                                                                   -1,
138                                                                   NULL,
139                                                                   &error);
140         if (error)
141             g_warning ("Error updating seat property %s: %s", name, error->message);
142         if (result)
143         {
144             g_autoptr(GVariant) v = NULL;
145             g_variant_get (result, "(v)", &v);
146             update_property (seat, name, v);
147         }
148     }
149 }
150 
151 static Login1Seat *
add_seat(Login1Service * service,const gchar * id,const gchar * path)152 add_seat (Login1Service *service, const gchar *id, const gchar *path)
153 {
154     Login1ServicePrivate *priv = login1_service_get_instance_private (service);
155 
156     Login1Seat *seat = g_object_new (LOGIN1_SEAT_TYPE, NULL);
157     Login1SeatPrivate *s_priv = login1_seat_get_instance_private (seat);
158 
159     s_priv->connection = g_object_ref (priv->connection);
160     s_priv->id = g_strdup (id);
161     s_priv->path = g_strdup (path);
162 
163     s_priv->signal_id = g_dbus_connection_signal_subscribe (s_priv->connection,
164                                                             LOGIN1_SERVICE_NAME,
165                                                             "org.freedesktop.DBus.Properties",
166                                                             "PropertiesChanged",
167                                                             path,
168                                                             "org.freedesktop.login1.Seat",
169                                                             G_DBUS_SIGNAL_FLAGS_NONE,
170                                                             seat_properties_changed_cb,
171                                                             g_object_ref (seat),
172                                                             g_object_unref);
173 
174     /* Get properties for this seat */
175     g_autoptr(GError) error = NULL;
176     g_autoptr(GVariant) result = g_dbus_connection_call_sync (s_priv->connection,
177                                                               LOGIN1_SERVICE_NAME,
178                                                               path,
179                                                               "org.freedesktop.DBus.Properties",
180                                                               "GetAll",
181                                                               g_variant_new ("(s)", "org.freedesktop.login1.Seat"),
182                                                               G_VARIANT_TYPE ("(a{sv})"),
183                                                               G_DBUS_CALL_FLAGS_NONE,
184                                                               -1,
185                                                               NULL,
186                                                               &error);
187     if (error)
188         g_warning ("Failed to get seat properties: %s", error->message);
189     if (result)
190     {
191         g_autoptr(GVariantIter) properties = NULL;
192         g_variant_get (result, "(a{sv})", &properties);
193 
194         const gchar *name;
195         GVariant *value;
196         while (g_variant_iter_loop (properties, "{&sv}", &name, &value))
197         {
198             if (strcmp (name, "CanGraphical") == 0 && g_variant_is_of_type (value, G_VARIANT_TYPE_BOOLEAN))
199                 s_priv->can_graphical = g_variant_get_boolean (value);
200             else if (strcmp (name, "CanMultiSession") == 0 && g_variant_is_of_type (value, G_VARIANT_TYPE_BOOLEAN))
201                 s_priv->can_multi_session = g_variant_get_boolean (value);
202         }
203     }
204 
205     priv->seats = g_list_append (priv->seats, seat);
206 
207     return seat;
208 }
209 
210 static void
signal_cb(GDBusConnection * connection,const gchar * sender_name,const gchar * object_path,const gchar * interface_name,const gchar * signal_name,GVariant * parameters,gpointer user_data)211 signal_cb (GDBusConnection *connection,
212            const gchar *sender_name,
213            const gchar *object_path,
214            const gchar *interface_name,
215            const gchar *signal_name,
216            GVariant *parameters,
217            gpointer user_data)
218 {
219     Login1Service *service = user_data;
220     Login1ServicePrivate *priv = login1_service_get_instance_private (service);
221 
222     if (strcmp (signal_name, "SeatNew") == 0)
223     {
224         const gchar *id, *path;
225         g_variant_get (parameters, "(&s&o)", &id, &path);
226 
227         Login1Seat *seat = login1_service_get_seat (service, id);
228         if (!seat)
229         {
230             seat = add_seat (service, id, path);
231             g_signal_emit (service, service_signals[SEAT_ADDED], 0, seat);
232         }
233     }
234     else if (strcmp (signal_name, "SeatRemoved") == 0)
235     {
236         const gchar *id, *path;
237         g_variant_get (parameters, "(&s&o)", &id, &path);
238 
239         g_autoptr(Login1Seat) seat = login1_service_get_seat (service, id);
240         if (seat)
241         {
242             priv->seats = g_list_remove (priv->seats, seat);
243             g_signal_emit (service, service_signals[SEAT_REMOVED], 0, seat);
244         }
245     }
246 }
247 
248 gboolean
login1_service_connect(Login1Service * service)249 login1_service_connect (Login1Service *service)
250 {
251     Login1ServicePrivate *priv = login1_service_get_instance_private (service);
252 
253     g_return_val_if_fail (service != NULL, FALSE);
254 
255     if (priv->connected)
256         return TRUE;
257 
258     g_autoptr(GError) error = NULL;
259     priv->connection = g_bus_get_sync (G_BUS_TYPE_SYSTEM, NULL, &error);
260     if (error)
261         g_warning ("Failed to get system bus: %s", error->message);
262     if (!priv->connection)
263         return FALSE;
264 
265     priv->signal_id = g_dbus_connection_signal_subscribe (priv->connection,
266                                                           LOGIN1_SERVICE_NAME,
267                                                           LOGIN1_MANAGER_INTERFACE_NAME,
268                                                           NULL,
269                                                           LOGIN1_OBJECT_NAME,
270                                                           NULL,
271                                                           G_DBUS_SIGNAL_FLAGS_NONE,
272                                                           signal_cb,
273                                                           g_object_ref (service),
274                                                           g_object_unref);
275 
276     g_autoptr(GVariant) result = g_dbus_connection_call_sync (priv->connection,
277                                                               LOGIN1_SERVICE_NAME,
278                                                               LOGIN1_OBJECT_NAME,
279                                                               LOGIN1_MANAGER_INTERFACE_NAME,
280                                                               "ListSeats",
281                                                               g_variant_new ("()"),
282                                                               G_VARIANT_TYPE ("(a(so))"),
283                                                               G_DBUS_CALL_FLAGS_NONE,
284                                                               -1,
285                                                               NULL,
286                                                               &error);
287     if (error)
288         g_warning ("Failed to get list of logind seats: %s", error->message);
289     if (!result)
290         return FALSE;
291 
292     g_autoptr(GVariantIter) seat_iter = NULL;
293     g_variant_get (result, "(a(so))", &seat_iter);
294 
295     const gchar *id, *path;
296     while (g_variant_iter_loop (seat_iter, "(&s&o)", &id, &path))
297         add_seat (service, id, path);
298 
299     priv->connected = TRUE;
300 
301     return TRUE;
302 }
303 
304 gboolean
login1_service_get_is_connected(Login1Service * service)305 login1_service_get_is_connected (Login1Service *service)
306 {
307     Login1ServicePrivate *priv = login1_service_get_instance_private (service);
308     g_return_val_if_fail (service != NULL, FALSE);
309     return priv->connected;
310 }
311 
312 GList *
login1_service_get_seats(Login1Service * service)313 login1_service_get_seats (Login1Service *service)
314 {
315     Login1ServicePrivate *priv = login1_service_get_instance_private (service);
316     g_return_val_if_fail (service != NULL, NULL);
317     return priv->seats;
318 }
319 
320 Login1Seat *
login1_service_get_seat(Login1Service * service,const gchar * id)321 login1_service_get_seat (Login1Service *service, const gchar *id)
322 {
323     Login1ServicePrivate *priv = login1_service_get_instance_private (service);
324 
325     g_return_val_if_fail (service != NULL, NULL);
326 
327     for (GList *link = priv->seats; link; link = link->next)
328     {
329         Login1Seat *seat = link->data;
330         Login1SeatPrivate *s_priv = login1_seat_get_instance_private (seat);
331 
332         if (strcmp (s_priv->id, id) == 0)
333             return seat;
334     }
335 
336     return NULL;
337 }
338 
339 void
login1_service_lock_session(Login1Service * service,const gchar * session_id)340 login1_service_lock_session (Login1Service *service, const gchar *session_id)
341 {
342     Login1ServicePrivate *priv = login1_service_get_instance_private (service);
343 
344     g_return_if_fail (service != NULL);
345     g_return_if_fail (session_id != NULL);
346 
347     g_debug ("Locking login1 session %s", session_id);
348 
349     if (!session_id)
350         return;
351 
352     g_autoptr(GError) error = NULL;
353     g_autoptr(GVariant) result = g_dbus_connection_call_sync (priv->connection,
354                                                               LOGIN1_SERVICE_NAME,
355                                                               LOGIN1_OBJECT_NAME,
356                                                               LOGIN1_MANAGER_INTERFACE_NAME,
357                                                               "LockSession",
358                                                               g_variant_new ("(s)", session_id),
359                                                               G_VARIANT_TYPE ("()"),
360                                                               G_DBUS_CALL_FLAGS_NONE,
361                                                               -1,
362                                                               NULL,
363                                                               &error);
364     if (error)
365         g_warning ("Error locking login1 session: %s", error->message);
366 }
367 
368 void
login1_service_unlock_session(Login1Service * service,const gchar * session_id)369 login1_service_unlock_session (Login1Service *service, const gchar *session_id)
370 {
371     Login1ServicePrivate *priv = login1_service_get_instance_private (service);
372 
373     g_return_if_fail (service != NULL);
374     g_return_if_fail (session_id != NULL);
375 
376     g_debug ("Unlocking login1 session %s", session_id);
377 
378     if (!session_id)
379         return;
380 
381     g_autoptr(GError) error = NULL;
382     g_autoptr(GVariant) result = g_dbus_connection_call_sync (priv->connection,
383                                                               LOGIN1_SERVICE_NAME,
384                                                               LOGIN1_OBJECT_NAME,
385                                                               LOGIN1_MANAGER_INTERFACE_NAME,
386                                                               "UnlockSession",
387                                                               g_variant_new ("(s)", session_id),
388                                                               G_VARIANT_TYPE ("()"),
389                                                               G_DBUS_CALL_FLAGS_NONE,
390                                                               -1,
391                                                               NULL,
392                                                               &error);
393     if (error)
394         g_warning ("Error unlocking login1 session: %s", error->message);
395 }
396 
397 void
login1_service_activate_session(Login1Service * service,const gchar * session_id)398 login1_service_activate_session (Login1Service *service, const gchar *session_id)
399 {
400     Login1ServicePrivate *priv = login1_service_get_instance_private (service);
401 
402     g_return_if_fail (service != NULL);
403     g_return_if_fail (session_id != NULL);
404 
405     g_debug ("Activating login1 session %s", session_id);
406 
407     if (!session_id)
408         return;
409 
410     g_autoptr(GError) error = NULL;
411     g_autoptr(GVariant) result = g_dbus_connection_call_sync (priv->connection,
412                                                               LOGIN1_SERVICE_NAME,
413                                                               LOGIN1_OBJECT_NAME,
414                                                               LOGIN1_MANAGER_INTERFACE_NAME,
415                                                               "ActivateSession",
416                                                               g_variant_new ("(s)", session_id),
417                                                               G_VARIANT_TYPE ("()"),
418                                                               G_DBUS_CALL_FLAGS_NONE,
419                                                               -1,
420                                                               NULL,
421                                                               &error);
422     if (error)
423         g_warning ("Error activating login1 session: %s", error->message);
424 }
425 
426 void
login1_service_terminate_session(Login1Service * service,const gchar * session_id)427 login1_service_terminate_session (Login1Service *service, const gchar *session_id)
428 {
429     Login1ServicePrivate *priv = login1_service_get_instance_private (service);
430 
431     g_return_if_fail (service != NULL);
432     g_return_if_fail (session_id != NULL);
433 
434     g_debug ("Terminating login1 session %s", session_id);
435 
436     if (!session_id)
437         return;
438 
439     g_autoptr(GError) error = NULL;
440     g_autoptr(GVariant) result = g_dbus_connection_call_sync (priv->connection,
441                                                               LOGIN1_SERVICE_NAME,
442                                                               LOGIN1_OBJECT_NAME,
443                                                               LOGIN1_MANAGER_INTERFACE_NAME,
444                                                               "TerminateSession",
445                                                               g_variant_new ("(s)", session_id),
446                                                               G_VARIANT_TYPE ("()"),
447                                                               G_DBUS_CALL_FLAGS_NONE,
448                                                               -1,
449                                                               NULL,
450                                                               &error);
451     if (error)
452         g_warning ("Error terminating login1 session: %s", error->message);
453 }
454 
455 static void
login1_service_init(Login1Service * service)456 login1_service_init (Login1Service *service)
457 {
458 }
459 
460 static void
login1_service_finalize(GObject * object)461 login1_service_finalize (GObject *object)
462 {
463     Login1Service *self = LOGIN1_SERVICE (object);
464     Login1ServicePrivate *priv = login1_service_get_instance_private (self);
465 
466     g_list_free_full (priv->seats, g_object_unref);
467     g_dbus_connection_signal_unsubscribe (priv->connection, priv->signal_id);
468     g_clear_object (&priv->connection);
469 
470     G_OBJECT_CLASS (login1_service_parent_class)->finalize (object);
471 }
472 
473 static void
login1_service_class_init(Login1ServiceClass * klass)474 login1_service_class_init (Login1ServiceClass *klass)
475 {
476     GObjectClass *object_class = G_OBJECT_CLASS (klass);
477 
478     object_class->finalize = login1_service_finalize;
479 
480     service_signals[SEAT_ADDED] =
481         g_signal_new (LOGIN1_SERVICE_SIGNAL_SEAT_ADDED,
482                       G_TYPE_FROM_CLASS (klass),
483                       G_SIGNAL_RUN_LAST,
484                       G_STRUCT_OFFSET (Login1ServiceClass, seat_added),
485                       NULL, NULL,
486                       NULL,
487                       G_TYPE_NONE, 1, LOGIN1_SEAT_TYPE);
488     service_signals[SEAT_REMOVED] =
489         g_signal_new (LOGIN1_SERVICE_SIGNAL_SEAT_REMOVED,
490                       G_TYPE_FROM_CLASS (klass),
491                       G_SIGNAL_RUN_LAST,
492                       G_STRUCT_OFFSET (Login1ServiceClass, seat_removed),
493                       NULL, NULL,
494                       NULL,
495                       G_TYPE_NONE, 1, LOGIN1_SEAT_TYPE);
496 }
497 
498 const gchar *
login1_seat_get_id(Login1Seat * seat)499 login1_seat_get_id (Login1Seat *seat)
500 {
501     Login1SeatPrivate *priv = login1_seat_get_instance_private (seat);
502     g_return_val_if_fail (seat != NULL, NULL);
503     return priv->id;
504 }
505 
506 gboolean
login1_seat_get_can_graphical(Login1Seat * seat)507 login1_seat_get_can_graphical (Login1Seat *seat)
508 {
509     Login1SeatPrivate *priv = login1_seat_get_instance_private (seat);
510     g_return_val_if_fail (seat != NULL, FALSE);
511     return priv->can_graphical;
512 }
513 
514 gboolean
login1_seat_get_can_multi_session(Login1Seat * seat)515 login1_seat_get_can_multi_session (Login1Seat *seat)
516 {
517     Login1SeatPrivate *priv = login1_seat_get_instance_private (seat);
518     g_return_val_if_fail (seat != NULL, FALSE);
519     return priv->can_multi_session;
520 }
521 
522 static void
login1_seat_init(Login1Seat * seat)523 login1_seat_init (Login1Seat *seat)
524 {
525 }
526 
527 static void
login1_seat_finalize(GObject * object)528 login1_seat_finalize (GObject *object)
529 {
530     Login1Seat *self = LOGIN1_SEAT (object);
531     Login1SeatPrivate *priv = login1_seat_get_instance_private (self);
532 
533     g_clear_pointer (&priv->id, g_free);
534     g_clear_pointer (&priv->path, g_free);
535     g_dbus_connection_signal_unsubscribe (priv->connection, priv->signal_id);
536     g_clear_object (&priv->connection);
537 
538     G_OBJECT_CLASS (login1_seat_parent_class)->finalize (object);
539 }
540 
541 static void
login1_seat_class_init(Login1SeatClass * klass)542 login1_seat_class_init (Login1SeatClass *klass)
543 {
544     GObjectClass *object_class = G_OBJECT_CLASS (klass);
545 
546     object_class->finalize = login1_seat_finalize;
547 
548     seat_signals[CAN_GRAPHICAL_CHANGED] =
549         g_signal_new (LOGIN1_SEAT_SIGNAL_CAN_GRAPHICAL_CHANGED,
550                       G_TYPE_FROM_CLASS (klass),
551                       G_SIGNAL_RUN_LAST,
552                       G_STRUCT_OFFSET (Login1SeatClass, can_graphical_changed),
553                       NULL, NULL,
554                       NULL,
555                       G_TYPE_NONE, 0);
556 
557     seat_signals[ACTIVE_SESSION_CHANGED] =
558         g_signal_new (LOGIN1_SIGNAL_ACTIVE_SESION_CHANGED,
559                       G_TYPE_FROM_CLASS (klass),
560                       G_SIGNAL_RUN_LAST,
561                       G_STRUCT_OFFSET (Login1SeatClass, active_session_changed),
562                       NULL, NULL,
563                       NULL,
564                       G_TYPE_NONE, 1, G_TYPE_STRING);
565 }
566