]> rtime.felk.cvut.cz Git - sojka/lightdm.git/blob - liblightdm-gobject/user.c
Use paranoid error checking in liblightdm-gobject
[sojka/lightdm.git] / liblightdm-gobject / user.c
1 /*
2  * Copyright (C) 2010 Robert Ancell.
3  * Author: Robert Ancell <robert.ancell@canonical.com>
4  * 
5  * This library is free software; you can redistribute it and/or modify it under
6  * the terms of the GNU Lesser General Public License as published by the Free
7  * Software Foundation; either version 3 of the License, or (at your option) any
8  * later version. See http://www.gnu.org/copyleft/lgpl.html the full text of the
9  * license.
10  */
11
12 #include <config.h>
13
14 #include <errno.h>
15 #include <string.h>
16 #include <sys/utsname.h>
17 #include <pwd.h>
18 #include <gio/gio.h>
19
20 #include "lightdm/user.h"
21
22 enum
23 {
24     LIST_PROP_0,
25     LIST_PROP_NUM_USERS,
26     LIST_PROP_USERS,
27 };
28
29 enum
30 {
31     USER_PROP_0,
32     USER_PROP_NAME,
33     USER_PROP_REAL_NAME,
34     USER_PROP_DISPLAY_NAME,
35     USER_PROP_HOME_DIRECTORY,
36     USER_PROP_IMAGE,
37     USER_PROP_LANGUAGE,
38     USER_PROP_LAYOUT,
39     USER_PROP_SESSION,
40     USER_PROP_LOGGED_IN
41 };
42
43 enum
44 {
45     USER_ADDED,
46     USER_CHANGED,
47     USER_REMOVED,
48     LAST_LIST_SIGNAL
49 };
50 static guint list_signals[LAST_LIST_SIGNAL] = { 0 };
51
52 enum
53 {
54     CHANGED,
55     LAST_USER_SIGNAL
56 };
57 static guint user_signals[LAST_USER_SIGNAL] = { 0 };
58
59 typedef struct
60 {
61     /* Connection to AccountsService */
62     GDBusProxy *accounts_service_proxy;
63     GList *user_account_objects;
64
65     /* Connection to DisplayManager */
66     GDBusProxy *display_manager_proxy;
67
68     /* File monitor for password file */
69     GFileMonitor *passwd_monitor;
70   
71     /* TRUE if have scanned users */
72     gboolean have_users;
73
74     /* List of users */
75     GList *users;
76
77     /* List of sessions */
78     GList *sessions;
79 } LightDMUserListPrivate;
80
81 typedef struct
82 {
83     GDBusProxy *proxy;
84     LightDMUser *user;
85 } UserAccountObject;
86
87 typedef struct
88 {
89     LightDMUserList *user_list;
90
91     gchar *name;
92     gchar *real_name;
93     gchar *home_directory;
94     gchar *image;
95
96     GKeyFile *dmrc_file;
97     gchar *language;
98     gchar *layout;
99     gchar *session;
100 } LightDMUserPrivate;
101
102 typedef struct
103 {
104     GObject parent_instance;
105     gchar *path;
106     gchar *username;
107 } Session;
108
109 typedef struct
110 {
111     GObjectClass parent_class;
112 } SessionClass;
113
114 G_DEFINE_TYPE (LightDMUserList, lightdm_user_list, G_TYPE_OBJECT);
115 G_DEFINE_TYPE (LightDMUser, lightdm_user, G_TYPE_OBJECT);
116 #define SESSION(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), session_get_type (), Session))
117 GType session_get_type (void);
118 G_DEFINE_TYPE (Session, session, G_TYPE_OBJECT);
119
120 #define GET_LIST_PRIVATE(obj) G_TYPE_INSTANCE_GET_PRIVATE ((obj), LIGHTDM_TYPE_USER_LIST, LightDMUserListPrivate)
121 #define GET_USER_PRIVATE(obj) G_TYPE_INSTANCE_GET_PRIVATE ((obj), LIGHTDM_TYPE_USER, LightDMUserPrivate)
122
123 #define PASSWD_FILE      "/etc/passwd"
124 #define USER_CONFIG_FILE "/etc/lightdm/users.conf"
125
126 static LightDMUserList *singleton = NULL;
127
128 /**
129  * lightdm_user_list_get_instance:
130  *
131  * Get the user list.
132  *
133  * Return value: (tranfer none): the #LightDMUserList
134  **/
135 LightDMUserList *
136 lightdm_user_list_get_instance (void)
137 {
138     if (!singleton)
139         singleton = g_object_new (LIGHTDM_TYPE_USER_LIST, NULL);
140     return singleton;
141 }
142
143 static LightDMUser *
144 get_user_by_name (LightDMUserList *user_list, const gchar *username)
145 {
146     LightDMUserListPrivate *priv = GET_LIST_PRIVATE (user_list);
147     GList *link;
148   
149     for (link = priv->users; link; link = link->next)
150     {
151         LightDMUser *user = link->data;
152         if (strcmp (lightdm_user_get_name (user), username) == 0)
153             return user;
154     }
155
156     return NULL;
157 }
158   
159 static gint
160 compare_user (gconstpointer a, gconstpointer b)
161 {
162     LightDMUser *user_a = (LightDMUser *) a, *user_b = (LightDMUser *) b;
163     return strcmp (lightdm_user_get_display_name (user_a), lightdm_user_get_display_name (user_b));
164 }
165
166 static gboolean
167 update_passwd_user (LightDMUser *user, const gchar *real_name, const gchar *home_directory, const gchar *image)
168 {
169     LightDMUserPrivate *priv = GET_USER_PRIVATE (user);
170
171     if (g_strcmp0 (lightdm_user_get_real_name (user), real_name) == 0 &&
172         g_strcmp0 (lightdm_user_get_home_directory (user), home_directory) == 0 &&
173         g_strcmp0 (lightdm_user_get_image (user), image) == 0)
174         return FALSE;
175
176     g_free (priv->real_name);
177     priv->real_name = g_strdup (real_name);
178     g_free (priv->home_directory);
179     priv->home_directory = g_strdup (home_directory);
180     g_free (priv->image);
181     priv->image = g_strdup (image);
182
183     return TRUE;
184 }
185
186 static void
187 user_changed_cb (LightDMUser *user, LightDMUserList *user_list)
188 {
189     g_signal_emit (user_list, list_signals[USER_CHANGED], 0, user);
190 }
191
192 static void
193 load_passwd_file (LightDMUserList *user_list, gboolean emit_add_signal)
194 {
195     LightDMUserListPrivate *priv = GET_LIST_PRIVATE (user_list);
196     GKeyFile *config;
197     gchar *value;
198     gint minimum_uid;
199     gchar **hidden_users, **hidden_shells;
200     GList *users = NULL, *old_users, *new_users = NULL, *changed_users = NULL, *link;
201     GError *error = NULL;
202
203     g_debug ("Loading user config from %s", USER_CONFIG_FILE);
204
205     config = g_key_file_new ();
206     g_key_file_load_from_file (config, USER_CONFIG_FILE, G_KEY_FILE_NONE, &error);
207     if (error && !g_error_matches (error, G_FILE_ERROR, G_FILE_ERROR_NOENT))
208         g_warning ("Failed to load configuration from %s: %s", USER_CONFIG_FILE, error->message); // FIXME: Don't make warning on no file, just info
209     g_clear_error (&error);
210
211     if (g_key_file_has_key (config, "UserList", "minimum-uid", NULL))
212         minimum_uid = g_key_file_get_integer (config, "UserList", "minimum-uid", NULL);
213     else
214         minimum_uid = 500;
215
216     value = g_key_file_get_string (config, "UserList", "hidden-users", NULL);
217     if (!value)
218         value = g_strdup ("nobody nobody4 noaccess");
219     hidden_users = g_strsplit (value, " ", -1);
220     g_free (value);
221
222     value = g_key_file_get_string (config, "UserList", "hidden-shells", NULL);
223     if (!value)
224         value = g_strdup ("/bin/false /usr/sbin/nologin");
225     hidden_shells = g_strsplit (value, " ", -1);
226     g_free (value);
227
228     g_key_file_free (config);
229
230     setpwent ();
231
232     while (TRUE)
233     {
234         struct passwd *entry;
235         LightDMUser *user;
236         LightDMUserPrivate *user_priv;
237         char **tokens;
238         gchar *real_name, *image;
239         int i;
240
241         errno = 0;
242         entry = getpwent ();
243         if (!entry)
244             break;
245
246         /* Ignore system users */
247         if (entry->pw_uid < minimum_uid)
248             continue;
249
250         /* Ignore users disabled by shell */
251         if (entry->pw_shell)
252         {
253             for (i = 0; hidden_shells[i] && strcmp (entry->pw_shell, hidden_shells[i]) != 0; i++);
254             if (hidden_shells[i])
255                 continue;
256         }
257
258         /* Ignore certain users */
259         for (i = 0; hidden_users[i] && strcmp (entry->pw_name, hidden_users[i]) != 0; i++);
260         if (hidden_users[i])
261             continue;
262
263         tokens = g_strsplit (entry->pw_gecos, ",", -1);
264         if (tokens[0] != NULL && tokens[0][0] != '\0')
265             real_name = g_strdup (tokens[0]);
266         else
267             real_name = g_strdup ("");
268         g_strfreev (tokens);
269
270         image = g_build_filename (entry->pw_dir, ".face", NULL);
271         if (!g_file_test (image, G_FILE_TEST_EXISTS))
272         {
273             g_free (image);
274             image = g_build_filename (entry->pw_dir, ".face.icon", NULL);
275             if (!g_file_test (image, G_FILE_TEST_EXISTS))
276             {
277                 g_free (image);
278                 image = NULL;
279             }
280         }
281
282         user = g_object_new (LIGHTDM_TYPE_USER, NULL);
283         user_priv = GET_USER_PRIVATE (user);
284         user_priv->user_list = user_list;
285         g_free (user_priv->name);
286         user_priv->name = g_strdup (entry->pw_name);
287         g_free (user_priv->real_name);
288         user_priv->real_name = real_name;
289         g_free (user_priv->home_directory);
290         user_priv->home_directory = g_strdup (entry->pw_dir);
291         g_free (user_priv->image);
292         user_priv->image = image;
293
294         /* Update existing users if have them */
295         for (link = priv->users; link; link = link->next)
296         {
297             LightDMUser *info = link->data;
298             if (strcmp (lightdm_user_get_name (info), lightdm_user_get_name (user)) == 0)
299             {
300                 if (update_passwd_user (info, lightdm_user_get_real_name (user), lightdm_user_get_home_directory (user), lightdm_user_get_image (user)))
301                     changed_users = g_list_insert_sorted (changed_users, info, compare_user);
302                 g_object_unref (user);
303                 user = info;
304                 break;
305             }
306         }
307         if (!link)
308         {
309             /* Only notify once we have loaded the user list */
310             if (priv->have_users)
311                 new_users = g_list_insert_sorted (new_users, user, compare_user);
312         }
313         users = g_list_insert_sorted (users, user, compare_user);
314     }
315     g_strfreev (hidden_users);
316     g_strfreev (hidden_shells);
317
318     if (errno != 0)
319         g_warning ("Failed to read password database: %s", strerror (errno));
320
321     endpwent ();
322
323     /* Use new user list */
324     old_users = priv->users;
325     priv->users = users;
326   
327     /* Notify of changes */
328     for (link = new_users; link; link = link->next)
329     {
330         LightDMUser *info = link->data;
331         g_debug ("User %s added", lightdm_user_get_name (info));
332         g_signal_connect (info, "changed", G_CALLBACK (user_changed_cb), user_list);
333         if (emit_add_signal)
334             g_signal_emit (user_list, list_signals[USER_ADDED], 0, info);
335     }
336     g_list_free (new_users);
337     for (link = changed_users; link; link = link->next)
338     {
339         LightDMUser *info = link->data;
340         g_debug ("User %s changed", lightdm_user_get_name (info));
341         g_signal_emit (info, user_signals[CHANGED], 0);
342     }
343     g_list_free (changed_users);
344     for (link = old_users; link; link = link->next)
345     {
346         GList *new_link;
347
348         /* See if this user is in the current list */
349         for (new_link = priv->users; new_link; new_link = new_link->next)
350         {
351             if (new_link->data == link->data)
352                 break;
353         }
354
355         if (!new_link)
356         {
357             LightDMUser *info = link->data;
358             g_debug ("User %s removed", lightdm_user_get_name (info));
359             g_signal_emit (user_list, list_signals[USER_REMOVED], 0, info);
360             g_object_unref (info);
361         }
362     }
363     g_list_free (old_users);
364 }
365
366 static void
367 passwd_changed_cb (GFileMonitor *monitor, GFile *file, GFile *other_file, GFileMonitorEvent event_type, LightDMUserList *user_list)
368 {
369     if (event_type == G_FILE_MONITOR_EVENT_CHANGES_DONE_HINT)
370     {
371         g_debug ("%s changed, reloading user list", g_file_get_path (file));
372         load_passwd_file (user_list, TRUE);
373     }
374 }
375
376 static gboolean
377 update_user (UserAccountObject *object)
378 {
379     LightDMUserPrivate *priv = GET_USER_PRIVATE (object->user);
380     GVariant *result, *value;
381     GVariantIter *iter;
382     gchar *name;
383     GError *error = NULL;
384
385     result = g_dbus_connection_call_sync (g_dbus_proxy_get_connection (object->proxy),
386                                           "org.freedesktop.Accounts",
387                                           g_dbus_proxy_get_object_path (object->proxy),
388                                           "org.freedesktop.DBus.Properties",
389                                           "GetAll",
390                                           g_variant_new ("(s)", "org.freedesktop.Accounts.User"),
391                                           G_VARIANT_TYPE ("(a{sv})"),
392                                           G_DBUS_CALL_FLAGS_NONE,
393                                           -1,
394                                           NULL,
395                                           &error);
396     if (error)
397         g_warning ("Error updating user %s: %s", g_dbus_proxy_get_object_path (object->proxy), error->message);
398     g_clear_error (&error);
399     if (!result)
400         return FALSE;
401
402     g_variant_get (result, "(a{sv})", &iter);
403     while (g_variant_iter_loop (iter, "{&sv}", &name, &value))
404     {
405         if (strcmp (name, "UserName") == 0 && g_variant_is_of_type (value, G_VARIANT_TYPE_STRING))
406         {
407             gchar *user_name;
408             g_variant_get (value, "&s", &user_name);
409             g_free (priv->name);
410             priv->name = g_strdup (user_name);
411         }
412         else if (strcmp (name, "RealName") == 0 && g_variant_is_of_type (value, G_VARIANT_TYPE_STRING))
413         {
414             gchar *real_name;
415             g_variant_get (value, "&s", &real_name);
416             g_free (priv->real_name);
417             priv->real_name = g_strdup (real_name);
418         }
419         else if (strcmp (name, "HomeDirectory") == 0 && g_variant_is_of_type (value, G_VARIANT_TYPE_STRING))
420         {
421             gchar *home_directory;
422             g_variant_get (value, "&s", &home_directory);
423             g_free (priv->home_directory);
424             priv->home_directory = g_strdup (home_directory);
425         }
426         else if (strcmp (name, "IconFile") == 0 && g_variant_is_of_type (value, G_VARIANT_TYPE_STRING))
427         {
428             gchar *icon_file;
429             g_variant_get (value, "&s", &icon_file);
430             g_free (priv->image);
431             if (strcmp (icon_file, "") == 0)
432                 priv->image = NULL;
433             else
434                 priv->image = g_strdup (icon_file);
435         }
436     }
437     g_variant_iter_free (iter);
438
439     g_variant_unref (result);
440
441     return TRUE;
442 }
443
444 static void
445 user_signal_cb (GDBusProxy *proxy, gchar *sender_name, gchar *signal_name, GVariant *parameters, UserAccountObject *object)
446 {
447     if (strcmp (signal_name, "Changed") == 0)
448     {
449         if (g_variant_is_of_type (parameters, G_VARIANT_TYPE ("()")))
450         {
451             g_debug ("User %s changed", g_dbus_proxy_get_object_path (object->proxy));
452             update_user (object);
453             g_signal_emit (object->user, user_signals[CHANGED], 0);
454         }
455         else
456             g_warning ("Got org.freedesktop.Accounts.User signal Changed with unknown parameters %s", g_variant_get_type_string (parameters));
457     }
458 }
459
460 static UserAccountObject *
461 user_account_object_new (LightDMUserList *user_list, const gchar *path)
462 {
463     GDBusProxy *proxy;
464     UserAccountObject *object;
465     GError *error = NULL;
466
467     proxy = g_dbus_proxy_new_for_bus_sync (G_BUS_TYPE_SYSTEM,
468                                            G_DBUS_PROXY_FLAGS_NONE,
469                                            NULL,
470                                            "org.freedesktop.Accounts",
471                                            path,
472                                            "org.freedesktop.Accounts.User",
473                                            NULL,
474                                            &error);
475     if (error)
476         g_warning ("Error getting user %s: %s", path, error->message);
477     g_clear_error (&error);
478     if (!proxy)
479         return NULL;
480
481     object = g_malloc0 (sizeof (UserAccountObject));  
482     object->user = g_object_new (LIGHTDM_TYPE_USER, NULL);
483     GET_USER_PRIVATE (object->user)->user_list = user_list;
484     object->proxy = proxy;
485     g_signal_connect (proxy, "g-signal", G_CALLBACK (user_signal_cb), object);
486   
487     return object;
488 }
489
490 static void
491 user_account_object_free (UserAccountObject *object)
492 {
493     if (!object)
494         return;
495     g_object_unref (object->user);
496     g_object_unref (object->proxy);
497     g_free (object);
498 }
499
500 static UserAccountObject *
501 find_user_account_object (LightDMUserList *user_list, const gchar *path)
502 {
503     LightDMUserListPrivate *priv = GET_LIST_PRIVATE (user_list);
504     GList *link;
505
506     for (link = priv->user_account_objects; link; link = link->next)
507     {
508         UserAccountObject *object = link->data;
509         if (strcmp (g_dbus_proxy_get_object_path (object->proxy), path) == 0)
510             return object;
511     }
512
513     return NULL;
514 }
515
516 static void
517 user_accounts_signal_cb (GDBusProxy *proxy, gchar *sender_name, gchar *signal_name, GVariant *parameters, LightDMUserList *user_list)
518 {
519     LightDMUserListPrivate *priv = GET_LIST_PRIVATE (user_list);
520   
521     if (strcmp (signal_name, "UserAdded") == 0)
522     {
523         if (g_variant_is_of_type (parameters, G_VARIANT_TYPE ("(o)")))
524         {
525             gchar *path;
526             UserAccountObject *object;
527
528             g_variant_get (parameters, "(&o)", &path);
529
530             /* Ignore duplicate requests */
531             object = find_user_account_object (user_list, path);
532             if (object)
533                 return;
534
535             object = user_account_object_new (user_list, path);
536             if (object && update_user (object))
537             {
538                 g_debug ("User %s added", path);
539                 priv->user_account_objects = g_list_append (priv->user_account_objects, object);
540                 priv->users = g_list_insert_sorted (priv->users, g_object_ref (object->user), compare_user);
541                 g_signal_connect (object->user, "changed", G_CALLBACK (user_changed_cb), user_list);
542                 g_signal_emit (user_list, list_signals[USER_ADDED], 0, object->user);
543             }
544             else
545                 user_account_object_free (object);
546         }
547         else
548             g_warning ("Got UserAccounts signal UserAdded with unknown parameters %s", g_variant_get_type_string (parameters));
549     }
550     else if (strcmp (signal_name, "UserDeleted") == 0)
551     {
552         if (g_variant_is_of_type (parameters, G_VARIANT_TYPE ("(o)")))
553         {
554             gchar *path;
555             UserAccountObject *object;
556
557             g_variant_get (parameters, "(&o)", &path);
558
559             object = find_user_account_object (user_list, path);
560             if (!object)
561                 return;
562
563             g_debug ("User %s deleted", path);
564             priv->users = g_list_remove (priv->users, object->user);
565             g_object_unref (object->user);
566
567             g_signal_emit (user_list, list_signals[USER_REMOVED], 0, object->user);
568
569             priv->user_account_objects = g_list_remove (priv->user_account_objects, object);
570             user_account_object_free (object);
571         }
572         else
573             g_warning ("Got UserAccounts signal UserDeleted with unknown parameters %s", g_variant_get_type_string (parameters));
574     }
575 }
576
577 static Session *
578 load_session (LightDMUserList *user_list, const gchar *path)
579 {
580     LightDMUserListPrivate *priv = GET_LIST_PRIVATE (user_list);
581     Session *session = NULL;
582     GVariant *result, *username;
583     GError *error = NULL;
584
585     result = g_dbus_connection_call_sync (g_dbus_proxy_get_connection (priv->display_manager_proxy),
586                                           "org.freedesktop.DisplayManager",
587                                           path,
588                                           "org.freedesktop.DBus.Properties",
589                                           "Get",
590                                           g_variant_new ("(ss)", "org.freedesktop.DisplayManager.Session", "UserName"),
591                                           G_VARIANT_TYPE ("(v)"),
592                                           G_DBUS_CALL_FLAGS_NONE,
593                                           -1,
594                                           NULL,
595                                           &error);
596     if (error)
597         g_warning ("Error getting UserName from org.freedesktop.DisplayManager.Session: %s", error->message);
598     g_clear_error (&error);
599     if (!result)
600         return NULL;
601
602     g_variant_get (result, "(v)", &username);
603     if (g_variant_is_of_type (username, G_VARIANT_TYPE_STRING))
604     {
605         gchar *name;
606
607         g_variant_get (username, "&s", &name);
608
609         g_debug ("Loaded session %s (%s)", path, name);
610         session = g_object_new (session_get_type (), NULL);
611         session->username = g_strdup (name);
612         session->path = g_strdup (path);
613         priv->sessions = g_list_append (priv->sessions, session);
614     }
615     g_variant_unref (username);
616     g_variant_unref (result);
617
618     return session;
619 }
620
621 static void
622 display_manager_signal_cb (GDBusProxy *proxy, gchar *sender_name, gchar *signal_name, GVariant *parameters, LightDMUserList *user_list)
623 {
624     LightDMUserListPrivate *priv = GET_LIST_PRIVATE (user_list);
625
626     if (strcmp (signal_name, "SessionAdded") == 0)
627     {
628         if (g_variant_is_of_type (parameters, G_VARIANT_TYPE ("(o)")))
629         {
630             gchar *path;
631             Session *session;
632             LightDMUser *user = NULL;
633
634             g_variant_get (parameters, "(&o)", &path);
635             session = load_session (user_list, path);
636             if (session)
637                 user = get_user_by_name (user_list, session->username);
638             if (user)
639                 g_signal_emit (user, user_signals[CHANGED], 0);
640         }
641     }
642     else if (strcmp (signal_name, "SessionRemoved") == 0)
643     {
644         if (g_variant_is_of_type (parameters, G_VARIANT_TYPE ("(o)")))
645         {
646             gchar *path;
647             GList *link;
648
649             g_variant_get (parameters, "(&o)", &path);
650
651             for (link = priv->sessions; link; link = link->next)
652             {
653                 Session *session = link->data;
654                 if (strcmp (session->path, path) == 0)
655                 {
656                     LightDMUser *user;
657
658                     g_debug ("Session %s removed", path);
659                     priv->sessions = g_list_remove_link (priv->sessions, link);
660                     user = get_user_by_name (user_list, session->username);
661                     if (user)
662                         g_signal_emit (user, user_signals[CHANGED], 0);
663                     g_object_unref (session);
664                     break;
665                 }
666             }
667         }
668     }
669 }
670
671 static void
672 update_users (LightDMUserList *user_list)
673 {
674     LightDMUserListPrivate *priv = GET_LIST_PRIVATE (user_list);
675     GError *error = NULL;
676
677     if (priv->have_users)
678         return;
679     priv->have_users = TRUE;
680
681     priv->accounts_service_proxy = g_dbus_proxy_new_for_bus_sync (G_BUS_TYPE_SYSTEM,
682                                                                   G_DBUS_PROXY_FLAGS_NONE,
683                                                                   NULL,
684                                                                   "org.freedesktop.Accounts",
685                                                                   "/org/freedesktop/Accounts",
686                                                                   "org.freedesktop.Accounts",
687                                                                   NULL,
688                                                                   &error);
689     if (error)
690         g_warning ("Error contacting org.freedesktop.Accounts: %s", error->message);
691     g_clear_error (&error);
692
693     /* Check if the service exists */
694     if (priv->accounts_service_proxy)
695     {
696         gchar *name;
697
698         name = g_dbus_proxy_get_name_owner (priv->accounts_service_proxy);
699         if (!name)
700         {
701             g_debug ("org.freedesktop.Accounts does not exist, falling back to passwd file");
702             g_object_unref (priv->accounts_service_proxy);
703             priv->accounts_service_proxy = NULL;
704         }
705         g_free (name);
706     }
707
708     if (priv->accounts_service_proxy)
709     {
710         GVariant *result;
711
712         g_signal_connect (priv->accounts_service_proxy, "g-signal", G_CALLBACK (user_accounts_signal_cb), user_list);
713
714         result = g_dbus_proxy_call_sync (priv->accounts_service_proxy,
715                                          "ListCachedUsers",
716                                          g_variant_new ("()"),
717                                          G_DBUS_CALL_FLAGS_NONE,
718                                          -1,
719                                          NULL,
720                                          &error);
721         if (error)
722             g_warning ("Error getting user list from org.freedesktop.Accounts: %s", error->message);
723         g_clear_error (&error);
724         if (!result)
725             return;
726
727         if (g_variant_is_of_type (result, G_VARIANT_TYPE ("(ao)")))
728         {
729             GVariantIter *iter;
730             const gchar *path;
731
732             g_debug ("Loading users from org.freedesktop.Accounts");
733             g_variant_get (result, "(ao)", &iter);
734             while (g_variant_iter_loop (iter, "&o", &path))
735             {
736                 UserAccountObject *object;
737
738                 g_debug ("Loading user %s", path);
739
740                 object = user_account_object_new (user_list, path);
741                 if (object && update_user (object))
742                 {
743                     priv->user_account_objects = g_list_append (priv->user_account_objects, object);
744                     priv->users = g_list_insert_sorted (priv->users, g_object_ref (object->user), compare_user);
745                     g_signal_connect (object->user, "changed", G_CALLBACK (user_changed_cb), user_list);
746                 }
747                 else
748                     user_account_object_free (object);
749             }
750             g_variant_iter_free (iter);
751         }
752         else
753             g_warning ("Unexpected type from ListCachedUsers: %s", g_variant_get_type_string (result));
754
755         g_variant_unref (result);
756     }
757     else
758     {
759         GFile *passwd_file;
760
761         load_passwd_file (user_list, FALSE);
762
763         /* Watch for changes to user list */
764         passwd_file = g_file_new_for_path (PASSWD_FILE);
765         priv->passwd_monitor = g_file_monitor (passwd_file, G_FILE_MONITOR_NONE, NULL, &error);
766         g_object_unref (passwd_file);
767         if (error)
768             g_warning ("Error monitoring %s: %s", PASSWD_FILE, error->message);
769         else
770             g_signal_connect (priv->passwd_monitor, "changed", G_CALLBACK (passwd_changed_cb), user_list);
771         g_clear_error (&error);
772     }
773
774     priv->display_manager_proxy = g_dbus_proxy_new_for_bus_sync (G_BUS_TYPE_SYSTEM,
775                                                                  G_DBUS_PROXY_FLAGS_NONE,
776                                                                  NULL,
777                                                                  "org.freedesktop.DisplayManager",
778                                                                  "/org/freedesktop/DisplayManager",
779                                                                  "org.freedesktop.DisplayManager",
780                                                                  NULL,
781                                                                  &error);
782     if (error)
783         g_warning ("Error contacting org.freedesktop.DisplayManager: %s", error->message);
784     g_clear_error (&error);
785
786     if (priv->display_manager_proxy)
787     {
788         GVariant *result;
789
790         g_signal_connect (priv->display_manager_proxy, "g-signal", G_CALLBACK (display_manager_signal_cb), user_list);
791
792         result = g_dbus_connection_call_sync (g_dbus_proxy_get_connection (priv->display_manager_proxy),
793                                               "org.freedesktop.DisplayManager",
794                                               "/org/freedesktop/DisplayManager",
795                                               "org.freedesktop.DBus.Properties",
796                                               "Get",
797                                               g_variant_new ("(ss)", "org.freedesktop.DisplayManager", "Sessions"),
798                                               G_VARIANT_TYPE ("(v)"),
799                                               G_DBUS_CALL_FLAGS_NONE,
800                                               -1,
801                                               NULL,
802                                               &error);
803         if (error)
804             g_warning ("Error getting session list from org.freedesktop.DisplayManager: %s", error->message);
805         g_clear_error (&error);
806         if (!result)
807             return;
808
809         if (g_variant_is_of_type (result, G_VARIANT_TYPE ("(v)")))
810         {
811             GVariant *value;
812             GVariantIter *iter;
813             const gchar *path;
814
815             g_variant_get (result, "(v)", &value);
816
817             g_debug ("Loading sessions from org.freedesktop.DisplayManager");
818             g_variant_get (value, "ao", &iter);
819             while (g_variant_iter_loop (iter, "&o", &path))
820                 load_session (user_list, path);
821             g_variant_iter_free (iter);
822
823             g_variant_unref (value);
824         }
825         else
826             g_warning ("Unexpected type from org.freedesktop.DisplayManager.Sessions: %s", g_variant_get_type_string (result));
827
828         g_variant_unref (result);
829     }
830 }
831
832 /**
833  * lightdm_user_list_get_length:
834  * @user_list: a #LightDMUserList
835  *
836  * Return value: The number of users able to log in
837  **/
838 gint
839 lightdm_user_list_get_length (LightDMUserList *user_list)
840 {
841     g_return_val_if_fail (LIGHTDM_IS_USER_LIST (user_list), 0);
842     update_users (user_list);
843     return g_list_length (GET_LIST_PRIVATE (user_list)->users);
844 }
845
846 /**
847  * lightdm_user_list_get_users:
848  * @user_list: A #LightDMUserList
849  *
850  * Get a list of users to present to the user.  This list may be a subset of the
851  * available users and may be empty depending on the server configuration.
852  *
853  * Return value: (element-type LightDMUser) (transfer none): A list of #LightDMUser that should be presented to the user.
854  **/
855 GList *
856 lightdm_user_list_get_users (LightDMUserList *user_list)
857 {
858     g_return_val_if_fail (LIGHTDM_IS_USER_LIST (user_list), NULL);
859     update_users (user_list);
860     return GET_LIST_PRIVATE (user_list)->users;
861 }
862
863 /**
864  * lightdm_user_list_get_user_by_name:
865  * @user_list: A #LightDMUserList
866  * @username: Name of user to get.
867  *
868  * Get infomation about a given user or #NULL if this user doesn't exist.
869  *
870  * Return value: (transfer none): A #LightDMUser entry for the given user.
871  **/
872 LightDMUser *
873 lightdm_user_list_get_user_by_name (LightDMUserList *user_list, const gchar *username)
874 {
875     g_return_val_if_fail (LIGHTDM_IS_USER_LIST (user_list), NULL);
876     g_return_val_if_fail (username != NULL, NULL);
877
878     update_users (user_list);
879
880     return get_user_by_name (user_list, username);
881 }
882
883 static void
884 lightdm_user_list_init (LightDMUserList *user_list)
885 {
886 }
887
888 static void
889 lightdm_user_list_set_property (GObject    *object,
890                                 guint       prop_id,
891                                 const GValue *value,
892                                 GParamSpec *pspec)
893 {
894     G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
895 }
896
897 static void
898 lightdm_user_list_get_property (GObject    *object,
899                                 guint       prop_id,
900                                 GValue     *value,
901                                 GParamSpec *pspec)
902 {
903     LightDMUserList *self;
904
905     self = LIGHTDM_USER_LIST (object);
906
907     switch (prop_id)
908     {
909     case LIST_PROP_NUM_USERS:
910         g_value_set_int (value, lightdm_user_list_get_length (self));
911         break;
912     default:
913         G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
914         break;
915     }
916 }
917
918 static void
919 lightdm_user_list_finalize (GObject *object)
920 {
921     LightDMUserList *self = LIGHTDM_USER_LIST (object);
922     LightDMUserListPrivate *priv = GET_LIST_PRIVATE (self);
923
924     if (priv->accounts_service_proxy)
925         g_object_unref (priv->accounts_service_proxy);
926     g_list_free_full (priv->user_account_objects, (GDestroyNotify) user_account_object_free);
927     if (priv->passwd_monitor)
928         g_object_unref (priv->passwd_monitor);
929     g_list_free_full (priv->users, g_object_unref);
930     g_list_free_full (priv->sessions, g_object_unref);
931
932     G_OBJECT_CLASS (lightdm_user_list_parent_class)->finalize (object);
933 }
934
935 static void
936 lightdm_user_list_class_init (LightDMUserListClass *klass)
937 {
938     GObjectClass *object_class = G_OBJECT_CLASS (klass);
939
940     g_type_class_add_private (klass, sizeof (LightDMUserListPrivate));
941
942     object_class->set_property = lightdm_user_list_set_property;
943     object_class->get_property = lightdm_user_list_get_property;
944     object_class->finalize = lightdm_user_list_finalize;
945
946     g_object_class_install_property (object_class,
947                                      LIST_PROP_NUM_USERS,
948                                      g_param_spec_int ("num-users",
949                                                        "num-users",
950                                                        "Number of login users",
951                                                        0, G_MAXINT, 0,
952                                                        G_PARAM_READABLE));
953     /**
954      * LightDMUserList::user-added:
955      * @user_list: A #LightDMUserList
956      * @user: The #LightDM user that has been added.
957      *
958      * The ::user-added signal gets emitted when a user account is created.
959      **/
960     list_signals[USER_ADDED] =
961         g_signal_new ("user-added",
962                       G_TYPE_FROM_CLASS (klass),
963                       G_SIGNAL_RUN_LAST,
964                       G_STRUCT_OFFSET (LightDMUserListClass, user_added),
965                       NULL, NULL,
966                       g_cclosure_marshal_VOID__OBJECT,
967                       G_TYPE_NONE, 1, LIGHTDM_TYPE_USER);
968
969     /**
970      * LightDMUserList::user-changed:
971      * @user_list: A #LightDMUserList
972      * @user: The #LightDM user that has been changed.
973      *
974      * The ::user-changed signal gets emitted when a user account is modified.
975      **/
976     list_signals[USER_CHANGED] =
977         g_signal_new ("user-changed",
978                       G_TYPE_FROM_CLASS (klass),
979                       G_SIGNAL_RUN_LAST,
980                       G_STRUCT_OFFSET (LightDMUserListClass, user_changed),
981                       NULL, NULL,
982                       g_cclosure_marshal_VOID__OBJECT,
983                       G_TYPE_NONE, 1, LIGHTDM_TYPE_USER);
984
985     /**
986      * LightDMUserList::user-removed:
987      * @user_list: A #LightDMUserList
988      * @user: The #LightDM user that has been removed.
989      *
990      * The ::user-removed signal gets emitted when a user account is removed.
991      **/
992     list_signals[USER_REMOVED] =
993         g_signal_new ("user-removed",
994                       G_TYPE_FROM_CLASS (klass),
995                       G_SIGNAL_RUN_LAST,
996                       G_STRUCT_OFFSET (LightDMUserListClass, user_removed),
997                       NULL, NULL,
998                       g_cclosure_marshal_VOID__OBJECT,
999                       G_TYPE_NONE, 1, LIGHTDM_TYPE_USER);
1000 }
1001
1002 /**
1003  * lightdm_user_get_name:
1004  * @user: A #LightDMUser
1005  * 
1006  * Get the name of a user.
1007  * 
1008  * Return value: The name of the given user
1009  **/
1010 const gchar *
1011 lightdm_user_get_name (LightDMUser *user)
1012 {
1013     g_return_val_if_fail (LIGHTDM_IS_USER (user), NULL);
1014     return GET_USER_PRIVATE (user)->name;
1015 }
1016
1017 /**
1018  * lightdm_user_get_real_name:
1019  * @user: A #LightDMUser
1020  * 
1021  * Get the real name of a user.
1022  *
1023  * Return value: The real name of the given user
1024  **/
1025 const gchar *
1026 lightdm_user_get_real_name (LightDMUser *user)
1027 {
1028     g_return_val_if_fail (LIGHTDM_IS_USER (user), NULL);
1029     return GET_USER_PRIVATE (user)->real_name;
1030 }
1031
1032 /**
1033  * lightdm_user_get_display_name:
1034  * @user: A #LightDMUser
1035  * 
1036  * Get the display name of a user.
1037  * 
1038  * Return value: The display name of the given user
1039  **/
1040 const gchar *
1041 lightdm_user_get_display_name (LightDMUser *user)
1042 {
1043     LightDMUserPrivate *priv;
1044
1045     g_return_val_if_fail (LIGHTDM_IS_USER (user), NULL);
1046
1047     priv = GET_USER_PRIVATE (user);
1048     if (strcmp (priv->real_name, ""))
1049         return priv->real_name;
1050     else
1051         return priv->name;
1052 }
1053
1054 /**
1055  * lightdm_user_get_home_directory:
1056  * @user: A #LightDMUser
1057  * 
1058  * Get the home directory for a user.
1059  * 
1060  * Return value: The users home directory
1061  */
1062 const gchar *
1063 lightdm_user_get_home_directory (LightDMUser *user)
1064 {
1065     g_return_val_if_fail (LIGHTDM_IS_USER (user), NULL);
1066     return GET_USER_PRIVATE (user)->home_directory;
1067 }
1068
1069 /**
1070  * lightdm_user_get_image:
1071  * @user: A #LightDMUser
1072  * 
1073  * Get the image URI for a user.
1074  * 
1075  * Return value: The image URI for the given user or #NULL if no URI
1076  **/
1077 const gchar *
1078 lightdm_user_get_image (LightDMUser *user)
1079 {
1080     g_return_val_if_fail (LIGHTDM_IS_USER (user), NULL);
1081     return GET_USER_PRIVATE (user)->image;
1082 }
1083
1084 static void
1085 load_dmrc (LightDMUser *user)
1086 {
1087     LightDMUserPrivate *priv = GET_USER_PRIVATE (user);
1088     gchar *path;
1089     //gboolean have_dmrc;
1090
1091     priv->dmrc_file = g_key_file_new ();
1092
1093     /* Load from the user directory */  
1094     path = g_build_filename (priv->home_directory, ".dmrc", NULL);
1095     /*have_dmrc = */g_key_file_load_from_file (priv->dmrc_file, path, G_KEY_FILE_KEEP_COMMENTS, NULL);
1096     g_free (path);
1097
1098     /* If no ~/.dmrc, then load from the cache */
1099     // FIXME
1100
1101     // FIXME: Watch for changes
1102
1103     if (priv->language)
1104         g_free (priv->language);
1105     if (priv->layout)
1106         g_free (priv->layout);
1107     if (priv->session)
1108         g_free (priv->session);
1109
1110     /* The Language field is actually a locale, strip the codeset off it to get the language */
1111     priv->language = g_key_file_get_string (priv->dmrc_file, "Desktop", "Language", NULL);
1112     if (priv->language)
1113     {
1114         gchar *codeset = strchr (priv->language, '.');
1115         if (codeset)
1116             *codeset = '\0';
1117     }
1118
1119     priv->layout = g_key_file_get_string (priv->dmrc_file, "Desktop", "Layout", NULL);
1120     priv->session = g_key_file_get_string (priv->dmrc_file, "Desktop", "Session", NULL);
1121 }
1122
1123 static gchar *
1124 get_string_property (GDBusProxy *proxy, const gchar *property)
1125 {
1126     GVariant *answer;
1127     gchar *rv;
1128
1129     if (!proxy)
1130         return NULL;
1131
1132     answer = g_dbus_proxy_get_cached_property (proxy, property);
1133
1134     if (!answer)
1135     {
1136         g_warning ("Could not get accounts property %s", property);
1137         return NULL;
1138     }
1139
1140     if (!g_variant_is_of_type (answer, G_VARIANT_TYPE ("s")))
1141     {
1142         g_warning ("Unexpected accounts property type for %s: %s",
1143                    property, g_variant_get_type_string (answer));
1144         g_variant_unref (answer);
1145         return NULL;
1146     }
1147
1148     g_variant_get (answer, "s", &rv);
1149
1150     g_variant_unref (answer);
1151     return rv;
1152 }
1153
1154 static gboolean
1155 load_accounts_service (LightDMUser *user)
1156 {
1157     LightDMUserPrivate *priv = GET_USER_PRIVATE (user);
1158     LightDMUserListPrivate *list_priv = GET_LIST_PRIVATE (priv->user_list);
1159
1160     /* First, find AccountObject proxy */
1161     UserAccountObject *account = NULL;
1162     GList *iter;
1163     for (iter = list_priv->user_account_objects; iter; iter = iter->next)
1164     {
1165         if (((UserAccountObject *)iter->data)->user == user)
1166         {
1167             account = (UserAccountObject *)iter->data;
1168             break;
1169         }
1170     }
1171     if (!account)
1172         return FALSE;
1173
1174     /* We have proxy, let's grab some properties */
1175     if (priv->language)
1176         g_free (priv->language);
1177     if (priv->session)
1178         g_free (priv->session);
1179     priv->language = get_string_property (account->proxy, "Language");
1180     priv->session = get_string_property (account->proxy, "XSession");
1181
1182     return TRUE;
1183 }
1184
1185 /* Loads language/layout/session info for user */
1186 static void
1187 load_user_values (LightDMUser *user)
1188 {
1189     load_dmrc (user);
1190     load_accounts_service (user); // overrides dmrc values
1191 }
1192
1193 /**
1194  * lightdm_user_get_language
1195  * @user: A #LightDMUser
1196  * 
1197  * Get the language for a user.
1198  * 
1199  * Return value: The language for the given user or #NULL if using system defaults.
1200  **/
1201 const gchar *
1202 lightdm_user_get_language (LightDMUser *user)
1203 {
1204     g_return_val_if_fail (LIGHTDM_IS_USER (user), NULL);
1205     load_user_values (user);
1206     return GET_USER_PRIVATE (user)->language;
1207 }
1208
1209 /**
1210  * lightdm_user_get_layout
1211  * @user: A #LightDMUser
1212  * 
1213  * Get the keyboard layout for a user.
1214  * 
1215  * Return value: The keyboard layoyt for the given user or #NULL if using system defaults.
1216  **/
1217 const gchar *
1218 lightdm_user_get_layout (LightDMUser *user)
1219 {
1220     g_return_val_if_fail (LIGHTDM_IS_USER (user), NULL);
1221     load_user_values (user);
1222     return GET_USER_PRIVATE (user)->layout;
1223 }
1224
1225 /**
1226  * lightdm_user_get_session
1227  * @user: A #LightDMUser
1228  * 
1229  * Get the session for a user.
1230  * 
1231  * Return value: The session for the given user or #NULL if using system defaults.
1232  **/
1233 const gchar *
1234 lightdm_user_get_session (LightDMUser *user)
1235 {
1236     g_return_val_if_fail (LIGHTDM_IS_USER (user), NULL);
1237     load_user_values (user);
1238     return GET_USER_PRIVATE (user)->session; 
1239 }
1240
1241 /**
1242  * lightdm_user_get_logged_in:
1243  * @user: A #LightDMUser
1244  * 
1245  * Check if a user is logged in.
1246  * 
1247  * Return value: #TRUE if the user is currently logged in.
1248  **/
1249 gboolean
1250 lightdm_user_get_logged_in (LightDMUser *user)
1251 {
1252     LightDMUserPrivate *priv = GET_USER_PRIVATE (user);
1253     LightDMUserListPrivate *list_priv = GET_LIST_PRIVATE (priv->user_list);
1254     GList *link;
1255
1256     g_return_val_if_fail (LIGHTDM_IS_USER (user), FALSE);
1257
1258     for (link = list_priv->sessions; link; link = link->next)
1259     {
1260         Session *session = link->data;
1261         if (strcmp (session->username, priv->name) == 0)
1262             return TRUE;
1263     }
1264
1265     return FALSE;
1266 }
1267
1268 static void
1269 lightdm_user_init (LightDMUser *user)
1270 {
1271     LightDMUserPrivate *priv = GET_USER_PRIVATE (user);
1272
1273     priv->name = g_strdup ("");
1274     priv->real_name = g_strdup ("");
1275     priv->home_directory = g_strdup ("");
1276     priv->image = g_strdup ("");
1277     priv->language = g_strdup ("");
1278     priv->layout = g_strdup ("");
1279     priv->session = g_strdup ("");
1280 }
1281
1282 static void
1283 lightdm_user_set_property (GObject    *object,
1284                            guint       prop_id,
1285                            const GValue *value,
1286                            GParamSpec *pspec)
1287 {
1288     G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
1289 }
1290
1291 static void
1292 lightdm_user_get_property (GObject    *object,
1293                            guint       prop_id,
1294                            GValue     *value,
1295                            GParamSpec *pspec)
1296 {
1297     LightDMUser *self;
1298
1299     self = LIGHTDM_USER (object);
1300
1301     switch (prop_id)
1302     {
1303     case USER_PROP_NAME:
1304         g_value_set_string (value, lightdm_user_get_name (self));
1305         break;
1306     case USER_PROP_REAL_NAME:
1307         g_value_set_string (value, lightdm_user_get_real_name (self));
1308         break;
1309     case USER_PROP_DISPLAY_NAME:
1310         g_value_set_string (value, lightdm_user_get_display_name (self));
1311         break;
1312     case USER_PROP_HOME_DIRECTORY:
1313         g_value_set_string (value, lightdm_user_get_home_directory (self));
1314         break;
1315     case USER_PROP_IMAGE:
1316         g_value_set_string (value, lightdm_user_get_image (self));
1317         break;
1318     case USER_PROP_LANGUAGE:
1319         g_value_set_string (value, lightdm_user_get_language (self));
1320         break;
1321     case USER_PROP_LAYOUT:
1322         g_value_set_string (value, lightdm_user_get_layout (self));
1323         break;
1324     case USER_PROP_SESSION:
1325         g_value_set_string (value, lightdm_user_get_session (self));
1326         break;
1327     case USER_PROP_LOGGED_IN:
1328         g_value_set_boolean (value, lightdm_user_get_logged_in (self));
1329         break;
1330     default:
1331         G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
1332         break;
1333     }
1334 }
1335
1336 static void
1337 lightdm_user_finalize (GObject *object)
1338 {
1339     LightDMUser *self = LIGHTDM_USER (object);
1340     LightDMUserPrivate *priv = GET_USER_PRIVATE (self);
1341
1342     g_free (priv->name);
1343     g_free (priv->real_name);
1344     g_free (priv->home_directory);
1345     g_free (priv->image);
1346     if (priv->dmrc_file)
1347         g_key_file_free (priv->dmrc_file);
1348 }
1349
1350 static void
1351 lightdm_user_class_init (LightDMUserClass *klass)
1352 {
1353     GObjectClass *object_class = G_OBJECT_CLASS (klass);
1354   
1355     g_type_class_add_private (klass, sizeof (LightDMUserPrivate));
1356
1357     object_class->set_property = lightdm_user_set_property;
1358     object_class->get_property = lightdm_user_get_property;
1359     object_class->finalize = lightdm_user_finalize;
1360
1361     g_object_class_install_property (object_class,
1362                                      USER_PROP_NAME,
1363                                      g_param_spec_string ("name",
1364                                                           "name",
1365                                                           "Username",
1366                                                           NULL,
1367                                                           G_PARAM_READWRITE));
1368     g_object_class_install_property (object_class,
1369                                      USER_PROP_REAL_NAME,
1370                                      g_param_spec_string ("real-name",
1371                                                           "real-name",
1372                                                           "Users real name",
1373                                                           NULL,
1374                                                           G_PARAM_READWRITE));
1375     g_object_class_install_property (object_class,
1376                                      USER_PROP_DISPLAY_NAME,
1377                                      g_param_spec_string ("display-name",
1378                                                           "display-name",
1379                                                           "Users display name",
1380                                                           NULL,
1381                                                           G_PARAM_READABLE));
1382     g_object_class_install_property (object_class,
1383                                      USER_PROP_HOME_DIRECTORY,
1384                                      g_param_spec_string ("home-directory",
1385                                                           "home-directory",
1386                                                           "Home directory",
1387                                                           NULL,
1388                                                           G_PARAM_READWRITE));
1389     g_object_class_install_property (object_class,
1390                                      USER_PROP_IMAGE,
1391                                      g_param_spec_string ("image",
1392                                                           "image",
1393                                                           "Avatar image",
1394                                                           NULL,
1395                                                           G_PARAM_READWRITE));
1396     g_object_class_install_property (object_class,
1397                                      USER_PROP_LANGUAGE,
1398                                      g_param_spec_string ("language",
1399                                                          "language",
1400                                                          "Language used by this user",
1401                                                          NULL,
1402                                                          G_PARAM_READABLE));
1403     g_object_class_install_property (object_class,
1404                                      USER_PROP_LAYOUT,
1405                                      g_param_spec_string ("layout",
1406                                                           "layout",
1407                                                           "Keyboard layout used by this user",
1408                                                           NULL,
1409                                                           G_PARAM_READABLE));
1410     g_object_class_install_property (object_class,
1411                                      USER_PROP_SESSION,
1412                                      g_param_spec_string ("session",
1413                                                           "session",
1414                                                           "Session used by this user",
1415                                                           NULL,
1416                                                           G_PARAM_READABLE));
1417     g_object_class_install_property (object_class,
1418                                      USER_PROP_LOGGED_IN,
1419                                      g_param_spec_boolean ("logged-in",
1420                                                            "logged-in",
1421                                                            "TRUE if the user is currently in a session",
1422                                                            FALSE,
1423                                                            G_PARAM_READWRITE));
1424
1425     /**
1426      * LightDMUser::changed:
1427      * @user: A #LightDMUser
1428      *
1429      * The ::changed signal gets emitted this user account is modified.
1430      **/
1431     user_signals[CHANGED] =
1432         g_signal_new ("changed",
1433                       G_TYPE_FROM_CLASS (klass),
1434                       G_SIGNAL_RUN_LAST,
1435                       G_STRUCT_OFFSET (LightDMUserClass, changed),
1436                       NULL, NULL,
1437                       g_cclosure_marshal_VOID__VOID,
1438                       G_TYPE_NONE, 0);
1439 }
1440
1441 static void
1442 session_init (Session *session)
1443 {
1444 }
1445
1446 static void
1447 session_finalize (GObject *object)
1448 {
1449     Session *self = SESSION (object);
1450
1451     g_free (self->path);
1452     g_free (self->username);
1453 }
1454
1455 static void
1456 session_class_init (SessionClass *klass)
1457 {
1458     GObjectClass *object_class = G_OBJECT_CLASS (klass);
1459     object_class->finalize = session_finalize;
1460 }