diff --git a/src/Makefile-gdmuser.am b/src/Makefile-gdmuser.am index caba0d89a..84563b891 100644 --- a/src/Makefile-gdmuser.am +++ b/src/Makefile-gdmuser.am @@ -5,6 +5,7 @@ gdmuser_cflags = \ -DDATADIR=\""$(datadir)"\" \ -DG_DISABLE_DEPRECATED \ -DG_LOG_DOMAIN=\"GdmUser\" \ + -DGDM_CACHE_DIR=\""$(localstatedir)/cache/gdm"\" \ $(GDMUSER_CFLAGS) \ $(NULL) diff --git a/src/gdmuser/gdm-user-manager.c b/src/gdmuser/gdm-user-manager.c index 5f6bf85c7..b28ca2082 100644 --- a/src/gdmuser/gdm-user-manager.c +++ b/src/gdmuser/gdm-user-manager.c @@ -51,23 +51,20 @@ #define GDM_USER_MANAGER_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), GDM_TYPE_USER_MANAGER, GdmUserManagerPrivate)) #define CK_NAME "org.freedesktop.ConsoleKit" -#define CK_PATH "/org/freedesktop/ConsoleKit" -#define CK_INTERFACE "org.freedesktop.ConsoleKit" #define CK_MANAGER_PATH "/org/freedesktop/ConsoleKit/Manager" #define CK_MANAGER_INTERFACE "org.freedesktop.ConsoleKit.Manager" #define CK_SEAT_INTERFACE "org.freedesktop.ConsoleKit.Seat" #define CK_SESSION_INTERFACE "org.freedesktop.ConsoleKit.Session" +#define GDM_DBUS_TYPE_G_OBJECT_PATH_ARRAY (dbus_g_type_get_collection ("GPtrArray", DBUS_TYPE_G_OBJECT_PATH)) + /* Prefs Defaults */ -#define DEFAULT_ALLOW_ROOT TRUE -#define DEFAULT_MAX_ICON_SIZE 128 -#define DEFAULT_USER_MAX_FILE 65536 #ifdef __sun -#define DEFAULT_MINIMAL_UID 100 +#define FALLBACK_MINIMAL_UID 100 #else -#define DEFAULT_MINIMAL_UID 500 +#define FALLBACK_MINIMAL_UID 500 #endif #ifndef _PATH_SHELLS @@ -75,55 +72,134 @@ #endif #define PATH_PASSWD "/etc/passwd" -#define DEFAULT_GLOBAL_FACE_DIR DATADIR "/faces" -#define DEFAULT_USER_ICON "stock_person" -#define DEFAULT_EXCLUDE { "bin", \ - "root", \ - "daemon", \ - "adm", \ - "lp", \ - "sync", \ - "shutdown", \ - "halt", \ - "mail", \ - "news", \ - "uucp", \ - "operator", \ - "nobody", \ - "gdm", \ - "postgres", \ - "pvm", \ - "rpm", \ - "nfsnobody", \ - "pcap", \ - NULL } +#ifndef GDM_USERNAME +#define GDM_USERNAME "gdm" +#endif + +#define RELOAD_PASSWD_THROTTLE_SECS 5 + +/* approximately two months */ +#define LOGIN_FREQUENCY_TIME_WINDOW_SECS (60 * 24 * 60 * 60) + +#define ACCOUNTS_NAME "org.freedesktop.Accounts" +#define ACCOUNTS_PATH "/org/freedesktop/Accounts" +#define ACCOUNTS_INTERFACE "org.freedesktop.Accounts" + +typedef enum { + GDM_USER_MANAGER_SEAT_STATE_UNLOADED = 0, + GDM_USER_MANAGER_SEAT_STATE_GET_SESSION_ID, + GDM_USER_MANAGER_SEAT_STATE_GET_ID, + GDM_USER_MANAGER_SEAT_STATE_GET_PROXY, + GDM_USER_MANAGER_SEAT_STATE_LOADED, +} GdmUserManagerSeatState; + +typedef struct +{ + GdmUserManagerSeatState state; + char *id; + char *session_id; + union { + DBusGProxyCall *get_current_session_call; + DBusGProxyCall *get_seat_id_call; + }; + + DBusGProxy *proxy; +} GdmUserManagerSeat; + +typedef enum { + GDM_USER_MANAGER_NEW_SESSION_STATE_UNLOADED = 0, + GDM_USER_MANAGER_NEW_SESSION_STATE_GET_PROXY, + GDM_USER_MANAGER_NEW_SESSION_STATE_GET_UID, + GDM_USER_MANAGER_NEW_SESSION_STATE_GET_X11_DISPLAY, + GDM_USER_MANAGER_NEW_SESSION_STATE_MAYBE_ADD, + GDM_USER_MANAGER_NEW_SESSION_STATE_LOADED, +} GdmUserManagerNewSessionState; + +typedef struct +{ + GdmUserManager *manager; + GdmUserManagerNewSessionState state; + char *id; + + union { + DBusGProxyCall *get_unix_user_call; + DBusGProxyCall *get_x11_display_call; + }; + + DBusGProxy *proxy; + + uid_t uid; + char *x11_display; +} GdmUserManagerNewSession; + +typedef enum { + GDM_USER_MANAGER_GET_USER_STATE_UNFETCHED = 0, + GDM_USER_MANAGER_GET_USER_STATE_WAIT_FOR_LOADED, + GDM_USER_MANAGER_GET_USER_STATE_ASK_ACCOUNTS_SERVICE, + GDM_USER_MANAGER_GET_USER_STATE_FETCHED +} GdmUserManagerGetUserState; + +typedef struct +{ + GdmUserManager *manager; + GdmUserManagerGetUserState state; + GdmUser *user; + char *username; + char *object_path; + + DBusGProxyCall *call; +} GdmUserManagerFetchUserRequest; struct GdmUserManagerPrivate { - GHashTable *users; + GHashTable *users_by_name; + GHashTable *users_by_object_path; GHashTable *sessions; - GHashTable *exclusions; GHashTable *shells; DBusGConnection *connection; - DBusGProxy *seat_proxy; - char *seat_id; + DBusGProxyCall *get_sessions_call; + DBusGProxy *accounts_proxy; + + GdmUserManagerSeat seat; + + GSList *new_sessions; + GSList *new_users; + GSList *fetch_user_requests; GFileMonitor *passwd_monitor; GFileMonitor *shells_monitor; - guint reload_id; - guint ck_history_id; + GSList *exclude_usernames; + GSList *include_usernames; + gboolean include_all; - guint8 users_dirty : 1; + gboolean load_passwd_pending; + + guint load_id; + guint reload_passwd_id; + guint ck_history_id; + guint ck_history_watchdog_id; + GPid ck_history_pid; + + gboolean is_loaded; + gboolean has_multiple_users; + gboolean listing_cached_users; +}; + +enum { + PROP_0, + PROP_INCLUDE_ALL, + PROP_INCLUDE_USERNAMES_LIST, + PROP_EXCLUDE_USERNAMES_LIST, + PROP_IS_LOADED, + PROP_HAS_MULTIPLE_USERS }; enum { - LOADING_USERS, - USERS_LOADED, USER_ADDED, USER_REMOVED, USER_IS_LOGGED_IN_CHANGED, - USER_LOGIN_FREQUENCY_CHANGED, + USER_CHANGED, LAST_SIGNAL }; @@ -133,6 +209,28 @@ static void gdm_user_manager_class_init (GdmUserManagerClass *klass); static void gdm_user_manager_init (GdmUserManager *user_manager); static void gdm_user_manager_finalize (GObject *object); +static void load_users_manually (GdmUserManager *manager); +static void monitor_local_users (GdmUserManager *manager); +static void load_seat_incrementally (GdmUserManager *manager); +static void unload_seat (GdmUserManager *manager); +static void load_users (GdmUserManager *manager); +static void queue_load_seat_and_users (GdmUserManager *manager); +static void monitor_local_users (GdmUserManager *manager); + +static void load_new_session_incrementally (GdmUserManagerNewSession *new_session); +static void set_is_loaded (GdmUserManager *manager, gboolean is_loaded); + +static void on_new_user_loaded (GdmUser *user, + GParamSpec *pspec, + GdmUserManager *manager); +static void give_up_and_fetch_user_locally (GdmUserManager *manager, + GdmUserManagerFetchUserRequest *request); +static void fetch_user_locally (GdmUserManager *manager, + GdmUser *user, + const char *username); +static void fetch_user_incrementally (GdmUserManagerFetchUserRequest *request); + +static void maybe_set_is_loaded (GdmUserManager *manager); static gpointer user_manager_object = NULL; G_DEFINE_TYPE (GdmUserManager, gdm_user_manager, G_TYPE_OBJECT) @@ -156,76 +254,15 @@ start_new_login_session (GdmUserManager *manager) res = g_spawn_command_line_async ("gdmflexiserver -s", &error); if (! res) { - g_warning ("Unable to start new login: %s", error->message); - g_error_free (error); - } - - return res; -} - -/* needs to stay in sync with gdm-slave */ -static char * -_get_primary_user_session_id (GdmUserManager *manager, - GdmUser *user) -{ - gboolean res; - gboolean can_activate_sessions; - GError *error; - GList *sessions; - GList *l; - char *primary_ssid; - - if (manager->priv->seat_id == NULL || manager->priv->seat_id[0] == '\0') { - g_debug ("GdmUserManager: display seat id is not set; can't switch sessions"); - return NULL; - } - - primary_ssid = NULL; - sessions = NULL; - - g_debug ("GdmUserManager: checking if seat can activate sessions"); - - error = NULL; - res = dbus_g_proxy_call (manager->priv->seat_proxy, - "CanActivateSessions", - &error, - G_TYPE_INVALID, - G_TYPE_BOOLEAN, &can_activate_sessions, - G_TYPE_INVALID); - if (! res) { - g_warning ("unable to determine if seat can activate sessions: %s", - error->message); - g_error_free (error); - goto out; - } - - if (! can_activate_sessions) { - g_debug ("GdmUserManager: seat is unable to activate sessions"); - goto out; - } - - sessions = gdm_user_get_sessions (user); - if (sessions == NULL) { - g_warning ("unable to determine sessions for user: %s", - gdm_user_get_user_name (user)); - goto out; - } - - for (l = sessions; l != NULL; l = l->next) { - const char *ssid; - - ssid = l->data; - - /* FIXME: better way to choose? */ - if (ssid != NULL) { - primary_ssid = g_strdup (ssid); - break; + if (error != NULL) { + g_warning ("Unable to start new login: %s", error->message); + g_error_free (error); + } else { + g_warning ("Unable to start new login"); } } - out: - - return primary_ssid; + return res; } static gboolean @@ -312,8 +349,12 @@ session_is_login_window (GdmUserManager *manager, G_TYPE_STRING, &session_type, G_TYPE_INVALID); if (! res) { - g_debug ("Failed to identify the session type: %s", error->message); - g_error_free (error); + if (error != NULL) { + g_debug ("GdmUserManager: Failed to identify the session type: %s", error->message); + g_error_free (error); + } else { + g_debug ("GdmUserManager: Failed to identify the session type"); + } goto out; } @@ -341,46 +382,36 @@ _get_login_window_session_id (GdmUserManager *manager) char *primary_ssid; int i; - if (manager->priv->seat_id == NULL || manager->priv->seat_id[0] == '\0') { - g_debug ("GdmUserManager: display seat id is not set; can't switch sessions"); + if (manager->priv->seat.id == NULL || manager->priv->seat.id[0] == '\0') { + g_debug ("GdmUserManager: display seat ID is not set; can't switch sessions"); return NULL; } primary_ssid = NULL; sessions = NULL; - g_debug ("GdmSlave: checking if seat can activate sessions"); - - error = NULL; - res = dbus_g_proxy_call (manager->priv->seat_proxy, - "CanActivateSessions", - &error, - G_TYPE_INVALID, - G_TYPE_BOOLEAN, &can_activate_sessions, - G_TYPE_INVALID); - if (! res) { - g_warning ("unable to determine if seat can activate sessions: %s", - error->message); - g_error_free (error); - goto out; - } + can_activate_sessions = gdm_user_manager_can_switch (manager); if (! can_activate_sessions) { - g_debug ("GdmSlave: seat is unable to activate sessions"); + g_debug ("GdmUserManager: seat is unable to activate sessions"); goto out; } error = NULL; - res = dbus_g_proxy_call (manager->priv->seat_proxy, + res = dbus_g_proxy_call (manager->priv->seat.proxy, "GetSessions", &error, G_TYPE_INVALID, dbus_g_type_get_collection ("GPtrArray", DBUS_TYPE_G_OBJECT_PATH), &sessions, G_TYPE_INVALID); if (! res) { - g_warning ("unable to determine sessions for user: %s", - error->message); - g_error_free (error); + if (error != NULL) { + g_warning ("unable to determine sessions for user: %s", + error->message); + g_error_free (error); + } else { + g_warning ("unable to determine sessions for user"); + } goto out; } @@ -410,6 +441,7 @@ gdm_user_manager_goto_login_session (GdmUserManager *manager) char *ssid; g_return_val_if_fail (GDM_IS_USER_MANAGER (manager), FALSE); + g_return_val_if_fail (manager->priv->is_loaded, FALSE); ret = FALSE; @@ -418,7 +450,7 @@ gdm_user_manager_goto_login_session (GdmUserManager *manager) ssid = _get_login_window_session_id (manager); if (ssid != NULL) { - res = activate_session_id (manager, manager->priv->seat_id, ssid); + res = activate_session_id (manager, manager->priv->seat.id, ssid); if (res) { ret = TRUE; } @@ -434,25 +466,74 @@ gdm_user_manager_goto_login_session (GdmUserManager *manager) return ret; } +gboolean +gdm_user_manager_can_switch (GdmUserManager *manager) +{ + gboolean res; + gboolean can_activate_sessions; + GError *error; + + if (!manager->priv->is_loaded) { + g_debug ("GdmUserManager: Unable to switch sessions until fully loaded"); + return FALSE; + } + + if (manager->priv->seat.id == NULL || manager->priv->seat.id[0] == '\0') { + g_debug ("GdmUserManager: display seat ID is not set; can't switch sessions"); + return FALSE; + } + + g_debug ("GdmUserManager: checking if seat can activate sessions"); + + error = NULL; + res = dbus_g_proxy_call (manager->priv->seat.proxy, + "CanActivateSessions", + &error, + G_TYPE_INVALID, + G_TYPE_BOOLEAN, &can_activate_sessions, + G_TYPE_INVALID); + if (! res) { + if (error != NULL) { + g_warning ("unable to determine if seat can activate sessions: %s", + error->message); + g_error_free (error); + } else { + g_warning ("unable to determine if seat can activate sessions"); + } + return FALSE; + } + + return can_activate_sessions; +} + gboolean gdm_user_manager_activate_user_session (GdmUserManager *manager, GdmUser *user) { gboolean ret; - char *ssid; + const char *ssid; gboolean res; + gboolean can_activate_sessions; g_return_val_if_fail (GDM_IS_USER_MANAGER (manager), FALSE); g_return_val_if_fail (GDM_IS_USER (user), FALSE); + g_return_val_if_fail (manager->priv->is_loaded, FALSE); ret = FALSE; - ssid = _get_primary_user_session_id (manager, user); + can_activate_sessions = gdm_user_manager_can_switch (manager); + + if (! can_activate_sessions) { + g_debug ("GdmUserManager: seat is unable to activate sessions"); + goto out; + } + + ssid = gdm_user_get_primary_session_id (user); if (ssid == NULL) { goto out; } - res = activate_session_id (manager, manager->priv->seat_id, ssid); + res = activate_session_id (manager, manager->priv->seat.id, ssid); if (! res) { g_debug ("GdmUserManager: unable to activate session: %s", ssid); goto out; @@ -469,6 +550,10 @@ on_user_sessions_changed (GdmUser *user, { guint nsessions; + if (! manager->priv->is_loaded) { + return; + } + nsessions = gdm_user_get_num_sessions (user); g_debug ("GdmUserManager: sessions changed user=%s num=%d", @@ -484,330 +569,825 @@ on_user_sessions_changed (GdmUser *user, } static void -on_user_icon_changed (GdmUser *user, - GdmUserManager *manager) +on_user_changed (GdmUser *user, + GdmUserManager *manager) { - g_debug ("GdmUserManager: user icon changed"); + if (manager->priv->is_loaded) { + g_debug ("GdmUserManager: user changed"); + g_signal_emit (manager, signals[USER_CHANGED], 0, user); + } } -static char * -get_seat_id_for_session (DBusGConnection *connection, - const char *session_id) +static void +on_get_seat_id_finished (DBusGProxy *proxy, + DBusGProxyCall *call, + GdmUserManager *manager) { - DBusGProxy *proxy; - GError *error; - char *seat_id; - gboolean res; + GError *error; + char *seat_id; + gboolean res; - proxy = NULL; + g_assert (manager->priv->seat.get_seat_id_call == call); + + error = NULL; seat_id = NULL; + res = dbus_g_proxy_end_call (proxy, + call, + &error, + DBUS_TYPE_G_OBJECT_PATH, + &seat_id, + G_TYPE_INVALID); + manager->priv->seat.get_seat_id_call = NULL; + g_object_unref (proxy); - proxy = dbus_g_proxy_new_for_name (connection, - CK_NAME, - session_id, - CK_SESSION_INTERFACE); - if (proxy == NULL) { - g_warning ("Failed to connect to the ConsoleKit session object"); - goto out; - } - - error = NULL; - res = dbus_g_proxy_call (proxy, - "GetSeatId", - &error, - G_TYPE_INVALID, - DBUS_TYPE_G_OBJECT_PATH, &seat_id, - G_TYPE_INVALID); if (! res) { - g_debug ("Failed to identify the current seat: %s", error->message); - g_error_free (error); - } - out: - if (proxy != NULL) { - g_object_unref (proxy); + if (error != NULL) { + g_debug ("Failed to identify the seat of the " + "current session: %s", + error->message); + g_error_free (error); + } else { + g_debug ("Failed to identify the seat of the " + "current session"); + } + unload_seat (manager); + maybe_set_is_loaded (manager); + return; } - return seat_id; + g_debug ("GdmUserManager: Found current seat: %s", seat_id); + + manager->priv->seat.id = seat_id; + manager->priv->seat.state++; + + load_seat_incrementally (manager); } -static char * -get_x11_display_for_session (DBusGConnection *connection, - const char *session_id) +static void +get_seat_id_for_current_session (GdmUserManager *manager) { DBusGProxy *proxy; - GError *error; - char *x11_display; - gboolean res; + DBusGProxyCall *call; - proxy = NULL; - x11_display = NULL; - - proxy = dbus_g_proxy_new_for_name (connection, + proxy = dbus_g_proxy_new_for_name (manager->priv->connection, CK_NAME, - session_id, + manager->priv->seat.session_id, CK_SESSION_INTERFACE); if (proxy == NULL) { g_warning ("Failed to connect to the ConsoleKit session object"); - goto out; + goto failed; } - error = NULL; - res = dbus_g_proxy_call (proxy, - "GetX11Display", - &error, - G_TYPE_INVALID, - G_TYPE_STRING, &x11_display, - G_TYPE_INVALID); - if (! res) { - g_debug ("Failed to identify the x11 display: %s", error->message); - g_error_free (error); + call = dbus_g_proxy_begin_call (proxy, + "GetSeatId", + (DBusGProxyCallNotify) + on_get_seat_id_finished, + manager, + NULL, + G_TYPE_INVALID); + if (call == NULL) { + g_warning ("GdmUserManager: failed to make GetSeatId call"); + goto failed; } - out: + + manager->priv->seat.get_seat_id_call = call; + + return; + +failed: if (proxy != NULL) { g_object_unref (proxy); } - return x11_display; + unload_seat (manager); +} + +static gint +match_name_cmpfunc (gconstpointer a, + gconstpointer b) +{ + return g_strcmp0 ((char *) a, + (char *) b); } static gboolean -maybe_add_session_for_user (GdmUserManager *manager, - GdmUser *user, - const char *ssid) +username_in_exclude_list (GdmUserManager *manager, + const char *username) { - char *sid; - char *x11_display; - gboolean ret; + GSList *found; + gboolean ret = FALSE; - ret = FALSE; - sid = NULL; - x11_display = NULL; - - /* skip if on another seat */ - sid = get_seat_id_for_session (manager->priv->connection, ssid); - if (sid == NULL - || manager->priv->seat_id == NULL - || strcmp (sid, manager->priv->seat_id) != 0) { - g_debug ("GdmUserManager: not adding session on other seat: %s", ssid); - goto out; + /* always exclude the "gdm" user. */ + if (username == NULL || (strcmp (username, GDM_USERNAME) == 0)) { + return TRUE; } - /* skip if doesn't have an x11 display */ - x11_display = get_x11_display_for_session (manager->priv->connection, ssid); - if (x11_display == NULL || x11_display[0] == '\0') { - g_debug ("GdmUserManager: not adding session without a x11 display: %s", ssid); - goto out; + if (manager->priv->exclude_usernames != NULL) { + found = g_slist_find_custom (manager->priv->exclude_usernames, + username, + match_name_cmpfunc); + if (found != NULL) { + ret = TRUE; + } } - if (g_hash_table_lookup (manager->priv->exclusions, gdm_user_get_user_name (user))) { - g_debug ("GdmUserManager: excluding user '%s'", gdm_user_get_user_name (user)); - goto out; - } + return ret; +} +static void +add_session_for_user (GdmUserManager *manager, + GdmUser *user, + const char *ssid) +{ g_hash_table_insert (manager->priv->sessions, g_strdup (ssid), g_strdup (gdm_user_get_user_name (user))); _gdm_user_add_session (user, ssid); g_debug ("GdmUserManager: added session for user: %s", gdm_user_get_user_name (user)); - - ret = TRUE; - - out: - g_free (sid); - g_free (x11_display); - - return ret; } static void -add_sessions_for_user (GdmUserManager *manager, - GdmUser *user) +set_has_multiple_users (GdmUserManager *manager, + gboolean has_multiple_users) { - DBusGProxy *proxy; - GError *error; - gboolean res; - guint32 uid; - GPtrArray *sessions; - int i; - - proxy = dbus_g_proxy_new_for_name (manager->priv->connection, - CK_NAME, - CK_MANAGER_PATH, - CK_MANAGER_INTERFACE); - if (proxy == NULL) { - g_warning ("Failed to connect to the ConsoleKit manager object"); - goto out; - } - - uid = gdm_user_get_uid (user); - - g_debug ("Getting list of sessions for user %u", uid); - - error = NULL; - res = dbus_g_proxy_call (proxy, - "GetSessionsForUnixUser", - &error, - G_TYPE_UINT, uid, - G_TYPE_INVALID, - dbus_g_type_get_collection ("GPtrArray", DBUS_TYPE_G_OBJECT_PATH), - &sessions, - G_TYPE_INVALID); - if (! res) { - g_debug ("Failed to find sessions for user: %s", error->message); - g_error_free (error); - goto out; - } - - g_debug ("Found %d sessions for user %s", sessions->len, gdm_user_get_user_name (user)); - - for (i = 0; i < sessions->len; i++) { - char *ssid; - - ssid = g_ptr_array_index (sessions, i); - maybe_add_session_for_user (manager, user, ssid); - } - - g_ptr_array_foreach (sessions, (GFunc)g_free, NULL); - g_ptr_array_free (sessions, TRUE); - - out: - if (proxy != NULL) { - g_object_unref (proxy); + if (manager->priv->has_multiple_users != has_multiple_users) { + manager->priv->has_multiple_users = has_multiple_users; + g_object_notify (G_OBJECT (manager), "has-multiple-users"); } } static GdmUser * -create_user (GdmUserManager *manager) +create_new_user (GdmUserManager *manager) { GdmUser *user; - user = g_object_new (GDM_TYPE_USER, "manager", manager, NULL); - g_signal_connect (user, - "sessions-changed", - G_CALLBACK (on_user_sessions_changed), - manager); - g_signal_connect (user, - "icon-changed", - G_CALLBACK (on_user_icon_changed), - manager); - return user; + user = g_object_new (GDM_TYPE_USER, NULL); + + manager->priv->new_users = g_slist_prepend (manager->priv->new_users, user); + + g_signal_connect (user, "notify::is-loaded", G_CALLBACK (on_new_user_loaded), manager); + + return g_object_ref (user); } static void add_user (GdmUserManager *manager, GdmUser *user) { - add_sessions_for_user (manager, user); - g_hash_table_insert (manager->priv->users, + const char *object_path; + + g_hash_table_insert (manager->priv->users_by_name, g_strdup (gdm_user_get_user_name (user)), g_object_ref (user)); - g_signal_emit (manager, signals[USER_ADDED], 0, user); + object_path = gdm_user_get_object_path (user); + if (object_path != NULL) { + g_hash_table_insert (manager->priv->users_by_object_path, + (gpointer) object_path, + g_object_ref (user)); + } + + g_signal_connect (user, + "sessions-changed", + G_CALLBACK (on_user_sessions_changed), + manager); + g_signal_connect (user, + "changed", + G_CALLBACK (on_user_changed), + manager); + + if (manager->priv->is_loaded) { + g_signal_emit (manager, signals[USER_ADDED], 0, user); + } + + if (g_hash_table_size (manager->priv->users_by_name) > 1) { + set_has_multiple_users (manager, TRUE); + } +} + +static void +remove_user (GdmUserManager *manager, + GdmUser *user) +{ + g_object_ref (user); + + g_signal_handlers_disconnect_by_func (user, on_user_changed, manager); + g_signal_handlers_disconnect_by_func (user, on_user_sessions_changed, manager); + if (gdm_user_get_object_path (user) != NULL) { + g_hash_table_remove (manager->priv->users_by_object_path, gdm_user_get_object_path (user)); + } + g_hash_table_remove (manager->priv->users_by_name, gdm_user_get_user_name (user)); + + if (manager->priv->is_loaded) { + g_signal_emit (manager, signals[USER_REMOVED], 0, user); + } + + g_object_unref (user); + + if (g_hash_table_size (manager->priv->users_by_name) > 1) { + set_has_multiple_users (manager, FALSE); + } +} + +static void +on_new_user_loaded (GdmUser *user, + GParamSpec *pspec, + GdmUserManager *manager) +{ + const char *username; + GdmUser *old_user; + + if (!gdm_user_is_loaded (user)) { + return; + } + + g_signal_handlers_disconnect_by_func (user, on_new_user_loaded, manager); + manager->priv->new_users = g_slist_remove (manager->priv->new_users, + user); + + username = gdm_user_get_user_name (user); + + if (username == NULL) { + const char *object_path; + + object_path = gdm_user_get_object_path (user); + + if (object_path != NULL) { + g_warning ("GdmUserManager: user has no username " + "(object path: %s, uid: %lu)", + object_path, gdm_user_get_uid (user)); + } else { + g_warning ("GdmUserManager: user has no username (uid: %lu)", + gdm_user_get_uid (user)); + } + g_object_unref (user); + return; + } + + if (username_in_exclude_list (manager, username)) { + g_debug ("GdmUserManager: excluding user '%s'", username); + g_object_unref (user); + return; + } + + old_user = g_hash_table_lookup (manager->priv->users_by_name, username); + + /* If username got added earlier by a different means, trump it now. + */ + if (old_user != NULL) { + remove_user (manager, old_user); + } + + add_user (manager, user); + g_object_unref (user); + + if (manager->priv->new_users == NULL) { + set_is_loaded (manager, TRUE); + } } static GdmUser * -add_new_user_for_pwent (GdmUserManager *manager, - struct passwd *pwent) +add_new_user_for_object_path (const char *object_path, + GdmUserManager *manager) { GdmUser *user; - g_debug ("Creating new user"); + user = g_hash_table_lookup (manager->priv->users_by_object_path, object_path); - user = create_user (manager); - _gdm_user_update (user, pwent); - - add_user (manager, user); + if (user != NULL) { + return user; + } + user = create_new_user (manager); + _gdm_user_update_from_object_path (user, object_path); return user; } -static char * -get_current_seat_id (DBusGConnection *connection) +static void +on_new_user_in_accounts_service (DBusGProxy *proxy, + const char *object_path, + gpointer user_data) +{ + GdmUserManager *manager = GDM_USER_MANAGER (user_data); + + add_new_user_for_object_path (object_path, manager); +} + +static void +on_user_removed_in_accounts_service (DBusGProxy *proxy, + const char *object_path, + gpointer user_data) +{ + GdmUserManager *manager = GDM_USER_MANAGER (user_data); + GdmUser *user; + + user = g_hash_table_lookup (manager->priv->users_by_object_path, object_path); + + manager->priv->new_users = g_slist_remove (manager->priv->new_users, user); + + remove_user (manager, user); +} + +static void +on_get_current_session_finished (DBusGProxy *proxy, + DBusGProxyCall *call, + GdmUserManager *manager) +{ + GError *error; + char *session_id; + gboolean res; + + g_assert (manager->priv->seat.get_current_session_call == call); + g_assert (manager->priv->seat.state == GDM_USER_MANAGER_SEAT_STATE_GET_SESSION_ID); + + error = NULL; + session_id = NULL; + res = dbus_g_proxy_end_call (proxy, + call, + &error, + DBUS_TYPE_G_OBJECT_PATH, + &session_id, + G_TYPE_INVALID); + manager->priv->seat.get_current_session_call = NULL; + g_object_unref (proxy); + + if (! res) { + if (error != NULL) { + g_debug ("Failed to identify the current session: %s", + error->message); + g_error_free (error); + } else { + g_debug ("Failed to identify the current session"); + } + unload_seat (manager); + maybe_set_is_loaded (manager); + return; + } + + manager->priv->seat.session_id = session_id; + manager->priv->seat.state++; + + load_seat_incrementally (manager); +} + +static void +get_current_session_id (GdmUserManager *manager) { DBusGProxy *proxy; - GError *error; - char *session_id; - char *seat_id; - gboolean res; + DBusGProxyCall *call; - proxy = NULL; - session_id = NULL; - seat_id = NULL; - - proxy = dbus_g_proxy_new_for_name (connection, + proxy = dbus_g_proxy_new_for_name (manager->priv->connection, CK_NAME, CK_MANAGER_PATH, CK_MANAGER_INTERFACE); if (proxy == NULL) { g_warning ("Failed to connect to the ConsoleKit manager object"); - goto out; + goto failed; } - error = NULL; - res = dbus_g_proxy_call (proxy, - "GetCurrentSession", - &error, - G_TYPE_INVALID, - DBUS_TYPE_G_OBJECT_PATH, - &session_id, - G_TYPE_INVALID); - if (! res) { - g_debug ("Failed to identify the current session: %s", error->message); - g_error_free (error); - goto out; + call = dbus_g_proxy_begin_call (proxy, + "GetCurrentSession", + (DBusGProxyCallNotify) + on_get_current_session_finished, + manager, + NULL, + G_TYPE_INVALID); + if (call == NULL) { + g_warning ("GdmUserManager: failed to make GetCurrentSession call"); + goto failed; } - seat_id = get_seat_id_for_session (connection, session_id); + manager->priv->seat.get_current_session_call = call; - out: + return; + +failed: if (proxy != NULL) { g_object_unref (proxy); } - g_free (session_id); - return seat_id; + unload_seat (manager); } -static gboolean -get_uid_from_session_id (GdmUserManager *manager, - const char *session_id, - uid_t *uidp) +static void +unload_new_session (GdmUserManagerNewSession *new_session) { - DBusGProxy *proxy; - GError *error; - guint uid; - gboolean res; + GdmUserManager *manager; + + manager = new_session->manager; + + manager->priv->new_sessions = g_slist_remove (manager->priv->new_sessions, + new_session); + + if (new_session->proxy != NULL) { + g_object_unref (new_session->proxy); + } + + g_free (new_session->x11_display); + g_free (new_session->id); + + g_slice_free (GdmUserManagerNewSession, new_session); +} + +static void +get_proxy_for_new_session (GdmUserManagerNewSession *new_session) +{ + GdmUserManager *manager; + DBusGProxy *proxy; + + manager = new_session->manager; proxy = dbus_g_proxy_new_for_name (manager->priv->connection, CK_NAME, - session_id, + new_session->id, CK_SESSION_INTERFACE); if (proxy == NULL) { - g_warning ("Failed to connect to the ConsoleKit session object"); - return FALSE; + g_warning ("Failed to connect to the ConsoleKit '%s' object", + new_session->id); + unload_new_session (new_session); + return; } + new_session->proxy = proxy; + new_session->state++; + + load_new_session_incrementally (new_session); +} + +static void +on_get_unix_user_finished (DBusGProxy *proxy, + DBusGProxyCall *call, + GdmUserManagerNewSession *new_session) +{ + GdmUserManager *manager; + GError *error; + guint uid; + gboolean res; + + manager = new_session->manager; + + g_assert (new_session->get_unix_user_call == call); + error = NULL; - res = dbus_g_proxy_call (proxy, - "GetUnixUser", - &error, - G_TYPE_INVALID, - G_TYPE_UINT, &uid, - G_TYPE_INVALID); - g_object_unref (proxy); + + uid = (guint) -1; + res = dbus_g_proxy_end_call (proxy, + call, + &error, + G_TYPE_UINT, &uid, + G_TYPE_INVALID); + new_session->get_unix_user_call = NULL; if (! res) { - g_warning ("Failed to query the session: %s", error->message); + if (error != NULL) { + g_debug ("Failed to get uid of session '%s': %s", + new_session->id, error->message); + g_error_free (error); + } else { + g_debug ("Failed to get uid of session '%s'", + new_session->id); + } + unload_new_session (new_session); + return; + } + + g_debug ("GdmUserManager: Found uid of session '%s': %u", + new_session->id, uid); + + new_session->uid = (uid_t) uid; + new_session->state++; + + load_new_session_incrementally (new_session); +} + +static void +get_uid_for_new_session (GdmUserManagerNewSession *new_session) +{ + DBusGProxyCall *call; + + g_assert (new_session->proxy != NULL); + + call = dbus_g_proxy_begin_call (new_session->proxy, + "GetUnixUser", + (DBusGProxyCallNotify) + on_get_unix_user_finished, + new_session, + NULL, + G_TYPE_INVALID); + if (call == NULL) { + g_warning ("GdmUserManager: failed to make GetUnixUser call"); + goto failed; + } + + new_session->get_unix_user_call = call; + return; + +failed: + unload_new_session (new_session); +} + +static void +on_find_user_by_name_finished (DBusGProxy *proxy, + DBusGProxyCall *call, + GdmUserManagerFetchUserRequest *request) +{ + GdmUserManager *manager; + GError *error; + char *object_path; + gboolean res; + + g_assert (request->call == call); + + error = NULL; + object_path = NULL; + manager = request->manager; + res = dbus_g_proxy_end_call (manager->priv->accounts_proxy, + call, + &error, + DBUS_TYPE_G_OBJECT_PATH, + &object_path, + G_TYPE_INVALID); + if (! res) { + if (error != NULL) { + g_debug ("GdmUserManager: Failed to find user %s: %s", + request->username, error->message); + g_error_free (error); + } else { + g_debug ("GdmUserManager: Failed to find user %s", + request->username); + } + give_up_and_fetch_user_locally (manager, request); + return; + } + + g_debug ("GdmUserManager: Found object path of user '%s': %s", + request->username, object_path); + request->object_path = object_path; + request->state++; + + fetch_user_incrementally (request); +} + +static void +find_user_in_accounts_service (GdmUserManager *manager, + GdmUserManagerFetchUserRequest *request) +{ + DBusGProxyCall *call; + + g_debug ("GdmUserManager: Looking for user %s in accounts service", + request->username); + + g_assert (manager->priv->accounts_proxy != NULL); + + call = dbus_g_proxy_begin_call (manager->priv->accounts_proxy, + "FindUserByName", + (DBusGProxyCallNotify) + on_find_user_by_name_finished, + request, + NULL, + G_TYPE_STRING, + request->username, + G_TYPE_INVALID); + + if (call == NULL) { + g_warning ("GdmUserManager: failed to make FindUserByName('%s') call", + request->username); + goto failed; + } + + request->call = call; + return; + +failed: + give_up_and_fetch_user_locally (manager, request); +} + +static void +set_is_loaded (GdmUserManager *manager, + gboolean is_loaded) +{ + if (manager->priv->is_loaded != is_loaded) { + manager->priv->is_loaded = is_loaded; + g_object_notify (G_OBJECT (manager), "is-loaded"); + } +} + +static void +on_list_cached_users_finished (DBusGProxy *proxy, + DBusGProxyCall *call_id, + gpointer data) +{ + GdmUserManager *manager = data; + GError *error = NULL; + GPtrArray *paths; + + manager->priv->listing_cached_users = FALSE; + if (!dbus_g_proxy_end_call (proxy, + call_id, + &error, + dbus_g_type_get_collection ("GPtrArray", DBUS_TYPE_G_OBJECT_PATH), &paths, + G_TYPE_INVALID)) { + g_debug ("GdmUserManager: ListCachedUsers failed: %s", error->message); g_error_free (error); - return FALSE; + + g_object_unref (manager->priv->accounts_proxy); + manager->priv->accounts_proxy = NULL; + + load_users_manually (manager); + + return; } - if (uidp != NULL) { - *uidp = (uid_t) uid; + g_ptr_array_foreach (paths, (GFunc)add_new_user_for_object_path, manager); + + g_ptr_array_foreach (paths, (GFunc)g_free, NULL); + g_ptr_array_free (paths, TRUE); + + /* Add users who are specifically included */ + if (manager->priv->include_usernames != NULL) { + GSList *l; + + for (l = manager->priv->include_usernames; l != NULL; l = l->next) { + GdmUser *user; + + g_debug ("GdmUserManager: Adding included user %s", (char *)l->data); + /* + * The call to gdm_user_manager_get_user will add the user if it is + * valid and not already in the hash. + */ + user = gdm_user_manager_get_user (manager, l->data); + if (user == NULL) { + g_debug ("GdmUserManager: unable to lookup user '%s'", (char *)l->data); + } + } + } +} + +static void +on_get_x11_display_finished (DBusGProxy *proxy, + DBusGProxyCall *call, + GdmUserManagerNewSession *new_session) +{ + GError *error; + char *x11_display; + gboolean res; + + g_assert (new_session->get_x11_display_call == call); + + error = NULL; + x11_display = NULL; + res = dbus_g_proxy_end_call (proxy, + call, + &error, + G_TYPE_STRING, + &x11_display, + G_TYPE_INVALID); + new_session->get_x11_display_call = NULL; + + if (! res) { + if (error != NULL) { + g_debug ("Failed to get the x11 display of session '%s': %s", + new_session->id, error->message); + g_error_free (error); + } else { + g_debug ("Failed to get the x11 display of session '%s'", + new_session->id); + } + unload_new_session (new_session); + return; } - return TRUE; + g_debug ("GdmUserManager: Found x11 display of session '%s': %s", + new_session->id, x11_display); + + new_session->x11_display = x11_display; + new_session->state++; + + load_new_session_incrementally (new_session); +} + +static void +get_x11_display_for_new_session (GdmUserManagerNewSession *new_session) +{ + DBusGProxyCall *call; + + g_assert (new_session->proxy != NULL); + + call = dbus_g_proxy_begin_call (new_session->proxy, + "GetX11Display", + (DBusGProxyCallNotify) + on_get_x11_display_finished, + new_session, + NULL, + G_TYPE_INVALID); + if (call == NULL) { + g_warning ("GdmUserManager: failed to make GetX11Display call"); + goto failed; + } + + new_session->get_x11_display_call = call; + return; + +failed: + unload_new_session (new_session); +} + +static gboolean +get_pwent_for_name (const char *name, + struct passwd **pwentp) +{ + struct passwd *pwent; + + do { + errno = 0; + pwent = getpwnam (name); + } while (pwent == NULL && errno == EINTR); + + if (pwentp != NULL) { + *pwentp = pwent; + } + + return (pwent != NULL); +} + +static gboolean +get_pwent_for_uid (uid_t uid, + struct passwd **pwentp) +{ + struct passwd *pwent; + + do { + errno = 0; + pwent = getpwuid (uid); + } while (pwent == NULL && errno == EINTR); + + if (pwentp != NULL) { + *pwentp = pwent; + } + + return (pwent != NULL); +} + +static void +maybe_add_new_session (GdmUserManagerNewSession *new_session) +{ + GdmUserManager *manager; + struct passwd *pwent; + GdmUser *user; + + manager = GDM_USER_MANAGER (new_session->manager); + + errno = 0; + get_pwent_for_uid (new_session->uid, &pwent); + if (pwent == NULL) { + g_warning ("Unable to lookup user ID %d: %s", + (int) new_session->uid, g_strerror (errno)); + goto failed; + } + + /* check exclusions up front */ + if (username_in_exclude_list (manager, pwent->pw_name)) { + g_debug ("GdmUserManager: excluding user '%s'", pwent->pw_name); + goto failed; + } + + user = gdm_user_manager_get_user (manager, pwent->pw_name); + if (user == NULL) { + return; + } + + add_session_for_user (manager, user, new_session->id); + + /* if we haven't yet gotten the login frequency + then at least add one because the session exists */ + if (gdm_user_get_login_frequency (user) == 0) { + _gdm_user_update_login_frequency (user, 1); + } + + manager->priv->seat.state = GDM_USER_MANAGER_SEAT_STATE_LOADED; + unload_new_session (new_session); + return; + +failed: + unload_new_session (new_session); +} + +static void +load_new_session (GdmUserManager *manager, + const char *session_id) +{ + GdmUserManagerNewSession *new_session; + + new_session = g_slice_new0 (GdmUserManagerNewSession); + + new_session->manager = manager; + new_session->id = g_strdup (session_id); + new_session->state = GDM_USER_MANAGER_NEW_SESSION_STATE_UNLOADED + 1; + + manager->priv->new_sessions = g_slist_prepend (manager->priv->new_sessions, + new_session); + load_new_session_incrementally (new_session); } static void @@ -815,54 +1395,22 @@ seat_session_added (DBusGProxy *seat_proxy, const char *session_id, GdmUserManager *manager) { - uid_t uid; - gboolean res; - struct passwd *pwent; - GdmUser *user; - gboolean is_new; + g_debug ("GdmUserManager: Session added: %s", session_id); - g_debug ("Session added: %s", session_id); + load_new_session (manager, session_id); +} - res = get_uid_from_session_id (manager, session_id, &uid); - if (! res) { - g_warning ("Unable to lookup user for session"); - return; - } +static gint +match_new_session_cmpfunc (gconstpointer a, + gconstpointer b) +{ + GdmUserManagerNewSession *new_session; + const char *session_id; - errno = 0; - pwent = getpwuid (uid); - if (pwent == NULL) { - g_warning ("Unable to lookup user id %d: %s", (int)uid, g_strerror (errno)); - return; - } + new_session = (GdmUserManagerNewSession *) a; + session_id = (const char *) b; - /* check exclusions up front */ - if (g_hash_table_lookup (manager->priv->exclusions, pwent->pw_name)) { - g_debug ("GdmUserManager: excluding user '%s'", pwent->pw_name); - return; - } - - user = g_hash_table_lookup (manager->priv->users, pwent->pw_name); - if (user == NULL) { - g_debug ("Creating new user"); - - user = create_user (manager); - _gdm_user_update (user, pwent); - is_new = TRUE; - } else { - is_new = FALSE; - } - - res = maybe_add_session_for_user (manager, user, session_id); - - /* only add the user if we added a session */ - if (is_new) { - if (res) { - add_user (manager, user); - } else { - g_object_unref (user); - } - } + return strcmp (new_session->id, session_id); } static void @@ -870,10 +1418,36 @@ seat_session_removed (DBusGProxy *seat_proxy, const char *session_id, GdmUserManager *manager) { - GdmUser *user; - char *username; + GdmUser *user; + GSList *found; + char *username; - g_debug ("Session removed: %s", session_id); + g_debug ("GdmUserManager: Session removed: %s", session_id); + + found = g_slist_find_custom (manager->priv->new_sessions, + session_id, + match_new_session_cmpfunc); + + if (found != NULL) { + GdmUserManagerNewSession *new_session; + + new_session = (GdmUserManagerNewSession *) found->data; + + if (new_session->state > GDM_USER_MANAGER_NEW_SESSION_STATE_GET_X11_DISPLAY) { + g_debug ("GdmUserManager: New session for uid %d on " + "x11 display %s removed before fully loading", + (int) new_session->uid, new_session->x11_display); + } else if (new_session->state > GDM_USER_MANAGER_NEW_SESSION_STATE_GET_UID) { + g_debug ("GdmUserManager: New session for uid %d " + "removed before fully loading", + (int) new_session->uid); + } else { + g_debug ("GdmUserManager: New session removed " + "before fully loading"); + } + unload_new_session (new_session); + return; + } /* since the session object may already be gone * we can't query CK directly */ @@ -883,7 +1457,7 @@ seat_session_removed (DBusGProxy *seat_proxy, return; } - user = g_hash_table_lookup (manager->priv->users, username); + user = g_hash_table_lookup (manager->priv->users_by_name, username); if (user == NULL) { /* nothing to do */ return; @@ -894,12 +1468,12 @@ seat_session_removed (DBusGProxy *seat_proxy, } static void -on_proxy_destroy (DBusGProxy *proxy, - GdmUserManager *manager) +on_seat_proxy_destroy (DBusGProxy *proxy, + GdmUserManager *manager) { g_debug ("GdmUserManager: seat proxy destroyed"); - manager->priv->seat_proxy = NULL; + manager->priv->seat.proxy = NULL; } static void @@ -908,37 +1482,28 @@ get_seat_proxy (GdmUserManager *manager) DBusGProxy *proxy; GError *error; - g_assert (manager->priv->seat_proxy == NULL); - - error = NULL; - manager->priv->connection = dbus_g_bus_get (DBUS_BUS_SYSTEM, &error); - if (manager->priv->connection == NULL) { - g_warning ("Failed to connect to the D-Bus daemon: %s", error->message); - g_error_free (error); - return; - } - - manager->priv->seat_id = get_current_seat_id (manager->priv->connection); - if (manager->priv->seat_id == NULL) { - return; - } - - g_debug ("GdmUserManager: Found current seat: %s", manager->priv->seat_id); + g_assert (manager->priv->seat.proxy == NULL); error = NULL; proxy = dbus_g_proxy_new_for_name_owner (manager->priv->connection, CK_NAME, - manager->priv->seat_id, + manager->priv->seat.id, CK_SEAT_INTERFACE, &error); if (proxy == NULL) { - g_warning ("Failed to connect to the ConsoleKit seat object: %s", error->message); - g_error_free (error); + if (error != NULL) { + g_warning ("Failed to connect to the ConsoleKit seat object: %s", + error->message); + g_error_free (error); + } else { + g_warning ("Failed to connect to the ConsoleKit seat object"); + } + unload_seat (manager); return; } - g_signal_connect (proxy, "destroy", G_CALLBACK (on_proxy_destroy), manager); + g_signal_connect (proxy, "destroy", G_CALLBACK (on_seat_proxy_destroy), manager); dbus_g_proxy_add_signal (proxy, "SessionAdded", @@ -958,8 +1523,205 @@ get_seat_proxy (GdmUserManager *manager) G_CALLBACK (seat_session_removed), manager, NULL); - manager->priv->seat_proxy = proxy; + manager->priv->seat.proxy = proxy; + manager->priv->seat.state = GDM_USER_MANAGER_SEAT_STATE_LOADED; +} +static void +unload_seat (GdmUserManager *manager) +{ + manager->priv->seat.state = GDM_USER_MANAGER_SEAT_STATE_UNLOADED; + + if (manager->priv->seat.proxy != NULL) { + g_object_unref (manager->priv->seat.proxy); + manager->priv->seat.proxy = NULL; + } + + g_free (manager->priv->seat.id); + manager->priv->seat.id = NULL; + + g_free (manager->priv->seat.session_id); + manager->priv->seat.session_id = NULL; +} + +static void +get_accounts_proxy (GdmUserManager *manager) +{ + DBusGProxy *proxy; + GError *error; + + g_assert (manager->priv->accounts_proxy == NULL); + + error = NULL; + proxy = dbus_g_proxy_new_for_name (manager->priv->connection, + ACCOUNTS_NAME, + ACCOUNTS_PATH, + ACCOUNTS_INTERFACE); + manager->priv->accounts_proxy = proxy; + + dbus_g_proxy_add_signal (proxy, + "UserAdded", + DBUS_TYPE_G_OBJECT_PATH, + G_TYPE_INVALID); + dbus_g_proxy_add_signal (proxy, + "UserDeleted", + DBUS_TYPE_G_OBJECT_PATH, + G_TYPE_INVALID); + + dbus_g_proxy_connect_signal (proxy, + "UserAdded", + G_CALLBACK (on_new_user_in_accounts_service), + manager, + NULL); + dbus_g_proxy_connect_signal (proxy, + "UserDeleted", + G_CALLBACK (on_user_removed_in_accounts_service), + manager, + NULL); +} + +static void +load_new_session_incrementally (GdmUserManagerNewSession *new_session) +{ + switch (new_session->state) { + case GDM_USER_MANAGER_NEW_SESSION_STATE_GET_PROXY: + get_proxy_for_new_session (new_session); + break; + case GDM_USER_MANAGER_NEW_SESSION_STATE_GET_UID: + get_uid_for_new_session (new_session); + break; + case GDM_USER_MANAGER_NEW_SESSION_STATE_GET_X11_DISPLAY: + get_x11_display_for_new_session (new_session); + break; + case GDM_USER_MANAGER_NEW_SESSION_STATE_MAYBE_ADD: + maybe_add_new_session (new_session); + break; + case GDM_USER_MANAGER_NEW_SESSION_STATE_LOADED: + break; + default: + g_assert_not_reached (); + } +} + +static void +free_fetch_user_request (GdmUserManagerFetchUserRequest *request) +{ + GdmUserManager *manager; + + manager = request->manager; + + manager->priv->fetch_user_requests = g_slist_remove (manager->priv->fetch_user_requests, request); + g_free (request->username); + g_free (request->object_path); + g_slice_free (GdmUserManagerFetchUserRequest, request); +} + +static void +give_up_and_fetch_user_locally (GdmUserManager *manager, + GdmUserManagerFetchUserRequest *request) +{ + + g_debug ("GdmUserManager: account service unavailable, " + "fetching user %s locally", + request->username); + fetch_user_locally (manager, request->user, request->username); + request->state = GDM_USER_MANAGER_GET_USER_STATE_UNFETCHED; +} + +static void +on_user_manager_maybe_ready_for_request (GdmUserManager *manager, + GParamSpec *pspec, + GdmUserManagerFetchUserRequest *request) +{ + if (!manager->priv->is_loaded) { + return; + } + + g_signal_handlers_disconnect_by_func (manager, on_user_manager_maybe_ready_for_request, request); + + request->state++; + fetch_user_incrementally (request); +} + +static void +fetch_user_incrementally (GdmUserManagerFetchUserRequest *request) +{ + GdmUserManager *manager; + + g_debug ("GdmUserManager: finding user %s state %d", + request->username, request->state); + manager = request->manager; + switch (request->state) { + case GDM_USER_MANAGER_GET_USER_STATE_WAIT_FOR_LOADED: + if (manager->priv->is_loaded) { + request->state++; + fetch_user_incrementally (request); + } else { + g_debug ("GdmUserManager: waiting for user manager to load before finding user %s", + request->username); + g_signal_connect (manager, "notify::is-loaded", + G_CALLBACK (on_user_manager_maybe_ready_for_request), request); + + } + break; + + case GDM_USER_MANAGER_GET_USER_STATE_ASK_ACCOUNTS_SERVICE: + if (manager->priv->accounts_proxy == NULL) { + give_up_and_fetch_user_locally (manager, request); + } else { + find_user_in_accounts_service (manager, request); + } + break; + case GDM_USER_MANAGER_GET_USER_STATE_FETCHED: + g_debug ("GdmUserManager: user %s fetched", request->username); + _gdm_user_update_from_object_path (request->user, request->object_path); + break; + case GDM_USER_MANAGER_GET_USER_STATE_UNFETCHED: + g_debug ("GdmUserManager: user %s was not fetched", request->username); + break; + default: + g_assert_not_reached (); + } + + if (request->state == GDM_USER_MANAGER_GET_USER_STATE_FETCHED || + request->state == GDM_USER_MANAGER_GET_USER_STATE_UNFETCHED) { + g_debug ("GdmUserManager: finished handling request for user %s", + request->username); + free_fetch_user_request (request); + } +} + +static void +fetch_user_from_accounts_service (GdmUserManager *manager, + GdmUser *user, + const char *username) +{ + GdmUserManagerFetchUserRequest *request; + + request = g_slice_new0 (GdmUserManagerFetchUserRequest); + + request->manager = manager; + request->username = g_strdup (username); + request->user = user; + request->state = GDM_USER_MANAGER_GET_USER_STATE_UNFETCHED + 1; + + manager->priv->fetch_user_requests = g_slist_prepend (manager->priv->fetch_user_requests, + request); + fetch_user_incrementally (request); +} + +static void +fetch_user_locally (GdmUserManager *manager, + GdmUser *user, + const char *username) +{ + struct passwd *pwent; + + get_pwent_for_name (username, &pwent); + + if (pwent != NULL) { + _gdm_user_update_from_pwent (user, pwent); + } } /** @@ -967,10 +1729,12 @@ get_seat_proxy (GdmUserManager *manager) * @manager: the manager to query. * @username: the login name of the user to get. * - * Retrieves a pointer to the #GdmUser object for the login named @username - * from @manager. This pointer is not a reference, and should not be released. + * Retrieves a pointer to the #GdmUser object for the login @username + * from @manager. Trying to use this object before its + * #GdmUser:is-loaded property is %TRUE will result in undefined + * behavior. * - * Returns: (transfer none): a pointer to a #GdmUser object. + * Returns: (transfer none): #GdmUser object **/ GdmUser * gdm_user_manager_get_user (GdmUserManager *manager, @@ -981,54 +1745,49 @@ gdm_user_manager_get_user (GdmUserManager *manager, g_return_val_if_fail (GDM_IS_USER_MANAGER (manager), NULL); g_return_val_if_fail (username != NULL && username[0] != '\0', NULL); - user = g_hash_table_lookup (manager->priv->users, username); + user = g_hash_table_lookup (manager->priv->users_by_name, username); + /* if we don't have it loaded try to load it now */ if (user == NULL) { - struct passwd *pwent; + user = create_new_user (manager); - pwent = getpwnam (username); - - if (pwent != NULL) { - user = add_new_user_for_pwent (manager, pwent); + if (manager->priv->accounts_proxy != NULL) { + fetch_user_from_accounts_service (manager, user, username); + } else { + fetch_user_locally (manager, user, username); } } return user; } - /** * gdm_user_manager_get_user_by_uid: * @manager: the manager to query. - * @uid: the user id + * @uid: the uid of the user to get. * - * Retrieves a pointer to the #GdmUser object for the login named @username - * from @manager. + * Retrieves a pointer to the #GdmUser object for the uid @uid + * from @manager. Trying to use this object before its + * #GdmUser:is-loaded property is %TRUE will result in undefined + * behavior. * - * Returns: (transfer none): a pointer to a #GdmUser object. - **/ + * Returns: (transfer none): #GdmUser object + */ GdmUser * gdm_user_manager_get_user_by_uid (GdmUserManager *manager, gulong uid) { - GdmUser *user; struct passwd *pwent; g_return_val_if_fail (GDM_IS_USER_MANAGER (manager), NULL); - pwent = getpwuid (uid); + get_pwent_for_uid (uid, &pwent); if (pwent == NULL) { g_warning ("GdmUserManager: unable to lookup uid %d", (int)uid); return NULL; } - user = g_hash_table_lookup (manager->priv->users, pwent->pw_name); - - if (user == NULL) { - user = add_new_user_for_pwent (manager, pwent); - } - - return user; + return gdm_user_manager_get_user (manager, pwent->pw_name); } static void @@ -1043,8 +1802,11 @@ listify_hash_values_hfunc (gpointer key, /** * gdm_user_manager_list_users: + * @manager: a #GdmUserManager * - * Return value: (element-type GdmUser) (transfer container): list of #GdmUser elements + * Get a list of system user accounts + * + * Returns: (element-type GdmUser) (transfer full): List of #GdmUser objects */ GSList * gdm_user_manager_list_users (GdmUserManager *manager) @@ -1054,7 +1816,7 @@ gdm_user_manager_list_users (GdmUserManager *manager) g_return_val_if_fail (GDM_IS_USER_MANAGER (manager), NULL); retval = NULL; - g_hash_table_foreach (manager->priv->users, listify_hash_values_hfunc, &retval); + g_hash_table_foreach (manager->priv->users_by_name, listify_hash_values_hfunc, &retval); return g_slist_sort (retval, (GCompareFunc) gdm_user_collate); } @@ -1102,7 +1864,11 @@ parse_ck_history_line (const char *line, error = NULL; re = g_regex_new ("(?P[0-9a-zA-Z]+)[ ]+(?P[0-9]+)", 0, 0, &error); if (re == NULL) { - g_critical ("%s", error->message); + if (error != NULL) { + g_critical ("%s", error->message); + } else { + g_critical ("Error in regex call"); + } goto out; } @@ -1156,7 +1922,7 @@ process_ck_history_line (GdmUserManager *manager, return; } - if (g_hash_table_lookup (manager->priv->exclusions, username)) { + if (username_in_exclude_list (manager, username)) { g_debug ("GdmUserManager: excluding user '%s'", username); g_free (username); return; @@ -1169,11 +1935,44 @@ process_ck_history_line (GdmUserManager *manager, return; } - g_object_set (user, "login-frequency", frequency, NULL); - g_signal_emit (manager, signals [USER_LOGIN_FREQUENCY_CHANGED], 0, user); + _gdm_user_update_login_frequency (user, frequency); g_free (username); } +static void +maybe_set_is_loaded (GdmUserManager *manager) +{ + if (manager->priv->is_loaded) { + return; + } + + if (manager->priv->ck_history_pid != 0) { + return; + } + + if (manager->priv->load_passwd_pending) { + return; + } + + if (manager->priv->get_sessions_call != NULL) { + return; + } + + if (manager->priv->listing_cached_users) { + return; + } + + /* Don't set is_loaded yet unless the seat is already loaded + * or failed to load. + */ + if (manager->priv->seat.state != GDM_USER_MANAGER_SEAT_STATE_LOADED + && manager->priv->seat.state != GDM_USER_MANAGER_SEAT_STATE_UNLOADED) { + return; + } + + set_is_loaded (manager, TRUE); +} + static gboolean ck_history_watch (GIOChannel *source, GIOCondition condition, @@ -1208,45 +2007,105 @@ ck_history_watch (GIOChannel *source, } if (done) { - g_signal_emit (G_OBJECT (manager), signals[USERS_LOADED], 0); - manager->priv->ck_history_id = 0; + if (manager->priv->ck_history_watchdog_id != 0) { + g_source_remove (manager->priv->ck_history_watchdog_id); + manager->priv->ck_history_watchdog_id = 0; + } + manager->priv->ck_history_pid = 0; + + maybe_set_is_loaded (manager); + return FALSE; } return TRUE; } -static void -reload_ck_history (GdmUserManager *manager) +static int +signal_pid (int pid, + int signal) +{ + int status = -1; + + status = kill (pid, signal); + + if (status < 0) { + if (errno == ESRCH) { + g_debug ("Child process %lu was already dead.", + (unsigned long) pid); + } else { + char buf [1024]; + snprintf (buf, + sizeof (buf), + "Couldn't kill child process %lu", + (unsigned long) pid); + perror (buf); + } + } + + return status; +} + +static gboolean +ck_history_watchdog (GdmUserManager *manager) +{ + if (manager->priv->ck_history_pid > 0) { + g_debug ("Killing ck-history process"); + signal_pid (manager->priv->ck_history_pid, SIGTERM); + manager->priv->ck_history_pid = 0; + } + + manager->priv->ck_history_watchdog_id = 0; + return FALSE; +} + +static gboolean +load_ck_history (GdmUserManager *manager) { char *command; + char *since; const char *seat_id; GError *error; gboolean res; char **argv; int standard_out; GIOChannel *channel; + GTimeVal tv; + + g_assert (manager->priv->ck_history_id == 0); + + command = NULL; seat_id = NULL; - if (manager->priv->seat_id != NULL - && g_str_has_prefix (manager->priv->seat_id, "/org/freedesktop/ConsoleKit/")) { + if (manager->priv->seat.id != NULL + && g_str_has_prefix (manager->priv->seat.id, "/org/freedesktop/ConsoleKit/")) { - seat_id = manager->priv->seat_id + strlen ("/org/freedesktop/ConsoleKit/"); + seat_id = manager->priv->seat.id + strlen ("/org/freedesktop/ConsoleKit/"); } if (seat_id == NULL) { - g_debug ("Unable to find users: no seat-id found"); - return; + g_warning ("Unable to load CK history: no seat-id found"); + goto out; } - command = g_strdup_printf ("ck-history --frequent --seat='%s' --session-type=''", + g_get_current_time (&tv); + tv.tv_sec -= LOGIN_FREQUENCY_TIME_WINDOW_SECS; + since = g_time_val_to_iso8601 (&tv); + + command = g_strdup_printf ("ck-history --frequent --since='%s' --seat='%s' --session-type=''", + since, seat_id); + g_free (since); g_debug ("GdmUserManager: running '%s'", command); error = NULL; if (! g_shell_parse_argv (command, NULL, &argv, &error)) { - g_warning ("Could not parse command: %s", error->message); - g_error_free (error); + if (error != NULL) { + g_warning ("Could not parse command: %s", error->message); + g_error_free (error); + } else { + g_warning ("Could not parse command"); + } goto out; } @@ -1257,15 +2116,19 @@ reload_ck_history (GdmUserManager *manager) G_SPAWN_SEARCH_PATH, NULL, NULL, - NULL, /* pid */ + &manager->priv->ck_history_pid, /* pid */ NULL, &standard_out, NULL, &error); g_strfreev (argv); if (! res) { - g_warning ("Unable to run ck-history: %s", error->message); - g_error_free (error); + if (error != NULL) { + g_warning ("Unable to run ck-history: %s", error->message); + g_error_free (error); + } else { + g_warning ("Unable to run ck-history"); + } goto out; } @@ -1274,6 +2137,7 @@ reload_ck_history (GdmUserManager *manager) g_io_channel_set_flags (channel, g_io_channel_get_flags (channel) | G_IO_FLAG_NONBLOCK, NULL); + manager->priv->ck_history_watchdog_id = g_timeout_add_seconds (1, (GSourceFunc) ck_history_watchdog, manager); manager->priv->ck_history_id = g_io_add_watch (channel, G_IO_IN | G_IO_HUP | G_IO_ERR | G_IO_NVAL, (GIOFunc)ck_history_watch, @@ -1281,20 +2145,31 @@ reload_ck_history (GdmUserManager *manager) g_io_channel_unref (channel); out: + g_free (command); + + return manager->priv->ck_history_id != 0; } static void -reload_passwd (GdmUserManager *manager) +reload_passwd_file (GHashTable *valid_shells, + GSList *exclude_users, + GSList *include_users, + gboolean include_all, + GHashTable *current_users_by_name, + GSList **added_users, + GSList **removed_users) { - struct passwd *pwent; - GSList *old_users; - GSList *new_users; - GSList *list; - FILE *fp; + FILE *fp; + GHashTableIter iter; + GHashTable *new_users_by_name; + GdmUser *user; + char *name; - old_users = NULL; - new_users = NULL; + new_users_by_name = g_hash_table_new_full (g_str_hash, + g_str_equal, + NULL, + g_object_unref); errno = 0; fp = fopen (PATH_PASSWD, "r"); @@ -1303,74 +2178,127 @@ reload_passwd (GdmUserManager *manager) goto out; } - g_hash_table_foreach (manager->priv->users, listify_hash_values_hfunc, &old_users); - g_slist_foreach (old_users, (GFunc) g_object_ref, NULL); - /* Make sure we keep users who are logged in no matter what. */ - for (list = old_users; list; list = list->next) { - if (gdm_user_get_num_sessions (list->data) > 0) { - g_object_freeze_notify (G_OBJECT (list->data)); - new_users = g_slist_prepend (new_users, g_object_ref (list->data)); + g_hash_table_iter_init (&iter, current_users_by_name); + while (g_hash_table_iter_next (&iter, (gpointer *) &name, (gpointer *) &user)) { + struct passwd *pwent; + + get_pwent_for_name (name, &pwent); + if (pwent == NULL) { + continue; + } + + g_object_freeze_notify (G_OBJECT (user)); + _gdm_user_update_from_pwent (user, pwent); + g_hash_table_insert (new_users_by_name, (char *)gdm_user_get_user_name (user), g_object_ref (user)); + } + + if (include_users != NULL) { + GSList *l; + for (l = include_users; l != NULL; l = l->next) { + struct passwd *pwent; + + get_pwent_for_name (l->data, &pwent); + if (pwent == NULL) { + continue; + } + + user = g_hash_table_lookup (new_users_by_name, pwent->pw_name); + if (user != NULL) { + /* already there */ + continue; + } + + user = g_hash_table_lookup (current_users_by_name, pwent->pw_name); + if (user == NULL) { + user = g_object_new (GDM_TYPE_USER, NULL); + } else { + g_object_ref (user); + } + g_object_freeze_notify (G_OBJECT (user)); + _gdm_user_update_from_pwent (user, pwent); + g_hash_table_insert (new_users_by_name, (char *)gdm_user_get_user_name (user), user); } } - for (pwent = fgetpwent (fp); pwent != NULL; pwent = fgetpwent (fp)) { - GdmUser *user; + if (include_all != TRUE) { + g_debug ("GdmUserManager: include_all is FALSE"); + } else { + struct passwd *pwent; - user = NULL; + g_debug ("GdmUserManager: include_all is TRUE"); - /* Skip users below MinimalUID... */ - if (pwent->pw_uid < DEFAULT_MINIMAL_UID) { - continue; + for (pwent = fgetpwent (fp); + pwent != NULL; + pwent = fgetpwent (fp)) { + + /* Skip users below MinimalUID... */ + if (pwent->pw_uid < FALLBACK_MINIMAL_UID) { + continue; + } + + /* ...And users w/ invalid shells... */ + if (pwent->pw_shell == NULL + || !g_hash_table_lookup (valid_shells, pwent->pw_shell)) { + g_debug ("GdmUserManager: skipping user with bad shell: %s", pwent->pw_name); + continue; + } + + /* always exclude the "gdm" user. */ + if (strcmp (pwent->pw_name, GDM_USERNAME) == 0) { + continue; + } + + /* ...And explicitly excluded users */ + if (exclude_users != NULL) { + GSList *found; + + found = g_slist_find_custom (exclude_users, + pwent->pw_name, + match_name_cmpfunc); + if (found != NULL) { + g_debug ("GdmUserManager: explicitly skipping user: %s", pwent->pw_name); + continue; + } + } + + user = g_hash_table_lookup (new_users_by_name, pwent->pw_name); + if (user != NULL) { + /* already there */ + continue; + } + + user = g_hash_table_lookup (current_users_by_name, pwent->pw_name); + if (user == NULL) { + user = g_object_new (GDM_TYPE_USER, NULL); + } else { + g_object_ref (user); + } + + /* Freeze & update users not already in the new list */ + g_object_freeze_notify (G_OBJECT (user)); + _gdm_user_update_from_pwent (user, pwent); + g_hash_table_insert (new_users_by_name, (char *)gdm_user_get_user_name (user), user); } - - /* ...And users w/ invalid shells... */ - if (pwent->pw_shell == NULL || - !g_hash_table_lookup (manager->priv->shells, pwent->pw_shell)) { - g_debug ("GdmUserManager: skipping user with bad shell: %s", pwent->pw_name); - continue; - } - - /* ...And explicitly excluded users */ - if (g_hash_table_lookup (manager->priv->exclusions, pwent->pw_name)) { - g_debug ("GdmUserManager: explicitly skipping user: %s", pwent->pw_name); - continue; - } - - user = g_hash_table_lookup (manager->priv->users, pwent->pw_name); - - /* Update users already in the *new* list */ - if (g_slist_find (new_users, user)) { - _gdm_user_update (user, pwent); - continue; - } - - if (user == NULL) { - user = create_user (manager); - } else { - g_object_ref (user); - } - - /* Freeze & update users not already in the new list */ - g_object_freeze_notify (G_OBJECT (user)); - _gdm_user_update (user, pwent); - - new_users = g_slist_prepend (new_users, user); } /* Go through and handle added users */ - for (list = new_users; list; list = list->next) { - if (! g_slist_find (old_users, list->data)) { - add_user (manager, list->data); + g_hash_table_iter_init (&iter, new_users_by_name); + while (g_hash_table_iter_next (&iter, (gpointer *) &name, (gpointer *) &user)) { + GdmUser *user2; + user2 = g_hash_table_lookup (current_users_by_name, name); + if (user2 == NULL) { + *added_users = g_slist_prepend (*added_users, g_object_ref (user)); } } /* Go through and handle removed users */ - for (list = old_users; list; list = list->next) { - if (! g_slist_find (new_users, list->data)) { - g_signal_emit (manager, signals[USER_REMOVED], 0, list->data); - g_hash_table_remove (manager->priv->users, - gdm_user_get_user_name (list->data)); + g_hash_table_iter_init (&iter, current_users_by_name); + while (g_hash_table_iter_next (&iter, (gpointer *) &name, (gpointer *) &user)) { + GdmUser *user2; + user2 = g_hash_table_lookup (new_users_by_name, name); + if (user2 == NULL) { + *removed_users = g_slist_prepend (*removed_users, g_object_ref (user)); } } @@ -1379,39 +2307,222 @@ reload_passwd (GdmUserManager *manager) fclose (fp); - g_slist_foreach (new_users, (GFunc) g_object_thaw_notify, NULL); - g_slist_foreach (new_users, (GFunc) g_object_unref, NULL); - g_slist_free (new_users); + g_hash_table_iter_init (&iter, new_users_by_name); + while (g_hash_table_iter_next (&iter, (gpointer *) &name, (gpointer *) &user)) { + g_object_thaw_notify (G_OBJECT (user)); + } - g_slist_foreach (old_users, (GFunc) g_object_unref, NULL); - g_slist_free (old_users); + g_hash_table_destroy (new_users_by_name); } +typedef struct { + GdmUserManager *manager; + GSList *exclude_users; + GSList *include_users; + gboolean include_all; + GHashTable *shells; + GHashTable *current_users_by_name; + GSList *added_users; + GSList *removed_users; +} PasswdData; + static void -reload_users (GdmUserManager *manager) +passwd_data_free (PasswdData *data) { - reload_ck_history (manager); - reload_passwd (manager); + if (data->manager != NULL) { + g_object_unref (data->manager); + } + + g_slist_foreach (data->added_users, (GFunc) g_object_unref, NULL); + g_slist_free (data->added_users); + + g_slist_foreach (data->removed_users, (GFunc) g_object_unref, NULL); + g_slist_free (data->removed_users); + + g_slist_foreach (data->exclude_users, (GFunc) g_free, NULL); + g_slist_free (data->exclude_users); + + g_slist_foreach (data->include_users, (GFunc) g_free, NULL); + g_slist_free (data->include_users); + + g_slice_free (PasswdData, data); } static gboolean -reload_users_timeout (GdmUserManager *manager) +reload_passwd_job_done (PasswdData *data) { - reload_users (manager); - manager->priv->reload_id = 0; + GSList *l; + + g_debug ("GdmUserManager: done reloading passwd file"); + + /* Go through and handle added users */ + for (l = data->added_users; l != NULL; l = l->next) { + add_user (data->manager, l->data); + } + + /* Go through and handle removed users */ + for (l = data->removed_users; l != NULL; l = l->next) { + remove_user (data->manager, l->data); + } + + data->manager->priv->load_passwd_pending = FALSE; + + if (! data->manager->priv->is_loaded) { + maybe_set_is_loaded (data->manager); + + if (data->manager->priv->include_all == TRUE) { + monitor_local_users (data->manager); + } + } + + passwd_data_free (data); return FALSE; } -static void -queue_reload_users (GdmUserManager *manager) +static gboolean +do_reload_passwd_job (GIOSchedulerJob *job, + GCancellable *cancellable, + PasswdData *data) { - if (manager->priv->reload_id > 0) { + g_debug ("GdmUserManager: reloading passwd file worker"); + + reload_passwd_file (data->shells, + data->exclude_users, + data->include_users, + data->include_all, + data->current_users_by_name, + &data->added_users, + &data->removed_users); + + g_io_scheduler_job_send_to_mainloop_async (job, + (GSourceFunc) reload_passwd_job_done, + data, + NULL); + + return FALSE; +} + +static GSList * +slist_deep_copy (const GSList *list) +{ + GSList *retval; + GSList *l; + + if (list == NULL) + return NULL; + + retval = g_slist_copy ((GSList *) list); + for (l = retval; l != NULL; l = l->next) { + l->data = g_strdup (l->data); + } + + return retval; +} + +static void +schedule_reload_passwd (GdmUserManager *manager) +{ + PasswdData *passwd_data; + + manager->priv->load_passwd_pending = TRUE; + + passwd_data = g_slice_new0 (PasswdData); + passwd_data->manager = g_object_ref (manager); + passwd_data->shells = manager->priv->shells; + passwd_data->exclude_users = slist_deep_copy (manager->priv->exclude_usernames); + passwd_data->include_users = slist_deep_copy (manager->priv->include_usernames); + passwd_data->include_all = manager->priv->include_all; + passwd_data->current_users_by_name = manager->priv->users_by_name; + passwd_data->added_users = NULL; + passwd_data->removed_users = NULL; + + g_debug ("GdmUserManager: scheduling a passwd file update"); + + g_io_scheduler_push_job ((GIOSchedulerJobFunc) do_reload_passwd_job, + passwd_data, + NULL, + G_PRIORITY_DEFAULT, + NULL); +} + +static void +load_sessions_from_array (GdmUserManager *manager, + const char * const *session_ids, + int number_of_sessions) +{ + int i; + + for (i = 0; i < number_of_sessions; i++) { + load_new_session (manager, session_ids[i]); + } +} + +static void +on_get_sessions_finished (DBusGProxy *proxy, + DBusGProxyCall *call, + GdmUserManager *manager) +{ + GError *error; + gboolean res; + GPtrArray *sessions; + + g_assert (manager->priv->get_sessions_call == call); + + error = NULL; + sessions = NULL; + res = dbus_g_proxy_end_call (proxy, + call, + &error, + GDM_DBUS_TYPE_G_OBJECT_PATH_ARRAY, + &sessions, + G_TYPE_INVALID); + + if (! res) { + if (error != NULL) { + g_warning ("unable to determine sessions for seat: %s", + error->message); + g_error_free (error); + } else { + g_warning ("unable to determine sessions for seat"); + } return; } - g_signal_emit (G_OBJECT (manager), signals[LOADING_USERS], 0); - manager->priv->reload_id = g_idle_add ((GSourceFunc)reload_users_timeout, manager); + manager->priv->get_sessions_call = NULL; + g_assert (sessions->len <= G_MAXINT); + load_sessions_from_array (manager, + (const char * const *) sessions->pdata, + (int) sessions->len); + g_ptr_array_foreach (sessions, (GFunc) g_free, NULL); + g_ptr_array_free (sessions, TRUE); + maybe_set_is_loaded (manager); +} + +static void +load_sessions (GdmUserManager *manager) +{ + DBusGProxyCall *call; + + if (manager->priv->seat.proxy == NULL) { + g_debug ("GdmUserManager: no seat proxy; can't load sessions"); + return; + } + + call = dbus_g_proxy_begin_call (manager->priv->seat.proxy, + "GetSessions", + (DBusGProxyCallNotify) + on_get_sessions_finished, + manager, + NULL, + G_TYPE_INVALID); + + if (call == NULL) { + g_warning ("GdmUserManager: failed to make GetSessions call"); + return; + } + + manager->priv->get_sessions_call = call; } static void @@ -1438,6 +2549,109 @@ reload_shells (GdmUserManager *manager) endusershell (); } +static void +load_users_manually (GdmUserManager *manager) +{ + gboolean res; + + manager->priv->shells = g_hash_table_new_full (g_str_hash, + g_str_equal, + g_free, + NULL); + reload_shells (manager); + + load_sessions (manager); + + res = load_ck_history (manager); + schedule_reload_passwd (manager); +} + +static void +load_users (GdmUserManager *manager) +{ + g_assert (manager->priv->accounts_proxy != NULL); + g_debug ("GdmUserManager: calling 'ListCachedUsers'"); + + dbus_g_proxy_begin_call (manager->priv->accounts_proxy, + "ListCachedUsers", + on_list_cached_users_finished, + manager, + NULL, + G_TYPE_INVALID); + manager->priv->listing_cached_users = TRUE; +} + +static void +load_seat_incrementally (GdmUserManager *manager) +{ + g_assert (manager->priv->seat.proxy == NULL); + + switch (manager->priv->seat.state) { + case GDM_USER_MANAGER_SEAT_STATE_GET_SESSION_ID: + get_current_session_id (manager); + break; + case GDM_USER_MANAGER_SEAT_STATE_GET_ID: + get_seat_id_for_current_session (manager); + break; + case GDM_USER_MANAGER_SEAT_STATE_GET_PROXY: + get_seat_proxy (manager); + break; + case GDM_USER_MANAGER_SEAT_STATE_LOADED: + break; + default: + g_assert_not_reached (); + } + + if (manager->priv->seat.state == GDM_USER_MANAGER_SEAT_STATE_LOADED) { + gboolean res; + + load_sessions (manager); + res = load_ck_history (manager); + } + + maybe_set_is_loaded (manager); +} + +static gboolean +load_idle (GdmUserManager *manager) +{ + manager->priv->seat.state = GDM_USER_MANAGER_SEAT_STATE_UNLOADED + 1; + load_seat_incrementally (manager); + load_users (manager); + manager->priv->load_id = 0; + + return FALSE; +} + +static void +queue_load_seat_and_users (GdmUserManager *manager) +{ + if (manager->priv->load_id > 0) { + return; + } + + manager->priv->load_id = g_idle_add ((GSourceFunc)load_idle, manager); +} + +static gboolean +reload_passwd_idle (GdmUserManager *manager) +{ + schedule_reload_passwd (manager); + manager->priv->reload_passwd_id = 0; + + return FALSE; +} + +static void +queue_reload_passwd (GdmUserManager *manager) +{ + if (manager->priv->reload_passwd_id > 0) { + g_source_remove (manager->priv->reload_passwd_id); + } + + manager->priv->reload_passwd_id = g_timeout_add_seconds (RELOAD_PASSWD_THROTTLE_SECS, (GSourceFunc)reload_passwd_idle, manager); +} + static void on_shells_monitor_changed (GFileMonitor *monitor, GFile *file, @@ -1451,7 +2665,7 @@ on_shells_monitor_changed (GFileMonitor *monitor, } reload_shells (manager); - reload_passwd (manager); + queue_reload_passwd (manager); } static void @@ -1466,54 +2680,186 @@ on_passwd_monitor_changed (GFileMonitor *monitor, return; } - reload_passwd (manager); + queue_reload_passwd (manager); } static void -ignore_log_handler (const char *log_domain, - GLogLevelFlags log_level, - const char *message, - gpointer user_data) +gdm_user_manager_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) { - return; -} + GdmUserManager *manager; -static void -check_gdm_debug (void) -{ - GDebugKey gdmkeys[] = { { "gdm", 1 } }; - const char *gnome_shell_debug = g_getenv ("GNOME_SHELL_DEBUG"); + manager = GDM_USER_MANAGER (object); - if (!gnome_shell_debug || - !g_parse_debug_string (gnome_shell_debug, gdmkeys, 1)) { - g_log_set_handler (G_LOG_DOMAIN, G_LOG_LEVEL_DEBUG, - ignore_log_handler, NULL); + switch (prop_id) { + case PROP_IS_LOADED: + g_value_set_boolean (value, manager->priv->is_loaded); + break; + case PROP_HAS_MULTIPLE_USERS: + g_value_set_boolean (value, manager->priv->has_multiple_users); + break; + case PROP_INCLUDE_ALL: + g_value_set_boolean (value, manager->priv->include_all); + break; + case PROP_INCLUDE_USERNAMES_LIST: + g_value_set_pointer (value, manager->priv->include_usernames); + break; + case PROP_EXCLUDE_USERNAMES_LIST: + g_value_set_pointer (value, manager->priv->exclude_usernames); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; } } +static void +set_include_usernames (GdmUserManager *manager, + GSList *list) +{ + if (manager->priv->include_usernames != NULL) { + g_slist_foreach (manager->priv->include_usernames, (GFunc) g_free, NULL); + g_slist_free (manager->priv->include_usernames); + } + manager->priv->include_usernames = slist_deep_copy (list); +} + +static void +set_exclude_usernames (GdmUserManager *manager, + GSList *list) +{ + if (manager->priv->exclude_usernames != NULL) { + g_slist_foreach (manager->priv->exclude_usernames, (GFunc) g_free, NULL); + g_slist_free (manager->priv->exclude_usernames); + } + manager->priv->exclude_usernames = slist_deep_copy (list); +} + +static void +set_include_all (GdmUserManager *manager, + gboolean all) +{ + if (manager->priv->include_all != all) { + manager->priv->include_all = all; + } +} + +static void +gdm_user_manager_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + GdmUserManager *self; + + self = GDM_USER_MANAGER (object); + + switch (prop_id) { + case PROP_INCLUDE_ALL: + set_include_all (self, g_value_get_boolean (value)); + break; + case PROP_INCLUDE_USERNAMES_LIST: + set_include_usernames (self, g_value_get_pointer (value)); + break; + case PROP_EXCLUDE_USERNAMES_LIST: + set_exclude_usernames (self, g_value_get_pointer (value)); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +monitor_local_users (GdmUserManager *manager) +{ + GFile *file; + GError *error; + + g_debug ("GdmUserManager: Monitoring local users"); + + /* /etc/shells */ + file = g_file_new_for_path (_PATH_SHELLS); + error = NULL; + manager->priv->shells_monitor = g_file_monitor_file (file, + G_FILE_MONITOR_NONE, + NULL, + &error); + if (manager->priv->shells_monitor != NULL) { + g_signal_connect (manager->priv->shells_monitor, + "changed", + G_CALLBACK (on_shells_monitor_changed), + manager); + } else { + g_warning ("Unable to monitor %s: %s", _PATH_SHELLS, error->message); + g_error_free (error); + } + g_object_unref (file); + + /* /etc/passwd */ + file = g_file_new_for_path (PATH_PASSWD); + manager->priv->passwd_monitor = g_file_monitor_file (file, + G_FILE_MONITOR_NONE, + NULL, + &error); + if (manager->priv->passwd_monitor != NULL) { + g_signal_connect (manager->priv->passwd_monitor, + "changed", + G_CALLBACK (on_passwd_monitor_changed), + manager); + } else { + g_warning ("Unable to monitor %s: %s", PATH_PASSWD, error->message); + g_error_free (error); + } + g_object_unref (file); +} + static void gdm_user_manager_class_init (GdmUserManagerClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS (klass); object_class->finalize = gdm_user_manager_finalize; + object_class->get_property = gdm_user_manager_get_property; + object_class->set_property = gdm_user_manager_set_property; + + g_object_class_install_property (object_class, + PROP_IS_LOADED, + g_param_spec_boolean ("is-loaded", + NULL, + NULL, + FALSE, + G_PARAM_READABLE)); + g_object_class_install_property (object_class, + PROP_HAS_MULTIPLE_USERS, + g_param_spec_boolean ("has-multiple-users", + NULL, + NULL, + FALSE, + G_PARAM_READABLE)); + g_object_class_install_property (object_class, + PROP_INCLUDE_ALL, + g_param_spec_boolean ("include-all", + NULL, + NULL, + FALSE, + G_PARAM_READWRITE)); + g_object_class_install_property (object_class, + PROP_INCLUDE_USERNAMES_LIST, + g_param_spec_pointer ("include-usernames-list", + NULL, + NULL, + G_PARAM_READWRITE)); + + g_object_class_install_property (object_class, + PROP_EXCLUDE_USERNAMES_LIST, + g_param_spec_pointer ("exclude-usernames-list", + NULL, + NULL, + G_PARAM_READWRITE)); - signals [LOADING_USERS] = - g_signal_new ("loading-users", - G_TYPE_FROM_CLASS (klass), - G_SIGNAL_RUN_LAST, - G_STRUCT_OFFSET (GdmUserManagerClass, loading_users), - NULL, NULL, - g_cclosure_marshal_VOID__VOID, - G_TYPE_NONE, 0); - signals [USERS_LOADED] = - g_signal_new ("users-loaded", - G_TYPE_FROM_CLASS (klass), - G_SIGNAL_RUN_LAST, - G_STRUCT_OFFSET (GdmUserManagerClass, users_loaded), - NULL, NULL, - g_cclosure_marshal_VOID__VOID, - G_TYPE_NONE, 0); signals [USER_ADDED] = g_signal_new ("user-added", G_TYPE_FROM_CLASS (klass), @@ -1538,26 +2884,40 @@ gdm_user_manager_class_init (GdmUserManagerClass *klass) NULL, NULL, g_cclosure_marshal_VOID__OBJECT, G_TYPE_NONE, 1, GDM_TYPE_USER); - signals [USER_LOGIN_FREQUENCY_CHANGED] = - g_signal_new ("user-login-frequency-changed", + signals [USER_CHANGED] = + g_signal_new ("user-changed", G_TYPE_FROM_CLASS (klass), G_SIGNAL_RUN_LAST, - G_STRUCT_OFFSET (GdmUserManagerClass, user_login_frequency_changed), + G_STRUCT_OFFSET (GdmUserManagerClass, user_changed), NULL, NULL, g_cclosure_marshal_VOID__OBJECT, G_TYPE_NONE, 1, GDM_TYPE_USER); g_type_class_add_private (klass, sizeof (GdmUserManagerPrivate)); - check_gdm_debug (); +} + +/** + * gdm_user_manager_queue_load: + * @manager: a #GdmUserManager + * + * Queue loading users into user manager. This must be called, and the + * #GdmUserManager:is-loaded property must be %TRUE before calling + * gdm_user_manager_list_users() + */ +void +gdm_user_manager_queue_load (GdmUserManager *manager) +{ + g_return_if_fail (GDM_IS_USER_MANAGER (manager)); + + if (! manager->priv->is_loaded) { + queue_load_seat_and_users (manager); + } } static void gdm_user_manager_init (GdmUserManager *manager) { - int i; - GFile *file; GError *error; - const char *exclude_default[] = DEFAULT_EXCLUDE; manager->priv = GDM_USER_MANAGER_GET_PRIVATE (manager); @@ -1567,73 +2927,41 @@ gdm_user_manager_init (GdmUserManager *manager) g_free, g_free); - /* exclusions */ - manager->priv->exclusions = g_hash_table_new_full (g_str_hash, - g_str_equal, - g_free, - NULL); - for (i = 0; exclude_default[i] != NULL; i++) { - g_hash_table_insert (manager->priv->exclusions, - g_strdup (exclude_default [i]), - GUINT_TO_POINTER (TRUE)); - } + /* users */ + manager->priv->users_by_name = g_hash_table_new_full (g_str_hash, + g_str_equal, + g_free, + g_object_unref); + + manager->priv->users_by_object_path = g_hash_table_new_full (g_str_hash, + g_str_equal, + NULL, + g_object_unref); + + g_assert (manager->priv->seat.proxy == NULL); - /* /etc/shells */ - manager->priv->shells = g_hash_table_new_full (g_str_hash, - g_str_equal, - g_free, - NULL); - reload_shells (manager); - file = g_file_new_for_path (_PATH_SHELLS); error = NULL; - manager->priv->shells_monitor = g_file_monitor_file (file, - G_FILE_MONITOR_NONE, - NULL, - &error); - if (manager->priv->shells_monitor != NULL) { - g_signal_connect (manager->priv->shells_monitor, - "changed", - G_CALLBACK (on_shells_monitor_changed), - manager); - } else { - g_warning ("Unable to monitor %s: %s", _PATH_SHELLS, error->message); - g_error_free (error); + manager->priv->connection = dbus_g_bus_get (DBUS_BUS_SYSTEM, &error); + if (manager->priv->connection == NULL) { + if (error != NULL) { + g_warning ("Failed to connect to the D-Bus daemon: %s", error->message); + g_error_free (error); + } else { + g_warning ("Failed to connect to the D-Bus daemon"); + } + return; } - g_object_unref (file); - /* /etc/passwd */ - manager->priv->users = g_hash_table_new_full (g_str_hash, - g_str_equal, - g_free, - (GDestroyNotify) g_object_run_dispose); - file = g_file_new_for_path (PATH_PASSWD); - manager->priv->passwd_monitor = g_file_monitor_file (file, - G_FILE_MONITOR_NONE, - NULL, - &error); - if (manager->priv->passwd_monitor != NULL) { - g_signal_connect (manager->priv->passwd_monitor, - "changed", - G_CALLBACK (on_passwd_monitor_changed), - manager); - } else { - g_warning ("Unable to monitor %s: %s", PATH_PASSWD, error->message); - g_error_free (error); - } - g_object_unref (file); + get_accounts_proxy (manager); - - get_seat_proxy (manager); - - queue_reload_users (manager); - - manager->priv->users_dirty = FALSE; + manager->priv->seat.state = GDM_USER_MANAGER_SEAT_STATE_UNLOADED; } static void gdm_user_manager_finalize (GObject *object) { GdmUserManager *manager; + GSList *node; g_return_if_fail (object != NULL); g_return_if_fail (GDM_IS_USER_MANAGER (object)); @@ -1642,8 +2970,51 @@ gdm_user_manager_finalize (GObject *object) g_return_if_fail (manager->priv != NULL); - if (manager->priv->seat_proxy != NULL) { - g_object_unref (manager->priv->seat_proxy); + if (manager->priv->ck_history_pid > 0) { + g_debug ("Killing ck-history process"); + signal_pid (manager->priv->ck_history_pid, SIGTERM); + } + + g_slist_foreach (manager->priv->new_sessions, + (GFunc) unload_new_session, NULL); + g_slist_free (manager->priv->new_sessions); + + g_slist_foreach (manager->priv->fetch_user_requests, + (GFunc) free_fetch_user_request, NULL); + g_slist_free (manager->priv->fetch_user_requests); + + node = manager->priv->new_users; + while (node != NULL) { + GdmUser *user; + GSList *next_node; + + user = GDM_USER (node->data); + next_node = node->next; + + g_signal_handlers_disconnect_by_func (user, on_new_user_loaded, manager); + g_object_unref (user); + manager->priv->new_users = g_slist_delete_link (manager->priv->new_users, node); + node = next_node; + } + + unload_seat (manager); + + if (manager->priv->exclude_usernames != NULL) { + g_slist_foreach (manager->priv->exclude_usernames, (GFunc) g_free, NULL); + g_slist_free (manager->priv->exclude_usernames); + } + + if (manager->priv->include_usernames != NULL) { + g_slist_foreach (manager->priv->include_usernames, (GFunc) g_free, NULL); + g_slist_free (manager->priv->include_usernames); + } + + if (manager->priv->seat.proxy != NULL) { + g_object_unref (manager->priv->seat.proxy); + } + + if (manager->priv->accounts_proxy != NULL) { + g_object_unref (manager->priv->accounts_proxy); } if (manager->priv->ck_history_id != 0) { @@ -1651,20 +3022,37 @@ gdm_user_manager_finalize (GObject *object) manager->priv->ck_history_id = 0; } - if (manager->priv->reload_id > 0) { - g_source_remove (manager->priv->reload_id); - manager->priv->reload_id = 0; + if (manager->priv->ck_history_watchdog_id != 0) { + g_source_remove (manager->priv->ck_history_watchdog_id); + manager->priv->ck_history_watchdog_id = 0; + } + + if (manager->priv->load_id > 0) { + g_source_remove (manager->priv->load_id); + manager->priv->load_id = 0; + } + + if (manager->priv->reload_passwd_id > 0) { + g_source_remove (manager->priv->reload_passwd_id); + manager->priv->reload_passwd_id = 0; } g_hash_table_destroy (manager->priv->sessions); - g_file_monitor_cancel (manager->priv->passwd_monitor); - g_hash_table_destroy (manager->priv->users); + if (manager->priv->passwd_monitor != NULL) { + g_file_monitor_cancel (manager->priv->passwd_monitor); + } - g_file_monitor_cancel (manager->priv->shells_monitor); - g_hash_table_destroy (manager->priv->shells); + g_hash_table_destroy (manager->priv->users_by_name); + g_hash_table_destroy (manager->priv->users_by_object_path); - g_free (manager->priv->seat_id); + if (manager->priv->shells_monitor != NULL) { + g_file_monitor_cancel (manager->priv->shells_monitor); + } + + if (manager->priv->shells != NULL) { + g_hash_table_destroy (manager->priv->shells); + } G_OBJECT_CLASS (gdm_user_manager_parent_class)->finalize (object); } @@ -1672,7 +3060,11 @@ gdm_user_manager_finalize (GObject *object) /** * gdm_user_manager_ref_default: * - * Returns: (transfer full): The singleton #GdmUserManager object. + * Queue loading users into user manager. This must be called, and the + * #GdmUserManager:is-loaded property must be %TRUE before calling + * gdm_user_manager_list_users() + * + * Returns: (transfer full): user manager object */ GdmUserManager * gdm_user_manager_ref_default (void) diff --git a/src/gdmuser/gdm-user-manager.h b/src/gdmuser/gdm-user-manager.h index f57b4cd1f..8dd9edec3 100644 --- a/src/gdmuser/gdm-user-manager.h +++ b/src/gdmuser/gdm-user-manager.h @@ -18,8 +18,8 @@ * */ -#ifndef __GDM_USER_MANAGER_H -#define __GDM_USER_MANAGER_H +#ifndef __GDM_USER_MANAGER_H__ +#define __GDM_USER_MANAGER_H__ #include @@ -35,34 +35,35 @@ G_BEGIN_DECLS #define GDM_USER_MANAGER_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), GDM_TYPE_USER_MANAGER, GdmUserManagerClass)) typedef struct GdmUserManagerPrivate GdmUserManagerPrivate; +typedef struct GdmUserManager GdmUserManager; +typedef struct GdmUserManagerClass GdmUserManagerClass; +typedef enum GdmUserManagerError GdmUserManagerError; -typedef struct +struct GdmUserManager { GObject parent; GdmUserManagerPrivate *priv; -} GdmUserManager; +}; -typedef struct +struct GdmUserManagerClass { GObjectClass parent_class; - void (* loading_users) (GdmUserManager *user_manager); - void (* users_loaded) (GdmUserManager *user_manager); void (* user_added) (GdmUserManager *user_manager, GdmUser *user); void (* user_removed) (GdmUserManager *user_manager, GdmUser *user); void (* user_is_logged_in_changed) (GdmUserManager *user_manager, GdmUser *user); - void (* user_login_frequency_changed) (GdmUserManager *user_manager, - GdmUser *user); -} GdmUserManagerClass; + void (* user_changed) (GdmUserManager *user_manager, + GdmUser *user); +}; -typedef enum +enum GdmUserManagerError { GDM_USER_MANAGER_ERROR_GENERAL, GDM_USER_MANAGER_ERROR_KEY_NOT_FOUND -} GdmUserManagerError; +}; #define GDM_USER_MANAGER_ERROR gdm_user_manager_error_quark () @@ -71,6 +72,7 @@ GType gdm_user_manager_get_type (void); GdmUserManager * gdm_user_manager_ref_default (void); +void gdm_user_manager_queue_load (GdmUserManager *manager); GSList * gdm_user_manager_list_users (GdmUserManager *manager); GdmUser * gdm_user_manager_get_user (GdmUserManager *manager, const char *username); @@ -80,6 +82,8 @@ GdmUser * gdm_user_manager_get_user_by_uid (GdmUserManager *mana gboolean gdm_user_manager_activate_user_session (GdmUserManager *manager, GdmUser *user); +gboolean gdm_user_manager_can_switch (GdmUserManager *manager); + gboolean gdm_user_manager_goto_login_session (GdmUserManager *manager); G_END_DECLS diff --git a/src/gdmuser/gdm-user-private.h b/src/gdmuser/gdm-user-private.h index d716f3ef5..05bda556d 100644 --- a/src/gdmuser/gdm-user-private.h +++ b/src/gdmuser/gdm-user-private.h @@ -21,8 +21,8 @@ * Private interfaces to the GdmUser object */ -#ifndef __GDM_USER_PRIVATE_H -#define __GDM_USER_PRIVATE_H +#ifndef __GDM_USER_PRIVATE_H_ +#define __GDM_USER_PRIVATE_H_ #include @@ -30,15 +30,20 @@ G_BEGIN_DECLS -void _gdm_user_update (GdmUser *user, - const struct passwd *pwent); +void _gdm_user_update_from_object_path (GdmUser *user, + const char *object_path); + +void _gdm_user_update_from_pwent (GdmUser *user, + const struct passwd *pwent); + +void _gdm_user_update_login_frequency (GdmUser *user, + guint64 login_frequency); + void _gdm_user_add_session (GdmUser *user, const char *session_id); void _gdm_user_remove_session (GdmUser *user, const char *session_id); -void _gdm_user_icon_changed (GdmUser *user); - G_END_DECLS -#endif /* !__GDM_USER_PRIVATE_H */ +#endif /* !__GDM_USER_PRIVATE__ */ diff --git a/src/gdmuser/gdm-user.c b/src/gdmuser/gdm-user.c index bd73b9559..710510a44 100644 --- a/src/gdmuser/gdm-user.c +++ b/src/gdmuser/gdm-user.c @@ -26,12 +26,13 @@ #include #include -/* Note on sync with gdm; need to use -lib here */ -#include +#include + +#include +#include #include #include -#include "gdm-user-manager.h" #include "gdm-user-private.h" #define GDM_USER_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GDM_TYPE_USER, GdmUserClass)) @@ -39,25 +40,18 @@ #define GDM_USER_GET_CLASS(object) (G_TYPE_INSTANCE_GET_CLASS ((object), GDM_TYPE_USER, GdmUserClass)) #define GLOBAL_FACEDIR DATADIR "/faces" -#define MAX_ICON_SIZE 128 #define MAX_FILE_SIZE 65536 -#define MINIMAL_UID 100 -#define RELAX_GROUP TRUE -#define RELAX_OTHER TRUE + +#define ACCOUNTS_NAME "org.freedesktop.Accounts" +#define ACCOUNTS_USER_INTERFACE "org.freedesktop.Accounts.User" enum { PROP_0, - PROP_MANAGER, - PROP_REAL_NAME, - PROP_USER_NAME, - PROP_UID, - PROP_HOME_DIR, - PROP_SHELL, - PROP_LOGIN_FREQUENCY, + PROP_IS_LOADED }; enum { - ICON_CHANGED, + CHANGED, SESSIONS_CHANGED, LAST_SIGNAL }; @@ -65,28 +59,29 @@ enum { struct _GdmUser { GObject parent; - GdmUserManager *manager; + DBusGConnection *connection; + DBusGProxy *accounts_proxy; + DBusGProxy *object_proxy; + DBusGProxyCall *get_all_call; + char *object_path; uid_t uid; char *user_name; char *real_name; - char *home_dir; - char *shell; + char *icon_file; GList *sessions; gulong login_frequency; - GFileMonitor *icon_monitor; + guint is_loaded : 1; }; struct _GdmUserClass { GObjectClass parent_class; - - void (* icon_changed) (GdmUser *user); - void (* sessions_changed) (GdmUser *user); }; static void gdm_user_finalize (GObject *object); +static gboolean check_user_file (const char *filename); static guint signals[LAST_SIGNAL] = { 0 }; @@ -150,84 +145,39 @@ gdm_user_get_num_sessions (GdmUser *user) return g_list_length (user->sessions); } -/** - * gdm_user_get_sessions: - * @user: a #GdmUser - * - * Returns: (transfer none) (element-type utf8): Session identifier strings - */ -GList * -gdm_user_get_sessions (GdmUser *user) -{ - return user->sessions; -} - static void -_gdm_user_set_login_frequency (GdmUser *user, - gulong login_frequency) -{ - user->login_frequency = login_frequency; - g_object_notify (G_OBJECT (user), "login-frequency"); -} - -static void -gdm_user_set_property (GObject *object, - guint param_id, - const GValue *value, - GParamSpec *pspec) +gdm_user_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec) { GdmUser *user; user = GDM_USER (object); - switch (param_id) { - case PROP_MANAGER: - user->manager = g_value_get_object (value); - g_assert (user->manager); - break; - case PROP_LOGIN_FREQUENCY: - _gdm_user_set_login_frequency (user, g_value_get_ulong (value)); - break; + switch (prop_id) { default: - G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec); + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; } } static void -gdm_user_get_property (GObject *object, - guint param_id, - GValue *value, - GParamSpec *pspec) +gdm_user_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) { GdmUser *user; user = GDM_USER (object); - switch (param_id) { - case PROP_MANAGER: - g_value_set_object (value, user->manager); - break; - case PROP_USER_NAME: - g_value_set_string (value, user->user_name); - break; - case PROP_REAL_NAME: - g_value_set_string (value, user->real_name); - break; - case PROP_HOME_DIR: - g_value_set_string (value, user->home_dir); - break; - case PROP_UID: - g_value_set_ulong (value, user->uid); - break; - case PROP_SHELL: - g_value_set_string (value, user->shell); - break; - case PROP_LOGIN_FREQUENCY: - g_value_set_ulong (value, user->login_frequency); + switch (prop_id) { + case PROP_IS_LOADED: + g_value_set_boolean (value, user->is_loaded); break; default: - G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec); + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; } } @@ -239,70 +189,23 @@ gdm_user_class_init (GdmUserClass *class) gobject_class = G_OBJECT_CLASS (class); + gobject_class->finalize = gdm_user_finalize; gobject_class->set_property = gdm_user_set_property; gobject_class->get_property = gdm_user_get_property; - gobject_class->finalize = gdm_user_finalize; g_object_class_install_property (gobject_class, - PROP_MANAGER, - g_param_spec_object ("manager", - "Manager", - "The user manager object this user is controlled by.", - GDM_TYPE_USER_MANAGER, - (G_PARAM_READWRITE | - G_PARAM_CONSTRUCT_ONLY))); + PROP_IS_LOADED, + g_param_spec_boolean ("is-loaded", + NULL, + NULL, + FALSE, + G_PARAM_READABLE)); - g_object_class_install_property (gobject_class, - PROP_REAL_NAME, - g_param_spec_string ("real-name", - "Real Name", - "The real name to display for this user.", - NULL, - G_PARAM_READABLE)); - - g_object_class_install_property (gobject_class, - PROP_UID, - g_param_spec_ulong ("uid", - "User ID", - "The UID for this user.", - 0, G_MAXULONG, 0, - G_PARAM_READABLE)); - g_object_class_install_property (gobject_class, - PROP_USER_NAME, - g_param_spec_string ("user-name", - "User Name", - "The login name for this user.", - NULL, - G_PARAM_READABLE)); - g_object_class_install_property (gobject_class, - PROP_HOME_DIR, - g_param_spec_string ("home-directory", - "Home Directory", - "The home directory for this user.", - NULL, - G_PARAM_READABLE)); - g_object_class_install_property (gobject_class, - PROP_SHELL, - g_param_spec_string ("shell", - "Shell", - "The shell for this user.", - NULL, - G_PARAM_READABLE)); - g_object_class_install_property (gobject_class, - PROP_LOGIN_FREQUENCY, - g_param_spec_ulong ("login-frequency", - "login frequency", - "login frequency", - 0, - G_MAXULONG, - 0, - G_PARAM_READWRITE | G_PARAM_CONSTRUCT)); - - signals [ICON_CHANGED] = - g_signal_new ("icon-changed", + signals [CHANGED] = + g_signal_new ("changed", G_TYPE_FROM_CLASS (class), G_SIGNAL_RUN_LAST, - G_STRUCT_OFFSET (GdmUserClass, icon_changed), + 0, NULL, NULL, g_cclosure_marshal_VOID__VOID, G_TYPE_NONE, 0); @@ -310,74 +213,26 @@ gdm_user_class_init (GdmUserClass *class) g_signal_new ("sessions-changed", G_TYPE_FROM_CLASS (class), G_SIGNAL_RUN_LAST, - G_STRUCT_OFFSET (GdmUserClass, sessions_changed), + 0, NULL, NULL, g_cclosure_marshal_VOID__VOID, G_TYPE_NONE, 0); } - -static void -on_icon_monitor_changed (GFileMonitor *monitor, - GFile *file, - GFile *other_file, - GFileMonitorEvent event_type, - GdmUser *user) -{ - g_debug ("Icon changed: %d", event_type); - - if (event_type != G_FILE_MONITOR_EVENT_CHANGED && - event_type != G_FILE_MONITOR_EVENT_CREATED) { - return; - } - - _gdm_user_icon_changed (user); -} - -static void -update_icon_monitor (GdmUser *user) -{ - GFile *file; - GError *error; - char *path; - - if (user->home_dir == NULL) { - return; - } - - if (user->icon_monitor != NULL) { - g_file_monitor_cancel (user->icon_monitor); - user->icon_monitor = NULL; - } - - path = g_build_filename (user->home_dir, ".face", NULL); - g_debug ("adding monitor for '%s'", path); - file = g_file_new_for_path (path); - error = NULL; - user->icon_monitor = g_file_monitor_file (file, - G_FILE_MONITOR_NONE, - NULL, - &error); - if (user->icon_monitor != NULL) { - g_signal_connect (user->icon_monitor, - "changed", - G_CALLBACK (on_icon_monitor_changed), - user); - } else { - g_warning ("Unable to monitor %s: %s", path, error->message); - g_error_free (error); - } - g_object_unref (file); - g_free (path); -} - static void gdm_user_init (GdmUser *user) { - user->manager = NULL; + GError *error; + user->user_name = NULL; user->real_name = NULL; user->sessions = NULL; + + error = NULL; + user->connection = dbus_g_bus_get (DBUS_BUS_SYSTEM, &error); + if (user->connection == NULL) { + g_warning ("Couldn't connect to system bus: %s", error->message); + } } static void @@ -387,51 +242,81 @@ gdm_user_finalize (GObject *object) user = GDM_USER (object); - g_file_monitor_cancel (user->icon_monitor); - g_free (user->user_name); g_free (user->real_name); + g_free (user->icon_file); + g_free (user->object_path); + + if (user->accounts_proxy != NULL) { + g_object_unref (user->accounts_proxy); + } + + if (user->object_proxy != NULL) { + g_object_unref (user->object_proxy); + } + + if (user->connection != NULL) { + dbus_g_connection_unref (user->connection); + } if (G_OBJECT_CLASS (gdm_user_parent_class)->finalize) (*G_OBJECT_CLASS (gdm_user_parent_class)->finalize) (object); } +static void +set_is_loaded (GdmUser *user, + gboolean is_loaded) +{ + if (user->is_loaded != is_loaded) { + user->is_loaded = is_loaded; + g_object_notify (G_OBJECT (user), "is-loaded"); + } +} + /** - * _gdm_user_update: + * _gdm_user_update_from_pwent: * @user: the user object to update. * @pwent: the user data to use. * * Updates the properties of @user using the data in @pwent. - * - * Since: 1.0 **/ void -_gdm_user_update (GdmUser *user, - const struct passwd *pwent) +_gdm_user_update_from_pwent (GdmUser *user, + const struct passwd *pwent) { - gchar *real_name; + gchar *real_name = NULL; + gboolean changed; g_return_if_fail (GDM_IS_USER (user)); g_return_if_fail (pwent != NULL); + g_return_if_fail (user->object_path == NULL); - g_object_freeze_notify (G_OBJECT (user)); + changed = FALSE; /* Display Name */ if (pwent->pw_gecos && pwent->pw_gecos[0] != '\0') { - gchar *first_comma; - gchar *real_name_utf8; + gchar *first_comma = NULL; + gchar *valid_utf8_name = NULL; - real_name_utf8 = g_locale_to_utf8 (pwent->pw_gecos, -1, NULL, NULL, NULL); - - first_comma = strchr (real_name_utf8, ','); - if (first_comma) { - real_name = g_strndup (real_name_utf8, first_comma - real_name_utf8); - g_free (real_name_utf8); + if (g_utf8_validate (pwent->pw_gecos, -1, NULL)) { + valid_utf8_name = pwent->pw_gecos; + first_comma = strchr (valid_utf8_name, ','); } else { - real_name = real_name_utf8; + g_warning ("User %s has invalid UTF-8 in GECOS field. " + "It would be a good thing to check /etc/passwd.", + pwent->pw_name ? pwent->pw_name : ""); } - if (real_name[0] == '\0') { + if (first_comma) { + real_name = g_strndup (valid_utf8_name, + (first_comma - valid_utf8_name)); + } else if (valid_utf8_name) { + real_name = g_strdup (valid_utf8_name); + } else { + real_name = NULL; + } + + if (real_name && real_name[0] == '\0') { g_free (real_name); real_name = NULL; } @@ -439,14 +324,10 @@ _gdm_user_update (GdmUser *user, real_name = NULL; } - if ((real_name && !user->real_name) || - (!real_name && user->real_name) || - (real_name && - user->real_name && - strcmp (real_name, user->real_name) != 0)) { + if (g_strcmp0 (real_name, user->real_name) != 0) { g_free (user->real_name); user->real_name = real_name; - g_object_notify (G_OBJECT (user), "real-name"); + changed = TRUE; } else { g_free (real_name); } @@ -454,60 +335,58 @@ _gdm_user_update (GdmUser *user, /* UID */ if (pwent->pw_uid != user->uid) { user->uid = pwent->pw_uid; - g_object_notify (G_OBJECT (user), "uid"); + changed = TRUE; } /* Username */ - if ((pwent->pw_name && !user->user_name) || - (!pwent->pw_name && user->user_name) || - (pwent->pw_name && - user->user_name && - strcmp (user->user_name, pwent->pw_name) != 0)) { + if (g_strcmp0 (pwent->pw_name, user->user_name) != 0) { + g_free (user->icon_file); + user->icon_file = NULL; + if (pwent->pw_name != NULL) { + gboolean res; + + user->icon_file = g_build_filename (GDM_CACHE_DIR, pwent->pw_name, "face", NULL); + + res = check_user_file (user->icon_file); + if (!res) { + g_free (user->icon_file); + user->icon_file = g_build_filename (GLOBAL_FACEDIR, pwent->pw_name, NULL); + } + } + g_free (user->user_name); user->user_name = g_strdup (pwent->pw_name); - g_object_notify (G_OBJECT (user), "user-name"); + changed = TRUE; } - /* Home Directory */ - if ((pwent->pw_dir && !user->home_dir) || - (!pwent->pw_dir && user->home_dir) || - strcmp (user->home_dir, pwent->pw_dir) != 0) { - g_free (user->home_dir); - user->home_dir = g_strdup (pwent->pw_dir); - g_object_notify (G_OBJECT (user), "home-directory"); - g_signal_emit (user, signals[ICON_CHANGED], 0); + if (!user->is_loaded) { + set_is_loaded (user, TRUE); } - /* Shell */ - if ((pwent->pw_shell && !user->shell) || - (!pwent->pw_shell && user->shell) || - (pwent->pw_shell && - user->shell && - strcmp (user->shell, pwent->pw_shell) != 0)) { - g_free (user->shell); - user->shell = g_strdup (pwent->pw_shell); - g_object_notify (G_OBJECT (user), "shell"); + if (changed) { + g_signal_emit (user, signals[CHANGED], 0); } - - update_icon_monitor (user); - - g_object_thaw_notify (G_OBJECT (user)); } /** - * _gdm_user_icon_changed: - * @user: the user to emit the signal for. + * _gdm_user_update_login_frequency: + * @user: the user object to update + * @login_frequency: the number of times the user has logged in * - * Emits the "icon-changed" signal for @user. - * - * Since: 1.0 + * Updates the login frequency of @user **/ void -_gdm_user_icon_changed (GdmUser *user) +_gdm_user_update_login_frequency (GdmUser *user, + guint64 login_frequency) { g_return_if_fail (GDM_IS_USER (user)); - g_signal_emit (user, signals[ICON_CHANGED], 0); + if (login_frequency == user->login_frequency) { + return; + } + + user->login_frequency = login_frequency; + g_signal_emit (user, signals[CHANGED], 0); } /** @@ -516,10 +395,8 @@ _gdm_user_icon_changed (GdmUser *user) * * Retrieves the ID of @user. * - * Returns: a pointer to an array of characters which must not be modified or + * Returns: (transfer none): a pointer to an array of characters which must not be modified or * freed, or %NULL. - * - * Since: 1.0 **/ gulong @@ -536,12 +413,10 @@ gdm_user_get_uid (GdmUser *user) * * Retrieves the display name of @user. * - * Returns: a pointer to an array of characters which must not be modified or + * Returns: (transfer none): a pointer to an array of characters which must not be modified or * freed, or %NULL. - * - * Since: 1.0 **/ -G_CONST_RETURN gchar * +const char * gdm_user_get_real_name (GdmUser *user) { g_return_val_if_fail (GDM_IS_USER (user), NULL); @@ -555,13 +430,11 @@ gdm_user_get_real_name (GdmUser *user) * * Retrieves the login name of @user. * - * Returns: a pointer to an array of characters which must not be modified or + * Returns: (transfer none): a pointer to an array of characters which must not be modified or * freed, or %NULL. - * - * Since: 1.0 **/ -G_CONST_RETURN gchar * +const char * gdm_user_get_user_name (GdmUser *user) { g_return_val_if_fail (GDM_IS_USER (user), NULL); @@ -570,45 +443,13 @@ gdm_user_get_user_name (GdmUser *user) } /** - * gdm_user_get_home_directory: - * @user: the user object to examine. + * gdm_user_get_login_frequency: + * @user: a #GdmUser * - * Retrieves the home directory of @user. + * Returns the number of times @user has logged in. * - * Returns: a pointer to an array of characters which must not be modified or - * freed, or %NULL. - * - * Since: 1.0 - **/ - -G_CONST_RETURN gchar * -gdm_user_get_home_directory (GdmUser *user) -{ - g_return_val_if_fail (GDM_IS_USER (user), NULL); - - return user->home_dir; -} - -/** - * gdm_user_get_shell: - * @user: the user object to examine. - * - * Retrieves the login shell of @user. - * - * Returns: a pointer to an array of characters which must not be modified or - * freed, or %NULL. - * - * Since: 1.0 - **/ - -G_CONST_RETURN gchar * -gdm_user_get_shell (GdmUser *user) -{ - g_return_val_if_fail (GDM_IS_USER (user), NULL); - - return user->shell; -} - + * Returns: the login frequency + */ gulong gdm_user_get_login_frequency (GdmUser *user) { @@ -625,10 +466,36 @@ gdm_user_collate (GdmUser *user1, const char *str2; gulong num1; gulong num2; + guint len1; + guint len2; - g_return_val_if_fail (user1 == NULL || GDM_IS_USER (user1), 0); - g_return_val_if_fail (user2 == NULL || GDM_IS_USER (user2), 0); + g_return_val_if_fail (GDM_IS_USER (user1), 0); + g_return_val_if_fail (GDM_IS_USER (user2), 0); + num1 = user1->login_frequency; + num2 = user2->login_frequency; + + if (num1 > num2) { + return -1; + } + + if (num1 < num2) { + return 1; + } + + + len1 = g_list_length (user1->sessions); + len2 = g_list_length (user2->sessions); + + if (len1 > len2) { + return -1; + } + + if (len1 < len2) { + return 1; + } + + /* if login frequency is equal try names */ if (user1->real_name != NULL) { str1 = user1->real_name; } else { @@ -641,18 +508,6 @@ gdm_user_collate (GdmUser *user1, str2 = user2->user_name; } - num1 = user1->login_frequency; - num2 = user2->login_frequency; - g_debug ("Login freq 1=%u 2=%u", (guint)num1, (guint)num2); - if (num1 > num2) { - return -1; - } - - if (num1 < num2) { - return 1; - } - - /* if login frequency is equal try names */ if (str1 == NULL && str2 != NULL) { return -1; } @@ -669,18 +524,11 @@ gdm_user_collate (GdmUser *user1, } static gboolean -check_user_file (const char *filename, - uid_t user, - gssize max_file_size, - gboolean relax_group, - gboolean relax_other) +check_user_file (const char *filename) { + gssize max_file_size = MAX_FILE_SIZE; struct stat fileinfo; - if (max_file_size < 0) { - max_file_size = G_MAXSIZE; - } - /* Exists/Readable? */ if (stat (filename, &fileinfo) < 0) { return FALSE; @@ -691,21 +539,6 @@ check_user_file (const char *filename, return FALSE; } - /* Owned by user? */ - if (G_UNLIKELY (fileinfo.st_uid != user)) { - return FALSE; - } - - /* Group not writable or relax_group? */ - if (G_UNLIKELY ((fileinfo.st_mode & S_IWGRP) == S_IWGRP && !relax_group)) { - return FALSE; - } - - /* Other not writable or relax_other? */ - if (G_UNLIKELY ((fileinfo.st_mode & S_IWOTH) == S_IWOTH && !relax_other)) { - return FALSE; - } - /* Size is kosher? */ if (G_UNLIKELY (fileinfo.st_size > max_file_size)) { return FALSE; @@ -714,224 +547,46 @@ check_user_file (const char *filename, return TRUE; } -static char * -get_filesystem_type (const char *path) -{ - GFile *file; - GFileInfo *file_info; - GError *error; - char *filesystem_type; - - file = g_file_new_for_path (path); - error = NULL; - file_info = g_file_query_filesystem_info (file, - G_FILE_ATTRIBUTE_FILESYSTEM_TYPE, - NULL, - &error); - if (file_info == NULL) { - g_warning ("Unable to query filesystem type for %s: %s", path, error->message); - g_error_free (error); - g_object_unref (file); - return NULL; - } - - filesystem_type = g_strdup (g_file_info_get_attribute_string (file_info, - G_FILE_ATTRIBUTE_FILESYSTEM_TYPE)); - if (filesystem_type == NULL) { - g_warning ("GIO returned NULL filesystem type for %s", path); - } - - g_object_unref (file); - g_object_unref (file_info); - - return filesystem_type; -} - -static GdkPixbuf * -render_icon_from_home (GdmUser *user, - int icon_size) -{ - GdkPixbuf *retval; - char *path; - gboolean is_local; - gboolean is_autofs; - gboolean res; - char *filesystem_type; - - is_local = FALSE; - - /* special case: look at parent of home to detect autofs - this is so we don't try to trigger an automount */ - path = g_path_get_dirname (user->home_dir); - filesystem_type = get_filesystem_type (path); - is_autofs = (filesystem_type != NULL && strcmp (filesystem_type, "autofs") == 0); - g_free (filesystem_type); - g_free (path); - - if (is_autofs) { - return NULL; - } - - /* now check that home dir itself is local */ - filesystem_type = get_filesystem_type (user->home_dir); - is_local = ((filesystem_type != NULL) && - (strcmp (filesystem_type, "nfs") != 0) && - (strcmp (filesystem_type, "afs") != 0) && - (strcmp (filesystem_type, "autofs") != 0) && - (strcmp (filesystem_type, "unknown") != 0) && - (strcmp (filesystem_type, "ncpfs") != 0)); - g_free (filesystem_type); - - /* only look at local home directories so we don't try to - read from remote (e.g. NFS) volumes */ - if (! is_local) { - return NULL; - } - - /* First, try "~/.face" */ - path = g_build_filename (user->home_dir, ".face", NULL); - res = check_user_file (path, - user->uid, - MAX_FILE_SIZE, - RELAX_GROUP, - RELAX_OTHER); - if (res) { - retval = gdk_pixbuf_new_from_file_at_size (path, - icon_size, - icon_size, - NULL); - } else { - retval = NULL; - } - g_free (path); - - /* Next, try "~/.face.icon" */ - if (retval == NULL) { - path = g_build_filename (user->home_dir, - ".face.icon", - NULL); - res = check_user_file (path, - user->uid, - MAX_FILE_SIZE, - RELAX_GROUP, - RELAX_OTHER); - if (res) { - retval = gdk_pixbuf_new_from_file_at_size (path, - icon_size, - icon_size, - NULL); - } else { - retval = NULL; - } - - g_free (path); - } - - /* Still nothing, try the user's personal GDM config */ - if (retval == NULL) { - path = g_build_filename (user->home_dir, - ".gnome", - "gdm", - NULL); - res = check_user_file (path, - user->uid, - MAX_FILE_SIZE, - RELAX_GROUP, - RELAX_OTHER); - if (res) { - GKeyFile *keyfile; - char *icon_path; - - keyfile = g_key_file_new (); - g_key_file_load_from_file (keyfile, - path, - G_KEY_FILE_NONE, - NULL); - - icon_path = g_key_file_get_string (keyfile, - "face", - "picture", - NULL); - res = check_user_file (icon_path, - user->uid, - MAX_FILE_SIZE, - RELAX_GROUP, - RELAX_OTHER); - if (icon_path && res) { - retval = gdk_pixbuf_new_from_file_at_size (path, - icon_size, - icon_size, - NULL); - } else { - retval = NULL; - } - - g_free (icon_path); - g_key_file_free (keyfile); - } else { - retval = NULL; - } - - g_free (path); - } - - return retval; -} - static void -curved_rectangle (cairo_t *cr, - double x0, - double y0, - double width, - double height, - double radius) +rounded_rectangle (cairo_t *cr, + gdouble aspect, + gdouble x, + gdouble y, + gdouble corner_radius, + gdouble width, + gdouble height) { - double x1; - double y1; + gdouble radius; + gdouble degrees; - x1 = x0 + width; - y1 = y0 + height; - - if (width < FLT_EPSILON || height < FLT_EPSILON) { - return; - } - - if (width / 2 < radius) { - if (height / 2 < radius) { - cairo_move_to (cr, x0, (y0 + y1) / 2); - cairo_curve_to (cr, x0 ,y0, x0, y0, (x0 + x1) / 2, y0); - cairo_curve_to (cr, x1, y0, x1, y0, x1, (y0 + y1) / 2); - cairo_curve_to (cr, x1, y1, x1, y1, (x1 + x0) / 2, y1); - cairo_curve_to (cr, x0, y1, x0, y1, x0, (y0 + y1) / 2); - } else { - cairo_move_to (cr, x0, y0 + radius); - cairo_curve_to (cr, x0, y0, x0, y0, (x0 + x1) / 2, y0); - cairo_curve_to (cr, x1, y0, x1, y0, x1, y0 + radius); - cairo_line_to (cr, x1, y1 - radius); - cairo_curve_to (cr, x1, y1, x1, y1, (x1 + x0) / 2, y1); - cairo_curve_to (cr, x0, y1, x0, y1, x0, y1 - radius); - } - } else { - if (height / 2 < radius) { - cairo_move_to (cr, x0, (y0 + y1) / 2); - cairo_curve_to (cr, x0, y0, x0 , y0, x0 + radius, y0); - cairo_line_to (cr, x1 - radius, y0); - cairo_curve_to (cr, x1, y0, x1, y0, x1, (y0 + y1) / 2); - cairo_curve_to (cr, x1, y1, x1, y1, x1 - radius, y1); - cairo_line_to (cr, x0 + radius, y1); - cairo_curve_to (cr, x0, y1, x0, y1, x0, (y0 + y1) / 2); - } else { - cairo_move_to (cr, x0, y0 + radius); - cairo_curve_to (cr, x0 , y0, x0 , y0, x0 + radius, y0); - cairo_line_to (cr, x1 - radius, y0); - cairo_curve_to (cr, x1, y0, x1, y0, x1, y0 + radius); - cairo_line_to (cr, x1, y1 - radius); - cairo_curve_to (cr, x1, y1, x1, y1, x1 - radius, y1); - cairo_line_to (cr, x0 + radius, y1); - cairo_curve_to (cr, x0, y1, x0, y1, x0, y1 - radius); - } - } + radius = corner_radius / aspect; + degrees = G_PI / 180.0; + cairo_new_sub_path (cr); + cairo_arc (cr, + x + width - radius, + y + radius, + radius, + -90 * degrees, + 0 * degrees); + cairo_arc (cr, + x + width - radius, + y + height - radius, + radius, + 0 * degrees, + 90 * degrees); + cairo_arc (cr, + x + radius, + y + height - radius, + radius, + 90 * degrees, + 180 * degrees); + cairo_arc (cr, + x + radius, + y + radius, + radius, + 180 * degrees, + 270 * degrees); cairo_close_path (cr); } @@ -1065,7 +720,7 @@ frame_pixbuf (GdkPixbuf *source) w = gdk_pixbuf_get_width (source) + frame_width * 2; h = gdk_pixbuf_get_height (source) + frame_width * 2; - radius = w / 3.0; + radius = w / 10; dest = gdk_pixbuf_new (GDK_COLORSPACE_RGB, TRUE, @@ -1090,9 +745,13 @@ frame_pixbuf (GdkPixbuf *source) cairo_set_source_rgba (cr, 1.0, 1.0, 1.0, 0.0); cairo_fill (cr); - curved_rectangle (cr, frame_width, frame_width, - w - frame_width * 2, h - frame_width * 2, - radius); + rounded_rectangle (cr, + 1.0, + frame_width + 0.5, + frame_width + 0.5, + radius, + w - frame_width * 2 - 1, + h - frame_width * 2 - 1); cairo_set_source_rgba (cr, 0.5, 0.5, 0.5, 0.3); cairo_fill_preserve (cr); @@ -1110,11 +769,28 @@ frame_pixbuf (GdkPixbuf *source) } /** - * gdm_user_render_icon: - * @user: a #GdmUser: - * @icon_size: icon size in pixels + * gdm_user_is_logged_in: + * @user: a #GdmUser * - * Returns: (transfer full): A new icon for the user + * Returns whether or not #GdmUser is currently logged in. + * + * Returns: %TRUE or %FALSE + */ +gboolean +gdm_user_is_logged_in (GdmUser *user) +{ + return user->sessions != NULL; +} + +/** + * gdm_user_render_icon: + * @user: a #GdmUser + * @icon_size: the size to render the icon at + * + * Returns a #GdkPixbuf of the account icon belonging to @user + * at the pixel size specified by @icon_size. + * + * Returns: (transfer full): a #GdkPixbuf */ GdkPixbuf * gdm_user_render_icon (GdmUser *user, @@ -1122,59 +798,40 @@ gdm_user_render_icon (GdmUser *user, { GdkPixbuf *pixbuf; GdkPixbuf *framed; - char *path; - char *tmp; gboolean res; + GError *error; g_return_val_if_fail (GDM_IS_USER (user), NULL); g_return_val_if_fail (icon_size > 12, NULL); - path = NULL; + pixbuf = NULL; + if (user->icon_file) { + res = check_user_file (user->icon_file); + if (res) { + pixbuf = gdk_pixbuf_new_from_file_at_size (user->icon_file, + icon_size, + icon_size, + NULL); + } else { + pixbuf = NULL; + } + } - pixbuf = render_icon_from_home (user, icon_size); if (pixbuf != NULL) { goto out; } - /* Try ${GlobalFaceDir}/${username} */ - path = g_build_filename (GLOBAL_FACEDIR, user->user_name, NULL); - res = check_user_file (path, - user->uid, - MAX_FILE_SIZE, - RELAX_GROUP, - RELAX_OTHER); - if (res) { - pixbuf = gdk_pixbuf_new_from_file_at_size (path, - icon_size, - icon_size, - NULL); - } else { - pixbuf = NULL; - } + error = NULL; + pixbuf = gtk_icon_theme_load_icon (gtk_icon_theme_get_default (), - g_free (path); - if (pixbuf != NULL) { - goto out; + "avatar-default", + icon_size, + GTK_ICON_LOOKUP_FORCE_SIZE, + &error); + if (error) { + g_warning ("%s", error->message); + g_error_free (error); } - - /* Finally, ${GlobalFaceDir}/${username}.png */ - tmp = g_strconcat (user->user_name, ".png", NULL); - path = g_build_filename (GLOBAL_FACEDIR, tmp, NULL); - g_free (tmp); - res = check_user_file (path, - user->uid, - MAX_FILE_SIZE, - RELAX_GROUP, - RELAX_OTHER); - if (res) { - pixbuf = gdk_pixbuf_new_from_file_at_size (path, - icon_size, - icon_size, - NULL); - } else { - pixbuf = NULL; - } - g_free (path); out: if (pixbuf != NULL) { @@ -1187,3 +844,253 @@ gdm_user_render_icon (GdmUser *user, return pixbuf; } + +/** + * gdm_user_get_icon_file: + * @user: a #GdmUser + * + * Returns the path to the account icon belonging to @user. + * + * Returns: (transfer none): a path to an icon + */ +const char * +gdm_user_get_icon_file (GdmUser *user) +{ + g_return_val_if_fail (GDM_IS_USER (user), NULL); + + return user->icon_file; +} + +/** + * gdm_user_get_object_path: + * @user: a #GdmUser + * + * Returns the user accounts service object path of @user, + * or %NULL if @user doesn't have an object path associated + * with it. + * + * Returns: (transfer none): the primary ConsoleKit session id of the user + */ +const char * +gdm_user_get_object_path (GdmUser *user) +{ + g_return_val_if_fail (GDM_IS_USER (user), NULL); + + return user->object_path; +} + +/** + * gdm_user_get_primary_session_id: + * @user: a #GdmUser + * + * Returns the primary ConsoleKit session id of @user, or %NULL if @user isn't + * logged in. + * + * Returns: (transfer none): the primary ConsoleKit session id of the user + */ +const char * +gdm_user_get_primary_session_id (GdmUser *user) +{ + if (!gdm_user_is_logged_in (user)) { + g_debug ("User %s is not logged in, so has no primary session", + gdm_user_get_user_name (user)); + return NULL; + } + + /* FIXME: better way to choose? */ + return user->sessions->data; +} + +static void +collect_props (const gchar *key, + const GValue *value, + GdmUser *user) +{ + gboolean handled = TRUE; + + if (strcmp (key, "Uid") == 0) { + user->uid = g_value_get_uint64 (value); + } else if (strcmp (key, "UserName") == 0) { + g_free (user->user_name); + user->user_name = g_value_dup_string (value); + } else if (strcmp (key, "RealName") == 0) { + g_free (user->real_name); + user->real_name = g_value_dup_string (value); + } else if (strcmp (key, "AccountType") == 0) { + /* ignore */ + } else if (strcmp (key, "Email") == 0) { + /* ignore */ + } else if (strcmp (key, "Language") == 0) { + /* ignore */ + } else if (strcmp (key, "Location") == 0) { + /* ignore */ + } else if (strcmp (key, "LoginFrequency") == 0) { + user->login_frequency = g_value_get_uint64 (value); + } else if (strcmp (key, "IconFile") == 0) { + gboolean res; + + g_free (user->icon_file); + user->icon_file = g_value_dup_string (value); + + res = check_user_file (user->icon_file); + if (!res) { + g_free (user->icon_file); + user->icon_file = g_build_filename (GLOBAL_FACEDIR, user->user_name, NULL); + } + } else if (strcmp (key, "Locked") == 0) { + /* ignore */ + } else if (strcmp (key, "AutomaticLogin") == 0) { + /* ignore */ + } else if (strcmp (key, "PasswordMode") == 0) { + /* ignore */ + } else if (strcmp (key, "PasswordHint") == 0) { + /* ignore */ + } else if (strcmp (key, "HomeDirectory") == 0) { + /* ignore */ + } else if (strcmp (key, "Shell") == 0) { + /* ignore */ + } else { + handled = FALSE; + } + + if (!handled) { + g_debug ("unhandled property %s", key); + } +} + +static void +on_get_all_finished (DBusGProxy *proxy, + DBusGProxyCall *call, + GdmUser *user) +{ + GError *error; + GHashTable *hash_table; + gboolean res; + + g_assert (user->get_all_call == call); + g_assert (user->object_proxy == proxy); + + error = NULL; + res = dbus_g_proxy_end_call (proxy, + call, + &error, + dbus_g_type_get_map ("GHashTable", G_TYPE_STRING, G_TYPE_VALUE), + &hash_table, + G_TYPE_INVALID); + user->get_all_call = NULL; + user->object_proxy = NULL; + + if (! res) { + g_debug ("Error calling GetAll() when retrieving properties for %s: %s", + user->object_path, error->message); + g_error_free (error); + goto out; + } + g_hash_table_foreach (hash_table, (GHFunc) collect_props, user); + g_hash_table_unref (hash_table); + + if (!user->is_loaded) { + set_is_loaded (user, TRUE); + } + + g_signal_emit (user, signals[CHANGED], 0); + +out: + g_object_unref (proxy); +} + +static gboolean +update_info (GdmUser *user) +{ + DBusGProxy *proxy; + DBusGProxyCall *call; + + proxy = dbus_g_proxy_new_for_name (user->connection, + ACCOUNTS_NAME, + user->object_path, + DBUS_INTERFACE_PROPERTIES); + + call = dbus_g_proxy_begin_call (proxy, + "GetAll", + (DBusGProxyCallNotify) + on_get_all_finished, + user, + NULL, + G_TYPE_STRING, + ACCOUNTS_USER_INTERFACE, + G_TYPE_INVALID); + + if (call == NULL) { + g_warning ("GdmUser: failed to make GetAll call"); + goto failed; + } + + user->get_all_call = call; + user->object_proxy = proxy; + return TRUE; + +failed: + if (proxy != NULL) { + g_object_unref (proxy); + } + + return FALSE; +} + +static void +changed_handler (DBusGProxy *proxy, + gpointer *data) +{ + GdmUser *user = GDM_USER (data); + + update_info (user); +} + +/** + * _gdm_user_update_from_object_path: + * @user: the user object to update. + * @object_path: the object path of the user to use. + * + * Updates the properties of @user from the accounts service via + * the object path in @object_path. + **/ +void +_gdm_user_update_from_object_path (GdmUser *user, + const char *object_path) +{ + g_return_if_fail (GDM_IS_USER (user)); + g_return_if_fail (object_path != NULL); + g_return_if_fail (user->object_path == NULL); + + user->object_path = g_strdup (object_path); + + user->accounts_proxy = dbus_g_proxy_new_for_name (user->connection, + ACCOUNTS_NAME, + user->object_path, + ACCOUNTS_USER_INTERFACE); + dbus_g_proxy_set_default_timeout (user->accounts_proxy, INT_MAX); + dbus_g_proxy_add_signal (user->accounts_proxy, "Changed", G_TYPE_INVALID); + + dbus_g_proxy_connect_signal (user->accounts_proxy, "Changed", + G_CALLBACK (changed_handler), user, NULL); + + if (!update_info (user)) { + g_warning ("Couldn't update info for user with object path %s", object_path); + } +} + +/** + * gdm_user_is_loaded: + * @user: a #GdmUser + * + * Determines whether or not the user object is loaded and ready to read from. + * #GdmUserManager:is-loaded property must be %TRUE before calling + * gdm_user_manager_list_users() + * + * Returns: %TRUE or %FALSE + */ +gboolean +gdm_user_is_loaded (GdmUser *user) +{ + return user->is_loaded; +} diff --git a/src/gdmuser/gdm-user.h b/src/gdmuser/gdm-user.h index f9063e21a..2f2616ba5 100644 --- a/src/gdmuser/gdm-user.h +++ b/src/gdmuser/gdm-user.h @@ -22,8 +22,8 @@ * Facade object for user data, owned by GdmUserManager */ -#ifndef __GDM_USER_H -#define __GDM_USER_H +#ifndef __GDM_USER_H__ +#define __GDM_USER_H__ #include #include @@ -40,20 +40,24 @@ typedef struct _GdmUserClass GdmUserClass; GType gdm_user_get_type (void) G_GNUC_CONST; +GdmUser *gdm_user_new_from_object_path (const char *path); +const char *gdm_user_get_object_path (GdmUser *user); + gulong gdm_user_get_uid (GdmUser *user); -G_CONST_RETURN char *gdm_user_get_user_name (GdmUser *user); -G_CONST_RETURN char *gdm_user_get_real_name (GdmUser *user); -G_CONST_RETURN char *gdm_user_get_home_directory (GdmUser *user); -G_CONST_RETURN char *gdm_user_get_shell (GdmUser *user); +const char *gdm_user_get_user_name (GdmUser *user); +const char *gdm_user_get_real_name (GdmUser *user); guint gdm_user_get_num_sessions (GdmUser *user); -GList *gdm_user_get_sessions (GdmUser *user); +gboolean gdm_user_is_logged_in (GdmUser *user); gulong gdm_user_get_login_frequency (GdmUser *user); +const char *gdm_user_get_icon_file (GdmUser *user); +const char *gdm_user_get_primary_session_id (GdmUser *user); GdkPixbuf *gdm_user_render_icon (GdmUser *user, gint icon_size); gint gdm_user_collate (GdmUser *user1, GdmUser *user2); +gboolean gdm_user_is_loaded (GdmUser *user); G_END_DECLS