diff options
-rw-r--r-- | plugins/mpris/Makefile.am | 2 | ||||
-rw-r--r-- | plugins/mpris/bus-watch-namespace.c | 347 | ||||
-rw-r--r-- | plugins/mpris/bus-watch-namespace.h | 34 | ||||
-rw-r--r-- | plugins/mpris/msd-mpris-manager.c | 58 |
4 files changed, 402 insertions, 39 deletions
diff --git a/plugins/mpris/Makefile.am b/plugins/mpris/Makefile.am index f382b3d..3b000bb 100644 --- a/plugins/mpris/Makefile.am +++ b/plugins/mpris/Makefile.am @@ -2,6 +2,8 @@ plugin_LTLIBRARIES = \ libmpris.la libmpris_la_SOURCES = \ + bus-watch-namespace.c \ + bus-watch-namespace.h \ msd-mpris-manager.c \ msd-mpris-manager.h \ msd-mpris-plugin.c \ diff --git a/plugins/mpris/bus-watch-namespace.c b/plugins/mpris/bus-watch-namespace.c new file mode 100644 index 0000000..1ffdff4 --- /dev/null +++ b/plugins/mpris/bus-watch-namespace.c @@ -0,0 +1,347 @@ +/* + * Copyright 2013 Canonical Ltd. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + * Author: Lars Uebernickel <[email protected]> + */ + +#include <gio/gio.h> +#include <string.h> +#include "bus-watch-namespace.h" + +typedef struct +{ + guint id; + gchar *name_space; + GBusNameAppearedCallback appeared_handler; + GBusNameVanishedCallback vanished_handler; + gpointer user_data; + GDestroyNotify user_data_destroy; + + GDBusConnection *connection; + GCancellable *cancellable; + GHashTable *names; + guint subscription_id; +} NamespaceWatcher; + +typedef struct +{ + NamespaceWatcher *watcher; + gchar *name; +} GetNameOwnerData; + +static guint namespace_watcher_next_id; +static GHashTable *namespace_watcher_watchers; + +static void +namespace_watcher_stop (gpointer data) +{ + NamespaceWatcher *watcher = data; + + g_cancellable_cancel (watcher->cancellable); + g_object_unref (watcher->cancellable); + + if (watcher->subscription_id) + g_dbus_connection_signal_unsubscribe (watcher->connection, watcher->subscription_id); + + if (watcher->vanished_handler) + { + GHashTableIter it; + const gchar *name; + + g_hash_table_iter_init (&it, watcher->names); + while (g_hash_table_iter_next (&it, (gpointer *) &name, NULL)) + watcher->vanished_handler (watcher->connection, name, watcher->user_data); + } + + if (watcher->user_data_destroy) + watcher->user_data_destroy (watcher->user_data); + + if (watcher->connection) + { + g_signal_handlers_disconnect_by_func (watcher->connection, namespace_watcher_stop, watcher); + g_object_unref (watcher->connection); + } + + g_hash_table_unref (watcher->names); + + g_hash_table_remove (namespace_watcher_watchers, GUINT_TO_POINTER (watcher->id)); + if (g_hash_table_size (namespace_watcher_watchers) == 0) + g_clear_pointer (&namespace_watcher_watchers, g_hash_table_destroy); + + g_free (watcher); +} + +static void +namespace_watcher_name_appeared (NamespaceWatcher *watcher, + const gchar *name, + const gchar *owner) +{ + /* There's a race between NameOwnerChanged signals arriving and the + * ListNames/GetNameOwner sequence returning, so this function might + * be called more than once for the same name. To ensure that + * appeared_handler is only called once for each name, it is only + * called when inserting the name into watcher->names (each name is + * only inserted once there). + */ + if (g_hash_table_contains (watcher->names, name)) + return; + + g_hash_table_add (watcher->names, g_strdup (name)); + + if (watcher->appeared_handler) + watcher->appeared_handler (watcher->connection, name, owner, watcher->user_data); +} + +static void +namespace_watcher_name_vanished (NamespaceWatcher *watcher, + const gchar *name) +{ + if (g_hash_table_remove (watcher->names, name) && watcher->vanished_handler) + watcher->vanished_handler (watcher->connection, name, watcher->user_data); +} + +static gboolean +dbus_name_has_namespace (const gchar *name, + const gchar *name_space) +{ + gint len_name; + gint len_namespace; + + len_name = strlen (name); + len_namespace = strlen (name_space); + + if (len_name < len_namespace) + return FALSE; + + if (memcmp (name_space, name, len_namespace) != 0) + return FALSE; + + return len_namespace == len_name || name[len_namespace] == '.'; +} + +static void +name_owner_changed (GDBusConnection *connection, + const gchar *sender_name, + const gchar *object_path, + const gchar *interface_name, + const gchar *signal_name, + GVariant *parameters, + gpointer user_data) +{ + NamespaceWatcher *watcher = user_data; + const gchar *name; + const gchar *old_owner; + const gchar *new_owner; + + g_variant_get (parameters, "(&s&s&s)", &name, &old_owner, &new_owner); + + if (old_owner[0] != '\0') + namespace_watcher_name_vanished (watcher, name); + + if (new_owner[0] != '\0') + namespace_watcher_name_appeared (watcher, name, new_owner); +} + +static void +got_name_owner (GObject *object, + GAsyncResult *result, + gpointer user_data) +{ + GetNameOwnerData *data = user_data; + GError *error = NULL; + GVariant *reply; + const gchar *owner; + + reply = g_dbus_connection_call_finish (G_DBUS_CONNECTION (object), result, &error); + + if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) + { + g_error_free (error); + goto out; + } + + if (reply == NULL) + { + if (!g_error_matches (error, G_DBUS_ERROR, G_DBUS_ERROR_NAME_HAS_NO_OWNER)) + g_warning ("bus_watch_namespace: error calling org.freedesktop.DBus.GetNameOwner: %s", error->message); + g_error_free (error); + goto out; + } + + g_variant_get (reply, "(&s)", &owner); + namespace_watcher_name_appeared (data->watcher, data->name, owner); + + g_variant_unref (reply); + +out: + g_free (data->name); + g_slice_free (GetNameOwnerData, data); +} + +static void +names_listed (GObject *object, + GAsyncResult *result, + gpointer user_data) +{ + NamespaceWatcher *watcher; + GError *error = NULL; + GVariant *reply; + GVariantIter *iter; + const gchar *name; + + reply = g_dbus_connection_call_finish (G_DBUS_CONNECTION (object), result, &error); + + if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) + { + g_error_free (error); + return; + } + + watcher = user_data; + + if (reply == NULL) + { + g_warning ("bus_watch_namespace: error calling org.freedesktop.DBus.ListNames: %s", error->message); + g_error_free (error); + return; + } + + g_variant_get (reply, "(as)", &iter); + while (g_variant_iter_next (iter, "&s", &name)) + { + if (dbus_name_has_namespace (name, watcher->name_space)) + { + GetNameOwnerData *data = g_slice_new (GetNameOwnerData); + data->watcher = watcher; + data->name = g_strdup (name); + g_dbus_connection_call (watcher->connection, "org.freedesktop.DBus", "/", + "org.freedesktop.DBus", "GetNameOwner", + g_variant_new ("(s)", name), G_VARIANT_TYPE ("(s)"), + G_DBUS_CALL_FLAGS_NONE, -1, watcher->cancellable, + got_name_owner, data); + } + } + + g_variant_iter_free (iter); + g_variant_unref (reply); +} + +static void +connection_closed (GDBusConnection *connection, + gboolean remote_peer_vanished, + GError *error, + gpointer user_data) +{ + NamespaceWatcher *watcher = user_data; + + namespace_watcher_stop (watcher); +} + +static void +got_bus (GObject *object, + GAsyncResult *result, + gpointer user_data) +{ + GDBusConnection *connection; + NamespaceWatcher *watcher; + GError *error = NULL; + + connection = g_bus_get_finish (result, &error); + + if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) + { + g_error_free (error); + return; + } + + watcher = user_data; + + if (connection == NULL) + { + namespace_watcher_stop (watcher); + return; + } + + watcher->connection = connection; + g_signal_connect (watcher->connection, "closed", G_CALLBACK (connection_closed), watcher); + + watcher->subscription_id = + g_dbus_connection_signal_subscribe (watcher->connection, "org.freedesktop.DBus", + "org.freedesktop.DBus", "NameOwnerChanged", "/org/freedesktop/DBus", + watcher->name_space, G_DBUS_SIGNAL_FLAGS_MATCH_ARG0_NAMESPACE, + name_owner_changed, watcher, NULL); + + g_dbus_connection_call (watcher->connection, "org.freedesktop.DBus", "/", + "org.freedesktop.DBus", "ListNames", NULL, G_VARIANT_TYPE ("(as)"), + G_DBUS_CALL_FLAGS_NONE, -1, watcher->cancellable, + names_listed, watcher); +} + +guint +bus_watch_namespace (GBusType bus_type, + const gchar *name_space, + GBusNameAppearedCallback appeared_handler, + GBusNameVanishedCallback vanished_handler, + gpointer user_data, + GDestroyNotify user_data_destroy) +{ + NamespaceWatcher *watcher; + + /* same rules for interfaces and well-known names */ + g_return_val_if_fail (name_space != NULL && g_dbus_is_interface_name (name_space), 0); + g_return_val_if_fail (appeared_handler || vanished_handler, 0); + + watcher = g_new0 (NamespaceWatcher, 1); + watcher->id = namespace_watcher_next_id++; + watcher->name_space = g_strdup (name_space); + watcher->appeared_handler = appeared_handler; + watcher->vanished_handler = vanished_handler; + watcher->user_data = user_data; + watcher->user_data_destroy = user_data_destroy; + watcher->cancellable = g_cancellable_new ();; + watcher->names = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL); + + if (namespace_watcher_watchers == NULL) + namespace_watcher_watchers = g_hash_table_new (g_direct_hash, g_direct_equal); + g_hash_table_insert (namespace_watcher_watchers, GUINT_TO_POINTER (watcher->id), watcher); + + g_bus_get (bus_type, watcher->cancellable, got_bus, watcher); + + return watcher->id; +} + +void +bus_unwatch_namespace (guint id) +{ + /* namespace_watcher_stop() might have already removed the watcher + * with @id in the case of a connection error. Thus, this function + * doesn't warn when @id is absent from the hash table. + */ + + if (namespace_watcher_watchers) + { + NamespaceWatcher *watcher; + + watcher = g_hash_table_lookup (namespace_watcher_watchers, GUINT_TO_POINTER (id)); + if (watcher) + { + /* make sure vanished() is not called as a result of this function */ + g_hash_table_remove_all (watcher->names); + + namespace_watcher_stop (watcher); + } + } +} diff --git a/plugins/mpris/bus-watch-namespace.h b/plugins/mpris/bus-watch-namespace.h new file mode 100644 index 0000000..215f6be --- /dev/null +++ b/plugins/mpris/bus-watch-namespace.h @@ -0,0 +1,34 @@ +/* + * Copyright 2013 Canonical Ltd. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + * Author: Lars Uebernickel <[email protected]> + */ + +#ifndef __BUS_WATCH_NAMESPACE_H__ +#define __BUS_WATCH_NAMESPACE_H__ + +#include <gio/gio.h> + +guint bus_watch_namespace (GBusType bus_type, + const gchar *name_space, + GBusNameAppearedCallback appeared_handler, + GBusNameVanishedCallback vanished_handler, + gpointer user_data, + GDestroyNotify user_data_destroy); + +void bus_unwatch_namespace (guint id); + +#endif diff --git a/plugins/mpris/msd-mpris-manager.c b/plugins/mpris/msd-mpris-manager.c index ba89ba5..36febae 100644 --- a/plugins/mpris/msd-mpris-manager.c +++ b/plugins/mpris/msd-mpris-manager.c @@ -44,38 +44,18 @@ #include "mate-settings-profile.h" #include "msd-mpris-manager.h" +#include "bus-watch-namespace.h" #define MPRIS_OBJECT_PATH "/org/mpris/MediaPlayer2" #define MPRIS_INTERFACE "org.mpris.MediaPlayer2.Player" #define MPRIS_PREFIX "org.mpris.MediaPlayer2." -/* Number of media players supported. - * Correlates to the number of elements in BUS_NAMES */ -static const gint NUM_BUS_NAMES = 16; - -/* Names to we want to watch */ -static const gchar *BUS_NAMES[] = {"org.mpris.MediaPlayer2.audacious", - "org.mpris.MediaPlayer2.clementine", - "org.mpris.MediaPlayer2.vlc", - "org.mpris.MediaPlayer2.mpd", - "org.mpris.MediaPlayer2.exaile", - "org.mpris.MediaPlayer2.banshee", - "org.mpris.MediaPlayer2.rhythmbox", - "org.mpris.MediaPlayer2.pragha", - "org.mpris.MediaPlayer2.quodlibet", - "org.mpris.MediaPlayer2.guayadeque", - "org.mpris.MediaPlayer2.amarok", - "org.mpris.MediaPlayer2.nuvolaplayer", - "org.mpris.MediaPlayer2.xbmc", - "org.mpris.MediaPlayer2.xnoise", - "org.mpris.MediaPlayer2.gmusicbrowser", - "org.mpris.MediaPlayer2.spotify"}; - struct MsdMprisManagerPrivate { GQueue *media_player_queue; GDBusProxy *media_keys_proxy; guint watch_id; + guint namespace_watcher_id; }; enum { @@ -111,8 +91,9 @@ static void mp_name_appeared (GDBusConnection *connection, const gchar *name, const gchar *name_owner, - MsdMprisManager *manager) + gpointer user_data) { + MsdMprisManager *manager = user_data; gchar *player_name; g_debug ("MPRIS Name acquired: %s\n", name); @@ -127,8 +108,9 @@ mp_name_appeared (GDBusConnection *connection, static void mp_name_vanished (GDBusConnection *connection, const gchar *name, - MsdMprisManager *manager) + gpointer user_data) { + MsdMprisManager *manager = user_data; gchar *player_name; GList *player_list; @@ -315,26 +297,18 @@ gboolean msd_mpris_manager_start (MsdMprisManager *manager, GError **error) { - GBusNameWatcherFlags flags = G_BUS_NAME_WATCHER_FLAGS_NONE; - int i; - g_debug ("Starting mpris manager"); mate_settings_profile_start (NULL); manager->priv->media_player_queue = g_queue_new(); - /* Register all the names we wish to watch.*/ - for (i = 0; i < NUM_BUS_NAMES; i++) - { - g_bus_watch_name(G_BUS_TYPE_SESSION, - BUS_NAMES[i], - flags, - (GBusNameAppearedCallback) mp_name_appeared, - (GBusNameVanishedCallback) mp_name_vanished, - manager, - NULL); - } - + /* Register the namespace we wish to watch. */ + manager->priv->namespace_watcher_id = bus_watch_namespace (G_BUS_TYPE_SESSION, + "org.mpris.MediaPlayer2", + mp_name_appeared, + mp_name_vanished, + manager, + NULL); manager->priv->watch_id = g_bus_watch_name (G_BUS_TYPE_SESSION, "org.mate.SettingsDaemon", @@ -361,6 +335,12 @@ msd_mpris_manager_stop (MsdMprisManager *manager) g_bus_unwatch_name (manager->priv->watch_id); manager->priv->watch_id = 0; } + + if (manager->priv->namespace_watcher_id != 0) { + bus_unwatch_namespace (manager->priv->namespace_watcher_id); + manager->priv->namespace_watcher_id = 0; + } + } static void |