From 7ce5ea4142949745a3b3a805a099b8de9d5ed5f4 Mon Sep 17 00:00:00 2001 From: Ray Strode Date: Thu, 7 Oct 2010 16:59:03 -0400 Subject: [PATCH 01/21] gdm: resync cut-and-paste code from gdm tree. The GDM code upstream talks to the account service now, has better introspection annotations, and is more asynchronous. This commit updates the shell's copy to the latest upstream. Note, the API changed somewhat and so the callers will need to be fixed up subsequently. https://bugzilla.gnome.org/show_bug.cgi?id=631888 --- src/Makefile-gdmuser.am | 1 + src/gdmuser/gdm-user-manager.c | 2696 ++++++++++++++++++++++++-------- src/gdmuser/gdm-user-manager.h | 28 +- src/gdmuser/gdm-user-private.h | 19 +- src/gdmuser/gdm-user.c | 1115 ++++++------- src/gdmuser/gdm-user.h | 18 +- 6 files changed, 2595 insertions(+), 1282 deletions(-) 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 From ba0a0382477eeac9eeb9d57928881e86e91daeb4 Mon Sep 17 00:00:00 2001 From: Ray Strode Date: Thu, 7 Oct 2010 18:22:20 -0400 Subject: [PATCH 02/21] statusMenu: port to new gdmuser api https://bugzilla.gnome.org/show_bug.cgi?id=631888 --- js/ui/statusMenu.js | 20 +++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/js/ui/statusMenu.js b/js/ui/statusMenu.js index 8de3b6511..9aae26cb4 100644 --- a/js/ui/statusMenu.js +++ b/js/ui/statusMenu.js @@ -31,6 +31,8 @@ StatusMenuButton.prototype = { this.actor.set_child(box); this._gdm = Gdm.UserManager.ref_default(); + this._gdm.queue_load() + this._user = this._gdm.get_user(GLib.get_user_name()); this._presence = new GnomeSession.Presence(); @@ -48,27 +50,31 @@ StatusMenuButton.prototype = { this._presence.connect('StatusChanged', Lang.bind(this, this._updatePresenceIcon)); this._presence.getStatus(Lang.bind(this, this._updatePresenceIcon)); - this._name = new St.Label({ text: this._user.get_real_name() }); + this._name = new St.Label(); box.add(this._name, { y_align: St.Align.MIDDLE, y_fill: false }); - this._userNameChangedId = this._user.connect('notify::display-name', Lang.bind(this, this._updateUserName)); + this._userLoadedId = this._user.connect('notify::is-loaded', Lang.bind(this, this._updateUserName)); + this._userChangedId = this._user.connect('changed', Lang.bind(this, this._updateUserName)); this._createSubMenu(); - this._gdm.connect('users-loaded', Lang.bind(this, this._updateSwitchUser)); + this._gdm.connect('notify::is-loaded', Lang.bind(this, this._updateSwitchUser)); this._gdm.connect('user-added', Lang.bind(this, this._updateSwitchUser)); this._gdm.connect('user-removed', Lang.bind(this, this._updateSwitchUser)); }, _onDestroy: function() { - this._user.disconnect(this._userNameChangedId); + this._user.disconnect(this._userLoadedId); + this._user.disconnect(this._userChangedId); }, _updateUserName: function() { - this._name.set_text(this._user.get_real_name()); + if (this._user.is_loaded) + this._name.set_text(this._user.get_real_name()); + else + this._name.set_text(""); }, _updateSwitchUser: function() { - let users = this._gdm.list_users(); - if (users.length > 1) + if (this._gdm.can_switch ()) this._loginScreenItem.actor.show(); else this._loginScreenItem.actor.hide(); From 627f1cb59a9937b984bbce47c52ca0947e51ac46 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Florian=20M=C3=BCllner?= Date: Thu, 14 Oct 2010 14:27:22 +0200 Subject: [PATCH 03/21] iconGrid: Add alignment parameter Currently the grid's content is centered, which is not appropriate for all use cases. Add an optional parameter to control the alignment. https://bugzilla.gnome.org/show_bug.cgi?id=632147 --- js/ui/iconGrid.js | 21 +++++++++++++++++---- 1 file changed, 17 insertions(+), 4 deletions(-) diff --git a/js/ui/iconGrid.js b/js/ui/iconGrid.js index 05abe1bdb..3625316e8 100644 --- a/js/ui/iconGrid.js +++ b/js/ui/iconGrid.js @@ -135,9 +135,12 @@ function IconGrid(params) { IconGrid.prototype = { _init: function(params) { - params = Params.parse(params, { rowLimit: null, columnLimit: null }); + params = Params.parse(params, { rowLimit: null, + columnLimit: null, + xAlign: St.Align.MIDDLE }); this._rowLimit = params.rowLimit; this._colLimit = params.columnLimit; + this._xAlign = params.xAlign; this.actor = new St.BoxLayout({ style_class: 'icon-grid', vertical: true }); @@ -189,9 +192,19 @@ IconGrid.prototype = { let [nColumns, usedWidth] = this._computeLayout(availWidth); - let overallPaddingX = Math.floor((availWidth - usedWidth) / 2); + let leftPadding; + switch(this._xAlign) { + case St.Align.START: + leftPadding = 0; + break; + case St.Align.MIDDLE: + leftPadding = Math.floor((availWidth - usedWidth) / 2); + break; + case St.Align.END: + leftPadding = availWidth - usedWidth; + } - let x = box.x1 + overallPaddingX; + let x = box.x1 + leftPadding; let y = box.y1; let columnIndex = 0; let rowIndex = 0; @@ -231,7 +244,7 @@ IconGrid.prototype = { if (columnIndex == 0) { y += this._item_size + this._spacing; - x = box.x1 + overallPaddingX; + x = box.x1 + leftPadding; } else { x += this._item_size + this._spacing; } From 700911ca5fcf8a11077c2d824bf21185360e76e2 Mon Sep 17 00:00:00 2001 From: Ray Strode Date: Thu, 14 Oct 2010 13:29:32 -0400 Subject: [PATCH 04/21] gdm: mute debug messages Since we're just using a cut-and-paste of gdm code there's a slight impedance mismatch between how that code and the rest of the shell manage debug messages. In GDM, they're out of sight by default, but in gnome-shell they're visible and quite verbose. This muddies up and masks the useful messages that other parts of the code generate. This commit just mutes the messages unconditionally by default, since they aren't that useful anyway. --- src/gdmuser/gdm-user-manager.c | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/src/gdmuser/gdm-user-manager.c b/src/gdmuser/gdm-user-manager.c index b28ca2082..b60e5a6d0 100644 --- a/src/gdmuser/gdm-user-manager.c +++ b/src/gdmuser/gdm-user-manager.c @@ -3057,6 +3057,15 @@ gdm_user_manager_finalize (GObject *object) G_OBJECT_CLASS (gdm_user_manager_parent_class)->finalize (object); } +static void +gdm_user_muted_debug_log_handler (const char *log_domain, + GLogLevelFlags log_level, + const char *message, + gpointer data) +{ + /* Intentionally empty to discard message */ +} + /** * gdm_user_manager_ref_default: * @@ -3075,6 +3084,11 @@ gdm_user_manager_ref_default (void) user_manager_object = g_object_new (GDM_TYPE_USER_MANAGER, NULL); g_object_add_weak_pointer (user_manager_object, (gpointer *) &user_manager_object); + + /* We don't normally care about user manager messages in the shell, + * so mute them */ + g_log_set_handler (G_LOG_DOMAIN, G_LOG_LEVEL_DEBUG, + gdm_user_muted_debug_log_handler, NULL); } return GDM_USER_MANAGER (user_manager_object); From 42c736614c7441c43ac04512af2565cd845fb686 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Florian=20M=C3=BCllner?= Date: Fri, 15 Oct 2010 09:18:37 +0200 Subject: [PATCH 05/21] st-drawing-area: Avoid unnecessary repaints StDrawingArea uses the member variable needs_repaint to keep track of whether it needs repainting. The variable is set to TRUE correctly, e.g. on allocation or style changes - alas, it is never set to FALSE, resulting in the area being repainted continuously. https://bugzilla.gnome.org/show_bug.cgi?id=632197 --- src/st/st-drawing-area.c | 1 + 1 file changed, 1 insertion(+) diff --git a/src/st/st-drawing-area.c b/src/st/st-drawing-area.c index fa99d11e6..a10da15db 100644 --- a/src/st/st-drawing-area.c +++ b/src/st/st-drawing-area.c @@ -115,6 +115,7 @@ st_drawing_area_paint (ClutterActor *self) surface = cairo_image_surface_create (CAIRO_FORMAT_ARGB32, width, height); priv->context = cairo_create (surface); priv->in_repaint = TRUE; + priv->needs_repaint = FALSE; g_signal_emit ((GObject*)area, st_drawing_area_signals[REPAINT], 0); From 4333b829eb31b82f8f1970f7cb5ba074d346c8f7 Mon Sep 17 00:00:00 2001 From: Kjartan Maraas Date: Sat, 16 Oct 2010 21:24:19 +0200 Subject: [PATCH 06/21] =?UTF-8?q?Updated=20Norwegian=20bokm=C3=A5l=20trans?= =?UTF-8?q?lation?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- po/nb.po | 52 +++++++++++++++++++++++++--------------------------- 1 file changed, 25 insertions(+), 27 deletions(-) diff --git a/po/nb.po b/po/nb.po index 7149abba0..f82b0bf31 100644 --- a/po/nb.po +++ b/po/nb.po @@ -1,15 +1,15 @@ # Norwegian bokmål translation of gnome-shell. # Copyright (C) 2009 THE PACKAGE'S COPYRIGHT HOLDER # This file is distributed under the same license as the gnome-shell package. -# Kjartan Maraas , 2009-2010. +# Kjartan Maraas , 2009-2010. # msgid "" msgstr "" "Project-Id-Version: gnome-shell 2.31.x\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2010-10-06 16:05+0200\n" -"PO-Revision-Date: 2010-10-06 16:07+0200\n" -"Last-Translator: Kjartan Maraas \n" +"POT-Creation-Date: 2010-10-16 21:23+0200\n" +"PO-Revision-Date: 2010-10-16 21:24+0200\n" +"Last-Translator: Kjartan Maraas \n" "Language-Team: Norwegian bokmål \n" "Language: \n" "MIME-Version: 1.0\n" @@ -37,9 +37,7 @@ msgstr "Tilpass klokken på panelet" msgid "" "Allows access to internal debugging and monitoring tools using the Alt-F2 " "dialog." -msgstr "" -"Tillat tilgang til interne feilsøkings og overvåkingsverktøy ved å bruke Alt-" -"F2-dialogen." +msgstr "Tillat tilgang til interne feilsøkings og overvåkingsverktøy ved å bruke Alt-F2-dialogen." #: ../data/org.gnome.shell.gschema.xml.in.h:2 msgid "Custom format of the clock" @@ -427,58 +425,58 @@ msgid "Undo" msgstr "Angre" #. TODO - _quit() doesn't really work on apps in state STARTING yet -#: ../js/ui/panel.js:461 +#: ../js/ui/panel.js:468 #, c-format msgid "Quit %s" msgstr "Avslutt %s" -#: ../js/ui/panel.js:486 +#: ../js/ui/panel.js:493 msgid "Preferences" msgstr "Brukervalg" #. Translators: This is the time format with date used #. in 24-hour mode. -#: ../js/ui/panel.js:572 +#: ../js/ui/panel.js:579 msgid "%a %b %e, %R:%S" msgstr "%a %e %b, %R.%S" -#: ../js/ui/panel.js:573 +#: ../js/ui/panel.js:580 msgid "%a %b %e, %R" msgstr "%a %e %b, %R" #. Translators: This is the time format without date used #. in 24-hour mode. -#: ../js/ui/panel.js:577 +#: ../js/ui/panel.js:584 msgid "%a %R:%S" msgstr "%a %R.%S" -#: ../js/ui/panel.js:578 +#: ../js/ui/panel.js:585 msgid "%a %R" msgstr "%a %R" #. Translators: This is a time format with date used #. for AM/PM. -#: ../js/ui/panel.js:585 +#: ../js/ui/panel.js:592 msgid "%a %b %e, %l:%M:%S %p" msgstr "%a %e %b, %l.%M.%S %p" -#: ../js/ui/panel.js:586 +#: ../js/ui/panel.js:593 msgid "%a %b %e, %l:%M %p" msgstr "%a %e %b, %l.%M %p" #. Translators: This is a time format without date used #. for AM/PM. -#: ../js/ui/panel.js:590 +#: ../js/ui/panel.js:597 msgid "%a %l:%M:%S %p" msgstr "%a %l.%M.%S %p" -#: ../js/ui/panel.js:591 +#: ../js/ui/panel.js:598 msgid "%a %l:%M %p" msgstr "%a %l.%M %p" #. Button on the left side of the panel. #. Translators: If there is no suitable word for "Activities" in your language, you can use the word for "Overview". -#: ../js/ui/panel.js:736 +#: ../js/ui/panel.js:743 msgid "Activities" msgstr "Aktiviteter" @@ -513,39 +511,39 @@ msgstr "Oppgi en kommando:" msgid "Execution of '%s' failed:" msgstr "Kjøring av «%s» feilet:" -#: ../js/ui/statusMenu.js:91 +#: ../js/ui/statusMenu.js:97 msgid "Available" msgstr "Tilgjengelig" -#: ../js/ui/statusMenu.js:95 +#: ../js/ui/statusMenu.js:101 msgid "Busy" msgstr "Opptatt" -#: ../js/ui/statusMenu.js:99 +#: ../js/ui/statusMenu.js:105 msgid "Invisible" msgstr "Usynlig" -#: ../js/ui/statusMenu.js:106 +#: ../js/ui/statusMenu.js:112 msgid "Account Information..." msgstr "Kontoinformasjon..." -#: ../js/ui/statusMenu.js:110 +#: ../js/ui/statusMenu.js:116 msgid "System Settings..." msgstr "Innstillinger for systemet..." -#: ../js/ui/statusMenu.js:117 +#: ../js/ui/statusMenu.js:123 msgid "Lock Screen" msgstr "Lås skjerm" -#: ../js/ui/statusMenu.js:121 +#: ../js/ui/statusMenu.js:127 msgid "Switch User" msgstr "Bytt bruker" -#: ../js/ui/statusMenu.js:126 +#: ../js/ui/statusMenu.js:132 msgid "Log Out..." msgstr "Logg ut..." -#: ../js/ui/statusMenu.js:130 +#: ../js/ui/statusMenu.js:136 msgid "Shut Down..." msgstr "Avslutt..." From 83aa23b27498166f470ae4f587e1ee3348035644 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mattias=20P=C3=B5ldaru?= Date: Sun, 17 Oct 2010 17:03:20 +0300 Subject: [PATCH 07/21] [l10n] Updated Estonian translation --- po/et.po | 297 +++++++++++++++---------------------------------------- 1 file changed, 79 insertions(+), 218 deletions(-) diff --git a/po/et.po b/po/et.po index dea27b92a..39cde8f51 100644 --- a/po/et.po +++ b/po/et.po @@ -1,30 +1,29 @@ -# GNOME Kesta eesti keele tõlge. -# Estonian translation of GNOME Shell. -# -# Copyright (C) 2010 The GNOME Project. +# Estonian translation for gnome-shell. +# Copyright (C) 2010 The Gnome Project # This file is distributed under the same license as the gnome-shell package. -# -# Ivar Smolin , 2010. +# Mattias Põldaru , 2010. # msgid "" msgstr "" "Project-Id-Version: gnome-shell master\n" "Report-Msgid-Bugs-To: http://bugzilla.gnome.org/enter_bug.cgi?product=gnome-" "shell&component=general\n" -"POT-Creation-Date: 2010-09-10 02:04+0000\n" -"PO-Revision-Date: 2010-09-10 08:29+0300\n" -"Last-Translator: Ivar Smolin \n" -"Language-Team: Estonian \n" +"POT-Creation-Date: 2010-05-19 17:17+0000\n" +"PO-Revision-Date: 2010-05-19 20:55+0300\n" +"Last-Translator: Mattias Põldaru \n" +"Language-Team: Estonian \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n" +"X-Poedit-Language: Estonian\n" +"X-Poedit-Country: Estonia\n" msgid "GNOME Shell" -msgstr "GNOME Kest" +msgstr "GNOME kest" msgid "Window management and application launching" -msgstr "Aknahaldus ja rakenduste käivitamine" +msgstr "Aknahaldur ja rakenduste käivitaja" msgid "Clock" msgstr "Kell" @@ -32,195 +31,75 @@ msgstr "Kell" msgid "Customize the panel clock" msgstr "Paneelikella kohandamine" -msgid "" -"Allows access to internal debugging and monitoring tools using the Alt-F2 " -"dialog." -msgstr "" - -msgid "Custom format of the clock" -msgstr "Kella kohandatud vorming" - -msgid "Enable internal tools useful for developers and testers from Alt-F2" -msgstr "" - -msgid "File extension used for storing the screencast" -msgstr "" - -msgid "Framerate used for recording screencasts." -msgstr "" - -msgid "" -"GNOME Shell extensions have a uuid property; this key lists extensions which " -"should not be loaded." -msgstr "" - -msgid "History for command (Alt-F2) dialog" -msgstr "" - -msgid "Hour format" -msgstr "Tundide vorming" - -msgid "" -"If true and format is either \"12-hour\" or \"24-hour\", display date in the " -"clock, in addition to time." -msgstr "" - -msgid "" -"If true and format is either \"12-hour\" or \"24-hour\", display seconds in " -"time." -msgstr "" - -msgid "If true, display the ISO week date in the calendar." -msgstr "" - -msgid "List of desktop file IDs for favorite applications" -msgstr "" - -msgid "Overview workspace view mode" -msgstr "" - -msgid "" -"Sets the GStreamer pipeline used to encode recordings. It follows the syntax " -"used for gst-launch. The pipeline should have an unconnected sink pad where " -"the recorded video is recorded. It will normally have a unconnected source " -"pad; output from that pad will be written into the output file. However the " -"pipeline can also take care of its own output - this might be used to send " -"the output to an icecast server via shout2send or similar. When unset or set " -"to an empty value, the default pipeline will be used. This is currently " -"'videorate ! theoraenc ! oggmux' and records to Ogg Theora." -msgstr "" - -msgid "Show date in clock" -msgstr "Kuupäeva näitamine koos kellaajaga" - -msgid "Show the week date in the calendar" -msgstr "" - -msgid "Show time with seconds" -msgstr "Aja näitamine koos sekunditega" - -msgid "" -"The applications corresponding to these identifiers will be displayed in the " -"favorites area." -msgstr "" - -msgid "" -"The filename for recorded screencasts will be a unique filename based on the " -"current date, and use this extension. It should be changed when recording to " -"a different container format." -msgstr "" - -msgid "" -"The framerate of the resulting screencast recordered by GNOME Shell's " -"screencast recorder in frames-per-second." -msgstr "" - -msgid "The gstreamer pipeline used to encode the screencast" -msgstr "" - -msgid "" -"The selected workspace view mode in the overview. Supported values are " -"\"single\" and \"grid\"." -msgstr "" - -msgid "" -"The shell normally monitors active applications in order to present the most " -"used ones (e.g. in launchers). While this data will be kept private, you may " -"want to disable this for privacy reasons. Please note that doing so won't " -"remove already saved data." -msgstr "" - -msgid "" -"This key specifies the format used by the panel clock when the format key is " -"set to \"custom\". You can use conversion specifiers understood by strftime" -"() to obtain a specific format. See the strftime() manual for more " -"information." -msgstr "" - -msgid "" -"This key specifies the hour format used by the panel clock. Possible values " -"are \"12-hour\", \"24-hour\", \"unix\" and \"custom\". If set to \"unix\", " -"the clock will display time in seconds since Epoch, i.e. 1970-01-01. If set " -"to \"custom\", the clock will display time according to the format specified " -"in the custom_format key. Note that if set to either \"unix\" or \"custom\", " -"the show_date and show_seconds keys are ignored." -msgstr "" - -msgid "Uuids of extensions to disable" -msgstr "" - -msgid "Whether to collect stats about applications usage" -msgstr "" - msgid "Clock Format" -msgstr "Kella vorming" +msgstr "Kella formaat" msgid "Clock Preferences" msgstr "Kella eelistused" msgid "Panel Display" -msgstr "" +msgstr "Paneelikuva" msgid "Show seco_nds" -msgstr "_Sekundid on nähtaval" +msgstr "_Sekundeid näidatakse" msgid "Show the _date" -msgstr "_Kuupäev on nähtaval" +msgstr "_Kuupäeva näidatakse" msgid "_12 hour format" -msgstr "_12-tunnine vorming" +msgstr "_12 tunni vorming" msgid "_24 hour format" -msgstr "_24-tunnine vorming" +msgstr "_24 tunni vorming" #. **** Applications **** msgid "APPLICATIONS" -msgstr "RAKENDUSED" +msgstr "Rakendused" msgid "PREFERENCES" -msgstr "EELISTUSED" +msgstr "Eelistused" msgid "New Window" msgstr "Uus aken" msgid "Remove from Favorites" -msgstr "Eemalda lemmikute hulgast" +msgstr "Eemalda lemmikutest" msgid "Add to Favorites" -msgstr "Lisa lemmikute hulka" +msgstr "Lisa lemmikutesse" msgid "Drag here to add favorites" -msgstr "" +msgstr "Lemmikute lisamiseks lohista need siia" #, c-format msgid "%s has been added to your favorites." -msgstr "" +msgstr "%s lisati lemmikutesse." #, c-format msgid "%s has been removed from your favorites." -msgstr "" +msgstr "%s eemaldati lemmikutest." msgid "Find" -msgstr "" +msgstr "Otsi" msgid "Searching..." -msgstr "" +msgstr "Otsimine..." msgid "No matching results." -msgstr "Sobivaid vasteid ei leitud." +msgstr "Tulemused puuduvad." #. **** Places **** #. Translators: This is in the sense of locations for documents, #. network locations, etc. msgid "PLACES & DEVICES" -msgstr "ASUKOHAD JA SEADMED" +msgstr "Asukohad ja seadmed" #. **** Documents **** msgid "RECENT ITEMS" -msgstr "VIIMASED KIRJED" +msgstr "Hiljutised dokumendid" msgid "No extensions installed" -msgstr "Laiendusi ei ole paigaldatud" +msgstr "Ühtegi laiendust pole paigaldatud" msgid "Enabled" msgstr "Lubatud" @@ -232,88 +111,70 @@ msgid "Error" msgstr "Viga" msgid "Out of date" -msgstr "" +msgstr "Pole värske" msgid "View Source" -msgstr "" +msgstr "Kuva lähtekoodi" msgid "Web Page" msgstr "Veebileht" msgid "Undo" -msgstr "Unusta" - -#. TODO - _quit() doesn't really work on apps in state STARTING yet -#, c-format -msgid "Quit %s" -msgstr "Lõpeta %s" - -msgid "Preferences" -msgstr "Eelistused" - -#. Translators: This is the time format with date used -#. in 24-hour mode. -msgid "%a %b %e, %R:%S" -msgstr "%a, %e. %b %R:%S" - -msgid "%a %b %e, %R" -msgstr "%a, %e. %b %R" - -#. Translators: This is the time format without date used -#. in 24-hour mode. -msgid "%a %R:%S" -msgstr "%a %R:%S" - -msgid "%a %R" -msgstr "%a %R" - -#. Translators: This is a time format with date used -#. for AM/PM. -msgid "%a %b %e, %l:%M:%S %p" -msgstr "%a, %e. %b %l:%M:%S %p" - -msgid "%a %b %e, %l:%M %p" -msgstr "%a, %e. %b %l:%MS %p" - -#. Translators: This is a time format without date used -#. for AM/PM. -msgid "%a %l:%M:%S %p" -msgstr "%a %l:%M:%S %p" - -msgid "%a %l:%M %p" -msgstr "%a %l:%M %p" +msgstr "Võta tagasi" #. Button on the left side of the panel. #. Translators: If there is no suitable word for "Activities" in your language, you can use the word for "Overview". msgid "Activities" -msgstr "Ülevaade" +msgstr "Tegevused" + +#. Translators: This is the time format with date used +#. in 24-hour mode. +msgid "%a %b %e, %R:%S" +msgstr "%a, %e. %b, %R:%S" + +msgid "%a %b %e, %R" +msgstr "%a, %e. %b, %R" + +#. Translators: This is the time format without date used +#. in 24-hour mode. +msgid "%a %R:%S" +msgstr "%A %R:%S" + +msgid "%a %R" +msgstr "%A %R" + +#. Translators: This is a time format with date used +#. for AM/PM. +msgid "%a %b %e, %l:%M:%S %p" +msgstr "%a, %e. %b, %l:%M:%S %p" + +msgid "%a %b %e, %l:%M %p" +msgstr "%a, %e. %b, %l:%M %p" + +#. Translators: This is a time format without date used +#. for AM/PM. +msgid "%a %l:%M:%S %p" +msgstr "%A, %l:%M:%S %p" + +msgid "%a %l:%M %p" +msgstr "%A, %l:%M %p" #, c-format msgid "Failed to unmount '%s'" -msgstr "Tõrge '%s' lahtihaakimisel" +msgstr "'%s' lahtihaakimine nurjus" msgid "Retry" msgstr "Proovi uuesti" msgid "Connect to..." -msgstr "" - -#. Translators: the "ON" and "OFF" strings are used in the -#. toggle switches in the status area menus, and must be SHORT. -#. If you don't have suitable short words, consider initials, -#. "0"/"1", "⚪"/"⚫", etc. -msgid "ON" -msgstr "" - -msgid "OFF" -msgstr "" +msgstr "Ühendumine..." msgid "Please enter a command:" msgstr "Palun sisesta käsk:" #, c-format msgid "Execution of '%s' failed:" -msgstr "" +msgstr "'%s' käivitamine nurjus:" msgid "Available" msgstr "Saadaval" @@ -340,11 +201,11 @@ msgid "Log Out..." msgstr "Logi välja..." msgid "Shut Down..." -msgstr "Seiska..." +msgstr "Lülita välja..." #, c-format msgid "%s has finished starting" -msgstr "%s lõpetas käivitumise" +msgstr "%s läks käima" #, c-format msgid "'%s' is ready" @@ -352,13 +213,13 @@ msgstr "'%s' on valmis" msgid "" "Can't add a new workspace because maximum workspaces limit has been reached." -msgstr "" +msgstr "Pole võimalik uut tööala lisada, kuna tööalade piir on saavutatud." msgid "Can't remove the first workspace." -msgstr "" +msgstr "Esimest tööala pole võimalik eemaldada." msgid "Less than a minute ago" -msgstr "Vähem kui minut aega tagasi" +msgstr "Vähem kui minuti eest" #, c-format msgid "%d minute ago" @@ -369,14 +230,14 @@ msgstr[1] "%d minutit tagasi" #, c-format msgid "%d hour ago" msgid_plural "%d hours ago" -msgstr[0] "" -msgstr[1] "" +msgstr[0] "%d tund tagasi" +msgstr[1] "%d tundi tagasi" #, c-format msgid "%d day ago" msgid_plural "%d days ago" -msgstr[0] "" -msgstr[1] "" +msgstr[0] "%d päev tagasi" +msgstr[1] "%d päeva tagasi" #, c-format msgid "%d week ago" @@ -385,7 +246,7 @@ msgstr[0] "%d nädal tagasi" msgstr[1] "%d nädalat tagasi" msgid "Home Folder" -msgstr "Kodukataloog" +msgstr "Kodukaust" #. Translators: this is the same string as the one found in #. * nautilus From 25c6acf44ba4b2088035858d0d3dc1cebe6c0d29 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mattias=20P=C3=B5ldaru?= Date: Sun, 17 Oct 2010 17:31:04 +0300 Subject: [PATCH 08/21] [l10n] Updated Estonian translation --- po/et.po | 270 +++++++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 262 insertions(+), 8 deletions(-) diff --git a/po/et.po b/po/et.po index 39cde8f51..b2ef72cfa 100644 --- a/po/et.po +++ b/po/et.po @@ -8,10 +8,11 @@ msgstr "" "Project-Id-Version: gnome-shell master\n" "Report-Msgid-Bugs-To: http://bugzilla.gnome.org/enter_bug.cgi?product=gnome-" "shell&component=general\n" -"POT-Creation-Date: 2010-05-19 17:17+0000\n" -"PO-Revision-Date: 2010-05-19 20:55+0300\n" +"POT-Creation-Date: 2010-10-16 19:24+0000\n" +"PO-Revision-Date: 2010-10-17 17:16+0300\n" "Last-Translator: Mattias Põldaru \n" "Language-Team: Estonian \n" +"Language: et\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" @@ -31,6 +32,243 @@ msgstr "Kell" msgid "Customize the panel clock" msgstr "Paneelikella kohandamine" +msgid "" +"Allows access to internal debugging and monitoring tools using the Alt-F2 " +"dialog." +msgstr "" +"Lubab ligipääsu sisemistele silumise ja monitoorimise tööriistadele Alt-F2 " +"dialoogi kaudu." + +msgid "Custom format of the clock" +msgstr "Kellaaaja kohandatud vorming" + +msgid "Enable internal tools useful for developers and testers from Alt-F2" +msgstr "" +"Arendajate ja testijate jaoks sisemiste tööriistade lubamine Alt-F2 alt" + +msgid "File extension used for storing the screencast" +msgstr "Faililaiend, mida kasutatakse ekraanivideo salvestamisel" + +msgid "Framerate used for recording screencasts." +msgstr "Ekraanivideo lindistamisel kasutatav kaadrikiirus." + +msgid "" +"GNOME Shell extensions have a uuid property; this key lists extensions which " +"should not be loaded." +msgstr "" + +msgid "History for command (Alt-F2) dialog" +msgstr "Käsudialoogi (Alt-F2) ajalugu" + +msgid "Hour format" +msgstr "Tundide vorming" + +msgid "" +"If true and format is either \"12-hour\" or \"24-hour\", display date in the " +"clock, in addition to time." +msgstr "" +"Kui tõene ja vorming on kas \"12-tundi\" või \"24-tundi\", kuvatakse " +"kellaaja kõrval ka kuupäeva." + +msgid "" +"If true and format is either \"12-hour\" or \"24-hour\", display seconds in " +"time." +msgstr "" +"Kui tõene ja vorming on kas \"12-tundi\" või \"24-tundi\", kuvatakse " +"kellaaega koos sekunditega." + +msgid "If true, display the ISO week date in the calendar." +msgstr "Kui tõene, kuvatakse kalendris kuupäeva ISO nädalate järgi." + +msgid "List of desktop file IDs for favorite applications" +msgstr "Lemmikrakenduste töölauafailide ID-de loend" + +msgid "Overview workspace view mode" +msgstr "" + +msgid "" +"Sets the GStreamer pipeline used to encode recordings. It follows the syntax " +"used for gst-launch. The pipeline should have an unconnected sink pad where " +"the recorded video is recorded. It will normally have a unconnected source " +"pad; output from that pad will be written into the output file. However the " +"pipeline can also take care of its own output - this might be used to send " +"the output to an icecast server via shout2send or similar. When unset or set " +"to an empty value, the default pipeline will be used. This is currently " +"'videorate ! theoraenc ! oggmux' and records to Ogg Theora." +msgstr "" + +msgid "Show date in clock" +msgstr "Kell näitab kuupäeva" + +msgid "Show the week date in the calendar" +msgstr "Kalendris näidatakse nädala kuupäeva" + +msgid "Show time with seconds" +msgstr "Kellaaega näidatakse sekunditega" + +msgid "" +"The applications corresponding to these identifiers will be displayed in the " +"favorites area." +msgstr "" + +msgid "" +"The filename for recorded screencasts will be a unique filename based on the " +"current date, and use this extension. It should be changed when recording to " +"a different container format." +msgstr "" + +msgid "" +"The framerate of the resulting screencast recordered by GNOME Shell's " +"screencast recorder in frames-per-second." +msgstr "" + +msgid "The gstreamer pipeline used to encode the screencast" +msgstr "" + +msgid "" +"The selected workspace view mode in the overview. Supported values are " +"\"single\" and \"grid\"." +msgstr "" + +msgid "" +"The shell normally monitors active applications in order to present the most " +"used ones (e.g. in launchers). While this data will be kept private, you may " +"want to disable this for privacy reasons. Please note that doing so won't " +"remove already saved data." +msgstr "" + +msgid "" +"This key specifies the format used by the panel clock when the format key is " +"set to \"custom\". You can use conversion specifiers understood by strftime" +"() to obtain a specific format. See the strftime() manual for more " +"information." +msgstr "" + +msgid "" +"This key specifies the hour format used by the panel clock. Possible values " +"are \"12-hour\", \"24-hour\", \"unix\" and \"custom\". If set to \"unix\", " +"the clock will display time in seconds since Epoch, i.e. 1970-01-01. If set " +"to \"custom\", the clock will display time according to the format specified " +"in the custom_format key. Note that if set to either \"unix\" or \"custom\", " +"the show_date and show_seconds keys are ignored." +msgstr "" + +msgid "Uuids of extensions to disable" +msgstr "Keelatavate laienduste UUID-d" + +msgid "Whether to collect stats about applications usage" +msgstr "" + +msgid "Clip the crosshairs at the center" +msgstr "Niitristi keskel on auk" + +msgid "Color of the crosshairs" +msgstr "Niitristi värvus" + +msgid "" +"Determines the length of the vertical and horizontal lines that make up the " +"crosshairs." +msgstr "" + +msgid "" +"Determines the position of the magnified mouse image within the magnified " +"view and how it reacts to system mouse movement. The values are - none: no " +"mouse tracking; - centered: the mouse image is displayed at the center of " +"the zoom region (which also represents the point under the system mouse) and " +"the magnified contents are scrolled as the system mouse moves; - " +"proportional: the position of the magnified mouse in the zoom region is " +"proportionally the same as the position of the system mouse on screen; - " +"push: when the magnified mouse intersects a boundary of the zoom region, the " +"contents are scrolled into view." +msgstr "" + +msgid "" +"Determines the transparency of the crosshairs, from fully opaque to fully " +"transparent." +msgstr "" +"Määrab niitristi läbipaistvuse, alates täiesti läbipaistmatust kuni täiesti " +"läbipaistvani." + +msgid "" +"Determines whether the crosshairs intersect the magnified mouse sprite, or " +"are clipped such that the ends of the horizontal and vertical lines surround " +"the mouse image." +msgstr "" +"Määrab, kas niitrist kattub suurendatud hiire pildiga või on keskelt ära " +"lõigatud nii, et jooned ümbritsevad hiirekursori pilti." + +msgid "Enable lens mode" +msgstr "Läätsede režiimi lubamine" + +msgid "" +"Enables/disables display of crosshairs centered on the magnified mouse " +"sprite." +msgstr "Lubab/keelab niitristi kuvamise suurendatud hiirekursori kohal." + +msgid "" +"For centered mouse tracking, when the system pointer is at or near the edge " +"of the screen, the magnified contents continue to scroll such that the " +"screen edge moves into the magnified view." +msgstr "" + +msgid "Length of the crosshairs" +msgstr "Niitristi pikkus" + +msgid "Magnification factor" +msgstr "Suurendustegur" + +msgid "Mouse Tracking Mode" +msgstr "Hiire jälitamise režiim" + +msgid "Opacity of the crosshairs" +msgstr "Niitristi läbipaistvus" + +msgid "Screen position" +msgstr "Ekraani asukoht" + +msgid "Scroll magnified contents beyond the edges of the desktop" +msgstr "Suurendusklaas võib liikuda töölauapiiridest väljapoole" + +msgid "Show or hide crosshairs" +msgstr "Niitristi kuvamine või peitmine" + +msgid "Show or hide the magnifier" +msgstr "Suurendusklaasi kuvamine või peitmine" + +msgid "Show or hide the magnifier and all of its zoom regions." +msgstr "" +"Suurendusklaasi ja selle kõigi suurenduspiirkondade kuvamine või peitmine." + +msgid "" +"The color of the the vertical and horizontal lines that make up the " +"crosshairs." +msgstr "Niitristi püst- ja rõhtjoone värvus." + +msgid "" +"The magnified view either fills the entire screen, or occupies the top-half, " +"bottom-half, left-half, or right-half of the screen." +msgstr "" +"Suurendatud vaade täidab kas kogu ekraani või täidab ülemise, alumise, " +"vasaku või parema ekraanipoole." + +msgid "" +"The power of the magnification. A value of 1.0 means no magnification. A " +"value of 2.0 doubles the size." +msgstr "Suurendustegur. 1,0 tähendab originaalsuurust. 2,0 muudab kaks korda." + +msgid "Thickness of the crosshairs" +msgstr "Niitristi paksus" + +msgid "" +"Whether the magnified view should be centered over the location of the " +"system mouse and move with it." +msgstr "" +"Kas suurendatud vaate keskkoht peaks asetsema süsteemi hiire kohal ning " +"liikuma sellega kaasa." + +msgid "Width of the vertical and horizontal lines that make up the crosshairs." +msgstr "Niitristi moodustavate püst- ja rõhtjoone laius" + msgid "Clock Format" msgstr "Kella formaat" @@ -122,10 +360,13 @@ msgstr "Veebileht" msgid "Undo" msgstr "Võta tagasi" -#. Button on the left side of the panel. -#. Translators: If there is no suitable word for "Activities" in your language, you can use the word for "Overview". -msgid "Activities" -msgstr "Tegevused" +#. TODO - _quit() doesn't really work on apps in state STARTING yet +#, c-format +msgid "Quit %s" +msgstr "Lõpeta %s" + +msgid "Preferences" +msgstr "Eelistused" #. Translators: This is the time format with date used #. in 24-hour mode. @@ -159,6 +400,11 @@ msgstr "%A, %l:%M:%S %p" msgid "%a %l:%M %p" msgstr "%A, %l:%M %p" +#. Button on the left side of the panel. +#. Translators: If there is no suitable word for "Activities" in your language, you can use the word for "Overview". +msgid "Activities" +msgstr "Tegevused" + #, c-format msgid "Failed to unmount '%s'" msgstr "'%s' lahtihaakimine nurjus" @@ -169,6 +415,14 @@ msgstr "Proovi uuesti" msgid "Connect to..." msgstr "Ühendumine..." +#. Translators: this MUST be either "toggle-switch-us" +#. (for toggle switches containing the English words +#. "ON" and "OFF") or "toggle-switch-intl" (for toggle +#. switches containing "◯" and "|"). Other values will +#. simply result in invisible toggle switches. +msgid "toggle-switch-us" +msgstr "toggle-switch-intl" + msgid "Please enter a command:" msgstr "Palun sisesta käsk:" @@ -188,8 +442,8 @@ msgstr "Nähtamatu" msgid "Account Information..." msgstr "Konto andmed..." -msgid "System Preferences..." -msgstr "Süsteemi eelistused..." +msgid "System Settings..." +msgstr "Süsteemi sätted..." msgid "Lock Screen" msgstr "Lukusta ekraan" From 67543775e92b0545b40765bb653550dc2de17fe7 Mon Sep 17 00:00:00 2001 From: Luca Ferretti Date: Mon, 18 Oct 2010 10:57:55 +0200 Subject: [PATCH 09/21] [l10n] Added missing accessibility.js to POTFILES.in --- po/POTFILES.in | 1 + 1 file changed, 1 insertion(+) diff --git a/po/POTFILES.in b/po/POTFILES.in index 97b956b71..2aa7d7c48 100644 --- a/po/POTFILES.in +++ b/po/POTFILES.in @@ -14,6 +14,7 @@ js/ui/placeDisplay.js js/ui/popupMenu.js js/ui/runDialog.js js/ui/statusMenu.js +js/ui/status/accessibility.js js/ui/windowAttentionHandler.js js/ui/workspacesView.js src/gdmuser/gdm-user.c From 6bdfe638c543c318665e7dcb5bac7dee3b08c970 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jorge=20Gonz=C3=A1lez?= Date: Mon, 18 Oct 2010 11:17:13 +0200 Subject: [PATCH 10/21] Updated Spanish translation --- po/es.po | 90 +++++++++++++++++++++++++++++++++++++++++--------------- 1 file changed, 66 insertions(+), 24 deletions(-) diff --git a/po/es.po b/po/es.po index 455f3a583..30a2537c1 100644 --- a/po/es.po +++ b/po/es.po @@ -8,8 +8,8 @@ msgstr "" "Project-Id-Version: gnome-shell.master\n" "Report-Msgid-Bugs-To: http://bugzilla.gnome.org/enter_bug.cgi?product=gnome-" "shell&component=general\n" -"POT-Creation-Date: 2010-10-04 19:52+0000\n" -"PO-Revision-Date: 2010-10-13 14:29+0200\n" +"POT-Creation-Date: 2010-10-18 08:59+0000\n" +"PO-Revision-Date: 2010-10-18 11:12+0200\n" "Last-Translator: Jorge González \n" "Language-Team: Español \n" "MIME-Version: 1.0\n" @@ -284,7 +284,6 @@ msgstr "" "la imagen del ratón." #: ../data/org.gnome.accessibility.magnifier.gschema.xml.in.h:7 -#| msgid "Enabled" msgid "Enable lens mode" msgstr "Activar el modo lente" @@ -504,58 +503,58 @@ msgid "Undo" msgstr "Deshacer" #. TODO - _quit() doesn't really work on apps in state STARTING yet -#: ../js/ui/panel.js:461 +#: ../js/ui/panel.js:468 #, c-format msgid "Quit %s" msgstr "Salir de %s" -#: ../js/ui/panel.js:486 +#: ../js/ui/panel.js:493 msgid "Preferences" msgstr "Preferencias" #. Translators: This is the time format with date used #. in 24-hour mode. -#: ../js/ui/panel.js:572 +#: ../js/ui/panel.js:579 msgid "%a %b %e, %R:%S" msgstr "%a %e de %b, %R:%S" -#: ../js/ui/panel.js:573 +#: ../js/ui/panel.js:580 msgid "%a %b %e, %R" msgstr "%a %e de %b, %R" #. Translators: This is the time format without date used #. in 24-hour mode. -#: ../js/ui/panel.js:577 +#: ../js/ui/panel.js:584 msgid "%a %R:%S" msgstr "%a %R:%S" -#: ../js/ui/panel.js:578 +#: ../js/ui/panel.js:585 msgid "%a %R" msgstr "%a %R" #. Translators: This is a time format with date used #. for AM/PM. -#: ../js/ui/panel.js:585 +#: ../js/ui/panel.js:592 msgid "%a %b %e, %l:%M:%S %p" msgstr "%a %e de %b, %H:%M:%S" -#: ../js/ui/panel.js:586 +#: ../js/ui/panel.js:593 msgid "%a %b %e, %l:%M %p" msgstr "%a %e de %b, %H:%M" #. Translators: This is a time format without date used #. for AM/PM. -#: ../js/ui/panel.js:590 +#: ../js/ui/panel.js:597 msgid "%a %l:%M:%S %p" msgstr "%a %H:%M:%S" -#: ../js/ui/panel.js:591 +#: ../js/ui/panel.js:598 msgid "%a %l:%M %p" msgstr "%a %H:%M" #. Button on the left side of the panel. #. Translators: If there is no suitable word for "Activities" in your language, you can use the word for "Overview". -#: ../js/ui/panel.js:736 +#: ../js/ui/panel.js:743 msgid "Activities" msgstr "Actividades" @@ -590,43 +589,86 @@ msgstr "Introduzca un comando:" msgid "Execution of '%s' failed:" msgstr "Falló la ejecución de «%s»:" -#: ../js/ui/statusMenu.js:91 +#: ../js/ui/statusMenu.js:97 msgid "Available" msgstr "Disponible" -#: ../js/ui/statusMenu.js:95 +#: ../js/ui/statusMenu.js:101 msgid "Busy" msgstr "Ocupado" -#: ../js/ui/statusMenu.js:99 +#: ../js/ui/statusMenu.js:105 msgid "Invisible" msgstr "Invisible" -#: ../js/ui/statusMenu.js:106 +#: ../js/ui/statusMenu.js:112 msgid "Account Information..." msgstr "Información de la cuenta…" -#: ../js/ui/statusMenu.js:110 -#| msgid "System Preferences..." +#: ../js/ui/statusMenu.js:116 msgid "System Settings..." msgstr "Ajustes del sistema…" -#: ../js/ui/statusMenu.js:117 +#: ../js/ui/statusMenu.js:123 msgid "Lock Screen" msgstr "Bloquear la pantalla" -#: ../js/ui/statusMenu.js:121 +#: ../js/ui/statusMenu.js:127 msgid "Switch User" msgstr "Cambiar de usuario" -#: ../js/ui/statusMenu.js:126 +#: ../js/ui/statusMenu.js:132 msgid "Log Out..." msgstr "Salir…" -#: ../js/ui/statusMenu.js:130 +#: ../js/ui/statusMenu.js:136 msgid "Shut Down..." msgstr "Apagar…" +#: ../js/ui/status/accessibility.js:88 +msgid "Screen Reader" +msgstr "Lector de pantalla" + +#: ../js/ui/status/accessibility.js:91 +msgid "Screen Keyboard" +msgstr "Teclado en pantalla" + +#: ../js/ui/status/accessibility.js:94 +msgid "Visual Alerts" +msgstr "Alertas visuales" + +#: ../js/ui/status/accessibility.js:97 +msgid "Sticky Keys" +msgstr "Teclas persistentes" + +#: ../js/ui/status/accessibility.js:100 +msgid "Slow Keys" +msgstr "Teclas lentas" + +#: ../js/ui/status/accessibility.js:103 +msgid "Bounce Keys" +msgstr "Rechazo de teclas" + +#: ../js/ui/status/accessibility.js:106 +msgid "Mouse Keys" +msgstr "Teclas del ratón" + +#: ../js/ui/status/accessibility.js:110 +msgid "Universal Access Settings" +msgstr "Preferencias del acceso universal" + +#: ../js/ui/status/accessibility.js:163 +msgid "High Contrast" +msgstr "Contraste alto" + +#: ../js/ui/status/accessibility.js:202 +msgid "Large Text" +msgstr "Texto:" + +#: ../js/ui/status/accessibility.js:223 +msgid "Zoom" +msgstr "Ampliación" + #: ../js/ui/windowAttentionHandler.js:43 #, c-format msgid "%s has finished starting" From 20b10b4d1650a695c12dae2acaac244045fa1815 Mon Sep 17 00:00:00 2001 From: Luca Ferretti Date: Mon, 18 Oct 2010 11:20:25 +0200 Subject: [PATCH 11/21] [l10n] Updated Italian traslation --- po/it.po | 268 +++++++++++++++++++++++++++++++++++++++++++++---------- 1 file changed, 221 insertions(+), 47 deletions(-) diff --git a/po/it.po b/po/it.po index c31d4d375..f20c6a6a6 100644 --- a/po/it.po +++ b/po/it.po @@ -3,13 +3,14 @@ # This file is distributed under the same license as the gnome-shell package. # # Milo Casagrande , 2009, 2010. +# Luca Ferretti , 2010. msgid "" msgstr "" "Project-Id-Version: gnome-shell\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2010-09-02 21:30+0200\n" -"PO-Revision-Date: 2010-08-27 21:12+0200\n" -"Last-Translator: Milo Casagrande \n" +"POT-Creation-Date: 2010-10-18 10:54+0200\n" +"PO-Revision-Date: 2010-10-18 11:04+0200\n" +"Last-Translator: Luca Ferretti \n" "Language-Team: Italian \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" @@ -225,6 +226,132 @@ msgid "Whether to collect stats about applications usage" msgstr "" "Indica se raccogliere statistiche riguardo l'utilizzo delle applicazioni" +#: ../data/org.gnome.accessibility.magnifier.gschema.xml.in.h:1 +msgid "Clip the crosshairs at the center" +msgstr "" + +#: ../data/org.gnome.accessibility.magnifier.gschema.xml.in.h:2 +msgid "Color of the crosshairs" +msgstr "" + +#: ../data/org.gnome.accessibility.magnifier.gschema.xml.in.h:3 +msgid "" +"Determines the length of the vertical and horizontal lines that make up the " +"crosshairs." +msgstr "" + +#: ../data/org.gnome.accessibility.magnifier.gschema.xml.in.h:4 +msgid "" +"Determines the position of the magnified mouse image within the magnified " +"view and how it reacts to system mouse movement. The values are - none: no " +"mouse tracking; - centered: the mouse image is displayed at the center of " +"the zoom region (which also represents the point under the system mouse) and " +"the magnified contents are scrolled as the system mouse moves; - " +"proportional: the position of the magnified mouse in the zoom region is " +"proportionally the same as the position of the system mouse on screen; - " +"push: when the magnified mouse intersects a boundary of the zoom region, the " +"contents are scrolled into view." +msgstr "" + +#: ../data/org.gnome.accessibility.magnifier.gschema.xml.in.h:5 +msgid "" +"Determines the transparency of the crosshairs, from fully opaque to fully " +"transparent." +msgstr "" + +#: ../data/org.gnome.accessibility.magnifier.gschema.xml.in.h:6 +msgid "" +"Determines whether the crosshairs intersect the magnified mouse sprite, or " +"are clipped such that the ends of the horizontal and vertical lines surround " +"the mouse image." +msgstr "" + +# (ndt) o abilitata? +#: ../data/org.gnome.accessibility.magnifier.gschema.xml.in.h:7 +msgid "Enable lens mode" +msgstr "" + +#: ../data/org.gnome.accessibility.magnifier.gschema.xml.in.h:8 +msgid "" +"Enables/disables display of crosshairs centered on the magnified mouse " +"sprite." +msgstr "" + +#: ../data/org.gnome.accessibility.magnifier.gschema.xml.in.h:9 +msgid "" +"For centered mouse tracking, when the system pointer is at or near the edge " +"of the screen, the magnified contents continue to scroll such that the " +"screen edge moves into the magnified view." +msgstr "" + +#: ../data/org.gnome.accessibility.magnifier.gschema.xml.in.h:10 +msgid "Length of the crosshairs" +msgstr "" + +#: ../data/org.gnome.accessibility.magnifier.gschema.xml.in.h:11 +msgid "Magnification factor" +msgstr "" + +#: ../data/org.gnome.accessibility.magnifier.gschema.xml.in.h:12 +msgid "Mouse Tracking Mode" +msgstr "" + +#: ../data/org.gnome.accessibility.magnifier.gschema.xml.in.h:13 +msgid "Opacity of the crosshairs" +msgstr "" + +#: ../data/org.gnome.accessibility.magnifier.gschema.xml.in.h:14 +msgid "Screen position" +msgstr "" + +#: ../data/org.gnome.accessibility.magnifier.gschema.xml.in.h:15 +msgid "Scroll magnified contents beyond the edges of the desktop" +msgstr "" + +#: ../data/org.gnome.accessibility.magnifier.gschema.xml.in.h:16 +msgid "Show or hide crosshairs" +msgstr "" + +#: ../data/org.gnome.accessibility.magnifier.gschema.xml.in.h:17 +msgid "Show or hide the magnifier" +msgstr "" + +#: ../data/org.gnome.accessibility.magnifier.gschema.xml.in.h:18 +msgid "Show or hide the magnifier and all of its zoom regions." +msgstr "" + +#: ../data/org.gnome.accessibility.magnifier.gschema.xml.in.h:19 +msgid "" +"The color of the the vertical and horizontal lines that make up the " +"crosshairs." +msgstr "" + +#: ../data/org.gnome.accessibility.magnifier.gschema.xml.in.h:20 +msgid "" +"The magnified view either fills the entire screen, or occupies the top-half, " +"bottom-half, left-half, or right-half of the screen." +msgstr "" + +#: ../data/org.gnome.accessibility.magnifier.gschema.xml.in.h:21 +msgid "" +"The power of the magnification. A value of 1.0 means no magnification. A " +"value of 2.0 doubles the size." +msgstr "" + +#: ../data/org.gnome.accessibility.magnifier.gschema.xml.in.h:22 +msgid "Thickness of the crosshairs" +msgstr "" + +#: ../data/org.gnome.accessibility.magnifier.gschema.xml.in.h:23 +msgid "" +"Whether the magnified view should be centered over the location of the " +"system mouse and move with it." +msgstr "" + +#: ../data/org.gnome.accessibility.magnifier.gschema.xml.in.h:24 +msgid "Width of the vertical and horizontal lines that make up the crosshairs." +msgstr "" + #: ../data/clock-preferences.ui.h:1 msgid "Clock Format" msgstr "Formato ora" @@ -254,27 +381,27 @@ msgid "_24 hour format" msgstr "Formato _24 ore" #. **** Applications **** -#: ../js/ui/appDisplay.js:384 ../js/ui/dash.js:778 +#: ../js/ui/appDisplay.js:316 ../js/ui/dash.js:778 msgid "APPLICATIONS" msgstr "Applicazioni" -#: ../js/ui/appDisplay.js:416 +#: ../js/ui/appDisplay.js:348 msgid "PREFERENCES" msgstr "Preferenze" -#: ../js/ui/appDisplay.js:721 +#: ../js/ui/appDisplay.js:648 msgid "New Window" msgstr "Nuova finestra" -#: ../js/ui/appDisplay.js:725 +#: ../js/ui/appDisplay.js:652 msgid "Remove from Favorites" msgstr "Rimuovi dai preferiti" -#: ../js/ui/appDisplay.js:726 +#: ../js/ui/appDisplay.js:653 msgid "Add to Favorites" msgstr "Aggiungi ai preferiti" -#: ../js/ui/appDisplay.js:1033 +#: ../js/ui/appDisplay.js:830 msgid "Drag here to add favorites" msgstr "Trascinare qui per aggiungere ai preferiti" @@ -349,59 +476,59 @@ msgid "Undo" msgstr "Annulla" #. TODO - _quit() doesn't really work on apps in state STARTING yet -#: ../js/ui/panel.js:473 +#: ../js/ui/panel.js:468 #, c-format msgid "Quit %s" msgstr "Chiudi %s" -#: ../js/ui/panel.js:498 +#: ../js/ui/panel.js:493 msgid "Preferences" msgstr "Preferenze" #. Translators: This is the time format with date used #. in 24-hour mode. -#: ../js/ui/panel.js:584 +#: ../js/ui/panel.js:579 msgid "%a %b %e, %R:%S" msgstr "%a %e %b, %k.%M.%S" # (ndt) proviamo col k, se non funge, sappiamo il perché... -#: ../js/ui/panel.js:585 +#: ../js/ui/panel.js:580 msgid "%a %b %e, %R" msgstr "%a %e %b, %k.%M" #. Translators: This is the time format without date used #. in 24-hour mode. -#: ../js/ui/panel.js:589 +#: ../js/ui/panel.js:584 msgid "%a %R:%S" msgstr "%a %k.%M.%S" -#: ../js/ui/panel.js:590 +#: ../js/ui/panel.js:585 msgid "%a %R" msgstr "%a %k.%M" #. Translators: This is a time format with date used #. for AM/PM. -#: ../js/ui/panel.js:597 +#: ../js/ui/panel.js:592 msgid "%a %b %e, %l:%M:%S %p" msgstr "%a %e %b, %l.%M.%S %P" -#: ../js/ui/panel.js:598 +#: ../js/ui/panel.js:593 msgid "%a %b %e, %l:%M %p" msgstr "%a %e %b, %l.%M %P" #. Translators: This is a time format without date used #. for AM/PM. -#: ../js/ui/panel.js:602 +#: ../js/ui/panel.js:597 msgid "%a %l:%M:%S %p" msgstr "%a %l.%M.%S %P" -#: ../js/ui/panel.js:603 +#: ../js/ui/panel.js:598 msgid "%a %l:%M %p" msgstr "%a %l.%M %P" #. Button on the left side of the panel. #. Translators: If there is no suitable word for "Activities" in your language, you can use the word for "Overview". -#: ../js/ui/panel.js:748 +#: ../js/ui/panel.js:743 msgid "Activities" msgstr "Attività" @@ -419,17 +546,14 @@ msgstr "Riprova" msgid "Connect to..." msgstr "Connetti a..." -#. Translators: the "ON" and "OFF" strings are used in the -#. toggle switches in the status area menus, and must be SHORT. -#. If you don't have suitable short words, consider initials, -#. "0"/"1", "⚪"/"⚫", etc. -#: ../js/ui/popupMenu.js:30 ../js/ui/popupMenu.js:40 -msgid "ON" -msgstr "On" - -#: ../js/ui/popupMenu.js:31 ../js/ui/popupMenu.js:45 -msgid "OFF" -msgstr "Off" +#. Translators: this MUST be either "toggle-switch-us" +#. (for toggle switches containing the English words +#. "ON" and "OFF") or "toggle-switch-intl" (for toggle +#. switches containing "◯" and "|"). Other values will +#. simply result in invisible toggle switches. +#: ../js/ui/popupMenu.js:33 +msgid "toggle-switch-us" +msgstr "toggle-switch-intl" #: ../js/ui/runDialog.js:233 msgid "Please enter a command:" @@ -440,42 +564,86 @@ msgstr "Inserire un comando:" msgid "Execution of '%s' failed:" msgstr "Esecuzione di «%s» non riuscita:" -#: ../js/ui/statusMenu.js:91 +#: ../js/ui/statusMenu.js:97 msgid "Available" msgstr "Disponibile" -#: ../js/ui/statusMenu.js:95 +#: ../js/ui/statusMenu.js:101 msgid "Busy" msgstr "Non disponibile" -#: ../js/ui/statusMenu.js:99 +#: ../js/ui/statusMenu.js:105 msgid "Invisible" msgstr "Invisibile" -#: ../js/ui/statusMenu.js:106 +#: ../js/ui/statusMenu.js:112 msgid "Account Information..." msgstr "Informazioni account..." -#: ../js/ui/statusMenu.js:110 -msgid "System Preferences..." -msgstr "Preferenze di sistema..." +#: ../js/ui/statusMenu.js:116 +msgid "System Settings..." +msgstr "Impostazioni di sistema..." -#: ../js/ui/statusMenu.js:117 +#: ../js/ui/statusMenu.js:123 msgid "Lock Screen" msgstr "Blocca schermo" -#: ../js/ui/statusMenu.js:121 +#: ../js/ui/statusMenu.js:127 msgid "Switch User" msgstr "Cambia utente" -#: ../js/ui/statusMenu.js:126 +#: ../js/ui/statusMenu.js:132 msgid "Log Out..." msgstr "Termina sessione..." -#: ../js/ui/statusMenu.js:130 +#: ../js/ui/statusMenu.js:136 msgid "Shut Down..." msgstr "Arresta..." +#: ../js/ui/status/accessibility.js:88 +msgid "Screen Reader" +msgstr "Lettore schermo" + +#: ../js/ui/status/accessibility.js:91 +msgid "Screen Keyboard" +msgstr "Tastiera a schermo" + +#: ../js/ui/status/accessibility.js:94 +msgid "Visual Alerts" +msgstr "Allerte visive" + +#: ../js/ui/status/accessibility.js:97 +msgid "Sticky Keys" +msgstr "Permanenza tasti" + +#: ../js/ui/status/accessibility.js:100 +msgid "Slow Keys" +msgstr "Rallentamento tasti" + +#: ../js/ui/status/accessibility.js:103 +msgid "Bounce Keys" +msgstr "Pressione ravvicinata tasti" + +#: ../js/ui/status/accessibility.js:106 +msgid "Mouse Keys" +msgstr "Mouse da tastiera" + +#: ../js/ui/status/accessibility.js:110 +msgid "Universal Access Settings" +msgstr "Impostazioni accesso universale" + +#: ../js/ui/status/accessibility.js:163 +msgid "High Contrast" +msgstr "Contrasto elevato" + +#: ../js/ui/status/accessibility.js:202 +msgid "Large Text" +msgstr "Caratteri grandi" + +#: ../js/ui/status/accessibility.js:223 +msgid "Zoom" +msgstr "Ingrandimento" + #: ../js/ui/windowAttentionHandler.js:43 #, c-format msgid "%s has finished starting" @@ -499,32 +667,32 @@ msgstr "" msgid "Can't remove the first workspace." msgstr "Impossibile rimuovere il primo spazio di lavoro." -#: ../src/shell-global.c:1105 +#: ../src/shell-global.c:1196 msgid "Less than a minute ago" msgstr "Meno di un minuto fa" -#: ../src/shell-global.c:1109 +#: ../src/shell-global.c:1200 #, c-format msgid "%d minute ago" msgid_plural "%d minutes ago" msgstr[0] "%d minuto fa" msgstr[1] "%d minuti fa" -#: ../src/shell-global.c:1114 +#: ../src/shell-global.c:1205 #, c-format msgid "%d hour ago" msgid_plural "%d hours ago" msgstr[0] "%d ora fa" msgstr[1] "%d ore fa" -#: ../src/shell-global.c:1119 +#: ../src/shell-global.c:1210 #, c-format msgid "%d day ago" msgid_plural "%d days ago" msgstr[0] "%d giorno fa" msgstr[1] "%d giorni fa" -#: ../src/shell-global.c:1124 +#: ../src/shell-global.c:1215 #, c-format msgid "%d week ago" msgid_plural "%d weeks ago" @@ -555,3 +723,9 @@ msgstr "Cerca" #, c-format msgid "%1$s: %2$s" msgstr "%1$s: %2$s" + +#~ msgid "ON" +#~ msgstr "On" + +#~ msgid "OFF" +#~ msgstr "Off" From 6df48b68fadf38d171b318ebb000548d6371672c Mon Sep 17 00:00:00 2001 From: Ray Strode Date: Mon, 18 Oct 2010 11:41:39 -0400 Subject: [PATCH 12/21] extensionSystem: Don't load extensions multiple times Right now if XDG_DATA_DIRS is set to have duplicate entries, then the extension system will try to load all extensions more than once. This commit prevents an extension from getting repeatedly loaded by checking if its uuid is already registered. https://bugzilla.gnome.org/show_bug.cgi?id=632477 --- js/ui/extensionSystem.js | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/js/ui/extensionSystem.js b/js/ui/extensionSystem.js index 944e2e546..5315d90c3 100644 --- a/js/ui/extensionSystem.js +++ b/js/ui/extensionSystem.js @@ -51,6 +51,12 @@ function loadExtension(dir, enabled, type) { return; } } + + if (extensions[meta.uuid] != undefined) { + global.logError(baseErrorString + "extension already loaded"); + return; + } + // Encourage people to add this if (!meta['url']) { global.log(baseErrorString + 'Warning: Missing "url" property in metadata.json'); From a5d4abda007c37b9b7e92617bce4ae361d7d16e1 Mon Sep 17 00:00:00 2001 From: Ray Strode Date: Mon, 18 Oct 2010 11:45:34 -0400 Subject: [PATCH 13/21] st-theme: ref items in custom stylesheets list st-theme stores some loaded stylesheets in a custom stylesheets list. When removing items from this list it unrefs them, but when adding items to the list it neglects to ref them. This means that under certain circumstances the list will contain items that have already been freed. https://bugzilla.gnome.org/show_bug.cgi?id=632477 --- src/st/st-theme.c | 1 + 1 file changed, 1 insertion(+) diff --git a/src/st/st-theme.c b/src/st/st-theme.c index 4134b758b..6e2fd2c74 100644 --- a/src/st/st-theme.c +++ b/src/st/st-theme.c @@ -320,6 +320,7 @@ st_theme_load_stylesheet (StTheme *theme, return FALSE; insert_stylesheet (theme, path, stylesheet); + cr_stylesheet_ref (stylesheet); theme->custom_stylesheets = g_slist_prepend (theme->custom_stylesheets, stylesheet); return TRUE; From 8064c6c827c1aabfb23231eca4221c400a60fc65 Mon Sep 17 00:00:00 2001 From: Bastien Nocera Date: Mon, 27 Sep 2010 15:27:15 +0100 Subject: [PATCH 14/21] Add private gnome-volume-control library The library is introspected, and should not require using Pulseaudio directly. With help from Giovanni Campagna (introspection annotations, build fixes) https://bugzilla.gnome.org/show_bug.cgi?id=629455 --- configure.ac | 1 + src/Makefile-gvc.am | 69 + src/Makefile.am | 6 +- src/gnome-shell-plugin.c | 13 + src/gvc/gvc-channel-map-private.h | 39 + src/gvc/gvc-channel-map.c | 254 +++ src/gvc/gvc-channel-map.h | 73 + src/gvc/gvc-mixer-card-private.h | 35 + src/gvc/gvc-mixer-card.c | 506 ++++++ src/gvc/gvc-mixer-card.h | 83 + src/gvc/gvc-mixer-control-private.h | 35 + src/gvc/gvc-mixer-control.c | 2232 +++++++++++++++++++++++++++ src/gvc/gvc-mixer-control.h | 96 ++ src/gvc/gvc-mixer-event-role.c | 250 +++ src/gvc/gvc-mixer-event-role.h | 57 + src/gvc/gvc-mixer-sink-input.c | 199 +++ src/gvc/gvc-mixer-sink-input.h | 57 + src/gvc/gvc-mixer-sink.c | 231 +++ src/gvc/gvc-mixer-sink.h | 57 + src/gvc/gvc-mixer-source-output.c | 137 ++ src/gvc/gvc-mixer-source-output.h | 57 + src/gvc/gvc-mixer-source.c | 231 +++ src/gvc/gvc-mixer-source.h | 57 + src/gvc/gvc-mixer-stream-private.h | 34 + src/gvc/gvc-mixer-stream.c | 944 +++++++++++ src/gvc/gvc-mixer-stream.h | 125 ++ src/gvc/gvc-pulseaudio-fake.h | 34 + 27 files changed, 5910 insertions(+), 2 deletions(-) create mode 100644 src/Makefile-gvc.am create mode 100644 src/gvc/gvc-channel-map-private.h create mode 100644 src/gvc/gvc-channel-map.c create mode 100644 src/gvc/gvc-channel-map.h create mode 100644 src/gvc/gvc-mixer-card-private.h create mode 100644 src/gvc/gvc-mixer-card.c create mode 100644 src/gvc/gvc-mixer-card.h create mode 100644 src/gvc/gvc-mixer-control-private.h create mode 100644 src/gvc/gvc-mixer-control.c create mode 100644 src/gvc/gvc-mixer-control.h create mode 100644 src/gvc/gvc-mixer-event-role.c create mode 100644 src/gvc/gvc-mixer-event-role.h create mode 100644 src/gvc/gvc-mixer-sink-input.c create mode 100644 src/gvc/gvc-mixer-sink-input.h create mode 100644 src/gvc/gvc-mixer-sink.c create mode 100644 src/gvc/gvc-mixer-sink.h create mode 100644 src/gvc/gvc-mixer-source-output.c create mode 100644 src/gvc/gvc-mixer-source-output.h create mode 100644 src/gvc/gvc-mixer-source.c create mode 100644 src/gvc/gvc-mixer-source.h create mode 100644 src/gvc/gvc-mixer-stream-private.h create mode 100644 src/gvc/gvc-mixer-stream.c create mode 100644 src/gvc/gvc-mixer-stream.h create mode 100644 src/gvc/gvc-pulseaudio-fake.h diff --git a/configure.ac b/configure.ac index 91fd12040..ad6a1f84b 100644 --- a/configure.ac +++ b/configure.ac @@ -90,6 +90,7 @@ PKG_CHECK_MODULES(TIDY, clutter-1.0) PKG_CHECK_MODULES(ST, clutter-1.0 gtk+-3.0 libcroco-0.6 gnome-desktop-3.0 >= 2.90.0) PKG_CHECK_MODULES(GDMUSER, dbus-glib-1 gtk+-3.0) PKG_CHECK_MODULES(TRAY, gtk+-3.0) +PKG_CHECK_MODULES(GVC, libpulse libpulse-mainloop-glib gobject-2.0) MUTTER_BIN_DIR=`$PKG_CONFIG --variable=exec_prefix mutter-plugins`/bin # FIXME: metacity-plugins.pc should point directly to its .gir file diff --git a/src/Makefile-gvc.am b/src/Makefile-gvc.am new file mode 100644 index 000000000..20dd4d835 --- /dev/null +++ b/src/Makefile-gvc.am @@ -0,0 +1,69 @@ +noinst_LTLIBRARIES += libgvc.la + +libgvc_la_CPPFLAGS = \ + $(WARN_CFLAGS) \ + $(GVC_CFLAGS) \ + -I$(srcdir)/gvc/ \ + -DWITH_INTROSPECTION \ + -DG_LOG_DOMAIN="\"Gvc\"" + +libgvc_la_LIBADD = \ + $(GVC_LIBS) + +libgvc_la_LDFLAGS = -avoid-version + +libgvc_la_gir_sources = \ + gvc/gvc-mixer-stream.h \ + gvc/gvc-mixer-stream.c \ + gvc/gvc-channel-map.h \ + gvc/gvc-channel-map.c \ + gvc/gvc-mixer-card.c \ + gvc/gvc-mixer-card.h \ + gvc/gvc-mixer-sink.h \ + gvc/gvc-mixer-sink.c \ + gvc/gvc-mixer-source.h \ + gvc/gvc-mixer-source.c \ + gvc/gvc-mixer-sink-input.h \ + gvc/gvc-mixer-sink-input.c \ + gvc/gvc-mixer-source-output.h \ + gvc/gvc-mixer-source-output.c \ + gvc/gvc-mixer-event-role.h \ + gvc/gvc-mixer-event-role.c \ + gvc/gvc-mixer-control.h \ + gvc/gvc-mixer-control.c \ + gvc/gvc-pulseaudio-fake.h + +libgvc_la_private_sources = \ + gvc/gvc-mixer-stream-private.h \ + gvc/gvc-channel-map-private.h \ + gvc/gvc-mixer-card-private.h \ + gvc/gvc-mixer-control-private.h \ + gvc/gvc-pulseaudio-fake.h + +libgvc_la_SOURCES = \ + $(libgvc_la_gir_sources) \ + $(libgvc_la_private_sources) + +Gvc-1.0.gir: $(G_IR_SCANNER) libgvc.la Makefile + $(AM_V_GEN) $(G_IR_SCANNER) \ + --namespace=Gvc \ + --nsversion=1.0 \ + --warn-all \ + --quiet \ + --libtool="$(LIBTOOL)" \ + --add-include-path=$(builddir) \ + --include=GObject-2.0 \ + --library=libgvc.la \ + --identifier-prefix=Gvc \ + --symbol-prefix=gvc_ \ + $(addprefix $(srcdir)/,$(libgvc_la_gir_sources)) \ + $(INCLUDES) \ + -I $(srcdir) \ + -I $(srcdir)/gvc \ + -DWITH_INTROSPECTION \ + -o $@ +CLEANFILES += Gvc-1.0.gir + +Gvc-1.0.typelib: Gvc-1.0.gir + $(AM_V_GEN) $(G_IR_COMPILER) $< -o $@ +CLEANFILES += Gvc-1.0.typelib diff --git a/src/Makefile.am b/src/Makefile.am index e64082437..6bdc0d261 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -37,6 +37,7 @@ EXTRA_DIST += gnome-shell-clock-preferences.in include Makefile-gdmuser.am include Makefile-st.am include Makefile-tray.am +include Makefile-gvc.am gnome_shell_cflags = \ $(MUTTER_PLUGIN_CFLAGS) \ @@ -190,11 +191,12 @@ libgnome_shell_la_LIBADD = \ $(LIBGNOMEUI_LIBS) \ libst-1.0.la \ libgdmuser-1.0.la \ - libtray.la + libtray.la \ + libgvc.la libgnome_shell_la_CPPFLAGS = $(gnome_shell_cflags) typelibdir = $(pkglibdir) -typelib_DATA = Shell-0.1.typelib St-1.0.typelib Gdm-1.0.typelib +typelib_DATA = Shell-0.1.typelib St-1.0.typelib Gdm-1.0.typelib Gvc-1.0.typelib Shell-0.1.gir: $(mutter) $(G_IR_SCANNER) St-1.0.gir libgnome-shell.la Makefile $(AM_V_GEN) $(G_IR_SCANNER) \ diff --git a/src/gnome-shell-plugin.c b/src/gnome-shell-plugin.c index 91626f9f1..f2f6137da 100644 --- a/src/gnome-shell-plugin.c +++ b/src/gnome-shell-plugin.c @@ -296,6 +296,15 @@ add_statistics (GnomeShellPlugin *shell_plugin) NULL, NULL); } +static void +gvc_muted_debug_log_handler (const char *log_domain, + GLogLevelFlags log_level, + const char *message, + gpointer data) +{ + /* Intentionally empty to discard message */ +} + static void gnome_shell_plugin_start (MutterPlugin *plugin) { @@ -355,6 +364,10 @@ gnome_shell_plugin_start (MutterPlugin *plugin) shell_plugin->gjs_context = gjs_context_new_with_search_path(search_path); g_strfreev(search_path); + /* Disable the gnome-volume-control debug */ + g_log_set_handler ("Gvc", G_LOG_LEVEL_DEBUG, + gvc_muted_debug_log_handler, NULL); + /* Initialize the global object here. */ global = shell_global_get (); diff --git a/src/gvc/gvc-channel-map-private.h b/src/gvc/gvc-channel-map-private.h new file mode 100644 index 000000000..3949de3a6 --- /dev/null +++ b/src/gvc/gvc-channel-map-private.h @@ -0,0 +1,39 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*- + * + * Copyright (C) 2008 Red Hat, Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + * + */ + +#ifndef __GVC_CHANNEL_MAP_PRIVATE_H +#define __GVC_CHANNEL_MAP_PRIVATE_H + +#include +#include + +G_BEGIN_DECLS + +GvcChannelMap * gvc_channel_map_new_from_pa_channel_map (const pa_channel_map *map); +const pa_channel_map * gvc_channel_map_get_pa_channel_map (const GvcChannelMap *map); + +void gvc_channel_map_volume_changed (GvcChannelMap *map, + const pa_cvolume *cv, + gboolean set); +const pa_cvolume * gvc_channel_map_get_cvolume (const GvcChannelMap *map); + +G_END_DECLS + +#endif /* __GVC_CHANNEL_MAP_PRIVATE_H */ diff --git a/src/gvc/gvc-channel-map.c b/src/gvc/gvc-channel-map.c new file mode 100644 index 000000000..a2073fd33 --- /dev/null +++ b/src/gvc/gvc-channel-map.c @@ -0,0 +1,254 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*- + * + * Copyright (C) 2008 William Jon McCann + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + * + */ + +#include "config.h" + +#include +#include +#include + +#include +#include + +#include + +#include "gvc-channel-map.h" +#include "gvc-channel-map-private.h" + +#define GVC_CHANNEL_MAP_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), GVC_TYPE_CHANNEL_MAP, GvcChannelMapPrivate)) + +struct GvcChannelMapPrivate +{ + pa_channel_map pa_map; + gboolean pa_volume_is_set; + pa_cvolume pa_volume; + gdouble extern_volume[NUM_TYPES]; /* volume, balance, fade, lfe */ + gboolean can_balance; + gboolean can_fade; +}; + +enum { + VOLUME_CHANGED, + LAST_SIGNAL +}; + +static guint signals [LAST_SIGNAL] = { 0, }; + +static void gvc_channel_map_class_init (GvcChannelMapClass *klass); +static void gvc_channel_map_init (GvcChannelMap *channel_map); +static void gvc_channel_map_finalize (GObject *object); + +G_DEFINE_TYPE (GvcChannelMap, gvc_channel_map, G_TYPE_OBJECT) + +guint +gvc_channel_map_get_num_channels (const GvcChannelMap *map) +{ + g_return_val_if_fail (GVC_IS_CHANNEL_MAP (map), 0); + + if (!pa_channel_map_valid(&map->priv->pa_map)) + return 0; + + return map->priv->pa_map.channels; +} + +const gdouble * +gvc_channel_map_get_volume (GvcChannelMap *map) +{ + g_return_val_if_fail (GVC_IS_CHANNEL_MAP (map), NULL); + + if (!pa_channel_map_valid(&map->priv->pa_map)) + return NULL; + + map->priv->extern_volume[VOLUME] = (gdouble) pa_cvolume_max (&map->priv->pa_volume); + if (gvc_channel_map_can_balance (map)) + map->priv->extern_volume[BALANCE] = (gdouble) pa_cvolume_get_balance (&map->priv->pa_volume, &map->priv->pa_map); + else + map->priv->extern_volume[BALANCE] = 0; + if (gvc_channel_map_can_fade (map)) + map->priv->extern_volume[FADE] = (gdouble) pa_cvolume_get_fade (&map->priv->pa_volume, &map->priv->pa_map); + else + map->priv->extern_volume[FADE] = 0; + if (gvc_channel_map_has_lfe (map)) + map->priv->extern_volume[LFE] = (gdouble) pa_cvolume_get_position (&map->priv->pa_volume, &map->priv->pa_map, PA_CHANNEL_POSITION_LFE); + else + map->priv->extern_volume[LFE] = 0; + + return map->priv->extern_volume; +} + +gboolean +gvc_channel_map_can_balance (const GvcChannelMap *map) +{ + g_return_val_if_fail (GVC_IS_CHANNEL_MAP (map), FALSE); + + return map->priv->can_balance; +} + +gboolean +gvc_channel_map_can_fade (const GvcChannelMap *map) +{ + g_return_val_if_fail (GVC_IS_CHANNEL_MAP (map), FALSE); + + return map->priv->can_fade; +} + +const char * +gvc_channel_map_get_mapping (const GvcChannelMap *map) +{ + g_return_val_if_fail (GVC_IS_CHANNEL_MAP (map), NULL); + + if (!pa_channel_map_valid(&map->priv->pa_map)) + return NULL; + + return pa_channel_map_to_pretty_name (&map->priv->pa_map); +} + +/** + * gvc_channel_map_has_position: (skip) + * + * @map: + * @position: + * + * Returns: + */ +gboolean +gvc_channel_map_has_position (const GvcChannelMap *map, + pa_channel_position_t position) +{ + g_return_val_if_fail (GVC_IS_CHANNEL_MAP (map), FALSE); + + return pa_channel_map_has_position (&(map->priv->pa_map), position); +} + +const pa_channel_map * +gvc_channel_map_get_pa_channel_map (const GvcChannelMap *map) +{ + g_return_val_if_fail (GVC_IS_CHANNEL_MAP (map), NULL); + + if (!pa_channel_map_valid(&map->priv->pa_map)) + return NULL; + + return &map->priv->pa_map; +} + +const pa_cvolume * +gvc_channel_map_get_cvolume (const GvcChannelMap *map) +{ + g_return_val_if_fail (GVC_IS_CHANNEL_MAP (map), NULL); + + if (!pa_channel_map_valid(&map->priv->pa_map)) + return NULL; + + return &map->priv->pa_volume; +} + +static void +gvc_channel_map_class_init (GvcChannelMapClass *klass) +{ + GObjectClass *gobject_class = G_OBJECT_CLASS (klass); + + gobject_class->finalize = gvc_channel_map_finalize; + + signals [VOLUME_CHANGED] = + g_signal_new ("volume-changed", + G_TYPE_FROM_CLASS (klass), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (GvcChannelMapClass, volume_changed), + NULL, NULL, + g_cclosure_marshal_VOID__BOOLEAN, + G_TYPE_NONE, 1, G_TYPE_BOOLEAN); + + g_type_class_add_private (klass, sizeof (GvcChannelMapPrivate)); +} + +void +gvc_channel_map_volume_changed (GvcChannelMap *map, + const pa_cvolume *cv, + gboolean set) +{ + g_return_if_fail (GVC_IS_CHANNEL_MAP (map)); + g_return_if_fail (cv != NULL); + g_return_if_fail (pa_cvolume_compatible_with_channel_map(cv, &map->priv->pa_map)); + + if (pa_cvolume_equal(cv, &map->priv->pa_volume)) + return; + + map->priv->pa_volume = *cv; + + if (map->priv->pa_volume_is_set == FALSE) { + map->priv->pa_volume_is_set = TRUE; + return; + } + g_signal_emit (map, signals[VOLUME_CHANGED], 0, set); +} + +static void +gvc_channel_map_init (GvcChannelMap *map) +{ + map->priv = GVC_CHANNEL_MAP_GET_PRIVATE (map); + map->priv->pa_volume_is_set = FALSE; +} + +static void +gvc_channel_map_finalize (GObject *object) +{ + GvcChannelMap *channel_map; + + g_return_if_fail (object != NULL); + g_return_if_fail (GVC_IS_CHANNEL_MAP (object)); + + channel_map = GVC_CHANNEL_MAP (object); + + g_return_if_fail (channel_map->priv != NULL); + + G_OBJECT_CLASS (gvc_channel_map_parent_class)->finalize (object); +} + +GvcChannelMap * +gvc_channel_map_new (void) +{ + GObject *map; + map = g_object_new (GVC_TYPE_CHANNEL_MAP, NULL); + return GVC_CHANNEL_MAP (map); +} + +static void +set_from_pa_map (GvcChannelMap *map, + const pa_channel_map *pa_map) +{ + g_assert (pa_channel_map_valid(pa_map)); + + map->priv->can_balance = pa_channel_map_can_balance (pa_map); + map->priv->can_fade = pa_channel_map_can_fade (pa_map); + + map->priv->pa_map = *pa_map; + pa_cvolume_set(&map->priv->pa_volume, pa_map->channels, PA_VOLUME_NORM); +} + +GvcChannelMap * +gvc_channel_map_new_from_pa_channel_map (const pa_channel_map *pa_map) +{ + GObject *map; + map = g_object_new (GVC_TYPE_CHANNEL_MAP, NULL); + + set_from_pa_map (GVC_CHANNEL_MAP (map), pa_map); + + return GVC_CHANNEL_MAP (map); +} diff --git a/src/gvc/gvc-channel-map.h b/src/gvc/gvc-channel-map.h new file mode 100644 index 000000000..85c577289 --- /dev/null +++ b/src/gvc/gvc-channel-map.h @@ -0,0 +1,73 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*- + * + * Copyright (C) 2008 Red Hat, Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + * + */ + +#ifndef __GVC_CHANNEL_MAP_H +#define __GVC_CHANNEL_MAP_H + +#include +#include + +G_BEGIN_DECLS + +#define GVC_TYPE_CHANNEL_MAP (gvc_channel_map_get_type ()) +#define GVC_CHANNEL_MAP(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), GVC_TYPE_CHANNEL_MAP, GvcChannelMap)) +#define GVC_CHANNEL_MAP_CLASS(k) (G_TYPE_CHECK_CLASS_CAST((k), GVC_TYPE_CHANNEL_MAP, GvcChannelMapClass)) +#define GVC_IS_CHANNEL_MAP(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), GVC_TYPE_CHANNEL_MAP)) +#define GVC_IS_CHANNEL_MAP_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE ((k), GVC_TYPE_CHANNEL_MAP)) +#define GVC_CHANNEL_MAP_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), GVC_TYPE_CHANNEL_MAP, GvcChannelMapClass)) + +typedef struct GvcChannelMapPrivate GvcChannelMapPrivate; + +typedef struct +{ + GObject parent; + GvcChannelMapPrivate *priv; +} GvcChannelMap; + +typedef struct +{ + GObjectClass parent_class; + void (*volume_changed) (GvcChannelMap *channel_map, gboolean set); +} GvcChannelMapClass; + +enum { + VOLUME, + BALANCE, + FADE, + LFE, + NUM_TYPES +}; + +GType gvc_channel_map_get_type (void); + +GvcChannelMap * gvc_channel_map_new (void); +guint gvc_channel_map_get_num_channels (const GvcChannelMap *map); +const gdouble * gvc_channel_map_get_volume (GvcChannelMap *map); +gboolean gvc_channel_map_can_balance (const GvcChannelMap *map); +gboolean gvc_channel_map_can_fade (const GvcChannelMap *map); +gboolean gvc_channel_map_has_position (const GvcChannelMap *map, + pa_channel_position_t position); +#define gvc_channel_map_has_lfe(x) gvc_channel_map_has_position (x, PA_CHANNEL_POSITION_LFE) + +const char * gvc_channel_map_get_mapping (const GvcChannelMap *map); + +G_END_DECLS + +#endif /* __GVC_CHANNEL_MAP_H */ diff --git a/src/gvc/gvc-mixer-card-private.h b/src/gvc/gvc-mixer-card-private.h new file mode 100644 index 000000000..e190f7f4b --- /dev/null +++ b/src/gvc/gvc-mixer-card-private.h @@ -0,0 +1,35 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*- + * + * Copyright (C) 2008-2009 Red Hat, Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + * + */ + +#ifndef __GVC_MIXER_CARD_PRIVATE_H +#define __GVC_MIXER_CARD_PRIVATE_H + +#include +#include "gvc-mixer-card.h" + +G_BEGIN_DECLS + +GvcMixerCard * gvc_mixer_card_new (pa_context *context, + guint index); +pa_context * gvc_mixer_card_get_pa_context (GvcMixerCard *card); + +G_END_DECLS + +#endif /* __GVC_MIXER_CARD_PRIVATE_H */ diff --git a/src/gvc/gvc-mixer-card.c b/src/gvc/gvc-mixer-card.c new file mode 100644 index 000000000..f198f1b1c --- /dev/null +++ b/src/gvc/gvc-mixer-card.c @@ -0,0 +1,506 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*- + * + * Copyright (C) 2008 William Jon McCann + * Copyright (C) 2009 Bastien Nocera + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + * + */ + +#include "config.h" + +#include +#include +#include + +#include +#include + +#include + +#include "gvc-mixer-card.h" +#include "gvc-mixer-card-private.h" + +#define GVC_MIXER_CARD_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), GVC_TYPE_MIXER_CARD, GvcMixerCardPrivate)) + +static guint32 card_serial = 1; + +struct GvcMixerCardPrivate +{ + pa_context *pa_context; + guint id; + guint index; + char *name; + char *icon_name; + char *profile; + char *target_profile; + char *human_profile; + GList *profiles; + pa_operation *profile_op; +}; + +enum +{ + PROP_0, + PROP_ID, + PROP_PA_CONTEXT, + PROP_INDEX, + PROP_NAME, + PROP_ICON_NAME, + PROP_PROFILE, + PROP_HUMAN_PROFILE, +}; + +static void gvc_mixer_card_class_init (GvcMixerCardClass *klass); +static void gvc_mixer_card_init (GvcMixerCard *mixer_card); +static void gvc_mixer_card_finalize (GObject *object); + +G_DEFINE_TYPE (GvcMixerCard, gvc_mixer_card, G_TYPE_OBJECT) + +static guint32 +get_next_card_serial (void) +{ + guint32 serial; + + serial = card_serial++; + + if ((gint32)card_serial < 0) { + card_serial = 1; + } + + return serial; +} + +pa_context * +gvc_mixer_card_get_pa_context (GvcMixerCard *card) +{ + g_return_val_if_fail (GVC_IS_MIXER_CARD (card), 0); + return card->priv->pa_context; +} + +guint +gvc_mixer_card_get_index (GvcMixerCard *card) +{ + g_return_val_if_fail (GVC_IS_MIXER_CARD (card), 0); + return card->priv->index; +} + +guint +gvc_mixer_card_get_id (GvcMixerCard *card) +{ + g_return_val_if_fail (GVC_IS_MIXER_CARD (card), 0); + return card->priv->id; +} + +const char * +gvc_mixer_card_get_name (GvcMixerCard *card) +{ + g_return_val_if_fail (GVC_IS_MIXER_CARD (card), NULL); + return card->priv->name; +} + +gboolean +gvc_mixer_card_set_name (GvcMixerCard *card, + const char *name) +{ + g_return_val_if_fail (GVC_IS_MIXER_CARD (card), FALSE); + + g_free (card->priv->name); + card->priv->name = g_strdup (name); + g_object_notify (G_OBJECT (card), "name"); + + return TRUE; +} + +const char * +gvc_mixer_card_get_icon_name (GvcMixerCard *card) +{ + g_return_val_if_fail (GVC_IS_MIXER_CARD (card), NULL); + return card->priv->icon_name; +} + +gboolean +gvc_mixer_card_set_icon_name (GvcMixerCard *card, + const char *icon_name) +{ + g_return_val_if_fail (GVC_IS_MIXER_CARD (card), FALSE); + + g_free (card->priv->icon_name); + card->priv->icon_name = g_strdup (icon_name); + g_object_notify (G_OBJECT (card), "icon-name"); + + return TRUE; +} + +/** + * gvc_mixer_card_get_profile: (skip) + * + * @card: + * + * Returns: + */ +GvcMixerCardProfile * +gvc_mixer_card_get_profile (GvcMixerCard *card) +{ + GList *l; + + g_return_val_if_fail (GVC_IS_MIXER_CARD (card), NULL); + g_return_val_if_fail (card->priv->profiles != NULL, FALSE); + + for (l = card->priv->profiles; l != NULL; l = l->next) { + GvcMixerCardProfile *p = l->data; + if (g_str_equal (card->priv->profile, p->profile)) { + return p; + } + } + + g_assert_not_reached (); + + return NULL; +} + +gboolean +gvc_mixer_card_set_profile (GvcMixerCard *card, + const char *profile) +{ + GList *l; + + g_return_val_if_fail (GVC_IS_MIXER_CARD (card), FALSE); + g_return_val_if_fail (card->priv->profiles != NULL, FALSE); + + g_free (card->priv->profile); + card->priv->profile = g_strdup (profile); + + g_free (card->priv->human_profile); + card->priv->human_profile = NULL; + + for (l = card->priv->profiles; l != NULL; l = l->next) { + GvcMixerCardProfile *p = l->data; + if (g_str_equal (card->priv->profile, p->profile)) { + card->priv->human_profile = g_strdup (p->human_profile); + break; + } + } + + g_object_notify (G_OBJECT (card), "profile"); + + return TRUE; +} + +static void +_pa_context_set_card_profile_by_index_cb (pa_context *context, + int success, + void *userdata) +{ + GvcMixerCard *card = GVC_MIXER_CARD (userdata); + + g_assert (card->priv->target_profile); + + if (success > 0) { + gvc_mixer_card_set_profile (card, card->priv->target_profile); + } else { + g_debug ("Failed to switch profile on '%s' from '%s' to '%s'", + card->priv->name, + card->priv->profile, + card->priv->target_profile); + } + g_free (card->priv->target_profile); + card->priv->target_profile = NULL; + + pa_operation_unref (card->priv->profile_op); + card->priv->profile_op = NULL; +} + +gboolean +gvc_mixer_card_change_profile (GvcMixerCard *card, + const char *profile) +{ + g_return_val_if_fail (GVC_IS_MIXER_CARD (card), FALSE); + g_return_val_if_fail (card->priv->profiles != NULL, FALSE); + + /* Same profile, or already requested? */ + if (g_strcmp0 (card->priv->profile, profile) == 0) + return TRUE; + if (g_strcmp0 (profile, card->priv->target_profile) == 0) + return TRUE; + if (card->priv->profile_op != NULL) { + pa_operation_cancel (card->priv->profile_op); + pa_operation_unref (card->priv->profile_op); + card->priv->profile_op = NULL; + } + + if (card->priv->profile != NULL) { + g_free (card->priv->target_profile); + card->priv->target_profile = g_strdup (profile); + + card->priv->profile_op = pa_context_set_card_profile_by_index (card->priv->pa_context, + card->priv->index, + card->priv->target_profile, + _pa_context_set_card_profile_by_index_cb, + card); + + if (card->priv->profile_op == NULL) { + g_warning ("pa_context_set_card_profile_by_index() failed"); + return FALSE; + } + } else { + g_assert (card->priv->human_profile == NULL); + card->priv->profile = g_strdup (profile); + } + + return TRUE; +} + +const GList * +gvc_mixer_card_get_profiles (GvcMixerCard *card) +{ + g_return_val_if_fail (GVC_IS_MIXER_CARD (card), FALSE); + return card->priv->profiles; +} + +static int +sort_profiles (GvcMixerCardProfile *a, + GvcMixerCardProfile *b) +{ + if (a->priority == b->priority) + return 0; + if (a->priority > b->priority) + return 1; + return -1; +} + +gboolean +gvc_mixer_card_set_profiles (GvcMixerCard *card, + GList *profiles) +{ + g_return_val_if_fail (GVC_IS_MIXER_CARD (card), FALSE); + g_return_val_if_fail (card->priv->profiles == NULL, FALSE); + + card->priv->profiles = g_list_sort (profiles, (GCompareFunc) sort_profiles); + + return TRUE; +} + +static void +gvc_mixer_card_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + GvcMixerCard *self = GVC_MIXER_CARD (object); + + switch (prop_id) { + case PROP_PA_CONTEXT: + self->priv->pa_context = g_value_get_pointer (value); + break; + case PROP_INDEX: + self->priv->index = g_value_get_ulong (value); + break; + case PROP_ID: + self->priv->id = g_value_get_ulong (value); + break; + case PROP_NAME: + gvc_mixer_card_set_name (self, g_value_get_string (value)); + break; + case PROP_ICON_NAME: + gvc_mixer_card_set_icon_name (self, g_value_get_string (value)); + break; + case PROP_PROFILE: + gvc_mixer_card_set_profile (self, g_value_get_string (value)); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +gvc_mixer_card_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + GvcMixerCard *self = GVC_MIXER_CARD (object); + + switch (prop_id) { + case PROP_PA_CONTEXT: + g_value_set_pointer (value, self->priv->pa_context); + break; + case PROP_INDEX: + g_value_set_ulong (value, self->priv->index); + break; + case PROP_ID: + g_value_set_ulong (value, self->priv->id); + break; + case PROP_NAME: + g_value_set_string (value, self->priv->name); + break; + case PROP_ICON_NAME: + g_value_set_string (value, self->priv->icon_name); + break; + case PROP_PROFILE: + g_value_set_string (value, self->priv->profile); + break; + case PROP_HUMAN_PROFILE: + g_value_set_string (value, self->priv->human_profile); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static GObject * +gvc_mixer_card_constructor (GType type, + guint n_construct_properties, + GObjectConstructParam *construct_params) +{ + GObject *object; + GvcMixerCard *self; + + object = G_OBJECT_CLASS (gvc_mixer_card_parent_class)->constructor (type, n_construct_properties, construct_params); + + self = GVC_MIXER_CARD (object); + + self->priv->id = get_next_card_serial (); + + return object; +} + +static void +gvc_mixer_card_class_init (GvcMixerCardClass *klass) +{ + GObjectClass *gobject_class = G_OBJECT_CLASS (klass); + + gobject_class->constructor = gvc_mixer_card_constructor; + gobject_class->finalize = gvc_mixer_card_finalize; + + gobject_class->set_property = gvc_mixer_card_set_property; + gobject_class->get_property = gvc_mixer_card_get_property; + + g_object_class_install_property (gobject_class, + PROP_INDEX, + g_param_spec_ulong ("index", + "Index", + "The index for this card", + 0, G_MAXULONG, 0, + G_PARAM_READWRITE|G_PARAM_CONSTRUCT_ONLY)); + g_object_class_install_property (gobject_class, + PROP_ID, + g_param_spec_ulong ("id", + "id", + "The id for this card", + 0, G_MAXULONG, 0, + G_PARAM_READWRITE|G_PARAM_CONSTRUCT_ONLY)); + g_object_class_install_property (gobject_class, + PROP_PA_CONTEXT, + g_param_spec_pointer ("pa-context", + "PulseAudio context", + "The PulseAudio context for this card", + G_PARAM_READWRITE|G_PARAM_CONSTRUCT_ONLY)); + g_object_class_install_property (gobject_class, + PROP_NAME, + g_param_spec_string ("name", + "Name", + "Name to display for this card", + NULL, + G_PARAM_READWRITE|G_PARAM_CONSTRUCT)); + g_object_class_install_property (gobject_class, + PROP_ICON_NAME, + g_param_spec_string ("icon-name", + "Icon Name", + "Name of icon to display for this card", + NULL, + G_PARAM_READWRITE|G_PARAM_CONSTRUCT)); + g_object_class_install_property (gobject_class, + PROP_PROFILE, + g_param_spec_string ("profile", + "Profile", + "Name of current profile for this card", + NULL, + G_PARAM_READWRITE)); + g_object_class_install_property (gobject_class, + PROP_HUMAN_PROFILE, + g_param_spec_string ("human-profile", + "Profile (Human readable)", + "Name of current profile for this card in human readable form", + NULL, + G_PARAM_READABLE)); + + g_type_class_add_private (klass, sizeof (GvcMixerCardPrivate)); +} + +static void +gvc_mixer_card_init (GvcMixerCard *card) +{ + card->priv = GVC_MIXER_CARD_GET_PRIVATE (card); +} + +GvcMixerCard * +gvc_mixer_card_new (pa_context *context, + guint index) +{ + GObject *object; + + object = g_object_new (GVC_TYPE_MIXER_CARD, + "index", index, + "pa-context", context, + NULL); + return GVC_MIXER_CARD (object); +} + +static void +free_profile (GvcMixerCardProfile *p) +{ + g_free (p->profile); + g_free (p->human_profile); + g_free (p->status); + g_free (p); +} + +static void +gvc_mixer_card_finalize (GObject *object) +{ + GvcMixerCard *mixer_card; + + g_return_if_fail (object != NULL); + g_return_if_fail (GVC_IS_MIXER_CARD (object)); + + mixer_card = GVC_MIXER_CARD (object); + + g_return_if_fail (mixer_card->priv != NULL); + + g_free (mixer_card->priv->name); + mixer_card->priv->name = NULL; + + g_free (mixer_card->priv->icon_name); + mixer_card->priv->icon_name = NULL; + + g_free (mixer_card->priv->target_profile); + mixer_card->priv->target_profile = NULL; + + g_free (mixer_card->priv->profile); + mixer_card->priv->profile = NULL; + + g_free (mixer_card->priv->human_profile); + mixer_card->priv->human_profile = NULL; + + g_list_foreach (mixer_card->priv->profiles, (GFunc) free_profile, NULL); + g_list_free (mixer_card->priv->profiles); + mixer_card->priv->profiles = NULL; + + G_OBJECT_CLASS (gvc_mixer_card_parent_class)->finalize (object); +} + diff --git a/src/gvc/gvc-mixer-card.h b/src/gvc/gvc-mixer-card.h new file mode 100644 index 000000000..5a3a7bc45 --- /dev/null +++ b/src/gvc/gvc-mixer-card.h @@ -0,0 +1,83 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*- + * + * Copyright (C) 2008-2009 Red Hat, Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + * + */ + +#ifndef __GVC_MIXER_CARD_H +#define __GVC_MIXER_CARD_H + +#include + +G_BEGIN_DECLS + +#define GVC_TYPE_MIXER_CARD (gvc_mixer_card_get_type ()) +#define GVC_MIXER_CARD(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), GVC_TYPE_MIXER_CARD, GvcMixerCard)) +#define GVC_MIXER_CARD_CLASS(k) (G_TYPE_CHECK_CLASS_CAST((k), GVC_TYPE_MIXER_CARD, GvcMixerCardClass)) +#define GVC_IS_MIXER_CARD(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), GVC_TYPE_MIXER_CARD)) +#define GVC_IS_MIXER_CARD_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE ((k), GVC_TYPE_MIXER_CARD)) +#define GVC_MIXER_CARD_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), GVC_TYPE_MIXER_CARD, GvcMixerCardClass)) + +typedef struct GvcMixerCardPrivate GvcMixerCardPrivate; + +typedef struct +{ + GObject parent; + GvcMixerCardPrivate *priv; +} GvcMixerCard; + +typedef struct +{ + GObjectClass parent_class; + + /* vtable */ +} GvcMixerCardClass; + +typedef struct +{ + char *profile; + char *human_profile; + char *status; + guint priority; + guint n_sinks, n_sources; +} GvcMixerCardProfile; + +GType gvc_mixer_card_get_type (void); + +guint gvc_mixer_card_get_id (GvcMixerCard *card); +guint gvc_mixer_card_get_index (GvcMixerCard *card); +const char * gvc_mixer_card_get_name (GvcMixerCard *card); +const char * gvc_mixer_card_get_icon_name (GvcMixerCard *card); +GvcMixerCardProfile * gvc_mixer_card_get_profile (GvcMixerCard *card); +const GList * gvc_mixer_card_get_profiles (GvcMixerCard *card); + +gboolean gvc_mixer_card_change_profile (GvcMixerCard *card, + const char *profile); + +/* private */ +gboolean gvc_mixer_card_set_name (GvcMixerCard *card, + const char *name); +gboolean gvc_mixer_card_set_icon_name (GvcMixerCard *card, + const char *name); +gboolean gvc_mixer_card_set_profile (GvcMixerCard *card, + const char *profile); +gboolean gvc_mixer_card_set_profiles (GvcMixerCard *card, + GList *profiles); + +G_END_DECLS + +#endif /* __GVC_MIXER_CARD_H */ diff --git a/src/gvc/gvc-mixer-control-private.h b/src/gvc/gvc-mixer-control-private.h new file mode 100644 index 000000000..ac79975aa --- /dev/null +++ b/src/gvc/gvc-mixer-control-private.h @@ -0,0 +1,35 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*- + * + * Copyright (C) 2008 Red Hat, Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + * + */ + +#ifndef __GVC_MIXER_CONTROL_PRIVATE_H +#define __GVC_MIXER_CONTROL_PRIVATE_H + +#include +#include +#include "gvc-mixer-stream.h" +#include "gvc-mixer-card.h" + +G_BEGIN_DECLS + +pa_context * gvc_mixer_control_get_pa_context (GvcMixerControl *control); + +G_END_DECLS + +#endif /* __GVC_MIXER_CONTROL_PRIVATE_H */ diff --git a/src/gvc/gvc-mixer-control.c b/src/gvc/gvc-mixer-control.c new file mode 100644 index 000000000..d8d80f6af --- /dev/null +++ b/src/gvc/gvc-mixer-control.c @@ -0,0 +1,2232 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*- + * + * Copyright (C) 2006-2008 Lennart Poettering + * Copyright (C) 2008 Sjoerd Simons + * Copyright (C) 2008 William Jon McCann + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + * + */ + +#include "config.h" + +#include +#include +#include + +#include +#include + +#include +#include +#include + +#include "gvc-mixer-control.h" +#include "gvc-mixer-sink.h" +#include "gvc-mixer-source.h" +#include "gvc-mixer-sink-input.h" +#include "gvc-mixer-source-output.h" +#include "gvc-mixer-event-role.h" +#include "gvc-mixer-card.h" +#include "gvc-mixer-card-private.h" +#include "gvc-channel-map-private.h" +#include "gvc-mixer-control-private.h" + +#define GVC_MIXER_CONTROL_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), GVC_TYPE_MIXER_CONTROL, GvcMixerControlPrivate)) + +#define RECONNECT_DELAY 5 + +enum { + PROP_0, + PROP_NAME +}; + +struct GvcMixerControlPrivate +{ + pa_glib_mainloop *pa_mainloop; + pa_mainloop_api *pa_api; + pa_context *pa_context; + int n_outstanding; + guint reconnect_id; + char *name; + + gboolean default_sink_is_set; + guint default_sink_id; + char *default_sink_name; + gboolean default_source_is_set; + guint default_source_id; + char *default_source_name; + + gboolean event_sink_input_is_set; + guint event_sink_input_id; + + GHashTable *all_streams; + GHashTable *sinks; /* fixed outputs */ + GHashTable *sources; /* fixed inputs */ + GHashTable *sink_inputs; /* routable output streams */ + GHashTable *source_outputs; /* routable input streams */ + GHashTable *clients; + GHashTable *cards; + + GvcMixerStream *new_default_stream; /* new default stream, used in gvc_mixer_control_set_default_sink () */ +}; + +enum { + CONNECTING, + READY, + STREAM_ADDED, + STREAM_REMOVED, + CARD_ADDED, + CARD_REMOVED, + DEFAULT_SINK_CHANGED, + DEFAULT_SOURCE_CHANGED, + LAST_SIGNAL +}; + +static guint signals [LAST_SIGNAL] = { 0, }; + +static void gvc_mixer_control_class_init (GvcMixerControlClass *klass); +static void gvc_mixer_control_init (GvcMixerControl *mixer_control); +static void gvc_mixer_control_finalize (GObject *object); + +G_DEFINE_TYPE (GvcMixerControl, gvc_mixer_control, G_TYPE_OBJECT) + +pa_context * +gvc_mixer_control_get_pa_context (GvcMixerControl *control) +{ + g_return_val_if_fail (GVC_IS_MIXER_CONTROL (control), NULL); + return control->priv->pa_context; +} + +/** + * gvc_mixer_control_get_event_sink_input: + * + * @control: + * + * Returns: (transfer none): + */ +GvcMixerStream * +gvc_mixer_control_get_event_sink_input (GvcMixerControl *control) +{ + GvcMixerStream *stream; + + g_return_val_if_fail (GVC_IS_MIXER_CONTROL (control), NULL); + + stream = g_hash_table_lookup (control->priv->all_streams, + GUINT_TO_POINTER (control->priv->event_sink_input_id)); + + return stream; +} + +static void +gvc_mixer_control_stream_restore_cb (pa_context *c, + const pa_ext_stream_restore_info *info, + int eol, + void *userdata) +{ + pa_operation *o; + GvcMixerControl *control = (GvcMixerControl *) userdata; + pa_ext_stream_restore_info new_info; + + if (eol || control->priv->new_default_stream == NULL) + return; + + new_info.name = info->name; + new_info.channel_map = info->channel_map; + new_info.volume = info->volume; + new_info.mute = info->mute; + + new_info.device = gvc_mixer_stream_get_name (control->priv->new_default_stream); + + o = pa_ext_stream_restore_write (control->priv->pa_context, + PA_UPDATE_REPLACE, + &new_info, 1, + TRUE, NULL, NULL); + + if (o == NULL) { + g_warning ("pa_ext_stream_restore_write() failed: %s", + pa_strerror (pa_context_errno (control->priv->pa_context))); + return; + } + + g_debug ("Changed default device for %s to %s", info->name, info->device); + + pa_operation_unref (o); +} + +gboolean +gvc_mixer_control_set_default_sink (GvcMixerControl *control, + GvcMixerStream *stream) +{ + pa_operation *o; + + g_return_val_if_fail (GVC_IS_MIXER_CONTROL (control), FALSE); + g_return_val_if_fail (GVC_IS_MIXER_STREAM (stream), FALSE); + + o = pa_context_set_default_sink (control->priv->pa_context, + gvc_mixer_stream_get_name (stream), + NULL, + NULL); + if (o == NULL) { + g_warning ("pa_context_set_default_sink() failed: %s", + pa_strerror (pa_context_errno (control->priv->pa_context))); + return FALSE; + } + + pa_operation_unref (o); + + control->priv->new_default_stream = stream; + g_object_add_weak_pointer (G_OBJECT (stream), (gpointer *) &control->priv->new_default_stream); + + o = pa_ext_stream_restore_read (control->priv->pa_context, + gvc_mixer_control_stream_restore_cb, + control); + + if (o == NULL) { + g_warning ("pa_ext_stream_restore_read() failed: %s", + pa_strerror (pa_context_errno (control->priv->pa_context))); + return FALSE; + } + + pa_operation_unref (o); + + return TRUE; +} + +gboolean +gvc_mixer_control_set_default_source (GvcMixerControl *control, + GvcMixerStream *stream) +{ + pa_operation *o; + + g_return_val_if_fail (GVC_IS_MIXER_CONTROL (control), FALSE); + g_return_val_if_fail (GVC_IS_MIXER_STREAM (stream), FALSE); + + o = pa_context_set_default_source (control->priv->pa_context, + gvc_mixer_stream_get_name (stream), + NULL, + NULL); + if (o == NULL) { + g_warning ("pa_context_set_default_source() failed"); + return FALSE; + } + + pa_operation_unref (o); + + return TRUE; +} + +/** + * gvc_mixer_control_get_default_sink: + * + * @control: + * + * Returns: (transfer none): + */ +GvcMixerStream * +gvc_mixer_control_get_default_sink (GvcMixerControl *control) +{ + GvcMixerStream *stream; + + g_return_val_if_fail (GVC_IS_MIXER_CONTROL (control), NULL); + + if (control->priv->default_sink_is_set) { + stream = g_hash_table_lookup (control->priv->all_streams, + GUINT_TO_POINTER (control->priv->default_sink_id)); + } else { + stream = NULL; + } + + return stream; +} + +/** + * gvc_mixer_control_get_default_source: + * + * @control: + * + * Returns: (transfer none): + */ +GvcMixerStream * +gvc_mixer_control_get_default_source (GvcMixerControl *control) +{ + GvcMixerStream *stream; + + g_return_val_if_fail (GVC_IS_MIXER_CONTROL (control), NULL); + + if (control->priv->default_source_is_set) { + stream = g_hash_table_lookup (control->priv->all_streams, + GUINT_TO_POINTER (control->priv->default_source_id)); + } else { + stream = NULL; + } + + return stream; +} + +static gpointer +gvc_mixer_control_lookup_id (GHashTable *hash_table, + guint id) +{ + return g_hash_table_lookup (hash_table, + GUINT_TO_POINTER (id)); +} + +/** + * gvc_mixer_control_lookup_stream_id: + * + * @control: + * @id: + * + * Returns: (transfer none): + */ +GvcMixerStream * +gvc_mixer_control_lookup_stream_id (GvcMixerControl *control, + guint id) +{ + g_return_val_if_fail (GVC_IS_MIXER_CONTROL (control), NULL); + + return gvc_mixer_control_lookup_id (control->priv->all_streams, id); +} + +/** + * gvc_mixer_control_lookup_card_id: + * + * @control: + * @id: + * + * Returns: (transfer none): + */ +GvcMixerCard * +gvc_mixer_control_lookup_card_id (GvcMixerControl *control, + guint id) +{ + g_return_val_if_fail (GVC_IS_MIXER_CONTROL (control), NULL); + + return gvc_mixer_control_lookup_id (control->priv->cards, id); +} + +static void +listify_hash_values_hfunc (gpointer key, + gpointer value, + gpointer user_data) +{ + GSList **list = user_data; + + *list = g_slist_prepend (*list, value); +} + +static int +gvc_name_collate (const char *namea, + const char *nameb) +{ + if (nameb == NULL && namea == NULL) + return 0; + if (nameb == NULL) + return 1; + if (namea == NULL) + return -1; + + return g_utf8_collate (namea, nameb); +} + +static int +gvc_card_collate (GvcMixerCard *a, + GvcMixerCard *b) +{ + const char *namea; + const char *nameb; + + g_return_val_if_fail (a == NULL || GVC_IS_MIXER_CARD (a), 0); + g_return_val_if_fail (b == NULL || GVC_IS_MIXER_CARD (b), 0); + + namea = gvc_mixer_card_get_name (a); + nameb = gvc_mixer_card_get_name (b); + + return gvc_name_collate (namea, nameb); +} + +/** + * gvc_mixer_control_get_cards: + * + * @control: + * + * Returns: (transfer container) (element-type Gvc.MixerCard): + */ +GSList * +gvc_mixer_control_get_cards (GvcMixerControl *control) +{ + GSList *retval; + + g_return_val_if_fail (GVC_IS_MIXER_CONTROL (control), NULL); + + retval = NULL; + g_hash_table_foreach (control->priv->cards, + listify_hash_values_hfunc, + &retval); + return g_slist_sort (retval, (GCompareFunc) gvc_card_collate); +} + +static int +gvc_stream_collate (GvcMixerStream *a, + GvcMixerStream *b) +{ + const char *namea; + const char *nameb; + + g_return_val_if_fail (a == NULL || GVC_IS_MIXER_STREAM (a), 0); + g_return_val_if_fail (b == NULL || GVC_IS_MIXER_STREAM (b), 0); + + namea = gvc_mixer_stream_get_name (a); + nameb = gvc_mixer_stream_get_name (b); + + return gvc_name_collate (namea, nameb); +} + +/** + * gvc_mixer_control_get_streams: + * + * @control: + * + * Returns: (transfer container) (element-type Gvc.MixerStream): + */ +GSList * +gvc_mixer_control_get_streams (GvcMixerControl *control) +{ + GSList *retval; + + g_return_val_if_fail (GVC_IS_MIXER_CONTROL (control), NULL); + + retval = NULL; + g_hash_table_foreach (control->priv->all_streams, + listify_hash_values_hfunc, + &retval); + return g_slist_sort (retval, (GCompareFunc) gvc_stream_collate); +} + +/** + * gvc_mixer_control_get_sinks: + * + * @control: + * + * Returns: (transfer container) (element-type Gvc.MixerSink): + */ +GSList * +gvc_mixer_control_get_sinks (GvcMixerControl *control) +{ + GSList *retval; + + g_return_val_if_fail (GVC_IS_MIXER_CONTROL (control), NULL); + + retval = NULL; + g_hash_table_foreach (control->priv->sinks, + listify_hash_values_hfunc, + &retval); + return g_slist_sort (retval, (GCompareFunc) gvc_stream_collate); +} + +/** + * gvc_mixer_control_get_sources: + * + * @control: + * + * Returns: (transfer container) (element-type Gvc.MixerSource): + */ +GSList * +gvc_mixer_control_get_sources (GvcMixerControl *control) +{ + GSList *retval; + + g_return_val_if_fail (GVC_IS_MIXER_CONTROL (control), NULL); + + retval = NULL; + g_hash_table_foreach (control->priv->sources, + listify_hash_values_hfunc, + &retval); + return g_slist_sort (retval, (GCompareFunc) gvc_stream_collate); +} + +/** + * gvc_mixer_control_get_sink_inputs: + * + * @control: + * + * Returns: (transfer container) (element-type Gvc.MixerSinkInput): + */ +GSList * +gvc_mixer_control_get_sink_inputs (GvcMixerControl *control) +{ + GSList *retval; + + g_return_val_if_fail (GVC_IS_MIXER_CONTROL (control), NULL); + + retval = NULL; + g_hash_table_foreach (control->priv->sink_inputs, + listify_hash_values_hfunc, + &retval); + return g_slist_sort (retval, (GCompareFunc) gvc_stream_collate); +} + +/** + * gvc_mixer_control_get_source_outputs: + * + * @control: + * + * Returns: (transfer container) (element-type Gvc.MixerSourceOutput): + */ +GSList * +gvc_mixer_control_get_source_outputs (GvcMixerControl *control) +{ + GSList *retval; + + g_return_val_if_fail (GVC_IS_MIXER_CONTROL (control), NULL); + + retval = NULL; + g_hash_table_foreach (control->priv->source_outputs, + listify_hash_values_hfunc, + &retval); + return g_slist_sort (retval, (GCompareFunc) gvc_stream_collate); +} + +static void +dec_outstanding (GvcMixerControl *control) +{ + if (control->priv->n_outstanding <= 0) { + return; + } + + if (--control->priv->n_outstanding <= 0) { + g_signal_emit (G_OBJECT (control), signals[READY], 0); + } +} + +gboolean +gvc_mixer_control_is_ready (GvcMixerControl *control) +{ + g_return_val_if_fail (GVC_IS_MIXER_CONTROL (control), FALSE); + + return (control->priv->n_outstanding == 0); +} + + +static void +_set_default_source (GvcMixerControl *control, + GvcMixerStream *stream) +{ + guint new_id; + + if (stream == NULL) { + control->priv->default_source_id = 0; + control->priv->default_source_is_set = FALSE; + g_signal_emit (control, + signals[DEFAULT_SOURCE_CHANGED], + 0, + PA_INVALID_INDEX); + return; + } + + new_id = gvc_mixer_stream_get_id (stream); + + if (control->priv->default_source_id != new_id) { + control->priv->default_source_id = new_id; + control->priv->default_source_is_set = TRUE; + g_signal_emit (control, + signals[DEFAULT_SOURCE_CHANGED], + 0, + new_id); + } +} + +static void +_set_default_sink (GvcMixerControl *control, + GvcMixerStream *stream) +{ + guint new_id; + + if (stream == NULL) { + /* Don't tell front-ends about an unset default + * sink if it's already unset */ + if (control->priv->default_sink_is_set == FALSE) + return; + control->priv->default_sink_id = 0; + control->priv->default_sink_is_set = FALSE; + g_signal_emit (control, + signals[DEFAULT_SINK_CHANGED], + 0, + PA_INVALID_INDEX); + return; + } + + new_id = gvc_mixer_stream_get_id (stream); + + if (control->priv->default_sink_id != new_id) { + control->priv->default_sink_id = new_id; + control->priv->default_sink_is_set = TRUE; + g_signal_emit (control, + signals[DEFAULT_SINK_CHANGED], + 0, + new_id); + } +} + +static gboolean +_stream_has_name (gpointer key, + GvcMixerStream *stream, + const char *name) +{ + const char *t_name; + + t_name = gvc_mixer_stream_get_name (stream); + + if (t_name != NULL + && name != NULL + && strcmp (t_name, name) == 0) { + return TRUE; + } + + return FALSE; +} + +static GvcMixerStream * +find_stream_for_name (GvcMixerControl *control, + const char *name) +{ + GvcMixerStream *stream; + + stream = g_hash_table_find (control->priv->all_streams, + (GHRFunc)_stream_has_name, + (char *)name); + return stream; +} + +static void +update_default_source_from_name (GvcMixerControl *control, + const char *name) +{ + gboolean changed; + + if ((control->priv->default_source_name == NULL + && name != NULL) + || (control->priv->default_source_name != NULL + && name == NULL) + || strcmp (control->priv->default_source_name, name) != 0) { + changed = TRUE; + } + + if (changed) { + GvcMixerStream *stream; + + g_free (control->priv->default_source_name); + control->priv->default_source_name = g_strdup (name); + + stream = find_stream_for_name (control, name); + _set_default_source (control, stream); + } +} + +static void +update_default_sink_from_name (GvcMixerControl *control, + const char *name) +{ + gboolean changed; + + if ((control->priv->default_sink_name == NULL + && name != NULL) + || (control->priv->default_sink_name != NULL + && name == NULL) + || strcmp (control->priv->default_sink_name, name) != 0) { + changed = TRUE; + } + + if (changed) { + GvcMixerStream *stream; + g_free (control->priv->default_sink_name); + control->priv->default_sink_name = g_strdup (name); + + stream = find_stream_for_name (control, name); + _set_default_sink (control, stream); + } +} + +static void +update_server (GvcMixerControl *control, + const pa_server_info *info) +{ + if (info->default_source_name != NULL) { + update_default_source_from_name (control, info->default_source_name); + } + if (info->default_sink_name != NULL) { + update_default_sink_from_name (control, info->default_sink_name); + } +} + +static void +remove_stream (GvcMixerControl *control, + GvcMixerStream *stream) +{ + guint id; + + g_object_ref (stream); + + id = gvc_mixer_stream_get_id (stream); + + if (id == control->priv->default_sink_id) { + _set_default_sink (control, NULL); + } else if (id == control->priv->default_source_id) { + _set_default_source (control, NULL); + } + + g_hash_table_remove (control->priv->all_streams, + GUINT_TO_POINTER (id)); + g_signal_emit (G_OBJECT (control), + signals[STREAM_REMOVED], + 0, + gvc_mixer_stream_get_id (stream)); + g_object_unref (stream); +} + +static void +add_stream (GvcMixerControl *control, + GvcMixerStream *stream) +{ + g_hash_table_insert (control->priv->all_streams, + GUINT_TO_POINTER (gvc_mixer_stream_get_id (stream)), + stream); + g_signal_emit (G_OBJECT (control), + signals[STREAM_ADDED], + 0, + gvc_mixer_stream_get_id (stream)); +} + +static void +update_sink (GvcMixerControl *control, + const pa_sink_info *info) +{ + GvcMixerStream *stream; + gboolean is_new; + pa_volume_t max_volume; + GvcChannelMap *map; + char map_buff[PA_CHANNEL_MAP_SNPRINT_MAX]; + + pa_channel_map_snprint (map_buff, PA_CHANNEL_MAP_SNPRINT_MAX, &info->channel_map); +#if 1 + g_debug ("Updating sink: index=%u name='%s' description='%s' map='%s'", + info->index, + info->name, + info->description, + map_buff); +#endif + + map = NULL; + is_new = FALSE; + stream = g_hash_table_lookup (control->priv->sinks, + GUINT_TO_POINTER (info->index)); + if (stream == NULL) { +#if PA_MICRO > 15 + GList *list = NULL; + guint i; +#endif /* PA_MICRO > 15 */ + + map = gvc_channel_map_new_from_pa_channel_map (&info->channel_map); + stream = gvc_mixer_sink_new (control->priv->pa_context, + info->index, + map); +#if PA_MICRO > 15 + for (i = 0; i < info->n_ports; i++) { + GvcMixerStreamPort *port; + + port = g_new0 (GvcMixerStreamPort, 1); + port->port = g_strdup (info->ports[i]->name); + port->human_port = g_strdup (info->ports[i]->description); + port->priority = info->ports[i]->priority; + list = g_list_prepend (list, port); + } + gvc_mixer_stream_set_ports (stream, list); +#endif /* PA_MICRO > 15 */ + g_object_unref (map); + is_new = TRUE; + } else if (gvc_mixer_stream_is_running (stream)) { + /* Ignore events if volume changes are outstanding */ + g_debug ("Ignoring event, volume changes are outstanding"); + return; + } + + max_volume = pa_cvolume_max (&info->volume); + gvc_mixer_stream_set_name (stream, info->name); + gvc_mixer_stream_set_card_index (stream, info->card); + gvc_mixer_stream_set_description (stream, info->description); + gvc_mixer_stream_set_icon_name (stream, "audio-card"); + gvc_mixer_stream_set_volume (stream, (guint)max_volume); + gvc_mixer_stream_set_is_muted (stream, info->mute); + gvc_mixer_stream_set_can_decibel (stream, !!(info->flags & PA_SINK_DECIBEL_VOLUME)); + gvc_mixer_stream_set_base_volume (stream, (guint32) info->base_volume); +#if PA_MICRO > 15 + if (info->active_port != NULL) + gvc_mixer_stream_set_port (stream, info->active_port->name); +#endif /* PA_MICRO > 15 */ + + if (is_new) { + g_hash_table_insert (control->priv->sinks, + GUINT_TO_POINTER (info->index), + g_object_ref (stream)); + add_stream (control, stream); + } + + if (control->priv->default_sink_name != NULL + && info->name != NULL + && strcmp (control->priv->default_sink_name, info->name) == 0) { + _set_default_sink (control, stream); + } + + if (map == NULL) + map = (GvcChannelMap *) gvc_mixer_stream_get_channel_map (stream); + gvc_channel_map_volume_changed (map, &info->volume, FALSE); +} + +static void +update_source (GvcMixerControl *control, + const pa_source_info *info) +{ + GvcMixerStream *stream; + gboolean is_new; + pa_volume_t max_volume; + +#if 1 + g_debug ("Updating source: index=%u name='%s' description='%s'", + info->index, + info->name, + info->description); +#endif + + /* completely ignore monitors, they're not real sources */ + if (info->monitor_of_sink != PA_INVALID_INDEX) { + return; + } + + is_new = FALSE; + + stream = g_hash_table_lookup (control->priv->sources, + GUINT_TO_POINTER (info->index)); + if (stream == NULL) { +#if PA_MICRO > 15 + GList *list = NULL; + guint i; +#endif /* PA_MICRO > 15 */ + GvcChannelMap *map; + + map = gvc_channel_map_new_from_pa_channel_map (&info->channel_map); + stream = gvc_mixer_source_new (control->priv->pa_context, + info->index, + map); +#if PA_MICRO > 15 + for (i = 0; i < info->n_ports; i++) { + GvcMixerStreamPort *port; + + port = g_new0 (GvcMixerStreamPort, 1); + port->port = g_strdup (info->ports[i]->name); + port->human_port = g_strdup (info->ports[i]->description); + port->priority = info->ports[i]->priority; + list = g_list_prepend (list, port); + } + gvc_mixer_stream_set_ports (stream, list); +#endif /* PA_MICRO > 15 */ + + g_object_unref (map); + is_new = TRUE; + } else if (gvc_mixer_stream_is_running (stream)) { + /* Ignore events if volume changes are outstanding */ + g_debug ("Ignoring event, volume changes are outstanding"); + return; + } + + max_volume = pa_cvolume_max (&info->volume); + + gvc_mixer_stream_set_name (stream, info->name); + gvc_mixer_stream_set_card_index (stream, info->card); + gvc_mixer_stream_set_description (stream, info->description); + gvc_mixer_stream_set_icon_name (stream, "audio-input-microphone"); + gvc_mixer_stream_set_volume (stream, (guint)max_volume); + gvc_mixer_stream_set_is_muted (stream, info->mute); + gvc_mixer_stream_set_can_decibel (stream, !!(info->flags & PA_SOURCE_DECIBEL_VOLUME)); + gvc_mixer_stream_set_base_volume (stream, (guint32) info->base_volume); +#if PA_MICRO > 15 + if (info->active_port != NULL) + gvc_mixer_stream_set_port (stream, info->active_port->name); +#endif /* PA_MICRO > 15 */ + + if (is_new) { + g_hash_table_insert (control->priv->sources, + GUINT_TO_POINTER (info->index), + g_object_ref (stream)); + add_stream (control, stream); + } + + if (control->priv->default_source_name != NULL + && info->name != NULL + && strcmp (control->priv->default_source_name, info->name) == 0) { + _set_default_source (control, stream); + } +} + +static void +set_icon_name_from_proplist (GvcMixerStream *stream, + pa_proplist *l, + const char *default_icon_name) +{ + const char *t; + + if ((t = pa_proplist_gets (l, PA_PROP_MEDIA_ICON_NAME))) { + goto finish; + } + + if ((t = pa_proplist_gets (l, PA_PROP_WINDOW_ICON_NAME))) { + goto finish; + } + + if ((t = pa_proplist_gets (l, PA_PROP_APPLICATION_ICON_NAME))) { + goto finish; + } + + if ((t = pa_proplist_gets (l, PA_PROP_MEDIA_ROLE))) { + + if (strcmp (t, "video") == 0 || + strcmp (t, "phone") == 0) { + goto finish; + } + + if (strcmp (t, "music") == 0) { + t = "audio"; + goto finish; + } + + if (strcmp (t, "game") == 0) { + t = "applications-games"; + goto finish; + } + + if (strcmp (t, "event") == 0) { + t = "dialog-information"; + goto finish; + } + } + + t = default_icon_name; + + finish: + gvc_mixer_stream_set_icon_name (stream, t); +} + +static void +set_is_event_stream_from_proplist (GvcMixerStream *stream, + pa_proplist *l) +{ + const char *t; + gboolean is_event_stream; + + is_event_stream = FALSE; + + if ((t = pa_proplist_gets (l, PA_PROP_MEDIA_ROLE))) { + if (g_str_equal (t, "event")) + is_event_stream = TRUE; + } + + gvc_mixer_stream_set_is_event_stream (stream, is_event_stream); +} + +static void +set_application_id_from_proplist (GvcMixerStream *stream, + pa_proplist *l) +{ + const char *t; + + if ((t = pa_proplist_gets (l, PA_PROP_APPLICATION_ID))) { + gvc_mixer_stream_set_application_id (stream, t); + } +} + +static void +update_sink_input (GvcMixerControl *control, + const pa_sink_input_info *info) +{ + GvcMixerStream *stream; + gboolean is_new; + pa_volume_t max_volume; + const char *name; + +#if 0 + g_debug ("Updating sink input: index=%u name='%s' client=%u sink=%u", + info->index, + info->name, + info->client, + info->sink); +#endif + + is_new = FALSE; + + stream = g_hash_table_lookup (control->priv->sink_inputs, + GUINT_TO_POINTER (info->index)); + if (stream == NULL) { + GvcChannelMap *map; + map = gvc_channel_map_new_from_pa_channel_map (&info->channel_map); + stream = gvc_mixer_sink_input_new (control->priv->pa_context, + info->index, + map); + g_object_unref (map); + is_new = TRUE; + } else if (gvc_mixer_stream_is_running (stream)) { + /* Ignore events if volume changes are outstanding */ + g_debug ("Ignoring event, volume changes are outstanding"); + return; + } + + max_volume = pa_cvolume_max (&info->volume); + + name = (const char *)g_hash_table_lookup (control->priv->clients, + GUINT_TO_POINTER (info->client)); + gvc_mixer_stream_set_name (stream, name); + gvc_mixer_stream_set_description (stream, info->name); + + set_application_id_from_proplist (stream, info->proplist); + set_is_event_stream_from_proplist (stream, info->proplist); + set_icon_name_from_proplist (stream, info->proplist, "applications-multimedia"); + gvc_mixer_stream_set_volume (stream, (guint)max_volume); + gvc_mixer_stream_set_is_muted (stream, info->mute); + gvc_mixer_stream_set_is_virtual (stream, info->client == PA_INVALID_INDEX); + + if (is_new) { + g_hash_table_insert (control->priv->sink_inputs, + GUINT_TO_POINTER (info->index), + g_object_ref (stream)); + add_stream (control, stream); + } +} + +static void +update_source_output (GvcMixerControl *control, + const pa_source_output_info *info) +{ + GvcMixerStream *stream; + gboolean is_new; + const char *name; + +#if 1 + g_debug ("Updating source output: index=%u name='%s' client=%u source=%u", + info->index, + info->name, + info->client, + info->source); +#endif + + is_new = FALSE; + stream = g_hash_table_lookup (control->priv->source_outputs, + GUINT_TO_POINTER (info->index)); + if (stream == NULL) { + GvcChannelMap *map; + map = gvc_channel_map_new_from_pa_channel_map (&info->channel_map); + stream = gvc_mixer_source_output_new (control->priv->pa_context, + info->index, + map); + g_object_unref (map); + is_new = TRUE; + } + + name = (const char *)g_hash_table_lookup (control->priv->clients, + GUINT_TO_POINTER (info->client)); + + gvc_mixer_stream_set_name (stream, name); + gvc_mixer_stream_set_description (stream, info->name); + set_application_id_from_proplist (stream, info->proplist); + set_is_event_stream_from_proplist (stream, info->proplist); + set_icon_name_from_proplist (stream, info->proplist, "audio-input-microphone"); + + if (is_new) { + g_hash_table_insert (control->priv->source_outputs, + GUINT_TO_POINTER (info->index), + g_object_ref (stream)); + add_stream (control, stream); + } +} + +static void +update_client (GvcMixerControl *control, + const pa_client_info *info) +{ +#if 1 + g_debug ("Updating client: index=%u name='%s'", + info->index, + info->name); +#endif + g_hash_table_insert (control->priv->clients, + GUINT_TO_POINTER (info->index), + g_strdup (info->name)); +} + +static char * +card_num_streams_to_status (guint sinks, + guint sources) +{ + char *sinks_str; + char *sources_str; + char *ret; + + if (sinks == 0 && sources == 0) { + /* translators: + * The device has been disabled */ + return g_strdup (_("Disabled")); + } + if (sinks == 0) { + sinks_str = NULL; + } else { + /* translators: + * The number of sound outputs on a particular device */ + sinks_str = g_strdup_printf (ngettext ("%u Output", + "%u Outputs", + sinks), + sinks); + } + if (sources == 0) { + sources_str = NULL; + } else { + /* translators: + * The number of sound inputs on a particular device */ + sources_str = g_strdup_printf (ngettext ("%u Input", + "%u Inputs", + sources), + sources); + } + if (sources_str == NULL) + return sinks_str; + if (sinks_str == NULL) + return sources_str; + ret = g_strdup_printf ("%s / %s", sinks_str, sources_str); + g_free (sinks_str); + g_free (sources_str); + return ret; +} + +static void +update_card (GvcMixerControl *control, + const pa_card_info *info) +{ + GvcMixerCard *card; + gboolean is_new; +#if 1 + guint i; + const char *key; + void *state; + + g_debug ("Udpating card %s (index: %u driver: %s):", + info->name, info->index, info->driver); + + for (i = 0; i < info->n_profiles; i++) { + struct pa_card_profile_info pi = info->profiles[i]; + gboolean is_default; + + is_default = (g_strcmp0 (pi.name, info->active_profile->name) == 0); + g_debug ("\tProfile '%s': %d sources %d sinks%s", + pi.name, pi.n_sources, pi.n_sinks, + is_default ? " (Current)" : ""); + } + state = NULL; + key = pa_proplist_iterate (info->proplist, &state); + while (key != NULL) { + g_debug ("\tProperty: '%s' = '%s'", + key, pa_proplist_gets (info->proplist, key)); + key = pa_proplist_iterate (info->proplist, &state); + } +#endif + card = g_hash_table_lookup (control->priv->cards, + GUINT_TO_POINTER (info->index)); + if (card == NULL) { + GList *list = NULL; + + for (i = 0; i < info->n_profiles; i++) { + struct pa_card_profile_info pi = info->profiles[i]; + GvcMixerCardProfile *profile; + + profile = g_new0 (GvcMixerCardProfile, 1); + profile->profile = g_strdup (pi.name); + profile->human_profile = g_strdup (pi.description); + profile->status = card_num_streams_to_status (pi.n_sinks, pi.n_sources); + profile->n_sinks = pi.n_sinks; + profile->n_sources = pi.n_sources; + profile->priority = pi.priority; + list = g_list_prepend (list, profile); + } + card = gvc_mixer_card_new (control->priv->pa_context, + info->index); + gvc_mixer_card_set_profiles (card, list); + is_new = TRUE; + } + + gvc_mixer_card_set_name (card, pa_proplist_gets (info->proplist, "device.description")); + gvc_mixer_card_set_icon_name (card, pa_proplist_gets (info->proplist, "device.icon_name")); + gvc_mixer_card_set_profile (card, info->active_profile->name); + + if (is_new) { + g_hash_table_insert (control->priv->cards, + GUINT_TO_POINTER (info->index), + g_object_ref (card)); + } + g_signal_emit (G_OBJECT (control), + signals[CARD_ADDED], + 0, + info->index); +} + +static void +_pa_context_get_sink_info_cb (pa_context *context, + const pa_sink_info *i, + int eol, + void *userdata) +{ + GvcMixerControl *control = GVC_MIXER_CONTROL (userdata); + + if (eol < 0) { + if (pa_context_errno (context) == PA_ERR_NOENTITY) { + return; + } + + g_warning ("Sink callback failure"); + return; + } + + if (eol > 0) { + dec_outstanding (control); + return; + } + + update_sink (control, i); +} + +static void +_pa_context_get_source_info_cb (pa_context *context, + const pa_source_info *i, + int eol, + void *userdata) +{ + GvcMixerControl *control = GVC_MIXER_CONTROL (userdata); + + if (eol < 0) { + if (pa_context_errno (context) == PA_ERR_NOENTITY) { + return; + } + + g_warning ("Source callback failure"); + return; + } + + if (eol > 0) { + dec_outstanding (control); + return; + } + + update_source (control, i); +} + +static void +_pa_context_get_sink_input_info_cb (pa_context *context, + const pa_sink_input_info *i, + int eol, + void *userdata) +{ + GvcMixerControl *control = GVC_MIXER_CONTROL (userdata); + + if (eol < 0) { + if (pa_context_errno (context) == PA_ERR_NOENTITY) { + return; + } + + g_warning ("Sink input callback failure"); + return; + } + + if (eol > 0) { + dec_outstanding (control); + return; + } + + update_sink_input (control, i); +} + +static void +_pa_context_get_source_output_info_cb (pa_context *context, + const pa_source_output_info *i, + int eol, + void *userdata) +{ + GvcMixerControl *control = GVC_MIXER_CONTROL (userdata); + + if (eol < 0) { + if (pa_context_errno (context) == PA_ERR_NOENTITY) { + return; + } + + g_warning ("Source output callback failure"); + return; + } + + if (eol > 0) { + dec_outstanding (control); + return; + } + + update_source_output (control, i); +} + +static void +_pa_context_get_client_info_cb (pa_context *context, + const pa_client_info *i, + int eol, + void *userdata) +{ + GvcMixerControl *control = GVC_MIXER_CONTROL (userdata); + + if (eol < 0) { + if (pa_context_errno (context) == PA_ERR_NOENTITY) { + return; + } + + g_warning ("Client callback failure"); + return; + } + + if (eol > 0) { + dec_outstanding (control); + return; + } + + update_client (control, i); +} + +static void +_pa_context_get_card_info_by_index_cb (pa_context *context, + const pa_card_info *i, + int eol, + void *userdata) +{ + GvcMixerControl *control = GVC_MIXER_CONTROL (userdata); + + if (eol < 0) { + if (pa_context_errno (context) == PA_ERR_NOENTITY) + return; + + g_warning ("Card callback failure"); + return; + } + + if (eol > 0) { + dec_outstanding (control); + return; + } + + update_card (control, i); +} + +static void +_pa_context_get_server_info_cb (pa_context *context, + const pa_server_info *i, + void *userdata) +{ + GvcMixerControl *control = GVC_MIXER_CONTROL (userdata); + + if (i == NULL) { + g_warning ("Server info callback failure"); + return; + } + + update_server (control, i); + dec_outstanding (control); +} + +static void +remove_event_role_stream (GvcMixerControl *control) +{ + g_debug ("Removing event role"); +} + +static void +update_event_role_stream (GvcMixerControl *control, + const pa_ext_stream_restore_info *info) +{ + GvcMixerStream *stream; + gboolean is_new; + pa_volume_t max_volume; + + if (strcmp (info->name, "sink-input-by-media-role:event") != 0) { + return; + } + +#if 0 + g_debug ("Updating event role: name='%s' device='%s'", + info->name, + info->device); +#endif + + is_new = FALSE; + + if (!control->priv->event_sink_input_is_set) { + pa_channel_map pa_map; + GvcChannelMap *map; + + pa_map.channels = 1; + pa_map.map[0] = PA_CHANNEL_POSITION_MONO; + map = gvc_channel_map_new_from_pa_channel_map (&pa_map); + + stream = gvc_mixer_event_role_new (control->priv->pa_context, + info->device, + map); + control->priv->event_sink_input_id = gvc_mixer_stream_get_id (stream); + control->priv->event_sink_input_is_set = TRUE; + + is_new = TRUE; + } else { + stream = g_hash_table_lookup (control->priv->all_streams, + GUINT_TO_POINTER (control->priv->event_sink_input_id)); + } + + max_volume = pa_cvolume_max (&info->volume); + + gvc_mixer_stream_set_name (stream, _("System Sounds")); + gvc_mixer_stream_set_icon_name (stream, "multimedia-volume-control"); + gvc_mixer_stream_set_volume (stream, (guint)max_volume); + gvc_mixer_stream_set_is_muted (stream, info->mute); + + if (is_new) { + add_stream (control, stream); + } +} + +static void +_pa_ext_stream_restore_read_cb (pa_context *context, + const pa_ext_stream_restore_info *i, + int eol, + void *userdata) +{ + GvcMixerControl *control = GVC_MIXER_CONTROL (userdata); + + if (eol < 0) { + g_debug ("Failed to initialized stream_restore extension: %s", + pa_strerror (pa_context_errno (context))); + remove_event_role_stream (control); + return; + } + + if (eol > 0) { + dec_outstanding (control); + /* If we don't have an event stream to restore, then + * set one up with a default 100% volume */ + if (!control->priv->event_sink_input_is_set) { + pa_ext_stream_restore_info info; + + memset (&info, 0, sizeof(info)); + info.name = "sink-input-by-media-role:event"; + info.volume.channels = 1; + info.volume.values[0] = PA_VOLUME_NORM; + update_event_role_stream (control, &info); + } + return; + } + + update_event_role_stream (control, i); +} + +static void +_pa_ext_stream_restore_subscribe_cb (pa_context *context, + void *userdata) +{ + GvcMixerControl *control = GVC_MIXER_CONTROL (userdata); + pa_operation *o; + + o = pa_ext_stream_restore_read (context, + _pa_ext_stream_restore_read_cb, + control); + if (o == NULL) { + g_warning ("pa_ext_stream_restore_read() failed"); + return; + } + + pa_operation_unref (o); +} + +static void +req_update_server_info (GvcMixerControl *control, + int index) +{ + pa_operation *o; + + o = pa_context_get_server_info (control->priv->pa_context, + _pa_context_get_server_info_cb, + control); + if (o == NULL) { + g_warning ("pa_context_get_server_info() failed"); + return; + } + pa_operation_unref (o); +} + +static void +req_update_client_info (GvcMixerControl *control, + int index) +{ + pa_operation *o; + + if (index < 0) { + o = pa_context_get_client_info_list (control->priv->pa_context, + _pa_context_get_client_info_cb, + control); + } else { + o = pa_context_get_client_info (control->priv->pa_context, + index, + _pa_context_get_client_info_cb, + control); + } + + if (o == NULL) { + g_warning ("pa_context_client_info_list() failed"); + return; + } + pa_operation_unref (o); +} + +static void +req_update_card (GvcMixerControl *control, + int index) +{ + pa_operation *o; + + if (index < 0) { + o = pa_context_get_card_info_list (control->priv->pa_context, + _pa_context_get_card_info_by_index_cb, + control); + } else { + o = pa_context_get_card_info_by_index (control->priv->pa_context, + index, + _pa_context_get_card_info_by_index_cb, + control); + } + + if (o == NULL) { + g_warning ("pa_context_get_card_info_by_index() failed"); + return; + } + pa_operation_unref (o); +} + +static void +req_update_sink_info (GvcMixerControl *control, + int index) +{ + pa_operation *o; + + if (index < 0) { + o = pa_context_get_sink_info_list (control->priv->pa_context, + _pa_context_get_sink_info_cb, + control); + } else { + o = pa_context_get_sink_info_by_index (control->priv->pa_context, + index, + _pa_context_get_sink_info_cb, + control); + } + + if (o == NULL) { + g_warning ("pa_context_get_sink_info_list() failed"); + return; + } + pa_operation_unref (o); +} + +static void +req_update_source_info (GvcMixerControl *control, + int index) +{ + pa_operation *o; + + if (index < 0) { + o = pa_context_get_source_info_list (control->priv->pa_context, + _pa_context_get_source_info_cb, + control); + } else { + o = pa_context_get_source_info_by_index(control->priv->pa_context, + index, + _pa_context_get_source_info_cb, + control); + } + + if (o == NULL) { + g_warning ("pa_context_get_source_info_list() failed"); + return; + } + pa_operation_unref (o); +} + +static void +req_update_sink_input_info (GvcMixerControl *control, + int index) +{ + pa_operation *o; + + if (index < 0) { + o = pa_context_get_sink_input_info_list (control->priv->pa_context, + _pa_context_get_sink_input_info_cb, + control); + } else { + o = pa_context_get_sink_input_info (control->priv->pa_context, + index, + _pa_context_get_sink_input_info_cb, + control); + } + + if (o == NULL) { + g_warning ("pa_context_get_sink_input_info_list() failed"); + return; + } + pa_operation_unref (o); +} + +static void +req_update_source_output_info (GvcMixerControl *control, + int index) +{ + pa_operation *o; + + if (index < 0) { + o = pa_context_get_source_output_info_list (control->priv->pa_context, + _pa_context_get_source_output_info_cb, + control); + } else { + o = pa_context_get_source_output_info (control->priv->pa_context, + index, + _pa_context_get_source_output_info_cb, + control); + } + + if (o == NULL) { + g_warning ("pa_context_get_source_output_info_list() failed"); + return; + } + pa_operation_unref (o); +} + +static void +remove_client (GvcMixerControl *control, + guint index) +{ + g_hash_table_remove (control->priv->clients, + GUINT_TO_POINTER (index)); +} + +static void +remove_card (GvcMixerControl *control, + guint index) +{ + g_hash_table_remove (control->priv->cards, + GUINT_TO_POINTER (index)); + + g_signal_emit (G_OBJECT (control), + signals[CARD_REMOVED], + 0, + index); +} + +static void +remove_sink (GvcMixerControl *control, + guint index) +{ + GvcMixerStream *stream; + +#if 0 + g_debug ("Removing sink: index=%u", index); +#endif + + stream = g_hash_table_lookup (control->priv->sinks, + GUINT_TO_POINTER (index)); + if (stream == NULL) { + return; + } + g_hash_table_remove (control->priv->sinks, + GUINT_TO_POINTER (index)); + + remove_stream (control, stream); +} + +static void +remove_source (GvcMixerControl *control, + guint index) +{ + GvcMixerStream *stream; + +#if 0 + g_debug ("Removing source: index=%u", index); +#endif + + stream = g_hash_table_lookup (control->priv->sources, + GUINT_TO_POINTER (index)); + if (stream == NULL) { + return; + } + g_hash_table_remove (control->priv->sources, + GUINT_TO_POINTER (index)); + + remove_stream (control, stream); +} + +static void +remove_sink_input (GvcMixerControl *control, + guint index) +{ + GvcMixerStream *stream; + +#if 0 + g_debug ("Removing sink input: index=%u", index); +#endif + stream = g_hash_table_lookup (control->priv->sink_inputs, + GUINT_TO_POINTER (index)); + if (stream == NULL) { + return; + } + g_hash_table_remove (control->priv->sink_inputs, + GUINT_TO_POINTER (index)); + + remove_stream (control, stream); +} + +static void +remove_source_output (GvcMixerControl *control, + guint index) +{ + GvcMixerStream *stream; + +#if 0 + g_debug ("Removing source output: index=%u", index); +#endif + + stream = g_hash_table_lookup (control->priv->source_outputs, + GUINT_TO_POINTER (index)); + if (stream == NULL) { + return; + } + g_hash_table_remove (control->priv->source_outputs, + GUINT_TO_POINTER (index)); + + remove_stream (control, stream); +} + +static void +_pa_context_subscribe_cb (pa_context *context, + pa_subscription_event_type_t t, + uint32_t index, + void *userdata) +{ + GvcMixerControl *control = GVC_MIXER_CONTROL (userdata); + + switch (t & PA_SUBSCRIPTION_EVENT_FACILITY_MASK) { + case PA_SUBSCRIPTION_EVENT_SINK: + if ((t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_REMOVE) { + remove_sink (control, index); + } else { + req_update_sink_info (control, index); + } + break; + + case PA_SUBSCRIPTION_EVENT_SOURCE: + if ((t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_REMOVE) { + remove_source (control, index); + } else { + req_update_source_info (control, index); + } + break; + + case PA_SUBSCRIPTION_EVENT_SINK_INPUT: + if ((t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_REMOVE) { + remove_sink_input (control, index); + } else { + req_update_sink_input_info (control, index); + } + break; + + case PA_SUBSCRIPTION_EVENT_SOURCE_OUTPUT: + if ((t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_REMOVE) { + remove_source_output (control, index); + } else { + req_update_source_output_info (control, index); + } + break; + + case PA_SUBSCRIPTION_EVENT_CLIENT: + if ((t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_REMOVE) { + remove_client (control, index); + } else { + req_update_client_info (control, index); + } + break; + + case PA_SUBSCRIPTION_EVENT_SERVER: + req_update_server_info (control, index); + break; + + case PA_SUBSCRIPTION_EVENT_CARD: + if ((t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_REMOVE) { + remove_card (control, index); + } else { + req_update_card (control, index); + } + break; + } +} + +static void +gvc_mixer_control_ready (GvcMixerControl *control) +{ + pa_operation *o; + + pa_context_set_subscribe_callback (control->priv->pa_context, + _pa_context_subscribe_cb, + control); + o = pa_context_subscribe (control->priv->pa_context, + (pa_subscription_mask_t) + (PA_SUBSCRIPTION_MASK_SINK| + PA_SUBSCRIPTION_MASK_SOURCE| + PA_SUBSCRIPTION_MASK_SINK_INPUT| + PA_SUBSCRIPTION_MASK_SOURCE_OUTPUT| + PA_SUBSCRIPTION_MASK_CLIENT| + PA_SUBSCRIPTION_MASK_SERVER| + PA_SUBSCRIPTION_MASK_CARD), + NULL, + NULL); + + if (o == NULL) { + g_warning ("pa_context_subscribe() failed"); + return; + } + pa_operation_unref (o); + + req_update_server_info (control, -1); + req_update_client_info (control, -1); + req_update_sink_info (control, -1); + req_update_source_info (control, -1); + req_update_sink_input_info (control, -1); + req_update_source_output_info (control, -1); + req_update_card (control, -1); + + control->priv->n_outstanding = 6; + + /* This call is not always supported */ + o = pa_ext_stream_restore_read (control->priv->pa_context, + _pa_ext_stream_restore_read_cb, + control); + if (o != NULL) { + pa_operation_unref (o); + control->priv->n_outstanding++; + + pa_ext_stream_restore_set_subscribe_cb (control->priv->pa_context, + _pa_ext_stream_restore_subscribe_cb, + control); + + o = pa_ext_stream_restore_subscribe (control->priv->pa_context, + 1, + NULL, + NULL); + if (o != NULL) { + pa_operation_unref (o); + } + + } else { + g_debug ("Failed to initialized stream_restore extension: %s", + pa_strerror (pa_context_errno (control->priv->pa_context))); + } +} + +static void +gvc_mixer_new_pa_context (GvcMixerControl *self) +{ + pa_proplist *proplist; + + g_return_if_fail (self); + g_return_if_fail (!self->priv->pa_context); + + proplist = pa_proplist_new (); + pa_proplist_sets (proplist, + PA_PROP_APPLICATION_NAME, + self->priv->name); + pa_proplist_sets (proplist, + PA_PROP_APPLICATION_ID, + "org.gnome.VolumeControl"); + pa_proplist_sets (proplist, + PA_PROP_APPLICATION_ICON_NAME, + "multimedia-volume-control"); + pa_proplist_sets (proplist, + PA_PROP_APPLICATION_VERSION, + PACKAGE_VERSION); + + self->priv->pa_context = pa_context_new_with_proplist (self->priv->pa_api, NULL, proplist); + + pa_proplist_free (proplist); + g_assert (self->priv->pa_context); +} + +static void +remove_all_streams (GvcMixerControl *control, GHashTable *hash_table) +{ + GHashTableIter iter; + gpointer key, value; + + g_hash_table_iter_init (&iter, hash_table); + while (g_hash_table_iter_next (&iter, &key, &value)) { + remove_stream (control, value); + g_hash_table_iter_remove (&iter); + } +} + +static gboolean +idle_reconnect (gpointer data) +{ + GvcMixerControl *control = GVC_MIXER_CONTROL (data); + GHashTableIter iter; + gpointer key, value; + + g_return_val_if_fail (control, FALSE); + + if (control->priv->pa_context) { + pa_context_unref (control->priv->pa_context); + control->priv->pa_context = NULL; + gvc_mixer_new_pa_context (control); + } + + remove_all_streams (control, control->priv->sinks); + remove_all_streams (control, control->priv->sources); + remove_all_streams (control, control->priv->sink_inputs); + remove_all_streams (control, control->priv->source_outputs); + + g_hash_table_iter_init (&iter, control->priv->clients); + while (g_hash_table_iter_next (&iter, &key, &value)) + g_hash_table_iter_remove (&iter); + + gvc_mixer_control_open (control); /* cannot fail */ + + control->priv->reconnect_id = 0; + return FALSE; +} + +static void +_pa_context_state_cb (pa_context *context, + void *userdata) +{ + GvcMixerControl *control = GVC_MIXER_CONTROL (userdata); + + switch (pa_context_get_state (context)) { + case PA_CONTEXT_UNCONNECTED: + case PA_CONTEXT_CONNECTING: + case PA_CONTEXT_AUTHORIZING: + case PA_CONTEXT_SETTING_NAME: + break; + + case PA_CONTEXT_READY: + gvc_mixer_control_ready (control); + break; + + case PA_CONTEXT_FAILED: + g_warning ("Connection failed, reconnecting..."); + if (control->priv->reconnect_id == 0) + control->priv->reconnect_id = g_timeout_add_seconds (RECONNECT_DELAY, idle_reconnect, control); + break; + + case PA_CONTEXT_TERMINATED: + default: + /* FIXME: */ + break; + } +} + +gboolean +gvc_mixer_control_open (GvcMixerControl *control) +{ + int res; + + g_return_val_if_fail (GVC_IS_MIXER_CONTROL (control), FALSE); + g_return_val_if_fail (control->priv->pa_context != NULL, FALSE); + g_return_val_if_fail (pa_context_get_state (control->priv->pa_context) == PA_CONTEXT_UNCONNECTED, FALSE); + + pa_context_set_state_callback (control->priv->pa_context, + _pa_context_state_cb, + control); + + g_signal_emit (G_OBJECT (control), signals[CONNECTING], 0); + res = pa_context_connect (control->priv->pa_context, NULL, (pa_context_flags_t) PA_CONTEXT_NOFAIL, NULL); + if (res < 0) { + g_warning ("Failed to connect context: %s", + pa_strerror (pa_context_errno (control->priv->pa_context))); + } + + return res; +} + +gboolean +gvc_mixer_control_close (GvcMixerControl *control) +{ + g_return_val_if_fail (GVC_IS_MIXER_CONTROL (control), FALSE); + g_return_val_if_fail (control->priv->pa_context != NULL, FALSE); + + pa_context_disconnect (control->priv->pa_context); + return TRUE; +} + +static void +gvc_mixer_control_dispose (GObject *object) +{ + GvcMixerControl *control = GVC_MIXER_CONTROL (object); + + if (control->priv->pa_context != NULL) { + pa_context_unref (control->priv->pa_context); + control->priv->pa_context = NULL; + } + + if (control->priv->default_source_name != NULL) { + g_free (control->priv->default_source_name); + control->priv->default_source_name = NULL; + } + if (control->priv->default_sink_name != NULL) { + g_free (control->priv->default_sink_name); + control->priv->default_sink_name = NULL; + } + + if (control->priv->pa_mainloop != NULL) { + pa_glib_mainloop_free (control->priv->pa_mainloop); + control->priv->pa_mainloop = NULL; + } + + if (control->priv->all_streams != NULL) { + g_hash_table_destroy (control->priv->all_streams); + control->priv->all_streams = NULL; + } + + if (control->priv->sinks != NULL) { + g_hash_table_destroy (control->priv->sinks); + control->priv->sinks = NULL; + } + if (control->priv->sources != NULL) { + g_hash_table_destroy (control->priv->sources); + control->priv->sources = NULL; + } + if (control->priv->sink_inputs != NULL) { + g_hash_table_destroy (control->priv->sink_inputs); + control->priv->sink_inputs = NULL; + } + if (control->priv->source_outputs != NULL) { + g_hash_table_destroy (control->priv->source_outputs); + control->priv->source_outputs = NULL; + } + if (control->priv->clients != NULL) { + g_hash_table_destroy (control->priv->clients); + control->priv->clients = NULL; + } + if (control->priv->cards != NULL) { + g_hash_table_destroy (control->priv->cards); + control->priv->cards = NULL; + } + + G_OBJECT_CLASS (gvc_mixer_control_parent_class)->dispose (object); +} + +static void +gvc_mixer_control_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + GvcMixerControl *self = GVC_MIXER_CONTROL (object); + + switch (prop_id) { + case PROP_NAME: + g_free (self->priv->name); + self->priv->name = g_value_dup_string (value); + g_object_notify (G_OBJECT (self), "name"); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +gvc_mixer_control_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + GvcMixerControl *self = GVC_MIXER_CONTROL (object); + + switch (prop_id) { + case PROP_NAME: + g_value_set_string (value, self->priv->name); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + + +static GObject * +gvc_mixer_control_constructor (GType type, + guint n_construct_properties, + GObjectConstructParam *construct_params) +{ + GObject *object; + GvcMixerControl *self; + + object = G_OBJECT_CLASS (gvc_mixer_control_parent_class)->constructor (type, n_construct_properties, construct_params); + + self = GVC_MIXER_CONTROL (object); + + gvc_mixer_new_pa_context (self); + + return object; +} + +static void +gvc_mixer_control_class_init (GvcMixerControlClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + object_class->constructor = gvc_mixer_control_constructor; + object_class->dispose = gvc_mixer_control_dispose; + object_class->finalize = gvc_mixer_control_finalize; + object_class->set_property = gvc_mixer_control_set_property; + object_class->get_property = gvc_mixer_control_get_property; + + g_object_class_install_property (object_class, + PROP_NAME, + g_param_spec_string ("name", + "Name", + "Name to display for this mixer control", + NULL, + G_PARAM_READWRITE|G_PARAM_CONSTRUCT_ONLY)); + + signals [CONNECTING] = + g_signal_new ("connecting", + G_TYPE_FROM_CLASS (klass), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (GvcMixerControlClass, connecting), + NULL, NULL, + g_cclosure_marshal_VOID__VOID, + G_TYPE_NONE, 0); + signals [READY] = + g_signal_new ("ready", + G_TYPE_FROM_CLASS (klass), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (GvcMixerControlClass, ready), + NULL, NULL, + g_cclosure_marshal_VOID__VOID, + G_TYPE_NONE, 0); + signals [STREAM_ADDED] = + g_signal_new ("stream-added", + G_TYPE_FROM_CLASS (klass), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (GvcMixerControlClass, stream_added), + NULL, NULL, + g_cclosure_marshal_VOID__UINT, + G_TYPE_NONE, 1, G_TYPE_UINT); + signals [STREAM_REMOVED] = + g_signal_new ("stream-removed", + G_TYPE_FROM_CLASS (klass), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (GvcMixerControlClass, stream_removed), + NULL, NULL, + g_cclosure_marshal_VOID__UINT, + G_TYPE_NONE, 1, G_TYPE_UINT); + signals [CARD_ADDED] = + g_signal_new ("card-added", + G_TYPE_FROM_CLASS (klass), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (GvcMixerControlClass, card_added), + NULL, NULL, + g_cclosure_marshal_VOID__UINT, + G_TYPE_NONE, 1, G_TYPE_UINT); + signals [CARD_REMOVED] = + g_signal_new ("card-removed", + G_TYPE_FROM_CLASS (klass), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (GvcMixerControlClass, card_removed), + NULL, NULL, + g_cclosure_marshal_VOID__UINT, + G_TYPE_NONE, 1, G_TYPE_UINT); + signals [DEFAULT_SINK_CHANGED] = + g_signal_new ("default-sink-changed", + G_TYPE_FROM_CLASS (klass), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (GvcMixerControlClass, default_sink_changed), + NULL, NULL, + g_cclosure_marshal_VOID__UINT, + G_TYPE_NONE, 1, G_TYPE_UINT); + signals [DEFAULT_SOURCE_CHANGED] = + g_signal_new ("default-source-changed", + G_TYPE_FROM_CLASS (klass), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (GvcMixerControlClass, default_source_changed), + NULL, NULL, + g_cclosure_marshal_VOID__UINT, + G_TYPE_NONE, 1, G_TYPE_UINT); + + g_type_class_add_private (klass, sizeof (GvcMixerControlPrivate)); +} + +static void +gvc_mixer_control_init (GvcMixerControl *control) +{ + control->priv = GVC_MIXER_CONTROL_GET_PRIVATE (control); + + control->priv->pa_mainloop = pa_glib_mainloop_new (g_main_context_default ()); + g_assert (control->priv->pa_mainloop); + + control->priv->pa_api = pa_glib_mainloop_get_api (control->priv->pa_mainloop); + g_assert (control->priv->pa_api); + + control->priv->all_streams = g_hash_table_new_full (NULL, NULL, NULL, (GDestroyNotify)g_object_unref); + control->priv->sinks = g_hash_table_new_full (NULL, NULL, NULL, (GDestroyNotify)g_object_unref); + control->priv->sources = g_hash_table_new_full (NULL, NULL, NULL, (GDestroyNotify)g_object_unref); + control->priv->sink_inputs = g_hash_table_new_full (NULL, NULL, NULL, (GDestroyNotify)g_object_unref); + control->priv->source_outputs = g_hash_table_new_full (NULL, NULL, NULL, (GDestroyNotify)g_object_unref); + control->priv->cards = g_hash_table_new_full (NULL, NULL, NULL, (GDestroyNotify)g_object_unref); + + control->priv->clients = g_hash_table_new_full (NULL, NULL, NULL, (GDestroyNotify)g_free); +} + +static void +gvc_mixer_control_finalize (GObject *object) +{ + GvcMixerControl *mixer_control; + + g_return_if_fail (object != NULL); + g_return_if_fail (GVC_IS_MIXER_CONTROL (object)); + + mixer_control = GVC_MIXER_CONTROL (object); + g_free (mixer_control->priv->name); + mixer_control->priv->name = NULL; + + g_return_if_fail (mixer_control->priv != NULL); + G_OBJECT_CLASS (gvc_mixer_control_parent_class)->finalize (object); +} + +GvcMixerControl * +gvc_mixer_control_new (const char *name) +{ + GObject *control; + control = g_object_new (GVC_TYPE_MIXER_CONTROL, + "name", name, + NULL); + return GVC_MIXER_CONTROL (control); +} diff --git a/src/gvc/gvc-mixer-control.h b/src/gvc/gvc-mixer-control.h new file mode 100644 index 000000000..d32b20493 --- /dev/null +++ b/src/gvc/gvc-mixer-control.h @@ -0,0 +1,96 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*- + * + * Copyright (C) 2008 Red Hat, Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + * + */ + +#ifndef __GVC_MIXER_CONTROL_H +#define __GVC_MIXER_CONTROL_H + +#include +#include "gvc-mixer-stream.h" +#include "gvc-mixer-card.h" + +G_BEGIN_DECLS + +#define GVC_TYPE_MIXER_CONTROL (gvc_mixer_control_get_type ()) +#define GVC_MIXER_CONTROL(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), GVC_TYPE_MIXER_CONTROL, GvcMixerControl)) +#define GVC_MIXER_CONTROL_CLASS(k) (G_TYPE_CHECK_CLASS_CAST((k), GVC_TYPE_MIXER_CONTROL, GvcMixerControlClass)) +#define GVC_IS_MIXER_CONTROL(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), GVC_TYPE_MIXER_CONTROL)) +#define GVC_IS_MIXER_CONTROL_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE ((k), GVC_TYPE_MIXER_CONTROL)) +#define GVC_MIXER_CONTROL_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), GVC_TYPE_MIXER_CONTROL, GvcMixerControlClass)) + +typedef struct GvcMixerControlPrivate GvcMixerControlPrivate; + +typedef struct +{ + GObject parent; + GvcMixerControlPrivate *priv; +} GvcMixerControl; + +typedef struct +{ + GObjectClass parent_class; + + void (*connecting) (GvcMixerControl *control); + void (*ready) (GvcMixerControl *control); + void (*stream_added) (GvcMixerControl *control, + guint id); + void (*stream_removed) (GvcMixerControl *control, + guint id); + void (*card_added) (GvcMixerControl *control, + guint id); + void (*card_removed) (GvcMixerControl *control, + guint id); + void (*default_sink_changed) (GvcMixerControl *control, + guint id); + void (*default_source_changed) (GvcMixerControl *control, + guint id); +} GvcMixerControlClass; + +GType gvc_mixer_control_get_type (void); + +GvcMixerControl * gvc_mixer_control_new (const char *name); + +gboolean gvc_mixer_control_open (GvcMixerControl *control); +gboolean gvc_mixer_control_close (GvcMixerControl *control); +gboolean gvc_mixer_control_is_ready (GvcMixerControl *control); + +GSList * gvc_mixer_control_get_cards (GvcMixerControl *control); +GSList * gvc_mixer_control_get_streams (GvcMixerControl *control); +GSList * gvc_mixer_control_get_sinks (GvcMixerControl *control); +GSList * gvc_mixer_control_get_sources (GvcMixerControl *control); +GSList * gvc_mixer_control_get_sink_inputs (GvcMixerControl *control); +GSList * gvc_mixer_control_get_source_outputs (GvcMixerControl *control); + +GvcMixerStream * gvc_mixer_control_lookup_stream_id (GvcMixerControl *control, + guint id); +GvcMixerCard * gvc_mixer_control_lookup_card_id (GvcMixerControl *control, + guint id); + +GvcMixerStream * gvc_mixer_control_get_default_sink (GvcMixerControl *control); +GvcMixerStream * gvc_mixer_control_get_default_source (GvcMixerControl *control); +GvcMixerStream * gvc_mixer_control_get_event_sink_input (GvcMixerControl *control); + +gboolean gvc_mixer_control_set_default_sink (GvcMixerControl *control, + GvcMixerStream *stream); +gboolean gvc_mixer_control_set_default_source (GvcMixerControl *control, + GvcMixerStream *stream); + +G_END_DECLS + +#endif /* __GVC_MIXER_CONTROL_H */ diff --git a/src/gvc/gvc-mixer-event-role.c b/src/gvc/gvc-mixer-event-role.c new file mode 100644 index 000000000..7eb3d00d7 --- /dev/null +++ b/src/gvc/gvc-mixer-event-role.c @@ -0,0 +1,250 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*- + * + * Copyright (C) 2008 William Jon McCann + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + * + */ + +#include "config.h" + +#include +#include +#include + +#include +#include + +#include +#include + +#include "gvc-mixer-event-role.h" +#include "gvc-mixer-stream-private.h" +#include "gvc-channel-map-private.h" + +#define GVC_MIXER_EVENT_ROLE_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), GVC_TYPE_MIXER_EVENT_ROLE, GvcMixerEventRolePrivate)) + +struct GvcMixerEventRolePrivate +{ + char *device; +}; + +enum +{ + PROP_0, + PROP_DEVICE +}; + +static void gvc_mixer_event_role_class_init (GvcMixerEventRoleClass *klass); +static void gvc_mixer_event_role_init (GvcMixerEventRole *mixer_event_role); +static void gvc_mixer_event_role_finalize (GObject *object); + +G_DEFINE_TYPE (GvcMixerEventRole, gvc_mixer_event_role, GVC_TYPE_MIXER_STREAM) + +static gboolean +update_settings (GvcMixerEventRole *role, + gboolean is_muted, + gpointer *op) +{ + pa_operation *o; + guint index; + const GvcChannelMap *map; + pa_context *context; + pa_ext_stream_restore_info info; + + index = gvc_mixer_stream_get_index (GVC_MIXER_STREAM (role)); + + map = gvc_mixer_stream_get_channel_map (GVC_MIXER_STREAM(role)); + + info.volume = *gvc_channel_map_get_cvolume(map); + info.name = "sink-input-by-media-role:event"; + info.channel_map = *gvc_channel_map_get_pa_channel_map(map); + info.device = role->priv->device; + info.mute = is_muted; + + context = gvc_mixer_stream_get_pa_context (GVC_MIXER_STREAM (role)); + + o = pa_ext_stream_restore_write (context, + PA_UPDATE_REPLACE, + &info, + 1, + TRUE, + NULL, + NULL); + + if (o == NULL) { + g_warning ("pa_ext_stream_restore_write() failed"); + return FALSE; + } + + if (op != NULL) + *op = o; + + return TRUE; +} + +static gboolean +gvc_mixer_event_role_push_volume (GvcMixerStream *stream, gpointer *op) +{ + return update_settings (GVC_MIXER_EVENT_ROLE (stream), + gvc_mixer_stream_get_is_muted (stream), op); +} + +static gboolean +gvc_mixer_event_role_change_is_muted (GvcMixerStream *stream, + gboolean is_muted) +{ + return update_settings (GVC_MIXER_EVENT_ROLE (stream), + is_muted, NULL); +} + +static gboolean +gvc_mixer_event_role_set_device (GvcMixerEventRole *role, + const char *device) +{ + g_return_val_if_fail (GVC_IS_MIXER_EVENT_ROLE (role), FALSE); + + g_free (role->priv->device); + role->priv->device = g_strdup (device); + g_object_notify (G_OBJECT (role), "device"); + + return TRUE; +} + +static void +gvc_mixer_event_role_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + GvcMixerEventRole *self = GVC_MIXER_EVENT_ROLE (object); + + switch (prop_id) { + case PROP_DEVICE: + gvc_mixer_event_role_set_device (self, g_value_get_string (value)); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +gvc_mixer_event_role_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + GvcMixerEventRole *self = GVC_MIXER_EVENT_ROLE (object); + + switch (prop_id) { + case PROP_DEVICE: + g_value_set_string (value, self->priv->device); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static GObject * +gvc_mixer_event_role_constructor (GType type, + guint n_construct_properties, + GObjectConstructParam *construct_params) +{ + GObject *object; + GvcMixerEventRole *self; + + object = G_OBJECT_CLASS (gvc_mixer_event_role_parent_class)->constructor (type, n_construct_properties, construct_params); + + self = GVC_MIXER_EVENT_ROLE (object); + + return object; +} + +static void +gvc_mixer_event_role_class_init (GvcMixerEventRoleClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + GvcMixerStreamClass *stream_class = GVC_MIXER_STREAM_CLASS (klass); + + object_class->constructor = gvc_mixer_event_role_constructor; + object_class->finalize = gvc_mixer_event_role_finalize; + object_class->set_property = gvc_mixer_event_role_set_property; + object_class->get_property = gvc_mixer_event_role_get_property; + + stream_class->push_volume = gvc_mixer_event_role_push_volume; + stream_class->change_is_muted = gvc_mixer_event_role_change_is_muted; + + g_object_class_install_property (object_class, + PROP_DEVICE, + g_param_spec_string ("device", + "Device", + "Device", + NULL, + G_PARAM_READWRITE|G_PARAM_CONSTRUCT)); + + g_type_class_add_private (klass, sizeof (GvcMixerEventRolePrivate)); +} + +static void +gvc_mixer_event_role_init (GvcMixerEventRole *event_role) +{ + event_role->priv = GVC_MIXER_EVENT_ROLE_GET_PRIVATE (event_role); + +} + +static void +gvc_mixer_event_role_finalize (GObject *object) +{ + GvcMixerEventRole *mixer_event_role; + + g_return_if_fail (object != NULL); + g_return_if_fail (GVC_IS_MIXER_EVENT_ROLE (object)); + + mixer_event_role = GVC_MIXER_EVENT_ROLE (object); + + g_return_if_fail (mixer_event_role->priv != NULL); + + g_free (mixer_event_role->priv->device); + + G_OBJECT_CLASS (gvc_mixer_event_role_parent_class)->finalize (object); +} + +/** + * gvc_mixer_event_role_new: (skip) + * + * @context: + * @index: + * @channel_map: + * + * Returns: + */ +GvcMixerStream * +gvc_mixer_event_role_new (pa_context *context, + const char *device, + GvcChannelMap *channel_map) +{ + GObject *object; + + object = g_object_new (GVC_TYPE_MIXER_EVENT_ROLE, + "pa-context", context, + "index", 0, + "device", device, + "channel-map", channel_map, + NULL); + + return GVC_MIXER_STREAM (object); +} diff --git a/src/gvc/gvc-mixer-event-role.h b/src/gvc/gvc-mixer-event-role.h new file mode 100644 index 000000000..ab4c509e5 --- /dev/null +++ b/src/gvc/gvc-mixer-event-role.h @@ -0,0 +1,57 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*- + * + * Copyright (C) 2008 Red Hat, Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + * + */ + +#ifndef __GVC_MIXER_EVENT_ROLE_H +#define __GVC_MIXER_EVENT_ROLE_H + +#include +#include "gvc-mixer-stream.h" + +G_BEGIN_DECLS + +#define GVC_TYPE_MIXER_EVENT_ROLE (gvc_mixer_event_role_get_type ()) +#define GVC_MIXER_EVENT_ROLE(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), GVC_TYPE_MIXER_EVENT_ROLE, GvcMixerEventRole)) +#define GVC_MIXER_EVENT_ROLE_CLASS(k) (G_TYPE_CHECK_CLASS_CAST((k), GVC_TYPE_MIXER_EVENT_ROLE, GvcMixerEventRoleClass)) +#define GVC_IS_MIXER_EVENT_ROLE(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), GVC_TYPE_MIXER_EVENT_ROLE)) +#define GVC_IS_MIXER_EVENT_ROLE_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE ((k), GVC_TYPE_MIXER_EVENT_ROLE)) +#define GVC_MIXER_EVENT_ROLE_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), GVC_TYPE_MIXER_EVENT_ROLE, GvcMixerEventRoleClass)) + +typedef struct GvcMixerEventRolePrivate GvcMixerEventRolePrivate; + +typedef struct +{ + GvcMixerStream parent; + GvcMixerEventRolePrivate *priv; +} GvcMixerEventRole; + +typedef struct +{ + GvcMixerStreamClass parent_class; +} GvcMixerEventRoleClass; + +GType gvc_mixer_event_role_get_type (void); + +GvcMixerStream * gvc_mixer_event_role_new (pa_context *context, + const char *device, + GvcChannelMap *channel_map); + +G_END_DECLS + +#endif /* __GVC_MIXER_EVENT_ROLE_H */ diff --git a/src/gvc/gvc-mixer-sink-input.c b/src/gvc/gvc-mixer-sink-input.c new file mode 100644 index 000000000..9429eca0a --- /dev/null +++ b/src/gvc/gvc-mixer-sink-input.c @@ -0,0 +1,199 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*- + * + * Copyright (C) 2008 William Jon McCann + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + * + */ + +#include "config.h" + +#include +#include +#include + +#include +#include + +#include + +#include "gvc-mixer-sink-input.h" +#include "gvc-mixer-stream-private.h" +#include "gvc-channel-map-private.h" + +#define GVC_MIXER_SINK_INPUT_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), GVC_TYPE_MIXER_SINK_INPUT, GvcMixerSinkInputPrivate)) + +struct GvcMixerSinkInputPrivate +{ + gpointer dummy; +}; + +static void gvc_mixer_sink_input_class_init (GvcMixerSinkInputClass *klass); +static void gvc_mixer_sink_input_init (GvcMixerSinkInput *mixer_sink_input); +static void gvc_mixer_sink_input_finalize (GObject *object); +static void gvc_mixer_sink_input_dispose (GObject *object); + +G_DEFINE_TYPE (GvcMixerSinkInput, gvc_mixer_sink_input, GVC_TYPE_MIXER_STREAM) + +static gboolean +gvc_mixer_sink_input_push_volume (GvcMixerStream *stream, gpointer *op) +{ + pa_operation *o; + guint index; + const GvcChannelMap *map; + pa_context *context; + const pa_cvolume *cv; + guint num_channels; + + index = gvc_mixer_stream_get_index (stream); + + map = gvc_mixer_stream_get_channel_map (stream); + num_channels = gvc_channel_map_get_num_channels (map); + + cv = gvc_channel_map_get_cvolume(map); + + context = gvc_mixer_stream_get_pa_context (stream); + + o = pa_context_set_sink_input_volume (context, + index, + cv, + NULL, + NULL); + + if (o == NULL) { + g_warning ("pa_context_set_sink_input_volume() failed"); + return FALSE; + } + + *op = o; + + return TRUE; +} + +static gboolean +gvc_mixer_sink_input_change_is_muted (GvcMixerStream *stream, + gboolean is_muted) +{ + pa_operation *o; + guint index; + pa_context *context; + + index = gvc_mixer_stream_get_index (stream); + context = gvc_mixer_stream_get_pa_context (stream); + + o = pa_context_set_sink_input_mute (context, + index, + is_muted, + NULL, + NULL); + + if (o == NULL) { + g_warning ("pa_context_set_sink_input_mute_by_index() failed"); + return FALSE; + } + + pa_operation_unref(o); + + return TRUE; +} + +static GObject * +gvc_mixer_sink_input_constructor (GType type, + guint n_construct_properties, + GObjectConstructParam *construct_params) +{ + GObject *object; + GvcMixerSinkInput *self; + + object = G_OBJECT_CLASS (gvc_mixer_sink_input_parent_class)->constructor (type, n_construct_properties, construct_params); + + self = GVC_MIXER_SINK_INPUT (object); + + return object; +} + +static void +gvc_mixer_sink_input_class_init (GvcMixerSinkInputClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + GvcMixerStreamClass *stream_class = GVC_MIXER_STREAM_CLASS (klass); + + object_class->constructor = gvc_mixer_sink_input_constructor; + object_class->dispose = gvc_mixer_sink_input_dispose; + object_class->finalize = gvc_mixer_sink_input_finalize; + + stream_class->push_volume = gvc_mixer_sink_input_push_volume; + stream_class->change_is_muted = gvc_mixer_sink_input_change_is_muted; + + g_type_class_add_private (klass, sizeof (GvcMixerSinkInputPrivate)); +} + +static void +gvc_mixer_sink_input_init (GvcMixerSinkInput *sink_input) +{ + sink_input->priv = GVC_MIXER_SINK_INPUT_GET_PRIVATE (sink_input); +} + +static void +gvc_mixer_sink_input_dispose (GObject *object) +{ + GvcMixerSinkInput *mixer_sink_input; + + g_return_if_fail (object != NULL); + g_return_if_fail (GVC_IS_MIXER_SINK_INPUT (object)); + + mixer_sink_input = GVC_MIXER_SINK_INPUT (object); + + G_OBJECT_CLASS (gvc_mixer_sink_input_parent_class)->dispose (object); +} + +static void +gvc_mixer_sink_input_finalize (GObject *object) +{ + GvcMixerSinkInput *mixer_sink_input; + + g_return_if_fail (object != NULL); + g_return_if_fail (GVC_IS_MIXER_SINK_INPUT (object)); + + mixer_sink_input = GVC_MIXER_SINK_INPUT (object); + + g_return_if_fail (mixer_sink_input->priv != NULL); + G_OBJECT_CLASS (gvc_mixer_sink_input_parent_class)->finalize (object); +} + +/** + * gvc_mixer_sink_input_new: (skip) + * + * @context: + * @index: + * @channel_map: + * + * Returns: + */ +GvcMixerStream * +gvc_mixer_sink_input_new (pa_context *context, + guint index, + GvcChannelMap *channel_map) +{ + GObject *object; + + object = g_object_new (GVC_TYPE_MIXER_SINK_INPUT, + "pa-context", context, + "index", index, + "channel-map", channel_map, + NULL); + + return GVC_MIXER_STREAM (object); +} diff --git a/src/gvc/gvc-mixer-sink-input.h b/src/gvc/gvc-mixer-sink-input.h new file mode 100644 index 000000000..8a4b71454 --- /dev/null +++ b/src/gvc/gvc-mixer-sink-input.h @@ -0,0 +1,57 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*- + * + * Copyright (C) 2008 Red Hat, Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + * + */ + +#ifndef __GVC_MIXER_SINK_INPUT_H +#define __GVC_MIXER_SINK_INPUT_H + +#include +#include "gvc-mixer-stream.h" + +G_BEGIN_DECLS + +#define GVC_TYPE_MIXER_SINK_INPUT (gvc_mixer_sink_input_get_type ()) +#define GVC_MIXER_SINK_INPUT(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), GVC_TYPE_MIXER_SINK_INPUT, GvcMixerSinkInput)) +#define GVC_MIXER_SINK_INPUT_CLASS(k) (G_TYPE_CHECK_CLASS_CAST((k), GVC_TYPE_MIXER_SINK_INPUT, GvcMixerSinkInputClass)) +#define GVC_IS_MIXER_SINK_INPUT(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), GVC_TYPE_MIXER_SINK_INPUT)) +#define GVC_IS_MIXER_SINK_INPUT_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE ((k), GVC_TYPE_MIXER_SINK_INPUT)) +#define GVC_MIXER_SINK_INPUT_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), GVC_TYPE_MIXER_SINK_INPUT, GvcMixerSinkInputClass)) + +typedef struct GvcMixerSinkInputPrivate GvcMixerSinkInputPrivate; + +typedef struct +{ + GvcMixerStream parent; + GvcMixerSinkInputPrivate *priv; +} GvcMixerSinkInput; + +typedef struct +{ + GvcMixerStreamClass parent_class; +} GvcMixerSinkInputClass; + +GType gvc_mixer_sink_input_get_type (void); + +GvcMixerStream * gvc_mixer_sink_input_new (pa_context *context, + guint index, + GvcChannelMap *map); + +G_END_DECLS + +#endif /* __GVC_MIXER_SINK_INPUT_H */ diff --git a/src/gvc/gvc-mixer-sink.c b/src/gvc/gvc-mixer-sink.c new file mode 100644 index 000000000..30fceac4d --- /dev/null +++ b/src/gvc/gvc-mixer-sink.c @@ -0,0 +1,231 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*- + * + * Copyright (C) 2008 William Jon McCann + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + * + */ + +#include "config.h" + +#include +#include +#include + +#include +#include + +#include + +#include "gvc-mixer-sink.h" +#include "gvc-mixer-stream-private.h" +#include "gvc-channel-map-private.h" + +#define GVC_MIXER_SINK_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), GVC_TYPE_MIXER_SINK, GvcMixerSinkPrivate)) + +struct GvcMixerSinkPrivate +{ + gpointer dummy; +}; + +static void gvc_mixer_sink_class_init (GvcMixerSinkClass *klass); +static void gvc_mixer_sink_init (GvcMixerSink *mixer_sink); +static void gvc_mixer_sink_finalize (GObject *object); +static void gvc_mixer_sink_dispose (GObject *object); + +G_DEFINE_TYPE (GvcMixerSink, gvc_mixer_sink, GVC_TYPE_MIXER_STREAM) + +static gboolean +gvc_mixer_sink_push_volume (GvcMixerStream *stream, gpointer *op) +{ + pa_operation *o; + guint index; + const GvcChannelMap *map; + pa_context *context; + const pa_cvolume *cv; + + index = gvc_mixer_stream_get_index (stream); + + map = gvc_mixer_stream_get_channel_map (stream); + + /* set the volume */ + cv = gvc_channel_map_get_cvolume(map); + + context = gvc_mixer_stream_get_pa_context (stream); + + o = pa_context_set_sink_volume_by_index (context, + index, + cv, + NULL, + NULL); + + if (o == NULL) { + g_warning ("pa_context_set_sink_volume_by_index() failed: %s", pa_strerror(pa_context_errno(context))); + return FALSE; + } + + *op = o; + + return TRUE; +} + +static gboolean +gvc_mixer_sink_change_is_muted (GvcMixerStream *stream, + gboolean is_muted) +{ + pa_operation *o; + guint index; + pa_context *context; + + index = gvc_mixer_stream_get_index (stream); + context = gvc_mixer_stream_get_pa_context (stream); + + o = pa_context_set_sink_mute_by_index (context, + index, + is_muted, + NULL, + NULL); + + if (o == NULL) { + g_warning ("pa_context_set_sink_mute_by_index() failed: %s", pa_strerror(pa_context_errno(context))); + return FALSE; + } + + pa_operation_unref(o); + + return TRUE; +} + +static gboolean +gvc_mixer_sink_change_port (GvcMixerStream *stream, + const char *port) +{ +#if PA_MICRO > 15 + pa_operation *o; + guint index; + pa_context *context; + + index = gvc_mixer_stream_get_index (stream); + context = gvc_mixer_stream_get_pa_context (stream); + + o = pa_context_set_sink_port_by_index (context, + index, + port, + NULL, + NULL); + + if (o == NULL) { + g_warning ("pa_context_set_sink_port_by_index() failed: %s", pa_strerror(pa_context_errno(context))); + return FALSE; + } + + pa_operation_unref(o); + + return TRUE; +#else + return FALSE; +#endif /* PA_MICRO > 15 */ +} + +static GObject * +gvc_mixer_sink_constructor (GType type, + guint n_construct_properties, + GObjectConstructParam *construct_params) +{ + GObject *object; + GvcMixerSink *self; + + object = G_OBJECT_CLASS (gvc_mixer_sink_parent_class)->constructor (type, n_construct_properties, construct_params); + + self = GVC_MIXER_SINK (object); + + return object; +} + +static void +gvc_mixer_sink_class_init (GvcMixerSinkClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + GvcMixerStreamClass *stream_class = GVC_MIXER_STREAM_CLASS (klass); + + object_class->constructor = gvc_mixer_sink_constructor; + object_class->dispose = gvc_mixer_sink_dispose; + object_class->finalize = gvc_mixer_sink_finalize; + + stream_class->push_volume = gvc_mixer_sink_push_volume; + stream_class->change_port = gvc_mixer_sink_change_port; + stream_class->change_is_muted = gvc_mixer_sink_change_is_muted; + + g_type_class_add_private (klass, sizeof (GvcMixerSinkPrivate)); +} + +static void +gvc_mixer_sink_init (GvcMixerSink *sink) +{ + sink->priv = GVC_MIXER_SINK_GET_PRIVATE (sink); +} + +static void +gvc_mixer_sink_dispose (GObject *object) +{ + GvcMixerSink *mixer_sink; + + g_return_if_fail (object != NULL); + g_return_if_fail (GVC_IS_MIXER_SINK (object)); + + mixer_sink = GVC_MIXER_SINK (object); + + G_OBJECT_CLASS (gvc_mixer_sink_parent_class)->dispose (object); +} + +static void +gvc_mixer_sink_finalize (GObject *object) +{ + GvcMixerSink *mixer_sink; + + g_return_if_fail (object != NULL); + g_return_if_fail (GVC_IS_MIXER_SINK (object)); + + mixer_sink = GVC_MIXER_SINK (object); + + g_return_if_fail (mixer_sink->priv != NULL); + G_OBJECT_CLASS (gvc_mixer_sink_parent_class)->finalize (object); +} + +/** + * gvc_mixer_sink_new: (skip) + * + * @context: + * @index: + * @channel_map: + * + * Returns: + */ +GvcMixerStream * +gvc_mixer_sink_new (pa_context *context, + guint index, + GvcChannelMap *channel_map) + +{ + GObject *object; + + object = g_object_new (GVC_TYPE_MIXER_SINK, + "pa-context", context, + "index", index, + "channel-map", channel_map, + NULL); + + return GVC_MIXER_STREAM (object); +} diff --git a/src/gvc/gvc-mixer-sink.h b/src/gvc/gvc-mixer-sink.h new file mode 100644 index 000000000..2a4a4badb --- /dev/null +++ b/src/gvc/gvc-mixer-sink.h @@ -0,0 +1,57 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*- + * + * Copyright (C) 2008 Red Hat, Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + * + */ + +#ifndef __GVC_MIXER_SINK_H +#define __GVC_MIXER_SINK_H + +#include +#include "gvc-mixer-stream.h" + +G_BEGIN_DECLS + +#define GVC_TYPE_MIXER_SINK (gvc_mixer_sink_get_type ()) +#define GVC_MIXER_SINK(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), GVC_TYPE_MIXER_SINK, GvcMixerSink)) +#define GVC_MIXER_SINK_CLASS(k) (G_TYPE_CHECK_CLASS_CAST((k), GVC_TYPE_MIXER_SINK, GvcMixerSinkClass)) +#define GVC_IS_MIXER_SINK(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), GVC_TYPE_MIXER_SINK)) +#define GVC_IS_MIXER_SINK_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE ((k), GVC_TYPE_MIXER_SINK)) +#define GVC_MIXER_SINK_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), GVC_TYPE_MIXER_SINK, GvcMixerSinkClass)) + +typedef struct GvcMixerSinkPrivate GvcMixerSinkPrivate; + +typedef struct +{ + GvcMixerStream parent; + GvcMixerSinkPrivate *priv; +} GvcMixerSink; + +typedef struct +{ + GvcMixerStreamClass parent_class; +} GvcMixerSinkClass; + +GType gvc_mixer_sink_get_type (void); + +GvcMixerStream * gvc_mixer_sink_new (pa_context *context, + guint index, + GvcChannelMap *map); + +G_END_DECLS + +#endif /* __GVC_MIXER_SINK_H */ diff --git a/src/gvc/gvc-mixer-source-output.c b/src/gvc/gvc-mixer-source-output.c new file mode 100644 index 000000000..636fc2ea7 --- /dev/null +++ b/src/gvc/gvc-mixer-source-output.c @@ -0,0 +1,137 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*- + * + * Copyright (C) 2008 William Jon McCann + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + * + */ + +#include "config.h" + +#include +#include +#include + +#include +#include + +#include + +#include "gvc-mixer-source-output.h" + +#define GVC_MIXER_SOURCE_OUTPUT_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), GVC_TYPE_MIXER_SOURCE_OUTPUT, GvcMixerSourceOutputPrivate)) + +struct GvcMixerSourceOutputPrivate +{ + gpointer dummy; +}; + +static void gvc_mixer_source_output_class_init (GvcMixerSourceOutputClass *klass); +static void gvc_mixer_source_output_init (GvcMixerSourceOutput *mixer_source_output); +static void gvc_mixer_source_output_finalize (GObject *object); + +G_DEFINE_TYPE (GvcMixerSourceOutput, gvc_mixer_source_output, GVC_TYPE_MIXER_STREAM) + +static gboolean +gvc_mixer_source_output_push_volume (GvcMixerStream *stream, gpointer *op) +{ + /* FIXME: */ + *op = NULL; + return TRUE; +} + +static gboolean +gvc_mixer_source_output_change_is_muted (GvcMixerStream *stream, + gboolean is_muted) +{ + /* FIXME: */ + return TRUE; +} + +static GObject * +gvc_mixer_source_output_constructor (GType type, + guint n_construct_properties, + GObjectConstructParam *construct_params) +{ + GObject *object; + GvcMixerSourceOutput *self; + + object = G_OBJECT_CLASS (gvc_mixer_source_output_parent_class)->constructor (type, n_construct_properties, construct_params); + + self = GVC_MIXER_SOURCE_OUTPUT (object); + + return object; +} + +static void +gvc_mixer_source_output_class_init (GvcMixerSourceOutputClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + GvcMixerStreamClass *stream_class = GVC_MIXER_STREAM_CLASS (klass); + + object_class->constructor = gvc_mixer_source_output_constructor; + object_class->finalize = gvc_mixer_source_output_finalize; + + stream_class->push_volume = gvc_mixer_source_output_push_volume; + stream_class->change_is_muted = gvc_mixer_source_output_change_is_muted; + + g_type_class_add_private (klass, sizeof (GvcMixerSourceOutputPrivate)); +} + +static void +gvc_mixer_source_output_init (GvcMixerSourceOutput *source_output) +{ + source_output->priv = GVC_MIXER_SOURCE_OUTPUT_GET_PRIVATE (source_output); + +} + +static void +gvc_mixer_source_output_finalize (GObject *object) +{ + GvcMixerSourceOutput *mixer_source_output; + + g_return_if_fail (object != NULL); + g_return_if_fail (GVC_IS_MIXER_SOURCE_OUTPUT (object)); + + mixer_source_output = GVC_MIXER_SOURCE_OUTPUT (object); + + g_return_if_fail (mixer_source_output->priv != NULL); + G_OBJECT_CLASS (gvc_mixer_source_output_parent_class)->finalize (object); +} + +/** + * gvc_mixer_source_output_new: (skip) + * + * @context: + * @index: + * @channel_map: + * + * Returns: + */ +GvcMixerStream * +gvc_mixer_source_output_new (pa_context *context, + guint index, + GvcChannelMap *channel_map) +{ + GObject *object; + + object = g_object_new (GVC_TYPE_MIXER_SOURCE_OUTPUT, + "pa-context", context, + "index", index, + "channel-map", channel_map, + NULL); + + return GVC_MIXER_STREAM (object); +} diff --git a/src/gvc/gvc-mixer-source-output.h b/src/gvc/gvc-mixer-source-output.h new file mode 100644 index 000000000..2283e3b67 --- /dev/null +++ b/src/gvc/gvc-mixer-source-output.h @@ -0,0 +1,57 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*- + * + * Copyright (C) 2008 Red Hat, Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + * + */ + +#ifndef __GVC_MIXER_SOURCE_OUTPUT_H +#define __GVC_MIXER_SOURCE_OUTPUT_H + +#include +#include "gvc-mixer-stream.h" + +G_BEGIN_DECLS + +#define GVC_TYPE_MIXER_SOURCE_OUTPUT (gvc_mixer_source_output_get_type ()) +#define GVC_MIXER_SOURCE_OUTPUT(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), GVC_TYPE_MIXER_SOURCE_OUTPUT, GvcMixerSourceOutput)) +#define GVC_MIXER_SOURCE_OUTPUT_CLASS(k) (G_TYPE_CHECK_CLASS_CAST((k), GVC_TYPE_MIXER_SOURCE_OUTPUT, GvcMixerSourceOutputClass)) +#define GVC_IS_MIXER_SOURCE_OUTPUT(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), GVC_TYPE_MIXER_SOURCE_OUTPUT)) +#define GVC_IS_MIXER_SOURCE_OUTPUT_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE ((k), GVC_TYPE_MIXER_SOURCE_OUTPUT)) +#define GVC_MIXER_SOURCE_OUTPUT_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), GVC_TYPE_MIXER_SOURCE_OUTPUT, GvcMixerSourceOutputClass)) + +typedef struct GvcMixerSourceOutputPrivate GvcMixerSourceOutputPrivate; + +typedef struct +{ + GvcMixerStream parent; + GvcMixerSourceOutputPrivate *priv; +} GvcMixerSourceOutput; + +typedef struct +{ + GvcMixerStreamClass parent_class; +} GvcMixerSourceOutputClass; + +GType gvc_mixer_source_output_get_type (void); + +GvcMixerStream * gvc_mixer_source_output_new (pa_context *context, + guint index, + GvcChannelMap *map); + +G_END_DECLS + +#endif /* __GVC_MIXER_SOURCE_OUTPUT_H */ diff --git a/src/gvc/gvc-mixer-source.c b/src/gvc/gvc-mixer-source.c new file mode 100644 index 000000000..46d640380 --- /dev/null +++ b/src/gvc/gvc-mixer-source.c @@ -0,0 +1,231 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*- + * + * Copyright (C) 2008 William Jon McCann + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + * + */ + +#include "config.h" + +#include +#include +#include + +#include +#include + +#include + +#include "gvc-mixer-source.h" +#include "gvc-mixer-stream-private.h" +#include "gvc-channel-map-private.h" + +#define GVC_MIXER_SOURCE_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), GVC_TYPE_MIXER_SOURCE, GvcMixerSourcePrivate)) + +struct GvcMixerSourcePrivate +{ + gpointer dummy; +}; + +static void gvc_mixer_source_class_init (GvcMixerSourceClass *klass); +static void gvc_mixer_source_init (GvcMixerSource *mixer_source); +static void gvc_mixer_source_finalize (GObject *object); +static void gvc_mixer_source_dispose (GObject *object); + +G_DEFINE_TYPE (GvcMixerSource, gvc_mixer_source, GVC_TYPE_MIXER_STREAM) + +static gboolean +gvc_mixer_source_push_volume (GvcMixerStream *stream, gpointer *op) +{ + pa_operation *o; + guint index; + const GvcChannelMap *map; + pa_context *context; + const pa_cvolume *cv; + + index = gvc_mixer_stream_get_index (stream); + + map = gvc_mixer_stream_get_channel_map (stream); + + /* set the volume */ + cv = gvc_channel_map_get_cvolume (map); + + context = gvc_mixer_stream_get_pa_context (stream); + + o = pa_context_set_source_volume_by_index (context, + index, + cv, + NULL, + NULL); + + if (o == NULL) { + g_warning ("pa_context_set_source_volume_by_index() failed: %s", pa_strerror(pa_context_errno(context))); + return FALSE; + } + + *op = o; + + return TRUE; +} + +static gboolean +gvc_mixer_source_change_is_muted (GvcMixerStream *stream, + gboolean is_muted) +{ + pa_operation *o; + guint index; + pa_context *context; + + index = gvc_mixer_stream_get_index (stream); + context = gvc_mixer_stream_get_pa_context (stream); + + o = pa_context_set_source_mute_by_index (context, + index, + is_muted, + NULL, + NULL); + + if (o == NULL) { + g_warning ("pa_context_set_source_mute_by_index() failed: %s", pa_strerror(pa_context_errno(context))); + return FALSE; + } + + pa_operation_unref(o); + + return TRUE; +} + +static gboolean +gvc_mixer_source_change_port (GvcMixerStream *stream, + const char *port) +{ +#if PA_MICRO > 15 + pa_operation *o; + guint index; + pa_context *context; + + index = gvc_mixer_stream_get_index (stream); + context = gvc_mixer_stream_get_pa_context (stream); + + o = pa_context_set_source_port_by_index (context, + index, + port, + NULL, + NULL); + + if (o == NULL) { + g_warning ("pa_context_set_source_port_by_index() failed: %s", pa_strerror(pa_context_errno(context))); + return FALSE; + } + + pa_operation_unref(o); + + return TRUE; +#else + return FALSE; +#endif /* PA_MICRO > 15 */ +} + +static GObject * +gvc_mixer_source_constructor (GType type, + guint n_construct_properties, + GObjectConstructParam *construct_params) +{ + GObject *object; + GvcMixerSource *self; + + object = G_OBJECT_CLASS (gvc_mixer_source_parent_class)->constructor (type, n_construct_properties, construct_params); + + self = GVC_MIXER_SOURCE (object); + + return object; +} + +static void +gvc_mixer_source_class_init (GvcMixerSourceClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + GvcMixerStreamClass *stream_class = GVC_MIXER_STREAM_CLASS (klass); + + object_class->constructor = gvc_mixer_source_constructor; + object_class->dispose = gvc_mixer_source_dispose; + object_class->finalize = gvc_mixer_source_finalize; + + stream_class->push_volume = gvc_mixer_source_push_volume; + stream_class->change_is_muted = gvc_mixer_source_change_is_muted; + stream_class->change_port = gvc_mixer_source_change_port; + + g_type_class_add_private (klass, sizeof (GvcMixerSourcePrivate)); +} + +static void +gvc_mixer_source_init (GvcMixerSource *source) +{ + source->priv = GVC_MIXER_SOURCE_GET_PRIVATE (source); +} + +static void +gvc_mixer_source_dispose (GObject *object) +{ + GvcMixerSource *mixer_source; + + g_return_if_fail (object != NULL); + g_return_if_fail (GVC_IS_MIXER_SOURCE (object)); + + mixer_source = GVC_MIXER_SOURCE (object); + + G_OBJECT_CLASS (gvc_mixer_source_parent_class)->dispose (object); +} + +static void +gvc_mixer_source_finalize (GObject *object) +{ + GvcMixerSource *mixer_source; + + g_return_if_fail (object != NULL); + g_return_if_fail (GVC_IS_MIXER_SOURCE (object)); + + mixer_source = GVC_MIXER_SOURCE (object); + + g_return_if_fail (mixer_source->priv != NULL); + G_OBJECT_CLASS (gvc_mixer_source_parent_class)->finalize (object); +} + +/** + * gvc_mixer_source_new: (skip) + * + * @context: + * @index: + * @channel_map: + * + * Returns: + */ +GvcMixerStream * +gvc_mixer_source_new (pa_context *context, + guint index, + GvcChannelMap *channel_map) + +{ + GObject *object; + + object = g_object_new (GVC_TYPE_MIXER_SOURCE, + "pa-context", context, + "index", index, + "channel-map", channel_map, + NULL); + + return GVC_MIXER_STREAM (object); +} diff --git a/src/gvc/gvc-mixer-source.h b/src/gvc/gvc-mixer-source.h new file mode 100644 index 000000000..503f1b54d --- /dev/null +++ b/src/gvc/gvc-mixer-source.h @@ -0,0 +1,57 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*- + * + * Copyright (C) 2008 Red Hat, Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + * + */ + +#ifndef __GVC_MIXER_SOURCE_H +#define __GVC_MIXER_SOURCE_H + +#include +#include "gvc-mixer-stream.h" + +G_BEGIN_DECLS + +#define GVC_TYPE_MIXER_SOURCE (gvc_mixer_source_get_type ()) +#define GVC_MIXER_SOURCE(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), GVC_TYPE_MIXER_SOURCE, GvcMixerSource)) +#define GVC_MIXER_SOURCE_CLASS(k) (G_TYPE_CHECK_CLASS_CAST((k), GVC_TYPE_MIXER_SOURCE, GvcMixerSourceClass)) +#define GVC_IS_MIXER_SOURCE(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), GVC_TYPE_MIXER_SOURCE)) +#define GVC_IS_MIXER_SOURCE_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE ((k), GVC_TYPE_MIXER_SOURCE)) +#define GVC_MIXER_SOURCE_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), GVC_TYPE_MIXER_SOURCE, GvcMixerSourceClass)) + +typedef struct GvcMixerSourcePrivate GvcMixerSourcePrivate; + +typedef struct +{ + GvcMixerStream parent; + GvcMixerSourcePrivate *priv; +} GvcMixerSource; + +typedef struct +{ + GvcMixerStreamClass parent_class; +} GvcMixerSourceClass; + +GType gvc_mixer_source_get_type (void); + +GvcMixerStream * gvc_mixer_source_new (pa_context *context, + guint index, + GvcChannelMap *map); + +G_END_DECLS + +#endif /* __GVC_MIXER_SOURCE_H */ diff --git a/src/gvc/gvc-mixer-stream-private.h b/src/gvc/gvc-mixer-stream-private.h new file mode 100644 index 000000000..b97ecf5e1 --- /dev/null +++ b/src/gvc/gvc-mixer-stream-private.h @@ -0,0 +1,34 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*- + * + * Copyright (C) 2008 Red Hat, Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + * + */ + +#ifndef __GVC_MIXER_STREAM_PRIVATE_H +#define __GVC_MIXER_STREAM_PRIVATE_H + +#include + +#include "gvc-channel-map.h" + +G_BEGIN_DECLS + +pa_context * gvc_mixer_stream_get_pa_context (GvcMixerStream *stream); + +G_END_DECLS + +#endif /* __GVC_MIXER_STREAM_PRIVATE_H */ diff --git a/src/gvc/gvc-mixer-stream.c b/src/gvc/gvc-mixer-stream.c new file mode 100644 index 000000000..3b4953aa0 --- /dev/null +++ b/src/gvc/gvc-mixer-stream.c @@ -0,0 +1,944 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*- + * + * Copyright (C) 2008 William Jon McCann + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + * + */ + +#include "config.h" + +#include +#include +#include + +#include +#include + +#include + +#include "gvc-mixer-stream.h" +#include "gvc-mixer-stream-private.h" +#include "gvc-channel-map-private.h" + +#define GVC_MIXER_STREAM_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), GVC_TYPE_MIXER_STREAM, GvcMixerStreamPrivate)) + +static guint32 stream_serial = 1; + +struct GvcMixerStreamPrivate +{ + pa_context *pa_context; + guint id; + guint index; + gint card_index; + GvcChannelMap *channel_map; + char *name; + char *description; + char *application_id; + char *icon_name; + gboolean is_muted; + gboolean can_decibel; + gboolean is_event_stream; + gboolean is_virtual; + pa_volume_t base_volume; + pa_operation *change_volume_op; + char *port; + char *human_port; + GList *ports; +}; + +enum +{ + PROP_0, + PROP_ID, + PROP_PA_CONTEXT, + PROP_CHANNEL_MAP, + PROP_INDEX, + PROP_NAME, + PROP_DESCRIPTION, + PROP_APPLICATION_ID, + PROP_ICON_NAME, + PROP_VOLUME, + PROP_DECIBEL, + PROP_IS_MUTED, + PROP_CAN_DECIBEL, + PROP_IS_EVENT_STREAM, + PROP_IS_VIRTUAL, + PROP_CARD_INDEX, + PROP_PORT, +}; + +static void gvc_mixer_stream_class_init (GvcMixerStreamClass *klass); +static void gvc_mixer_stream_init (GvcMixerStream *mixer_stream); +static void gvc_mixer_stream_finalize (GObject *object); + +G_DEFINE_ABSTRACT_TYPE (GvcMixerStream, gvc_mixer_stream, G_TYPE_OBJECT) + +static guint32 +get_next_stream_serial (void) +{ + guint32 serial; + + serial = stream_serial++; + + if ((gint32)stream_serial < 0) { + stream_serial = 1; + } + + return serial; +} + +pa_context * +gvc_mixer_stream_get_pa_context (GvcMixerStream *stream) +{ + g_return_val_if_fail (GVC_IS_MIXER_STREAM (stream), 0); + return stream->priv->pa_context; +} + +guint +gvc_mixer_stream_get_index (GvcMixerStream *stream) +{ + g_return_val_if_fail (GVC_IS_MIXER_STREAM (stream), 0); + return stream->priv->index; +} + +guint +gvc_mixer_stream_get_id (GvcMixerStream *stream) +{ + g_return_val_if_fail (GVC_IS_MIXER_STREAM (stream), 0); + return stream->priv->id; +} + +const GvcChannelMap * +gvc_mixer_stream_get_channel_map (GvcMixerStream *stream) +{ + g_return_val_if_fail (GVC_IS_MIXER_STREAM (stream), NULL); + return stream->priv->channel_map; +} + +/** + * gvc_mixer_stream_get_volume: + * + * @stream: + * + * Returns: (type guint32) (transfer none): + */ +pa_volume_t +gvc_mixer_stream_get_volume (GvcMixerStream *stream) +{ + g_return_val_if_fail (GVC_IS_MIXER_STREAM (stream), 0); + + return (pa_volume_t) gvc_channel_map_get_volume(stream->priv->channel_map)[VOLUME]; +} + +gdouble +gvc_mixer_stream_get_decibel (GvcMixerStream *stream) +{ + g_return_val_if_fail (GVC_IS_MIXER_STREAM (stream), 0); + + return pa_sw_volume_to_dB( + (pa_volume_t) gvc_channel_map_get_volume(stream->priv->channel_map)[VOLUME]); +} + +/** + * gvc_mixer_stream_set_volume: + * + * @stream: + * @volume: (type guint32): + * + * Returns: + */ +gboolean +gvc_mixer_stream_set_volume (GvcMixerStream *stream, + pa_volume_t volume) +{ + pa_cvolume cv; + + g_return_val_if_fail (GVC_IS_MIXER_STREAM (stream), FALSE); + + cv = *gvc_channel_map_get_cvolume(stream->priv->channel_map); + pa_cvolume_scale(&cv, volume); + + if (!pa_cvolume_equal(gvc_channel_map_get_cvolume(stream->priv->channel_map), &cv)) { + gvc_channel_map_volume_changed(stream->priv->channel_map, &cv, FALSE); + g_object_notify (G_OBJECT (stream), "volume"); + return TRUE; + } + + return FALSE; +} + +gboolean +gvc_mixer_stream_set_decibel (GvcMixerStream *stream, + gdouble db) +{ + pa_cvolume cv; + + g_return_val_if_fail (GVC_IS_MIXER_STREAM (stream), FALSE); + + cv = *gvc_channel_map_get_cvolume(stream->priv->channel_map); + pa_cvolume_scale(&cv, pa_sw_volume_from_dB(db)); + + if (!pa_cvolume_equal(gvc_channel_map_get_cvolume(stream->priv->channel_map), &cv)) { + gvc_channel_map_volume_changed(stream->priv->channel_map, &cv, FALSE); + g_object_notify (G_OBJECT (stream), "volume"); + } + + return TRUE; +} + +gboolean +gvc_mixer_stream_get_is_muted (GvcMixerStream *stream) +{ + g_return_val_if_fail (GVC_IS_MIXER_STREAM (stream), FALSE); + return stream->priv->is_muted; +} + +gboolean +gvc_mixer_stream_get_can_decibel (GvcMixerStream *stream) +{ + g_return_val_if_fail (GVC_IS_MIXER_STREAM (stream), FALSE); + return stream->priv->can_decibel; +} + +gboolean +gvc_mixer_stream_set_is_muted (GvcMixerStream *stream, + gboolean is_muted) +{ + g_return_val_if_fail (GVC_IS_MIXER_STREAM (stream), FALSE); + + if (is_muted != stream->priv->is_muted) { + stream->priv->is_muted = is_muted; + g_object_notify (G_OBJECT (stream), "is-muted"); + } + + return TRUE; +} + +gboolean +gvc_mixer_stream_set_can_decibel (GvcMixerStream *stream, + gboolean can_decibel) +{ + g_return_val_if_fail (GVC_IS_MIXER_STREAM (stream), FALSE); + + if (can_decibel != stream->priv->can_decibel) { + stream->priv->can_decibel = can_decibel; + g_object_notify (G_OBJECT (stream), "can-decibel"); + } + + return TRUE; +} + +const char * +gvc_mixer_stream_get_name (GvcMixerStream *stream) +{ + g_return_val_if_fail (GVC_IS_MIXER_STREAM (stream), NULL); + return stream->priv->name; +} + +const char * +gvc_mixer_stream_get_description (GvcMixerStream *stream) +{ + g_return_val_if_fail (GVC_IS_MIXER_STREAM (stream), NULL); + return stream->priv->description; +} + +gboolean +gvc_mixer_stream_set_name (GvcMixerStream *stream, + const char *name) +{ + g_return_val_if_fail (GVC_IS_MIXER_STREAM (stream), FALSE); + + g_free (stream->priv->name); + stream->priv->name = g_strdup (name); + g_object_notify (G_OBJECT (stream), "name"); + + return TRUE; +} + +gboolean +gvc_mixer_stream_set_description (GvcMixerStream *stream, + const char *description) +{ + g_return_val_if_fail (GVC_IS_MIXER_STREAM (stream), FALSE); + + g_free (stream->priv->description); + stream->priv->description = g_strdup (description); + g_object_notify (G_OBJECT (stream), "description"); + + return TRUE; +} + +gboolean +gvc_mixer_stream_is_event_stream (GvcMixerStream *stream) +{ + g_return_val_if_fail (GVC_IS_MIXER_STREAM (stream), FALSE); + + return stream->priv->is_event_stream; +} + +gboolean +gvc_mixer_stream_set_is_event_stream (GvcMixerStream *stream, + gboolean is_event_stream) +{ + g_return_val_if_fail (GVC_IS_MIXER_STREAM (stream), FALSE); + + stream->priv->is_event_stream = is_event_stream; + g_object_notify (G_OBJECT (stream), "is-event-stream"); + + return TRUE; +} + +gboolean +gvc_mixer_stream_is_virtual (GvcMixerStream *stream) +{ + g_return_val_if_fail (GVC_IS_MIXER_STREAM (stream), FALSE); + + return stream->priv->is_virtual; +} + +gboolean +gvc_mixer_stream_set_is_virtual (GvcMixerStream *stream, + gboolean is_virtual) +{ + g_return_val_if_fail (GVC_IS_MIXER_STREAM (stream), FALSE); + + stream->priv->is_virtual = is_virtual; + g_object_notify (G_OBJECT (stream), "is-virtual"); + + return TRUE; +} + +const char * +gvc_mixer_stream_get_application_id (GvcMixerStream *stream) +{ + g_return_val_if_fail (GVC_IS_MIXER_STREAM (stream), NULL); + return stream->priv->application_id; +} + +gboolean +gvc_mixer_stream_set_application_id (GvcMixerStream *stream, + const char *application_id) +{ + g_return_val_if_fail (GVC_IS_MIXER_STREAM (stream), FALSE); + + g_free (stream->priv->application_id); + stream->priv->application_id = g_strdup (application_id); + g_object_notify (G_OBJECT (stream), "application-id"); + + return TRUE; +} + +static void +on_channel_map_volume_changed (GvcChannelMap *channel_map, + gboolean set, + GvcMixerStream *stream) +{ + if (set == TRUE) + gvc_mixer_stream_push_volume (stream); + + g_object_notify (G_OBJECT (stream), "volume"); +} + +static gboolean +gvc_mixer_stream_set_channel_map (GvcMixerStream *stream, + GvcChannelMap *channel_map) +{ + g_return_val_if_fail (GVC_IS_MIXER_STREAM (stream), FALSE); + + if (channel_map != NULL) { + g_object_ref (channel_map); + } + + if (stream->priv->channel_map != NULL) { + g_signal_handlers_disconnect_by_func (stream->priv->channel_map, + on_channel_map_volume_changed, + stream); + g_object_unref (stream->priv->channel_map); + } + + stream->priv->channel_map = channel_map; + + if (stream->priv->channel_map != NULL) { + g_signal_connect (stream->priv->channel_map, + "volume-changed", + G_CALLBACK (on_channel_map_volume_changed), + stream); + + g_object_notify (G_OBJECT (stream), "channel-map"); + } + + return TRUE; +} + +const char * +gvc_mixer_stream_get_icon_name (GvcMixerStream *stream) +{ + g_return_val_if_fail (GVC_IS_MIXER_STREAM (stream), NULL); + return stream->priv->icon_name; +} + +gboolean +gvc_mixer_stream_set_icon_name (GvcMixerStream *stream, + const char *icon_name) +{ + g_return_val_if_fail (GVC_IS_MIXER_STREAM (stream), FALSE); + + g_free (stream->priv->icon_name); + stream->priv->icon_name = g_strdup (icon_name); + g_object_notify (G_OBJECT (stream), "icon-name"); + + return TRUE; +} + +/** + * gvc_mixer_stream_get_base_volume: + * + * @stream: + * + * Returns: (type guint32) (transfer none): + */ +pa_volume_t +gvc_mixer_stream_get_base_volume (GvcMixerStream *stream) +{ + g_return_val_if_fail (GVC_IS_MIXER_STREAM (stream), 0); + + return stream->priv->base_volume; +} + +/** + * gvc_mixer_stream_set_base_volume: + * + * @stream: + * @base_volume: (type guint32): + * + * Returns: + */ +gboolean +gvc_mixer_stream_set_base_volume (GvcMixerStream *stream, + pa_volume_t base_volume) +{ + g_return_val_if_fail (GVC_IS_MIXER_STREAM (stream), FALSE); + + stream->priv->base_volume = base_volume; + + return TRUE; +} + +const GvcMixerStreamPort * +gvc_mixer_stream_get_port (GvcMixerStream *stream) +{ + GList *l; + + g_return_val_if_fail (GVC_IS_MIXER_STREAM (stream), NULL); + g_return_val_if_fail (stream->priv->ports != NULL, NULL); + + for (l = stream->priv->ports; l != NULL; l = l->next) { + GvcMixerStreamPort *p = l->data; + if (g_strcmp0 (stream->priv->port, p->port) == 0) { + return p; + } + } + + g_assert_not_reached (); + + return NULL; +} + +gboolean +gvc_mixer_stream_set_port (GvcMixerStream *stream, + const char *port) +{ + GList *l; + + g_return_val_if_fail (GVC_IS_MIXER_STREAM (stream), FALSE); + g_return_val_if_fail (stream->priv->ports != NULL, FALSE); + + g_free (stream->priv->port); + stream->priv->port = g_strdup (port); + + g_free (stream->priv->human_port); + stream->priv->human_port = NULL; + + for (l = stream->priv->ports; l != NULL; l = l->next) { + GvcMixerStreamPort *p = l->data; + if (g_str_equal (stream->priv->port, p->port)) { + stream->priv->human_port = g_strdup (p->human_port); + break; + } + } + + g_object_notify (G_OBJECT (stream), "port"); + + return TRUE; +} + +gboolean +gvc_mixer_stream_change_port (GvcMixerStream *stream, + const char *port) +{ + g_return_val_if_fail (GVC_IS_MIXER_STREAM (stream), FALSE); + return GVC_MIXER_STREAM_GET_CLASS (stream)->change_port (stream, port); +} + +const GList * +gvc_mixer_stream_get_ports (GvcMixerStream *stream) +{ + g_return_val_if_fail (GVC_IS_MIXER_STREAM (stream), FALSE); + return stream->priv->ports; +} + +static int +sort_ports (GvcMixerStreamPort *a, + GvcMixerStreamPort *b) +{ + if (a->priority == b->priority) + return 0; + if (a->priority > b->priority) + return 1; + return -1; +} + +gboolean +gvc_mixer_stream_set_ports (GvcMixerStream *stream, + GList *ports) +{ + g_return_val_if_fail (GVC_IS_MIXER_STREAM (stream), FALSE); + g_return_val_if_fail (stream->priv->ports == NULL, FALSE); + + stream->priv->ports = g_list_sort (ports, (GCompareFunc) sort_ports); + + return TRUE; +} + +gint +gvc_mixer_stream_get_card_index (GvcMixerStream *stream) +{ + g_return_val_if_fail (GVC_IS_MIXER_STREAM (stream), PA_INVALID_INDEX); + return stream->priv->card_index; +} + +gboolean +gvc_mixer_stream_set_card_index (GvcMixerStream *stream, + gint card_index) +{ + g_return_val_if_fail (GVC_IS_MIXER_STREAM (stream), FALSE); + + stream->priv->card_index = card_index; + g_object_notify (G_OBJECT (stream), "card-index"); + + return TRUE; +} + +static void +gvc_mixer_stream_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + GvcMixerStream *self = GVC_MIXER_STREAM (object); + + switch (prop_id) { + case PROP_PA_CONTEXT: + self->priv->pa_context = g_value_get_pointer (value); + break; + case PROP_INDEX: + self->priv->index = g_value_get_ulong (value); + break; + case PROP_ID: + self->priv->id = g_value_get_ulong (value); + break; + case PROP_CHANNEL_MAP: + gvc_mixer_stream_set_channel_map (self, g_value_get_object (value)); + break; + case PROP_NAME: + gvc_mixer_stream_set_name (self, g_value_get_string (value)); + break; + case PROP_DESCRIPTION: + gvc_mixer_stream_set_description (self, g_value_get_string (value)); + break; + case PROP_APPLICATION_ID: + gvc_mixer_stream_set_application_id (self, g_value_get_string (value)); + break; + case PROP_ICON_NAME: + gvc_mixer_stream_set_icon_name (self, g_value_get_string (value)); + break; + case PROP_VOLUME: + gvc_mixer_stream_set_volume (self, g_value_get_ulong (value)); + break; + case PROP_DECIBEL: + gvc_mixer_stream_set_decibel (self, g_value_get_double (value)); + break; + case PROP_IS_MUTED: + gvc_mixer_stream_set_is_muted (self, g_value_get_boolean (value)); + break; + case PROP_IS_EVENT_STREAM: + gvc_mixer_stream_set_is_event_stream (self, g_value_get_boolean (value)); + break; + case PROP_IS_VIRTUAL: + gvc_mixer_stream_set_is_virtual (self, g_value_get_boolean (value)); + break; + case PROP_CAN_DECIBEL: + gvc_mixer_stream_set_can_decibel (self, g_value_get_boolean (value)); + break; + case PROP_PORT: + gvc_mixer_stream_set_port (self, g_value_get_string (value)); + break; + case PROP_CARD_INDEX: + self->priv->card_index = g_value_get_long (value); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +gvc_mixer_stream_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + GvcMixerStream *self = GVC_MIXER_STREAM (object); + + switch (prop_id) { + case PROP_PA_CONTEXT: + g_value_set_pointer (value, self->priv->pa_context); + break; + case PROP_INDEX: + g_value_set_ulong (value, self->priv->index); + break; + case PROP_ID: + g_value_set_ulong (value, self->priv->id); + break; + case PROP_CHANNEL_MAP: + g_value_set_object (value, self->priv->channel_map); + break; + case PROP_NAME: + g_value_set_string (value, self->priv->name); + break; + case PROP_DESCRIPTION: + g_value_set_string (value, self->priv->description); + break; + case PROP_APPLICATION_ID: + g_value_set_string (value, self->priv->application_id); + break; + case PROP_ICON_NAME: + g_value_set_string (value, self->priv->icon_name); + break; + case PROP_VOLUME: + g_value_set_ulong (value, + pa_cvolume_max(gvc_channel_map_get_cvolume(self->priv->channel_map))); + break; + case PROP_DECIBEL: + g_value_set_double (value, + pa_sw_volume_to_dB(pa_cvolume_max(gvc_channel_map_get_cvolume(self->priv->channel_map)))); + break; + case PROP_IS_MUTED: + g_value_set_boolean (value, self->priv->is_muted); + break; + case PROP_IS_EVENT_STREAM: + g_value_set_boolean (value, self->priv->is_event_stream); + break; + case PROP_IS_VIRTUAL: + g_value_set_boolean (value, self->priv->is_virtual); + break; + case PROP_CAN_DECIBEL: + g_value_set_boolean (value, self->priv->can_decibel); + break; + case PROP_PORT: + g_value_set_string (value, self->priv->port); + break; + case PROP_CARD_INDEX: + g_value_set_long (value, self->priv->card_index); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static GObject * +gvc_mixer_stream_constructor (GType type, + guint n_construct_properties, + GObjectConstructParam *construct_params) +{ + GObject *object; + GvcMixerStream *self; + + object = G_OBJECT_CLASS (gvc_mixer_stream_parent_class)->constructor (type, n_construct_properties, construct_params); + + self = GVC_MIXER_STREAM (object); + + self->priv->id = get_next_stream_serial (); + + return object; +} + +static gboolean +gvc_mixer_stream_real_change_port (GvcMixerStream *stream, + const char *port) +{ + return FALSE; +} + +static gboolean +gvc_mixer_stream_real_push_volume (GvcMixerStream *stream, gpointer *op) +{ + return FALSE; +} + +static gboolean +gvc_mixer_stream_real_change_is_muted (GvcMixerStream *stream, + gboolean is_muted) +{ + return FALSE; +} + +gboolean +gvc_mixer_stream_push_volume (GvcMixerStream *stream) +{ + pa_operation *op; + gboolean ret; + + g_return_val_if_fail (GVC_IS_MIXER_STREAM (stream), FALSE); + + if (stream->priv->is_event_stream != FALSE) + return TRUE; + + g_debug ("Pushing new volume to stream '%s' (%s)", + stream->priv->description, stream->priv->name); + + ret = GVC_MIXER_STREAM_GET_CLASS (stream)->push_volume (stream, (gpointer *) &op); + if (ret) { + if (stream->priv->change_volume_op != NULL) + pa_operation_unref (stream->priv->change_volume_op); + stream->priv->change_volume_op = op; + } + return ret; +} + +gboolean +gvc_mixer_stream_change_is_muted (GvcMixerStream *stream, + gboolean is_muted) +{ + gboolean ret; + g_return_val_if_fail (GVC_IS_MIXER_STREAM (stream), FALSE); + ret = GVC_MIXER_STREAM_GET_CLASS (stream)->change_is_muted (stream, is_muted); + return ret; +} + +gboolean +gvc_mixer_stream_is_running (GvcMixerStream *stream) +{ + if (stream->priv->change_volume_op == NULL) + return FALSE; + + if ((pa_operation_get_state(stream->priv->change_volume_op) == PA_OPERATION_RUNNING)) + return TRUE; + + pa_operation_unref(stream->priv->change_volume_op); + stream->priv->change_volume_op = NULL; + + return FALSE; +} + +static void +gvc_mixer_stream_class_init (GvcMixerStreamClass *klass) +{ + GObjectClass *gobject_class = G_OBJECT_CLASS (klass); + + gobject_class->constructor = gvc_mixer_stream_constructor; + gobject_class->finalize = gvc_mixer_stream_finalize; + gobject_class->set_property = gvc_mixer_stream_set_property; + gobject_class->get_property = gvc_mixer_stream_get_property; + + klass->push_volume = gvc_mixer_stream_real_push_volume; + klass->change_port = gvc_mixer_stream_real_change_port; + klass->change_is_muted = gvc_mixer_stream_real_change_is_muted; + + g_object_class_install_property (gobject_class, + PROP_INDEX, + g_param_spec_ulong ("index", + "Index", + "The index for this stream", + 0, G_MAXULONG, 0, + G_PARAM_READWRITE|G_PARAM_CONSTRUCT_ONLY)); + g_object_class_install_property (gobject_class, + PROP_ID, + g_param_spec_ulong ("id", + "id", + "The id for this stream", + 0, G_MAXULONG, 0, + G_PARAM_READWRITE|G_PARAM_CONSTRUCT_ONLY)); + g_object_class_install_property (gobject_class, + PROP_CHANNEL_MAP, + g_param_spec_object ("channel-map", + "channel map", + "The channel map for this stream", + GVC_TYPE_CHANNEL_MAP, + G_PARAM_READWRITE|G_PARAM_CONSTRUCT)); + g_object_class_install_property (gobject_class, + PROP_PA_CONTEXT, + g_param_spec_pointer ("pa-context", + "PulseAudio context", + "The PulseAudio context for this stream", + G_PARAM_READWRITE|G_PARAM_CONSTRUCT_ONLY)); + g_object_class_install_property (gobject_class, + PROP_VOLUME, + g_param_spec_ulong ("volume", + "Volume", + "The volume for this stream", + 0, G_MAXULONG, 0, + G_PARAM_READWRITE)); + g_object_class_install_property (gobject_class, + PROP_DECIBEL, + g_param_spec_double ("decibel", + "Decibel", + "The decibel level for this stream", + -G_MAXDOUBLE, G_MAXDOUBLE, 0, + G_PARAM_READWRITE|G_PARAM_CONSTRUCT)); + + g_object_class_install_property (gobject_class, + PROP_NAME, + g_param_spec_string ("name", + "Name", + "Name to display for this stream", + NULL, + G_PARAM_READWRITE|G_PARAM_CONSTRUCT)); + g_object_class_install_property (gobject_class, + PROP_DESCRIPTION, + g_param_spec_string ("description", + "Description", + "Description to display for this stream", + NULL, + G_PARAM_READWRITE|G_PARAM_CONSTRUCT)); + g_object_class_install_property (gobject_class, + PROP_APPLICATION_ID, + g_param_spec_string ("application-id", + "Application identifier", + "Application identifier for this stream", + NULL, + G_PARAM_READWRITE|G_PARAM_CONSTRUCT)); + g_object_class_install_property (gobject_class, + PROP_ICON_NAME, + g_param_spec_string ("icon-name", + "Icon Name", + "Name of icon to display for this stream", + NULL, + G_PARAM_READWRITE|G_PARAM_CONSTRUCT)); + g_object_class_install_property (gobject_class, + PROP_IS_MUTED, + g_param_spec_boolean ("is-muted", + "is muted", + "Whether stream is muted", + FALSE, + G_PARAM_READWRITE|G_PARAM_CONSTRUCT)); + g_object_class_install_property (gobject_class, + PROP_CAN_DECIBEL, + g_param_spec_boolean ("can-decibel", + "can decibel", + "Whether stream volume can be converted to decibel units", + FALSE, + G_PARAM_READWRITE|G_PARAM_CONSTRUCT)); + g_object_class_install_property (gobject_class, + PROP_IS_EVENT_STREAM, + g_param_spec_boolean ("is-event-stream", + "is event stream", + "Whether stream's role is to play an event", + FALSE, + G_PARAM_READWRITE|G_PARAM_CONSTRUCT)); + g_object_class_install_property (gobject_class, + PROP_IS_VIRTUAL, + g_param_spec_boolean ("is-virtual", + "is virtual stream", + "Whether the stream is virtual", + FALSE, + G_PARAM_READWRITE|G_PARAM_CONSTRUCT)); + g_object_class_install_property (gobject_class, + PROP_PORT, + g_param_spec_string ("port", + "Port", + "The name of the current port for this stream", + NULL, + G_PARAM_READWRITE)); + g_object_class_install_property (gobject_class, + PROP_CARD_INDEX, + g_param_spec_long ("card-index", + "Card index", + "The index of the card for this stream", + PA_INVALID_INDEX, G_MAXLONG, PA_INVALID_INDEX, + G_PARAM_READWRITE|G_PARAM_CONSTRUCT)); + g_type_class_add_private (klass, sizeof (GvcMixerStreamPrivate)); +} + +static void +gvc_mixer_stream_init (GvcMixerStream *stream) +{ + stream->priv = GVC_MIXER_STREAM_GET_PRIVATE (stream); +} + +static void +free_port (GvcMixerStreamPort *p) +{ + g_free (p->port); + g_free (p->human_port); + g_free (p); +} + +static void +gvc_mixer_stream_finalize (GObject *object) +{ + GvcMixerStream *mixer_stream; + + g_return_if_fail (object != NULL); + g_return_if_fail (GVC_IS_MIXER_STREAM (object)); + + mixer_stream = GVC_MIXER_STREAM (object); + + g_return_if_fail (mixer_stream->priv != NULL); + + g_object_unref (mixer_stream->priv->channel_map); + mixer_stream->priv->channel_map = NULL; + + g_free (mixer_stream->priv->name); + mixer_stream->priv->name = NULL; + + g_free (mixer_stream->priv->description); + mixer_stream->priv->description = NULL; + + g_free (mixer_stream->priv->application_id); + mixer_stream->priv->application_id = NULL; + + g_free (mixer_stream->priv->icon_name); + mixer_stream->priv->icon_name = NULL; + + g_free (mixer_stream->priv->port); + mixer_stream->priv->port = NULL; + + g_free (mixer_stream->priv->human_port); + mixer_stream->priv->human_port = NULL; + + g_list_foreach (mixer_stream->priv->ports, (GFunc) free_port, NULL); + g_list_free (mixer_stream->priv->ports); + mixer_stream->priv->ports = NULL; + + if (mixer_stream->priv->change_volume_op) { + pa_operation_unref(mixer_stream->priv->change_volume_op); + mixer_stream->priv->change_volume_op = NULL; + } + + G_OBJECT_CLASS (gvc_mixer_stream_parent_class)->finalize (object); +} diff --git a/src/gvc/gvc-mixer-stream.h b/src/gvc/gvc-mixer-stream.h new file mode 100644 index 000000000..53b7eb6d6 --- /dev/null +++ b/src/gvc/gvc-mixer-stream.h @@ -0,0 +1,125 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*- + * + * Copyright (C) 2008 Red Hat, Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + * + */ + +#ifndef __GVC_MIXER_STREAM_H +#define __GVC_MIXER_STREAM_H + +#include +#include "gvc-pulseaudio-fake.h" +#include "gvc-channel-map.h" + +G_BEGIN_DECLS + +#define GVC_TYPE_MIXER_STREAM (gvc_mixer_stream_get_type ()) +#define GVC_MIXER_STREAM(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), GVC_TYPE_MIXER_STREAM, GvcMixerStream)) +#define GVC_MIXER_STREAM_CLASS(k) (G_TYPE_CHECK_CLASS_CAST((k), GVC_TYPE_MIXER_STREAM, GvcMixerStreamClass)) +#define GVC_IS_MIXER_STREAM(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), GVC_TYPE_MIXER_STREAM)) +#define GVC_IS_MIXER_STREAM_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE ((k), GVC_TYPE_MIXER_STREAM)) +#define GVC_MIXER_STREAM_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), GVC_TYPE_MIXER_STREAM, GvcMixerStreamClass)) + +typedef struct GvcMixerStreamPrivate GvcMixerStreamPrivate; + +typedef struct +{ + GObject parent; + GvcMixerStreamPrivate *priv; +} GvcMixerStream; + +typedef struct +{ + GObjectClass parent_class; + + /* vtable */ + gboolean (*push_volume) (GvcMixerStream *stream, + gpointer *operation); + gboolean (*change_is_muted) (GvcMixerStream *stream, + gboolean is_muted); + gboolean (*change_port) (GvcMixerStream *stream, + const char *port); +} GvcMixerStreamClass; + +typedef struct +{ + char *port; + char *human_port; + guint priority; +} GvcMixerStreamPort; + +GType gvc_mixer_stream_get_type (void); + +guint gvc_mixer_stream_get_index (GvcMixerStream *stream); +guint gvc_mixer_stream_get_id (GvcMixerStream *stream); +const GvcChannelMap *gvc_mixer_stream_get_channel_map(GvcMixerStream *stream); +const GvcMixerStreamPort *gvc_mixer_stream_get_port (GvcMixerStream *stream); +const GList * gvc_mixer_stream_get_ports (GvcMixerStream *stream); +gboolean gvc_mixer_stream_change_port (GvcMixerStream *stream, + const char *port); + +pa_volume_t gvc_mixer_stream_get_volume (GvcMixerStream *stream); +gdouble gvc_mixer_stream_get_decibel (GvcMixerStream *stream); +gboolean gvc_mixer_stream_push_volume (GvcMixerStream *stream); +pa_volume_t gvc_mixer_stream_get_base_volume (GvcMixerStream *stream); + +gboolean gvc_mixer_stream_get_is_muted (GvcMixerStream *stream); +gboolean gvc_mixer_stream_get_can_decibel (GvcMixerStream *stream); +gboolean gvc_mixer_stream_change_is_muted (GvcMixerStream *stream, + gboolean is_muted); +gboolean gvc_mixer_stream_is_running (GvcMixerStream *stream); +const char * gvc_mixer_stream_get_name (GvcMixerStream *stream); +const char * gvc_mixer_stream_get_icon_name (GvcMixerStream *stream); +const char * gvc_mixer_stream_get_description (GvcMixerStream *stream); +const char * gvc_mixer_stream_get_application_id (GvcMixerStream *stream); +gboolean gvc_mixer_stream_is_event_stream (GvcMixerStream *stream); +gboolean gvc_mixer_stream_is_virtual (GvcMixerStream *stream); +gint gvc_mixer_stream_get_card_index (GvcMixerStream *stream); + +/* private */ +gboolean gvc_mixer_stream_set_volume (GvcMixerStream *stream, + pa_volume_t volume); +gboolean gvc_mixer_stream_set_decibel (GvcMixerStream *stream, + gdouble db); +gboolean gvc_mixer_stream_set_is_muted (GvcMixerStream *stream, + gboolean is_muted); +gboolean gvc_mixer_stream_set_can_decibel (GvcMixerStream *stream, + gboolean can_decibel); +gboolean gvc_mixer_stream_set_name (GvcMixerStream *stream, + const char *name); +gboolean gvc_mixer_stream_set_description (GvcMixerStream *stream, + const char *description); +gboolean gvc_mixer_stream_set_icon_name (GvcMixerStream *stream, + const char *name); +gboolean gvc_mixer_stream_set_is_event_stream (GvcMixerStream *stream, + gboolean is_event_stream); +gboolean gvc_mixer_stream_set_is_virtual (GvcMixerStream *stream, + gboolean is_event_stream); +gboolean gvc_mixer_stream_set_application_id (GvcMixerStream *stream, + const char *application_id); +gboolean gvc_mixer_stream_set_base_volume (GvcMixerStream *stream, + pa_volume_t base_volume); +gboolean gvc_mixer_stream_set_port (GvcMixerStream *stream, + const char *port); +gboolean gvc_mixer_stream_set_ports (GvcMixerStream *stream, + GList *ports); +gboolean gvc_mixer_stream_set_card_index (GvcMixerStream *stream, + gint card_index); + +G_END_DECLS + +#endif /* __GVC_MIXER_STREAM_H */ diff --git a/src/gvc/gvc-pulseaudio-fake.h b/src/gvc/gvc-pulseaudio-fake.h new file mode 100644 index 000000000..65293cdef --- /dev/null +++ b/src/gvc/gvc-pulseaudio-fake.h @@ -0,0 +1,34 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*- + * + * Copyright (C) 2008 Red Hat, Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + * + */ + +#ifndef __GVC_PULSEAUDIO_FAKE_H +#define __GVC_PULSEAUDIO_FAKE_H + +#ifdef WITH_INTROSPECTION + +#ifndef PA_API_VERSION +typedef int pa_channel_position_t; +typedef guint32 pa_volume_t; +typedef gpointer pa_context; +#endif /* PA_API_VERSION */ + +#endif /* WITH_INTROSPECTION */ + +#endif /* __GVC_PULSEAUDIO_FAKE_H */ From ccaa3f4ebf2bbf811c1478a9d2a27060808f0cb0 Mon Sep 17 00:00:00 2001 From: "Owen W. Taylor" Date: Tue, 19 Oct 2010 15:16:22 -0400 Subject: [PATCH 15/21] gnome-shell-build-setup.sh: add libpulse to package lists --- tools/build/gnome-shell-build-setup.sh | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/tools/build/gnome-shell-build-setup.sh b/tools/build/gnome-shell-build-setup.sh index 35fa92a8c..ca0bbf9cf 100755 --- a/tools/build/gnome-shell-build-setup.sh +++ b/tools/build/gnome-shell-build-setup.sh @@ -58,7 +58,7 @@ fi # # Devel packages needed by gnome-shell and its deps: # dbus-glib, GL, gnome-menus, gstreamer, libffi, -# libjasper, libjpeg, libpng, libtiff, libwnck, +# libjasper, libjpeg, libpng, libpulse, libtiff, libwnck, # libxml2, ORBit2, python, readline, # spidermonkey ({mozilla,firefox,xulrunner}-js), startup-notification # xdamage, icon-naming-utils @@ -83,7 +83,7 @@ if test "x$system" = xUbuntu -o "x$system" = xDebian -o "x$system" = xLinuxMint gvfs gvfs-backends icon-naming-utils \ libdbus-glib-1-dev libffi-dev libgnome-menu-dev libgnome-desktop-dev \ libjasper-dev libjpeg-dev libpng-dev libstartup-notification0-dev libtiff-dev \ - libwnck-dev libgl1-mesa-dev liborbit2-dev libreadline5-dev libxml2-dev \ + libwnck-dev libgl1-mesa-dev liborbit2-dev libpulse-dev libreadline5-dev libxml2-dev \ mesa-common-dev mesa-utils python-dev python-gconf python-gobject \ xulrunner-dev xserver-xephyr gnome-terminal libcroco3-dev \ libgstreamer0.10-dev gstreamer0.10-plugins-base gstreamer0.10-plugins-good \ @@ -105,8 +105,8 @@ if test "x$system" = xFedora ; then automake bison flex gettext git gnome-common gnome-doc-utils gvfs intltool libtool pkgconfig dbus-glib-devel gnome-desktop-devel gnome-menus-devel gnome-python2-gconf jasper-devel libffi-devel libjpeg-devel libpng-devel - libtiff-devel libwnck-devel mesa-libGL-devel ORBit2-devel python-devel - pygobject2 readline-devel xulrunner-devel libXdamage-devel libcroco-devel + libtiff-devel libwnck-devel mesa-libGL-devel ORBit2-devel pulseaudio-libs-devel + python-devel pygobject2 readline-devel xulrunner-devel libXdamage-devel libcroco-devel libxml2-devel gstreamer-devel gstreamer-plugins-base gstreamer-plugins-good glx-utils startup-notification-devel xorg-x11-server-Xephyr gnome-terminal zenity icon-naming-utils @@ -132,7 +132,7 @@ if test "x$system" = xSUSE -o "x$system" = "xSUSE LINUX" ; then curl \ bison flex gtk-doc gnome-common gnome-doc-utils-devel \ gnome-desktop-devel gnome-menus-devel icon-naming-utils \ - libtiff-devel cups-devel libffi-devel \ + libpulse-devel libtiff-devel cups-devel libffi-devel \ orbit2-devel libwnck-devel xorg-x11-proto-devel readline-devel \ mozilla-xulrunner191-devel libcroco-devel \ xorg-x11-devel xorg-x11 xorg-x11-server-extra \ From 4d18d54d9d4d8f52aee545f5bfc743f2e06d45d4 Mon Sep 17 00:00:00 2001 From: Maxim Ermilov Date: Fri, 15 Oct 2010 08:53:34 +0400 Subject: [PATCH 16/21] boxpointer: add animateDisappear/animateAppear methods https://bugzilla.gnome.org/show_bug.cgi?id=624900 --- js/ui/boxpointer.js | 78 +++++++++++++++++++++++++++++++++++++++++++++ js/ui/popupMenu.js | 13 ++------ 2 files changed, 80 insertions(+), 11 deletions(-) diff --git a/js/ui/boxpointer.js b/js/ui/boxpointer.js index d6bd0d0c8..68cea44eb 100644 --- a/js/ui/boxpointer.js +++ b/js/ui/boxpointer.js @@ -5,6 +5,10 @@ const Lang = imports.lang; const St = imports.gi.St; const Shell = imports.gi.Shell; +const Tweener = imports.ui.tweener; + +const POPUP_ANIMATION_TIME = 0.15; + /** * BoxPointer: * @side: A St.Side type; currently only St.Side.TOP is implemented @@ -38,6 +42,80 @@ BoxPointer.prototype = { this.bin.raise(this._border); }, + animateAppear: function(onComplete) { + let x = this.actor.x; + let y = this.actor.y; + let themeNode = this.actor.get_theme_node(); + let [found, rise] = themeNode.get_length('-arrow-rise', false); + if (!found) + rise = 0; + + this.actor.opacity = 0; + this.actor.show(); + + switch (this._arrowSide) { + case St.Side.TOP: + this.actor.y -= rise; + break; + case St.Side.BOTTOM: + this.actor.y += rise; + break; + case St.Side.LEFT: + this.actor.x -= rise; + break; + case St.Side.RIGHT: + this.actor.x += rise; + break; + } + + Tweener.addTween(this.actor, { opacity: 255, + x: x, + y: y, + transition: "linear", + onComplete: onComplete, + time: POPUP_ANIMATION_TIME }); + }, + + animateDisappear: function(onComplete) { + let x = this.actor.x; + let y = this.actor.y; + let originalX = this.actor.x; + let originalY = this.actor.y; + let themeNode = this.actor.get_theme_node(); + let [found, rise] = themeNode.get_length('-arrow-rise', false); + if (!found) + rise = 0; + + switch (this._arrowSide) { + case St.Side.TOP: + y += rise; + break; + case St.Side.BOTTOM: + y -= rise; + break; + case St.Side.LEFT: + x += rise; + break; + case St.Side.RIGHT: + x -= rise; + break; + } + + Tweener.addTween(this.actor, { opacity: 0, + x: x, + y: y, + transition: "linear", + time: POPUP_ANIMATION_TIME, + onComplete: Lang.bind(this, function () { + this.actor.hide(); + this.actor.x = originalX; + this.actor.y = originalY; + if (onComplete) + onComplete(); + }) + }); + }, + _adjustAllocationForArrow: function(isWidth, alloc) { let themeNode = this.actor.get_theme_node(); let found, borderWidth, base, rise; diff --git a/js/ui/popupMenu.js b/js/ui/popupMenu.js index 7e60fe821..60f9011b1 100644 --- a/js/ui/popupMenu.js +++ b/js/ui/popupMenu.js @@ -16,8 +16,6 @@ const Tweener = imports.ui.tweener; const Gettext = imports.gettext.domain('gnome-shell'); const _ = Gettext.gettext; -const POPUP_ANIMATION_TIME = 0.1; - function Switch() { this._init.apply(this, arguments); } @@ -639,11 +637,8 @@ PopupMenu.prototype = { this.actor.y = Math.floor(menuY); // Now show it - this.actor.opacity = 0; this.actor.reactive = true; - Tweener.addTween(this.actor, { opacity: 255, - transition: "easeOutQuad", - time: POPUP_ANIMATION_TIME }); + this._boxPointer.animateAppear(); this.isOpen = true; this.emit('open-state-changed', true); }, @@ -657,11 +652,7 @@ PopupMenu.prototype = { if (this._activeMenuItem) this._activeMenuItem.setActive(false); this.actor.reactive = false; - Tweener.addTween(this.actor, { opacity: 0, - transition: "easeOutQuad", - time: POPUP_ANIMATION_TIME, - onComplete: Lang.bind(this, function () { this.actor.hide(); })}); - + this._boxPointer.animateDisappear(); this.isOpen = false; this.emit('open-state-changed', false); }, From 3efc7bf7f1054c26767bea8be962f872b68a5c86 Mon Sep 17 00:00:00 2001 From: Maxim Ermilov Date: Tue, 19 Oct 2010 23:10:48 +0400 Subject: [PATCH 17/21] boxpointer: add setPosition method split out this function from popupMenu https://bugzilla.gnome.org/show_bug.cgi?id=624900 --- js/ui/boxpointer.js | 86 +++++++++++++++++++++++++++++++++++++++++++++ js/ui/popupMenu.js | 63 +-------------------------------- 2 files changed, 87 insertions(+), 62 deletions(-) diff --git a/js/ui/boxpointer.js b/js/ui/boxpointer.js index 68cea44eb..916005a9f 100644 --- a/js/ui/boxpointer.js +++ b/js/ui/boxpointer.js @@ -264,6 +264,92 @@ BoxPointer.prototype = { cr.stroke(); }, + setPosition: function(sourceActor, gap, alignment) { + let primary = global.get_primary_monitor(); + + // We need to show it now to force an allocation, + // so that we can query the correct size. + this.actor.show(); + + // Position correctly relative to the sourceActor + let [sourceX, sourceY] = sourceActor.get_transformed_position(); + let [sourceWidth, sourceHeight] = sourceActor.get_transformed_size(); + + let [minWidth, minHeight, natWidth, natHeight] = this.actor.get_preferred_size(); + + let resX, resY; + + switch (this._arrowSide) { + case St.Side.TOP: + resY = sourceY + sourceHeight + gap; + break; + case St.Side.BOTTOM: + resY = sourceY - natHeight - gap; + break; + case St.Side.LEFT: + resX = sourceX + sourceWidth + gap; + break; + case St.Side.RIGHT: + resX = sourceX - natWidth - gap; + break; + } + + // Now align and position the pointing axis, making sure + // it fits on screen + switch (this._arrowSide) { + case St.Side.TOP: + case St.Side.BOTTOM: + switch (alignment) { + case St.Align.START: + resX = sourceX; + break; + case St.Align.MIDDLE: + resX = sourceX - Math.floor((natWidth - sourceWidth) / 2); + break; + case St.Align.END: + resX = sourceX - (natWidth - sourceWidth); + break; + } + + resX = Math.min(resX, primary.x + primary.width - natWidth); + resX = Math.max(resX, primary.x); + + this.setArrowOrigin((sourceX - resX) + Math.floor(sourceWidth / 2)); + break; + + case St.Side.LEFT: + case St.Side.RIGHT: + switch (alignment) { + case St.Align.START: + resY = sourceY; + break; + case St.Align.MIDDLE: + resY = sourceY - Math.floor((natHeight - sourceHeight) / 2); + break; + case St.Align.END: + resY = sourceY - (natHeight - sourceHeight); + break; + } + + resY = Math.min(resY, primary.y + primary.height - natHeight); + resY = Math.max(resY, primary.y); + + this.setArrowOrigin((sourceY - resY) + Math.floor(sourceHeight / 2)); + break; + } + + let parent = this.actor.get_parent(); + let success, x, y; + while (!success) { + [success, x, y] = parent.transform_stage_point(resX, resY); + parent = parent.get_parent(); + } + + // Actually set the position + this.actor.x = Math.floor(x); + this.actor.y = Math.floor(y); + }, + // @origin: Coordinate specifying middle of the arrow, along // the Y axis for St.Side.LEFT, St.Side.RIGHT from the top and X axis from // the left for St.Side.TOP and St.Side.BOTTOM. diff --git a/js/ui/popupMenu.js b/js/ui/popupMenu.js index 60f9011b1..889f29622 100644 --- a/js/ui/popupMenu.js +++ b/js/ui/popupMenu.js @@ -550,7 +550,6 @@ PopupMenu.prototype = { let [minWidth, minHeight, natWidth, natHeight] = this.actor.get_preferred_size(); - let menuX, menuY; let menuWidth = natWidth, menuHeight = natHeight; // Position the non-pointing axis @@ -573,68 +572,8 @@ PopupMenu.prototype = { this._boxPointer._arrowSide = this._arrowSide = St.Side.LEFT; } } - switch (this._arrowSide) { - case St.Side.TOP: - menuY = sourceY + sourceHeight + this._gap; - break; - case St.Side.BOTTOM: - menuY = sourceY - menuHeight - this._gap; - break; - case St.Side.LEFT: - menuX = sourceX + sourceWidth + this._gap; - break; - case St.Side.RIGHT: - menuX = sourceX - menuWidth - this._gap; - break; - } - // Now align and position the pointing axis, making sure - // it fits on screen - switch (this._arrowSide) { - case St.Side.TOP: - case St.Side.BOTTOM: - switch (this._alignment) { - case St.Align.START: - menuX = sourceX; - break; - case St.Align.MIDDLE: - menuX = sourceX - Math.floor((menuWidth - sourceWidth) / 2); - break; - case St.Align.END: - menuX = sourceX - (menuWidth - sourceWidth); - break; - } - - menuX = Math.min(menuX, primary.x + primary.width - menuWidth); - menuX = Math.max(menuX, primary.x); - - this._boxPointer.setArrowOrigin((sourceX - menuX) + Math.floor(sourceWidth / 2)); - break; - - case St.Side.LEFT: - case St.Side.RIGHT: - switch (this._alignment) { - case St.Align.START: - menuY = sourceY; - break; - case St.Align.MIDDLE: - menuY = sourceY - Math.floor((menuHeight - sourceHeight) / 2); - break; - case St.Align.END: - menuY = sourceY - (menuHeight - sourceHeight); - break; - } - - menuY = Math.min(menuY, primary.y + primary.height - menuHeight); - menuY = Math.max(menuY, primary.y); - - this._boxPointer.setArrowOrigin((sourceY - menuY) + Math.floor(sourceHeight / 2)); - break; - } - - // Actually set the position - this.actor.x = Math.floor(menuX); - this.actor.y = Math.floor(menuY); + this._boxPointer.setPosition(this.sourceActor, this._gap, this._alignment); // Now show it this.actor.reactive = true; From 13229a6d860f084fac78a488833a0a1ea4bc5291 Mon Sep 17 00:00:00 2001 From: Maxim Ermilov Date: Tue, 19 Oct 2010 23:17:36 +0400 Subject: [PATCH 18/21] MessageTray: use boxpointer for summary notification https://bugzilla.gnome.org/show_bug.cgi?id=624900 --- data/theme/gnome-shell.css | 16 +++++-- js/ui/messageTray.js | 95 +++++++++++++++++++++++--------------- 2 files changed, 72 insertions(+), 39 deletions(-) diff --git a/data/theme/gnome-shell.css b/data/theme/gnome-shell.css index c14709c6a..9cd879c6e 100644 --- a/data/theme/gnome-shell.css +++ b/data/theme/gnome-shell.css @@ -863,9 +863,19 @@ StTooltip { padding-bottom: 8px; } -#summary-notification-bin #notification { - /* message-tray.height + notification.padding-bottom */ - padding-bottom: 44px; +.summary-notification-boxpointer { + -arrow-border-radius: 9px; + -arrow-background-color: rgba(0,0,0,0.9); + -arrow-border-width: 2px; + -arrow-border-color: #5f5f5f; + -arrow-base: 30px; + -arrow-rise: 15px; +} + +.summary-notification-boxpointer #notification { + border-radius: 9px; + background: rgba(0,0,0,0) !important; + padding-bottom: 12px; } #notification-scrollview { diff --git a/js/ui/messageTray.js b/js/ui/messageTray.js index a028d3206..95183c6a1 100644 --- a/js/ui/messageTray.js +++ b/js/ui/messageTray.js @@ -12,6 +12,7 @@ const St = imports.gi.St; const Tweener = imports.ui.tweener; const Main = imports.ui.main; +const BoxPointer = imports.ui.boxpointer; const Params = imports.misc.params; const ANIMATION_TIME = 0.2; @@ -778,15 +779,19 @@ MessageTray.prototype = { this._summaryBin.child = this._summary; this._summaryBin.opacity = 0; - this._summaryNotificationBin = new St.Bin({ name: 'summary-notification-bin', - anchor_gravity: Clutter.Gravity.NORTH_EAST, - reactive: true, - track_hover: true }); - this.actor.add_actor(this._summaryNotificationBin); - this._summaryNotificationBin.lower_bottom(); - this._summaryNotificationBin.hide(); + this._summaryMotionId = 0; + + this._summaryNotificationBoxPointer = new BoxPointer.BoxPointer(St.Side.BOTTOM, + { reactive: true, + track_hover: true }); + this._summaryNotificationBoxPointer.actor.style_class = 'summary-notification-boxpointer'; + this.actor.add_actor(this._summaryNotificationBoxPointer.actor); + this._summaryNotificationBoxPointer.actor.lower_bottom(); + this._summaryNotificationBoxPointer.actor.hide(); + this._summaryNotification = null; this._clickedSummaryItem = null; + this._clickedSummaryItemAllocationChangedId = 0; this._expandedSummaryItem = null; this._summaryItemTitleWidth = 0; @@ -817,7 +822,7 @@ MessageTray.prototype = { Main.chrome.addActor(this.actor, { affectsStruts: false, visibleInOverview: true }); Main.chrome.trackActor(this._notificationBin); - Main.chrome.trackActor(this._summaryNotificationBin); + Main.chrome.trackActor(this._summaryNotificationBoxPointer.actor); global.gdk_screen.connect('monitors-changed', Lang.bind(this, this._setSizePosition)); @@ -859,7 +864,6 @@ MessageTray.prototype = { // These work because of their anchor_gravity this._summaryBin.x = primary.width; - this._summaryNotificationBin.x = primary.width; }, contains: function(source) { @@ -964,7 +968,7 @@ MessageTray.prototype = { needUpdate = true; } if (this._clickedSummaryItem && this._clickedSummaryItem.source == source) { - this._clickedSummaryItem = null; + this._unsetClickedSummaryItem(); needUpdate = true; } @@ -993,7 +997,7 @@ MessageTray.prototype = { if (!this._locked) return; this._locked = false; - this._clickedSummaryItem = null; + this._unsetClickedSummaryItem(); this._updateState(); }, @@ -1116,7 +1120,7 @@ MessageTray.prototype = { if (!this._clickedSummaryItem || this._clickedSummaryItem != summaryItem) this._clickedSummaryItem = summaryItem; else - this._clickedSummaryItem = null; + this._unsetClickedSummaryItem(); this._updateState(); }, @@ -1478,25 +1482,51 @@ MessageTray.prototype = { if (index != -1) this._notificationQueue.splice(index, 1); - this._summaryNotificationBin.child = this._summaryNotification.actor; + this._summaryNotificationBoxPointer.bin.child = this._summaryNotification.actor; this._summaryNotification.grabFocus(true); - this._summaryNotificationBin.opacity = 0; - this._summaryNotificationBin.y = this.actor.height; - this._summaryNotificationBin.show(); - if (!this._summaryNotificationExpandedId) this._summaryNotificationExpandedId = this._summaryNotification.connect('expanded', Lang.bind(this, this._onSummaryNotificationExpanded)); this._summaryNotification.expand(false); + + this._clickedSummaryItemAllocationChangedId = + this._clickedSummaryItem.actor.connect('allocation-changed', + Lang.bind(this, this._adjustNotificationBoxPointerPosition)); + // _clickedSummaryItem.actor can change absolute postiion without changing allocation + this._summaryMotionId = this._summary.connect('allocation-changed', + Lang.bind(this, this._adjustNotificationBoxPointerPosition)); + + this._summaryNotificationBoxPointer.actor.opacity = 0; + this._summaryNotificationBoxPointer.actor.show(); + this._adjustNotificationBoxPointerPosition(); + + this._summaryNotificationState = State.SHOWNING; + this._summaryNotificationBoxPointer.animateAppear(Lang.bind(this, function() { + this._summaryNotificationState = State.SHOWN; + })); + }, + + _adjustNotificationBoxPointerPosition: function() { + // The position of the arrow origin should be the same as center of this._clickedSummaryItem.actor + if (!this._clickedSummaryItem) + return; + + this._summaryNotificationBoxPointer.setPosition(this._clickedSummaryItem.actor, 0, St.Align.MIDDLE); + }, + + _unsetClickedSummaryItem: function() { + if (this._clickedSummaryItemAllocationChangedId) { + this._clickedSummaryItem.actor.disconnect(this._clickedSummaryItemAllocationChangedId); + this._summary.disconnect(this._summaryMotionId); + this._clickedSummaryItemAllocationChangedId = 0; + this._summaryMotionId = 0; + } + + this._clickedSummaryItem = null; }, _onSummaryNotificationExpanded: function() { - this._tween(this._summaryNotificationBin, '_summaryNotificationState', State.SHOWN, - { y: this.actor.height - this._summaryNotificationBin.height, - opacity: 255, - time: ANIMATION_TIME, - transition: 'easeOutQuad' - }); + this._adjustNotificationBoxPointerPosition(); }, _hideSummaryNotification: function() { @@ -1504,25 +1534,18 @@ MessageTray.prototype = { this._summaryNotification.disconnect(this._summaryNotificationExpandedId); this._summaryNotificationExpandedId = 0; } - // Unset this._clickedSummaryItem if we are no longer showing the summary if (this._summaryState != State.SHOWN) - this._clickedSummaryItem = null; - this._summaryNotification.ungrabFocus(); + this._unsetClickedSummaryItem(); - this._tween(this._summaryNotificationBin, '_summaryNotificationState', State.HIDDEN, - { y: this.actor.height, - opacity: 0, - time: ANIMATION_TIME, - transition: 'easeOutQuad', - onComplete: this._hideSummaryNotificationCompleted, - onCompleteScope: this - }); + this._summaryNotification.ungrabFocus(); + this._summaryNotificationState = State.HIDING; + this._summaryNotificationBoxPointer.animateDisappear(Lang.bind(this, this._hideSummaryNotificationCompleted)); }, _hideSummaryNotificationCompleted: function() { - this._summaryNotificationBin.hide(); - this._summaryNotificationBin.child = null; + this._summaryNotificationState = State.HIDDEN; + this._summaryNotificationBoxPointer.bin.child = null; this._summaryNotification.collapseCompleted(); let summaryNotification = this._summaryNotification; this._summaryNotification = null; From ee7e46fbf69ba004a7802e60731e1ae07d646d75 Mon Sep 17 00:00:00 2001 From: Giovanni Campagna Date: Fri, 8 Oct 2010 19:44:03 +0200 Subject: [PATCH 19/21] Solve spacing issue in system status area Shell implemented indicators got padding from default button style, and also spacing from their container, so too much space was left between them. This only became apparent when more than one was implemented, though. --- data/theme/gnome-shell.css | 4 ---- 1 file changed, 4 deletions(-) diff --git a/data/theme/gnome-shell.css b/data/theme/gnome-shell.css index 9cd879c6e..981b9743b 100644 --- a/data/theme/gnome-shell.css +++ b/data/theme/gnome-shell.css @@ -235,10 +235,6 @@ StTooltip { spacing: 4px; } -#statusTray { - spacing: 14px; -} - #legacyTray { spacing: 14px; padding-left: 14px; From a1389a0730360b7ceaee186712e18c5ce080e693 Mon Sep 17 00:00:00 2001 From: Reinout van Schouwen Date: Tue, 19 Oct 2010 23:19:24 +0200 Subject: [PATCH 20/21] Updated Dutch translation by Reinout van Schouwen --- po/nl.po | 579 ++++++++++++++++++++++++++++++++++++++++++++++++------- 1 file changed, 508 insertions(+), 71 deletions(-) diff --git a/po/nl.po b/po/nl.po index bc631564c..3d6a0dc33 100644 --- a/po/nl.po +++ b/po/nl.po @@ -3,14 +3,16 @@ # This file is distributed under the same license as the gnome-shell package. # # Sander Dijkhuis , 2009, 2010. +# Reinout van Schouwen , 2010 msgid "" msgstr "" "Project-Id-Version: gnome-shell master\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2010-03-31 13:34+0200\n" -"PO-Revision-Date: 2010-04-02 22:18+0200\n" -"Last-Translator: Sander Dijkhuis \n" +"POT-Creation-Date: 2010-10-19 23:19+0200\n" +"PO-Revision-Date: 2010-10-19 14:12+0100\n" +"Last-Translator: Reinout van Schouwen \n" "Language-Team: Dutch \n" +"Language: nl\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" @@ -24,32 +26,369 @@ msgstr "Gnome Shell" msgid "Window management and application launching" msgstr "Vensterbeheer en toepassingen starten" +#: ../data/gnome-shell-clock-preferences.desktop.in.in.h:1 +msgid "Clock" +msgstr "Klok" + +#: ../data/gnome-shell-clock-preferences.desktop.in.in.h:2 +msgid "Customize the panel clock" +msgstr "De paneelklok aanpassen" + +#: ../data/org.gnome.shell.gschema.xml.in.h:1 +msgid "" +"Allows access to internal debugging and monitoring tools using the Alt-F2 " +"dialog." +msgstr "" +"Geeft toegang tot interne debugging- en observatieprogramma's met behulp van " +"het Alt-F2-dialoogvenster." + +#: ../data/org.gnome.shell.gschema.xml.in.h:2 +msgid "Custom format of the clock" +msgstr "Aangepaste indeling van de klok" + +#: ../data/org.gnome.shell.gschema.xml.in.h:3 +msgid "Enable internal tools useful for developers and testers from Alt-F2" +msgstr "" +"Interne hulpprogramma's inschakelen die nuttig zijn voor gebruikers en " +"testers van Alt-F2" + +#: ../data/org.gnome.shell.gschema.xml.in.h:4 +msgid "File extension used for storing the screencast" +msgstr "Bestandsextensie voor het opslaan van de screencast" + +#: ../data/org.gnome.shell.gschema.xml.in.h:5 +msgid "Framerate used for recording screencasts." +msgstr "Framerate voor het opnemen van de screencasts." + +#: ../data/org.gnome.shell.gschema.xml.in.h:6 +msgid "" +"GNOME Shell extensions have a uuid property; this key lists extensions which " +"should not be loaded." +msgstr "" +"Gnome Shell-uitbreidingen hebben een uuid-eigenschap; deze sleutel toont " +"uitbreidingen die niet geladen mogen worden." + +#: ../data/org.gnome.shell.gschema.xml.in.h:7 +msgid "History for command (Alt-F2) dialog" +msgstr "Geschiedenis voor het opdracht-dialoogvenster (Alt-F2)" + +#: ../data/org.gnome.shell.gschema.xml.in.h:8 +msgid "Hour format" +msgstr "Uurindeling" + +#: ../data/org.gnome.shell.gschema.xml.in.h:9 +msgid "" +"If true and format is either \"12-hour\" or \"24-hour\", display date in the " +"clock, in addition to time." +msgstr "" +"Indien ingeschakeld en de indeling is ofwel ‘12-uur’ of ‘24-uur’, behalve de " +"tijd ook de datum in de klok weergeven." + +#: ../data/org.gnome.shell.gschema.xml.in.h:10 +msgid "" +"If true and format is either \"12-hour\" or \"24-hour\", display seconds in " +"time." +msgstr "" +"Indien ingeschakeld en de indeling is ofwel ‘12-uur’ of ‘24-uur’, seconden " +"in de klok weergeven." + +#: ../data/org.gnome.shell.gschema.xml.in.h:11 +msgid "If true, display the ISO week date in the calendar." +msgstr "Indien ingeschakeld worden weeknummers in de kalender getoond." + +#: ../data/org.gnome.shell.gschema.xml.in.h:12 +msgid "List of desktop file IDs for favorite applications" +msgstr "Lijst van desktopbestand-id's voor favoriete toepassingen" + +#: ../data/org.gnome.shell.gschema.xml.in.h:13 +msgid "Overview workspace view mode" +msgstr "Werkbladoverzichtsmodus" + +#: ../data/org.gnome.shell.gschema.xml.in.h:14 +msgid "" +"Sets the GStreamer pipeline used to encode recordings. It follows the syntax " +"used for gst-launch. The pipeline should have an unconnected sink pad where " +"the recorded video is recorded. It will normally have a unconnected source " +"pad; output from that pad will be written into the output file. However the " +"pipeline can also take care of its own output - this might be used to send " +"the output to an icecast server via shout2send or similar. When unset or set " +"to an empty value, the default pipeline will be used. This is currently " +"'videorate ! theoraenc ! oggmux' and records to Ogg Theora." +msgstr "" + +#: ../data/org.gnome.shell.gschema.xml.in.h:15 +msgid "Show date in clock" +msgstr "Datum tonen in klok" + +#: ../data/org.gnome.shell.gschema.xml.in.h:16 +msgid "Show the week date in the calendar" +msgstr "Weeknummers tonen in kalender" + +#: ../data/org.gnome.shell.gschema.xml.in.h:17 +msgid "Show time with seconds" +msgstr "Tijd tonen inclusief seconden" + +#: ../data/org.gnome.shell.gschema.xml.in.h:18 +msgid "" +"The applications corresponding to these identifiers will be displayed in the " +"favorites area." +msgstr "" + +#: ../data/org.gnome.shell.gschema.xml.in.h:19 +msgid "" +"The filename for recorded screencasts will be a unique filename based on the " +"current date, and use this extension. It should be changed when recording to " +"a different container format." +msgstr "" + +#: ../data/org.gnome.shell.gschema.xml.in.h:20 +msgid "" +"The framerate of the resulting screencast recordered by GNOME Shell's " +"screencast recorder in frames-per-second." +msgstr "" + +#: ../data/org.gnome.shell.gschema.xml.in.h:21 +msgid "The gstreamer pipeline used to encode the screencast" +msgstr "De gstreamer-pijplijn voor het coderen van de screencast" + +#: ../data/org.gnome.shell.gschema.xml.in.h:22 +msgid "" +"The selected workspace view mode in the overview. Supported values are " +"\"single\" and \"grid\"." +msgstr "" +"De geselecteerde werkbladmodus in het overzicht. Ondersteunde waarden zijn " +"‘single’ en ‘grid’." + +#: ../data/org.gnome.shell.gschema.xml.in.h:23 +msgid "" +"The shell normally monitors active applications in order to present the most " +"used ones (e.g. in launchers). While this data will be kept private, you may " +"want to disable this for privacy reasons. Please note that doing so won't " +"remove already saved data." +msgstr "" + +#: ../data/org.gnome.shell.gschema.xml.in.h:24 +msgid "" +"This key specifies the format used by the panel clock when the format key is " +"set to \"custom\". You can use conversion specifiers understood by strftime" +"() to obtain a specific format. See the strftime() manual for more " +"information." +msgstr "" +"Deze sleutel geeft aan welke weergave de paneelklok gebruikt wanneer de " +"weergavesleutel is ingsteld op ‘custom’. U kunt opmaakcodes voor de functie " +"‘strftime()’ gebruiken om een bepaalde weergave te verkrijgen. Zie de " +"handleiding van strftime() voor meer informatie." + +#: ../data/org.gnome.shell.gschema.xml.in.h:25 +msgid "" +"This key specifies the hour format used by the panel clock. Possible values " +"are \"12-hour\", \"24-hour\", \"unix\" and \"custom\". If set to \"unix\", " +"the clock will display time in seconds since Epoch, i.e. 1970-01-01. If set " +"to \"custom\", the clock will display time according to the format specified " +"in the custom_format key. Note that if set to either \"unix\" or \"custom\", " +"the show_date and show_seconds keys are ignored." +msgstr "" +"Deze sleutel geeft de uurindeling aan van de paneelklok. Mogelijke waarden " +"zijn ‘12-hour’, ‘24-hour’, ‘unix’ en ’custom’. Als ingesteld op ‘unix’ geeft " +"de klok tijd weer in seconden sinds Epoch, oftewel 01-01-1970. Als ingesteld " +"op ‘custom’ dan geeft de klok tijd weer aan de hand van de indeling die is " +"gespecificeerd in de sleutel custom_format. Merk op dat de sleutels " +"show_date en show_seconds worden genegeerd als de waarde staat op ‘unix’ of " +"‘custom’." + +#: ../data/org.gnome.shell.gschema.xml.in.h:26 +msgid "Uuids of extensions to disable" +msgstr "Uuid's van uit te schakelen uitbreidingen" + +#: ../data/org.gnome.shell.gschema.xml.in.h:27 +msgid "Whether to collect stats about applications usage" +msgstr "Of statistieken worden bijgehouden over toepassingsgebruik" + +#: ../data/org.gnome.accessibility.magnifier.gschema.xml.in.h:1 +msgid "Clip the crosshairs at the center" +msgstr "" + +#: ../data/org.gnome.accessibility.magnifier.gschema.xml.in.h:2 +#, fuzzy +msgid "Color of the crosshairs" +msgstr "Intersecties (kruisjes)" + +#: ../data/org.gnome.accessibility.magnifier.gschema.xml.in.h:3 +msgid "" +"Determines the length of the vertical and horizontal lines that make up the " +"crosshairs." +msgstr "" + +#: ../data/org.gnome.accessibility.magnifier.gschema.xml.in.h:4 +msgid "" +"Determines the position of the magnified mouse image within the magnified " +"view and how it reacts to system mouse movement. The values are - none: no " +"mouse tracking; - centered: the mouse image is displayed at the center of " +"the zoom region (which also represents the point under the system mouse) and " +"the magnified contents are scrolled as the system mouse moves; - " +"proportional: the position of the magnified mouse in the zoom region is " +"proportionally the same as the position of the system mouse on screen; - " +"push: when the magnified mouse intersects a boundary of the zoom region, the " +"contents are scrolled into view." +msgstr "" + +#: ../data/org.gnome.accessibility.magnifier.gschema.xml.in.h:5 +msgid "" +"Determines the transparency of the crosshairs, from fully opaque to fully " +"transparent." +msgstr "" + +#: ../data/org.gnome.accessibility.magnifier.gschema.xml.in.h:6 +msgid "" +"Determines whether the crosshairs intersect the magnified mouse sprite, or " +"are clipped such that the ends of the horizontal and vertical lines surround " +"the mouse image." +msgstr "" + +#: ../data/org.gnome.accessibility.magnifier.gschema.xml.in.h:7 +msgid "Enable lens mode" +msgstr "Lensmodus inschakelen" + +#: ../data/org.gnome.accessibility.magnifier.gschema.xml.in.h:8 +msgid "" +"Enables/disables display of crosshairs centered on the magnified mouse " +"sprite." +msgstr "" + +#: ../data/org.gnome.accessibility.magnifier.gschema.xml.in.h:9 +msgid "" +"For centered mouse tracking, when the system pointer is at or near the edge " +"of the screen, the magnified contents continue to scroll such that the " +"screen edge moves into the magnified view." +msgstr "" + +#: ../data/org.gnome.accessibility.magnifier.gschema.xml.in.h:10 +#, fuzzy +msgid "Length of the crosshairs" +msgstr "Intersecties (kruisjes)" + +#: ../data/org.gnome.accessibility.magnifier.gschema.xml.in.h:11 +msgid "Magnification factor" +msgstr "Vergrotingsfactor" + +#: ../data/org.gnome.accessibility.magnifier.gschema.xml.in.h:12 +msgid "Mouse Tracking Mode" +msgstr "Muisvolgmodus" + +#: ../data/org.gnome.accessibility.magnifier.gschema.xml.in.h:13 +#, fuzzy +msgid "Opacity of the crosshairs" +msgstr "Intersecties (kruisjes)" + +#: ../data/org.gnome.accessibility.magnifier.gschema.xml.in.h:14 +msgid "Screen position" +msgstr "Schermpositie" + +#: ../data/org.gnome.accessibility.magnifier.gschema.xml.in.h:15 +msgid "Scroll magnified contents beyond the edges of the desktop" +msgstr "" + +#: ../data/org.gnome.accessibility.magnifier.gschema.xml.in.h:16 +#, fuzzy +msgid "Show or hide crosshairs" +msgstr "De statusbalk tonen of verbergen" + +#: ../data/org.gnome.accessibility.magnifier.gschema.xml.in.h:17 +#, fuzzy +msgid "Show or hide the magnifier" +msgstr "De statusbalk tonen of verbergen" + +#: ../data/org.gnome.accessibility.magnifier.gschema.xml.in.h:18 +msgid "Show or hide the magnifier and all of its zoom regions." +msgstr "" + +#: ../data/org.gnome.accessibility.magnifier.gschema.xml.in.h:19 +msgid "" +"The color of the the vertical and horizontal lines that make up the " +"crosshairs." +msgstr "" + +#: ../data/org.gnome.accessibility.magnifier.gschema.xml.in.h:20 +msgid "" +"The magnified view either fills the entire screen, or occupies the top-half, " +"bottom-half, left-half, or right-half of the screen." +msgstr "" + +#: ../data/org.gnome.accessibility.magnifier.gschema.xml.in.h:21 +msgid "" +"The power of the magnification. A value of 1.0 means no magnification. A " +"value of 2.0 doubles the size." +msgstr "" + +#: ../data/org.gnome.accessibility.magnifier.gschema.xml.in.h:22 +#, fuzzy +msgid "Thickness of the crosshairs" +msgstr "Intersecties (kruisjes)" + +#: ../data/org.gnome.accessibility.magnifier.gschema.xml.in.h:23 +msgid "" +"Whether the magnified view should be centered over the location of the " +"system mouse and move with it." +msgstr "" + +#: ../data/org.gnome.accessibility.magnifier.gschema.xml.in.h:24 +msgid "Width of the vertical and horizontal lines that make up the crosshairs." +msgstr "" + +#: ../data/clock-preferences.ui.h:1 +msgid "Clock Format" +msgstr "Klokweergave" + +#: ../data/clock-preferences.ui.h:2 +msgid "Clock Preferences" +msgstr "Klok-voorkeuren" + +#: ../data/clock-preferences.ui.h:3 +msgid "Panel Display" +msgstr "Paneelweergave" + +#: ../data/clock-preferences.ui.h:4 +msgid "Show seco_nds" +msgstr "_Seconden tonen" + +#: ../data/clock-preferences.ui.h:5 +msgid "Show the _date" +msgstr "_Datum tonen" + +#: ../data/clock-preferences.ui.h:6 +msgid "_12 hour format" +msgstr "_12-uursklok" + +#: ../data/clock-preferences.ui.h:7 +msgid "_24 hour format" +msgstr "_24-uursklok" + #. **** Applications **** -#: ../js/ui/appDisplay.js:312 ../js/ui/dash.js:855 +#: ../js/ui/appDisplay.js:316 ../js/ui/dash.js:778 msgid "APPLICATIONS" msgstr "TOEPASSINGEN" -#: ../js/ui/appDisplay.js:344 +#: ../js/ui/appDisplay.js:348 msgid "PREFERENCES" msgstr "VOORKEUREN" -#: ../js/ui/appDisplay.js:734 +#: ../js/ui/appDisplay.js:648 msgid "New Window" msgstr "Nieuw venster" -#: ../js/ui/appDisplay.js:738 +#: ../js/ui/appDisplay.js:652 msgid "Remove from Favorites" msgstr "Uit favorieten verwijderen" -#: ../js/ui/appDisplay.js:739 +#: ../js/ui/appDisplay.js:653 msgid "Add to Favorites" msgstr "Aan favorieten toevoegen" -#: ../js/ui/appDisplay.js:1091 +#: ../js/ui/appDisplay.js:830 msgid "Drag here to add favorites" msgstr "Hierheen slepen om favorieten toe te voegen" -#: ../js/ui/appFavorites.js:89 +#: ../js/ui/appFavorites.js:88 #, c-format msgid "%s has been added to your favorites." msgstr "%s is toegevoegd aan uw favorieten." @@ -59,189 +398,275 @@ msgstr "%s is toegevoegd aan uw favorieten." msgid "%s has been removed from your favorites." msgstr "%s is verwijderd uit uw favorieten." -#: ../js/ui/dash.js:194 +#: ../js/ui/dash.js:142 msgid "Find" msgstr "Zoeken" -#: ../js/ui/dash.js:510 +#: ../js/ui/dash.js:473 msgid "Searching..." msgstr "Zoeken…" -#: ../js/ui/dash.js:524 +#: ../js/ui/dash.js:487 msgid "No matching results." msgstr "Geen overeenkomende resultaten." #. **** Places **** #. Translators: This is in the sense of locations for documents, #. network locations, etc. -#: ../js/ui/dash.js:874 ../js/ui/placeDisplay.js:543 +#: ../js/ui/dash.js:797 ../js/ui/placeDisplay.js:554 msgid "PLACES & DEVICES" msgstr "LOCATIES & APPARATEN" #. **** Documents **** -#: ../js/ui/dash.js:881 ../js/ui/docDisplay.js:489 +#: ../js/ui/dash.js:804 ../js/ui/docDisplay.js:494 msgid "RECENT ITEMS" msgstr "RECENTE ITEMS" -#: ../js/ui/lookingGlass.js:363 +#: ../js/ui/lookingGlass.js:552 msgid "No extensions installed" msgstr "Geen uitbreidingen geïnstalleerd" -#: ../js/ui/lookingGlass.js:400 +#: ../js/ui/lookingGlass.js:589 msgid "Enabled" msgstr "Ingeschakeld" -#: ../js/ui/lookingGlass.js:402 +#: ../js/ui/lookingGlass.js:591 msgid "Disabled" msgstr "Uitgeschakeld" -#: ../js/ui/lookingGlass.js:404 +#: ../js/ui/lookingGlass.js:593 msgid "Error" msgstr "Fout" -#: ../js/ui/lookingGlass.js:406 +#: ../js/ui/lookingGlass.js:595 msgid "Out of date" msgstr "Gedateerd" -#: ../js/ui/lookingGlass.js:431 +#: ../js/ui/lookingGlass.js:620 msgid "View Source" msgstr "Broncode weergeven" -#: ../js/ui/lookingGlass.js:437 +#: ../js/ui/lookingGlass.js:626 msgid "Web Page" msgstr "Webpagina" -#: ../js/ui/overview.js:182 +#: ../js/ui/overview.js:160 msgid "Undo" msgstr "Ongedaan maken" -#. Button on the left side of the panel. -#. Translators: If there is no suitable word for "Activities" in your language, you can use the word for "Overview". -#: ../js/ui/panel.js:388 -msgid "Activities" -msgstr "Activiteiten" +#. TODO - _quit() doesn't really work on apps in state STARTING yet +#: ../js/ui/panel.js:468 +#, c-format +msgid "Quit %s" +msgstr "%s afsluiten" -#. Translators: This is the time format used in 24-hour mode. -#: ../js/ui/panel.js:619 +#: ../js/ui/panel.js:493 +msgid "Preferences" +msgstr "Voorkeuren" + +#. Translators: This is the time format with date used +#. in 24-hour mode. +#: ../js/ui/panel.js:579 +msgid "%a %b %e, %R:%S" +msgstr "%a %e %b, %R:%S" + +#: ../js/ui/panel.js:580 +msgid "%a %b %e, %R" +msgstr "%a %e %b, %R" + +#. Translators: This is the time format without date used +#. in 24-hour mode. +#: ../js/ui/panel.js:584 +msgid "%a %R:%S" +msgstr "%a %R:%S" + +#: ../js/ui/panel.js:585 msgid "%a %R" msgstr "%a %R" -#. Translators: This is a time format used for AM/PM. -#: ../js/ui/panel.js:622 -msgid "%a %l:%M %p" -msgstr "%a %k:%M %p" +#. Translators: This is a time format with date used +#. for AM/PM. +#: ../js/ui/panel.js:592 +msgid "%a %b %e, %l:%M:%S %p" +msgstr "%a %e %b, %l:%M:%S %p" -#: ../js/ui/placeDisplay.js:108 +#: ../js/ui/panel.js:593 +msgid "%a %b %e, %l:%M %p" +msgstr "%a %e %b, %l:%M %p" + +#. Translators: This is a time format without date used +#. for AM/PM. +#: ../js/ui/panel.js:597 +msgid "%a %l:%M:%S %p" +msgstr "%a %l:%M:%S %p" + +#: ../js/ui/panel.js:598 +msgid "%a %l:%M %p" +msgstr "%a %l:%M %p" + +#. Button on the left side of the panel. +#. Translators: If there is no suitable word for "Activities" in your language, you can use the word for "Overview". +#: ../js/ui/panel.js:743 +msgid "Activities" +msgstr "Activiteiten" + +#: ../js/ui/placeDisplay.js:111 #, c-format msgid "Failed to unmount '%s'" msgstr "Ontkoppelen van ‘%s’ mislukt" -#: ../js/ui/placeDisplay.js:111 +#: ../js/ui/placeDisplay.js:114 msgid "Retry" msgstr "Opnieuw" -#: ../js/ui/placeDisplay.js:156 +#: ../js/ui/placeDisplay.js:159 msgid "Connect to..." msgstr "Verbinding maken met…" -#: ../js/ui/runDialog.js:232 +#. Translators: this MUST be either "toggle-switch-us" +#. (for toggle switches containing the English words +#. "ON" and "OFF") or "toggle-switch-intl" (for toggle +#. switches containing "◯" and "|"). Other values will +#. simply result in invisible toggle switches. +#: ../js/ui/popupMenu.js:31 +msgid "toggle-switch-us" +msgstr "toggle-switch-intl" + +#: ../js/ui/runDialog.js:233 msgid "Please enter a command:" msgstr "Voer een opdracht in:" -#: ../js/ui/runDialog.js:376 +#: ../js/ui/runDialog.js:378 #, c-format msgid "Execution of '%s' failed:" msgstr "Uitvoeren van ‘%s’ mislukt:" -#: ../js/ui/statusMenu.js:107 +#: ../js/ui/statusMenu.js:97 msgid "Available" msgstr "Beschikbaar" -#: ../js/ui/statusMenu.js:112 +#: ../js/ui/statusMenu.js:101 msgid "Busy" msgstr "Bezig" -#: ../js/ui/statusMenu.js:117 +#: ../js/ui/statusMenu.js:105 msgid "Invisible" msgstr "Onzichtbaar" -#: ../js/ui/statusMenu.js:126 +#: ../js/ui/statusMenu.js:112 msgid "Account Information..." msgstr "Accountinformatie…" -#: ../js/ui/statusMenu.js:132 -msgid "Sidebar" -msgstr "Zijbalk" +#: ../js/ui/statusMenu.js:116 +msgid "System Settings..." +msgstr "Systeeminstellingen…" -#: ../js/ui/statusMenu.js:142 -msgid "System Preferences..." -msgstr "Systeemvoorkeuren…" - -#: ../js/ui/statusMenu.js:151 +#: ../js/ui/statusMenu.js:123 msgid "Lock Screen" msgstr "Scherm vergrendelen" -#: ../js/ui/statusMenu.js:156 +#: ../js/ui/statusMenu.js:127 msgid "Switch User" msgstr "Gebruiker wisselen" -#: ../js/ui/statusMenu.js:162 +#: ../js/ui/statusMenu.js:132 msgid "Log Out..." msgstr "Afmelden…" -#: ../js/ui/statusMenu.js:167 +#: ../js/ui/statusMenu.js:136 msgid "Shut Down..." msgstr "Afsluiten…" -#. Translators: This is a time format. -#: ../js/ui/widget.js:163 -msgid "%H:%M" -msgstr "%H:%M" +#: ../js/ui/status/accessibility.js:88 +msgid "Screen Reader" +msgstr "Schermlezer" -#: ../js/ui/widget.js:317 -msgid "Applications" -msgstr "Toepassingen" +#: ../js/ui/status/accessibility.js:91 +msgid "Screen Keyboard" +msgstr "Schermtoetsenbord" -#: ../js/ui/widget.js:339 -msgid "Recent Documents" -msgstr "Recente documenten" +#: ../js/ui/status/accessibility.js:94 +msgid "Visual Alerts" +msgstr "Visuele attenderingen" -#: ../js/ui/windowAttentionHandler.js:47 +#: ../js/ui/status/accessibility.js:97 +msgid "Sticky Keys" +msgstr "PlakToetsen" + +#: ../js/ui/status/accessibility.js:100 +msgid "Slow Keys" +msgstr "Trage Toetsen" + +#: ../js/ui/status/accessibility.js:103 +msgid "Bounce Keys" +msgstr "Springende toetsen" + +#: ../js/ui/status/accessibility.js:106 +msgid "Mouse Keys" +msgstr "MuisToetsen" + +#: ../js/ui/status/accessibility.js:110 +msgid "Universal Access Settings" +msgstr "Toegankelijkheidsinstellingen" + +#: ../js/ui/status/accessibility.js:163 +msgid "High Contrast" +msgstr "Hoog contrast" + +#: ../js/ui/status/accessibility.js:202 +msgid "Large Text" +msgstr "Grote tekst" + +#: ../js/ui/status/accessibility.js:223 +msgid "Zoom" +msgstr "Zoomen" + +#: ../js/ui/windowAttentionHandler.js:43 #, c-format msgid "%s has finished starting" msgstr "%s is opgestart" -#: ../js/ui/windowAttentionHandler.js:49 +#: ../js/ui/windowAttentionHandler.js:45 #, c-format msgid "'%s' is ready" msgstr "‘%s’ is klaar" -#: ../src/shell-global.c:967 +#: ../js/ui/workspacesView.js:230 +msgid "" +"Can't add a new workspace because maximum workspaces limit has been reached." +msgstr "" +"Kan geen nieuw werkblad toevoegen omdat maximum aantal werkbladen is bereikt." + +#: ../js/ui/workspacesView.js:247 +msgid "Can't remove the first workspace." +msgstr "Kan het eerste werkblad niet verwijderen" + +#: ../src/shell-global.c:1196 msgid "Less than a minute ago" msgstr "Minder dan een minuut geleden" -#: ../src/shell-global.c:971 +#: ../src/shell-global.c:1200 #, c-format msgid "%d minute ago" msgid_plural "%d minutes ago" msgstr[0] "%d minuut geleden" msgstr[1] "%d minuten geleden" -#: ../src/shell-global.c:976 +#: ../src/shell-global.c:1205 #, c-format msgid "%d hour ago" msgid_plural "%d hours ago" msgstr[0] "%d uur geleden" msgstr[1] "%d uur geleden" -#: ../src/shell-global.c:981 +#: ../src/shell-global.c:1210 #, c-format msgid "%d day ago" msgid_plural "%d days ago" msgstr[0] "%d dag geleden" msgstr[1] "%d dagen geleden" -#: ../src/shell-global.c:986 +#: ../src/shell-global.c:1215 #, c-format msgid "%d week ago" msgid_plural "%d weeks ago" @@ -271,3 +696,15 @@ msgstr "Zoeken" #, c-format msgid "%1$s: %2$s" msgstr "%1$s: %2$s" + +#~ msgid "Sidebar" +#~ msgstr "Zijbalk" + +#~ msgid "%H:%M" +#~ msgstr "%H:%M" + +#~ msgid "Applications" +#~ msgstr "Toepassingen" + +#~ msgid "Recent Documents" +#~ msgstr "Recente documenten" From 0547a582d1a6f0a2dc8a4014632daf5407f8c67a Mon Sep 17 00:00:00 2001 From: Giovanni Campagna Date: Fri, 23 Jul 2010 02:39:44 +0200 Subject: [PATCH 21/21] Add volume indicator Add volume control indicator which uses API from gnome-volume-control to interact with PulseAudio and shows both input and output volumes. Also adds a small wrapper around libcanberra in ShellGlobal, used by the volume indicator to provide auditive feedback. https://bugzilla.gnome.org/show_bug.cgi?id=629455 --- configure.ac | 3 +- js/Makefile.am | 1 + js/ui/panel.js | 3 +- js/ui/popupMenu.js | 15 +-- js/ui/status/volume.js | 206 +++++++++++++++++++++++++++++++++++++++++ src/shell-global.c | 23 +++++ src/shell-global.h | 3 + 7 files changed, 245 insertions(+), 9 deletions(-) create mode 100644 js/ui/status/volume.js diff --git a/configure.ac b/configure.ac index ad6a1f84b..a9533aa4d 100644 --- a/configure.ac +++ b/configure.ac @@ -75,7 +75,8 @@ PKG_CHECK_MODULES(MUTTER_PLUGIN, gio-2.0 >= $GIO_MIN_VERSION clutter-x11-1.0 >= $CLUTTER_MIN_VERSION clutter-glx-1.0 >= $CLUTTER_MIN_VERSION libstartup-notification-1.0 - gobject-introspection-1.0 >= $GOBJECT_INTROSPECTION_MIN_VERSION) + gobject-introspection-1.0 >= $GOBJECT_INTROSPECTION_MIN_VERSION + libcanberra) saved_CFLAGS=$CFLAGS saved_LIBS=$LIBS diff --git a/js/Makefile.am b/js/Makefile.am index f35026e81..eca172601 100644 --- a/js/Makefile.am +++ b/js/Makefile.am @@ -43,6 +43,7 @@ nobase_dist_js_DATA = \ ui/statusIconDispatcher.js \ ui/statusMenu.js \ ui/status/accessibility.js \ + ui/status/volume.js \ ui/telepathyClient.js \ ui/tweener.js \ ui/windowAttentionHandler.js \ diff --git a/js/ui/panel.js b/js/ui/panel.js index 26c3a0330..d683a1813 100644 --- a/js/ui/panel.js +++ b/js/ui/panel.js @@ -31,7 +31,8 @@ const SPINNER_SPEED = 0.02; const STANDARD_TRAY_ICON_ORDER = ['a11y', 'display', 'keyboard', 'volume', 'bluetooth', 'network', 'battery']; const STANDARD_TRAY_ICON_SHELL_IMPLEMENTATION = { - 'a11y': imports.ui.status.accessibility.ATIndicator + 'a11y': imports.ui.status.accessibility.ATIndicator, + 'volume': imports.ui.status.volume.Indicator, }; const CLOCK_FORMAT_KEY = 'format'; diff --git a/js/ui/popupMenu.js b/js/ui/popupMenu.js index 889f29622..8694a601c 100644 --- a/js/ui/popupMenu.js +++ b/js/ui/popupMenu.js @@ -176,7 +176,7 @@ PopupSliderMenuItem.prototype = { if (isNaN(value)) // Avoid spreading NaNs around throw TypeError('The slider value must be a number'); - this._displayValue = this._value = Math.max(Math.min(value, 1), 0); + this._value = Math.max(Math.min(value, 1), 0); this._slider = new St.DrawingArea({ style_class: 'popup-slider-menu-item', reactive: true }); this.actor.set_child(this._slider); @@ -191,7 +191,7 @@ PopupSliderMenuItem.prototype = { if (isNaN(value)) throw TypeError('The slider value must be a number'); - this._displayValue = this._value = Math.max(Math.min(value, 1), 0); + this._value = Math.max(Math.min(value, 1), 0); this._slider.queue_repaint(); }, @@ -231,7 +231,7 @@ PopupSliderMenuItem.prototype = { cr.stroke(); let handleY = height / 2; - let handleX = handleRadius + (width - 2 * handleRadius) * this._displayValue; + let handleX = handleRadius + (width - 2 * handleRadius) * this._value; let color = new Clutter.Color(); themeNode.get_foreground_color(color); @@ -269,8 +269,7 @@ PopupSliderMenuItem.prototype = { Clutter.ungrab_pointer(); this._dragging = false; - this._value = this._displayValue; - this.emit('value-changed', this._value); + this.emit('drag-end'); } return true; }, @@ -299,8 +298,9 @@ PopupSliderMenuItem.prototype = { newvalue = 1; else newvalue = (relX - handleRadius) / (width - 2 * handleRadius); - this._displayValue = newvalue; + this._value = newvalue; this._slider.queue_repaint(); + this.emit('value-changed', this._value); }, get value() { @@ -311,9 +311,10 @@ PopupSliderMenuItem.prototype = { let key = event.get_key_symbol(); if (key == Clutter.Right || key == Clutter.Left) { let delta = key == Clutter.Right ? 0.1 : -0.1; - this._value = this._displayValue = Math.max(0, Math.min(this._value + delta, 1)); + this._value = Math.max(0, Math.min(this._value + delta, 1)); this._slider.queue_repaint(); this.emit('value-changed', this._value); + this.emit('drag-end'); return true; } return false; diff --git a/js/ui/status/volume.js b/js/ui/status/volume.js new file mode 100644 index 000000000..92f17def1 --- /dev/null +++ b/js/ui/status/volume.js @@ -0,0 +1,206 @@ +/* -*- mode: js2; js2-basic-offset: 4; indent-tabs-mode: nil -*- */ + +const DBus = imports.dbus; +const Lang = imports.lang; +const Mainloop = imports.mainloop; +const Shell = imports.gi.Shell; +const Gvc = imports.gi.Gvc; +const Signals = imports.signals; +const St = imports.gi.St; + +const PanelMenu = imports.ui.panelMenu; +const PopupMenu = imports.ui.popupMenu; + +const Gettext = imports.gettext.domain('gnome-shell'); +const _ = Gettext.gettext; + +const VOLUME_MAX = 65536.0; /* PA_VOLUME_NORM */ + +function Indicator() { + this._init.apply(this, arguments); +} + +Indicator.prototype = { + __proto__: PanelMenu.SystemStatusButton.prototype, + + _init: function() { + PanelMenu.SystemStatusButton.prototype._init.call(this, 'audio-volume-muted', null); + + this._control = new Gvc.MixerControl({ name: 'GNOME Shell Volume Control' }); + this._control.connect('ready', Lang.bind(this, this._onControlReady)); + this._control.connect('default-sink-changed', Lang.bind(this, this._readOutput)); + this._control.connect('default-source-changed', Lang.bind(this, this._readInput)); + this._control.connect('stream-added', Lang.bind(this, this._maybeShowInput)); + this._control.connect('stream-removed', Lang.bind(this, this._maybeShowInput)); + + this._output = null; + this._outputVolumeId = 0; + this._outputMutedId = 0; + this._outputSwitch = new PopupMenu.PopupSwitchMenuItem(_("Output: Muted"), false); + this._outputSwitch.connect('toggled', Lang.bind(this, this._switchToggled, '_output')); + this._outputSlider = new PopupMenu.PopupSliderMenuItem(0); + this._outputSlider.connect('value-changed', Lang.bind(this, this._sliderChanged, '_output')); + this._outputSlider.connect('drag-end', Lang.bind(this, this._notifyVolumeChange)); + this.menu.addMenuItem(this._outputSwitch); + this.menu.addMenuItem(this._outputSlider); + + this._separator = new PopupMenu.PopupSeparatorMenuItem(); + this.menu.addMenuItem(this._separator); + + this._input = null; + this._inputVolumeId = 0; + this._inputMutedId = 0; + this._inputSwitch = new PopupMenu.PopupSwitchMenuItem(_("Input: Muted"), false); + this._inputSwitch.connect('toggled', Lang.bind(this, this._switchToggled, '_input')); + this._inputSlider = new PopupMenu.PopupSliderMenuItem(0); + this._inputSlider.connect('value-changed', Lang.bind(this, this._sliderChanged, '_input')); + this._inputSlider.connect('drag-end', Lang.bind(this, this._notifyVolumeChange)); + this.menu.addMenuItem(this._inputSwitch); + this.menu.addMenuItem(this._inputSlider); + + this.menu.addMenuItem(new PopupMenu.PopupSeparatorMenuItem()); + this.menu.addAction(_("Sound Preferences"), function() { + let p = new Shell.Process({ args: ['gnome-control-center', 'volume'] }); + p.run(); + }); + + this._control.open(); + }, + + _onControlReady: function() { + this._readOutput(); + this._readInput(); + }, + + _readOutput: function() { + if (this._outputVolumeId) { + this._output.disconnect(this._outputVolumeId); + this._output.disconnect(this._outputMutedId); + this._outputVolumeId = 0; + this._outputMutedId = 0; + } + this._output = this._control.get_default_sink(); + if (this._output) { + this._outputMutedId = this._output.connect('notify::is-muted', Lang.bind(this, this._mutedChanged, '_output')); + this._outputVolumeId = this._output.connect('notify::volume', Lang.bind(this, this._volumeChanged, '_output')); + this._mutedChanged (null, null, '_output'); + this._volumeChanged (null, null, '_output'); + this.setIcon(this._volumeToIcon(this._output.volume)); + } else { + this._outputSwitch.label.text = _("Output: Muted"); + this._outputSwitch.setToggleState(false); + this.setIcon('audio-volume-muted-symbolic'); + } + }, + + _readInput: function() { + if (this._inputVolumeId) { + this._input.disconnect(this._inputVolumeId); + this._input.disconnect(this._inputMutedId); + this._inputVolumeId = 0; + this._inputMutedId = 0; + } + this._input = this._control.get_default_source(); + if (this._input) { + this._inputMutedId = this._input.connect('notify::is-muted', Lang.bind(this, this._mutedChanged, '_input')); + this._inputVolumeId = this._input.connect('notify::volume', Lang.bind(this, this._volumeChanged, '_input')); + this._mutedChanged (null, null, '_input'); + this._volumeChanged (null, null, '_input'); + } else { + this._separator.actor.hide(); + this._inputSwitch.actor.hide(); + this._inputSlider.actor.hide(); + } + }, + + _maybeShowInput: function() { + // only show input widgets if any application is recording audio + let showInput = false; + let recordingApps = this._control.get_source_outputs(); + if (this._source && recordingApps) { + for (let i = 0; i < recordingApp.length; i++) { + let outputStream = recordingApp[i]; + let id = outputStream.get_application_id(); + // but skip gnome-volume-control and pavucontrol + // (that appear as recording because they show the input level) + if (!id || (id != 'org.gnome.VolumeControl' && id != 'org.PulseAudio.pavucontrol')) { + showInput = true; + break; + } + } + } + if (showInput) { + this._separator.actor.show(); + this._inputSwitch.actor.show(); + this._inputSlider.actor.show(); + } else { + this._separator.actor.hide(); + this._inputSwitch.actor.hide(); + this._inputSlider.actor.hide(); + } + }, + + _volumeToIcon: function(volume) { + if (volume <= 0) { + return 'audio-volume-muted'; + } else { + let v = volume / VOLUME_MAX; + if (v < 0.33) + return 'audio-volume-low'; + if (v > 0.8) + return 'audio-volume-high'; + return 'audio-volume-medium'; + } + }, + + _sliderChanged: function(slider, value, property) { + if (this[property] == null) { + log ('Volume slider changed for %s, but %s does not exist'.format(property, property)); + return; + } + this[property].volume = value * VOLUME_MAX; + this[property].push_volume(); + }, + + _notifyVolumeChange: function() { + global.play_theme_sound('audio-volume-change'); + }, + + _switchToggled: function(switchItem, state, property) { + if (this[property] == null) { + log ('Volume mute switch toggled for %s, but %s does not exist'.format(property, property)); + return; + } + this[property].change_is_muted(!state); + this._notifyVolumeChange(); + }, + + _mutedChanged: function(object, param_spec, property) { + let muted = this[property].is_muted; + let toggleSwitch = this[property+'Switch']; + toggleSwitch.setToggleState(!muted); + this._updateLabel(property); + if (property == '_output') { + if (muted) + this.setIcon('audio-volume-muted'); + else + this.setIcon(this._volumeToIcon(this._output.volume)); + } + }, + + _volumeChanged: function(object, param_spec, property) { + this[property+'Slider'].setValue(this[property].volume / VOLUME_MAX); + this._updateLabel(property); + if (property == '_output') + this.setIcon(this._volumeToIcon(this._output.volume)); + }, + + _updateLabel: function(property) { + let label; + if (this[property].is_muted) + label = (property == '_output' ? _("Output: Muted") : _("Input: Muted")); + else + label = (property == '_output' ? _("Output: %3.0f%%") : _("Input: %3.0f%%")).format(this[property].volume / VOLUME_MAX * 100); + this[property+'Switch'].label.text = label; + } +}; diff --git a/src/shell-global.c b/src/shell-global.c index 5251380d1..1e42fc9f0 100644 --- a/src/shell-global.c +++ b/src/shell-global.c @@ -24,6 +24,7 @@ #include #include #include +#include #ifdef HAVE_SYS_RESOURCE_H #include #endif @@ -67,6 +68,9 @@ struct _ShellGlobal { guint work_count; GSList *leisure_closures; guint leisure_function_id; + + /* For sound notifications */ + ca_context *sound_context; }; enum { @@ -214,6 +218,10 @@ shell_global_init (ShellGlobal *global) global->last_change_screen_width = 0; global->last_change_screen_height = 0; + + ca_context_create (&global->sound_context); + ca_context_change_props (global->sound_context, CA_PROP_APPLICATION_NAME, PACKAGE_NAME, CA_PROP_APPLICATION_ID, "org.gnome.Shell", NULL); + ca_context_open (global->sound_context); } static void @@ -1788,3 +1796,18 @@ shell_global_run_at_leisure (ShellGlobal *global, if (global->work_count == 0) schedule_leisure_functions (global); } + +/** + * shell_global_play_theme_sound: + * @global: the #ShellGlobal + * @name: the sound name + * + * Plays a simple sound picked according to Freedesktop sound theme. + * Really just a workaround for libcanberra not being introspected. + */ +void +shell_global_play_theme_sound (ShellGlobal *global, + const char *name) +{ + ca_context_play (global->sound_context, 0, CA_PROP_EVENT_ID, name, NULL); +} diff --git a/src/shell-global.h b/src/shell-global.h index 34c56786b..5cf5e1373 100644 --- a/src/shell-global.h +++ b/src/shell-global.h @@ -130,6 +130,9 @@ void shell_global_run_at_leisure (ShellGlobal *global, gpointer user_data, GDestroyNotify notify); +void shell_global_play_theme_sound (ShellGlobal *global, + const char *name); + G_END_DECLS #endif /* __SHELL_GLOBAL_H__ */