aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorLars Uebernickel <lars.uebernickel@canonical.com>2013-08-02 12:06:55 +0200
committerLars Uebernickel <lars.uebernickel@canonical.com>2013-08-02 12:06:55 +0200
commit98c5556a14454fa988b0b81ffb22cef9a67011e9 (patch)
tree0f50d4b5315dfa095b7cccbe824ee242289ee31a /src
parentab72c8871394f1b197e606c2d5b20f149c27138a (diff)
downloadayatana-indicator-sound-98c5556a14454fa988b0b81ffb22cef9a67011e9.tar.gz
ayatana-indicator-sound-98c5556a14454fa988b0b81ffb22cef9a67011e9.tar.bz2
ayatana-indicator-sound-98c5556a14454fa988b0b81ffb22cef9a67011e9.zip
Use bus_watch_namespace() to monitor mpris players
This function is more robust than the current code and uses glib's G_DBUS_SIGNAL_FLAGS_MATCH_ARG0_NAMESPACE instead of creating match rules itself. bus-watch-namespace.[ch] is shared with gnome-settings-daemon.
Diffstat (limited to 'src')
-rw-r--r--src/Makefile.am6
-rw-r--r--src/bus-watch-namespace.c347
-rw-r--r--src/bus-watch-namespace.h34
-rw-r--r--src/media-player-list.vala22
-rw-r--r--src/mpris2-watcher.vala200
5 files changed, 398 insertions, 211 deletions
diff --git a/src/Makefile.am b/src/Makefile.am
index 8af02ce..1a82a18 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -7,9 +7,10 @@ indicator_sound_service_SOURCES = \
media-player.vala \
media-player-list.vala \
mpris2-interfaces.vala \
- mpris2-watcher.vala \
freedesktop-interfaces.vala \
- sound-menu.vala
+ sound-menu.vala \
+ bus-watch-namespace.c \
+ bus-watch-namespace.h
indicator_sound_service_VALAFLAGS = \
--ccode \
@@ -22,6 +23,7 @@ indicator_sound_service_VALAFLAGS = \
--pkg libxml-2.0 \
--pkg libpulse \
--pkg libpulse-mainloop-glib \
+ --pkg bus-watcher \
--target-glib=2.36
# -w to disable warnings for vala-generated code
diff --git a/src/bus-watch-namespace.c b/src/bus-watch-namespace.c
new file mode 100644
index 0000000..1ffdff4
--- /dev/null
+++ b/src/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 <lars.uebernickel@canonical.com>
+ */
+
+#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/src/bus-watch-namespace.h b/src/bus-watch-namespace.h
new file mode 100644
index 0000000..215f6be
--- /dev/null
+++ b/src/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 <lars.uebernickel@canonical.com>
+ */
+
+#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/src/media-player-list.vala b/src/media-player-list.vala
index 6eb5fc9..62badc2 100644
--- a/src/media-player-list.vala
+++ b/src/media-player-list.vala
@@ -26,9 +26,7 @@ public class MediaPlayerList {
public MediaPlayerList () {
this._players = new HashTable<string, MediaPlayer> (str_hash, str_equal);
- this.mpris_watcher = new Mpris2Watcher ();
- this.mpris_watcher.client_appeared.connect (this.player_appeared);
- this.mpris_watcher.client_disappeared.connect (this.player_disappeared);
+ BusWatcher.watch_namespace (BusType.SESSION, "org.mpris.MediaPlayer2", this.player_appeared, this.player_disappeared);
}
/* only valid while the list is not changed */
@@ -113,15 +111,21 @@ public class MediaPlayerList {
public signal void player_removed (MediaPlayer player);
HashTable<string, MediaPlayer> _players;
- Mpris2Watcher mpris_watcher;
- void player_appeared (string desktop_id, string dbus_name, bool use_playlists) {
- var player = this.insert (desktop_id);
- if (player != null)
- player.attach (dbus_name);
+ void player_appeared (DBusConnection connection, string name, string owner) {
+ try {
+ MprisRoot mpris2_root = Bus.get_proxy_sync (BusType.SESSION, name, MPRIS_MEDIA_PLAYER_PATH);
+
+ var player = this.insert (mpris2_root.DesktopEntry);
+ if (player != null)
+ player.attach (name);
+ }
+ catch (Error e) {
+ warning ("unable to create mpris proxy for '%s': %s", name, e.message);
+ }
}
- void player_disappeared (string dbus_name) {
+ void player_disappeared (DBusConnection connection, string dbus_name) {
MediaPlayer? player = this._players.find ( (name, player) => {
return player.dbus_name == dbus_name;
});
diff --git a/src/mpris2-watcher.vala b/src/mpris2-watcher.vala
deleted file mode 100644
index 4a1ab6e..0000000
--- a/src/mpris2-watcher.vala
+++ /dev/null
@@ -1,200 +0,0 @@
-/*
-Copyright 2010 Canonical Ltd.
-
-Authors:
- Conor Curran <conor.curran@canonical.com>
-
-This program is free software: you can redistribute it and/or modify it
-under the terms of the GNU General Public License version 3, as published
-by the Free Software Foundation.
-
-This program is distributed in the hope that it will be useful, but
-WITHOUT ANY WARRANTY; without even the implied warranties of
-MERCHANTABILITY, SATISFACTORY QUALITY, 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, see <http://www.gnu.org/licenses/>.
-*/
-
-using Xml;
-
-public class Mpris2Watcher : GLib.Object
-{
- DBusConnection session_bus;
-
- public signal void client_appeared ( string desktop_file_name,
- string dbus_name,
- bool use_playlists );
- public signal void client_disappeared ( string dbus_name );
-
- public Mpris2Watcher ()
- {
- }
-
- construct
- {
- const string match_rule = "type='signal'," +
- "sender='org.freedesktop.DBus'," +
- "interface='org.freedesktop.DBus'," +
- "member='NameOwnerChanged'," +
- "path='/org/freedesktop/DBus'," +
- "arg0namespace='org.mpris.MediaPlayer2'";
- try {
- this.session_bus = Bus.get_sync (BusType.SESSION);
-
- this.session_bus.call_sync ("org.freedesktop.DBus",
- "/",
- "org.freedesktop.DBus",
- "AddMatch",
- new Variant ("(s)", match_rule),
- VariantType.TUPLE,
- DBusCallFlags.NONE,
- -1);
-
- this.session_bus.signal_subscribe ("org.freedesktop.DBus",
- "org.freedesktop.DBus",
- "NameOwnerChanged",
- "/org/freedesktop/DBus",
- null,
- DBusSignalFlags.NO_MATCH_RULE,
- this.name_owner_changed);
-
- this.check_for_active_clients.begin();
- }
- catch (GLib.Error e) {
- warning ("unable to set up name watch for mrpis clients: %s", e.message);
- }
- }
-
- // At startup check to see if there are clients up that we are interested in
- // More relevant for development and daemon's like mpd.
- async void check_for_active_clients()
- {
- Variant interfaces;
-
- try {
- interfaces = yield this.session_bus.call ("org.freedesktop.DBus",
- "/",
- "org.freedesktop.DBus",
- "ListNames",
- null,
- new VariantType ("(as)"),
- DBusCallFlags.NONE,
- -1);
- }
- catch (GLib.Error e) {
- warning ("unable to search for existing mpris clients: %s ", e.message);
- return;
- }
-
- foreach (var val in interfaces.get_child_value (0)) {
- var address = (string) val;
- if (address.has_prefix (MPRIS_PREFIX)){
- MprisRoot? mpris2_root = this.create_mpris_root(address);
- if (mpris2_root == null) return;
- bool use_playlists = this.supports_playlists ( address );
- client_appeared (mpris2_root.DesktopEntry + ".desktop", address, use_playlists);
- }
- }
- }
-
- void name_owner_changed (DBusConnection con, string sender, string object_path,
- string interface_name, string signal_name, Variant parameters)
- {
- string name, previous_owner, current_owner;
-
- parameters.get ("(sss)", out name, out previous_owner, out current_owner);
-
- MprisRoot? mpris2_root = this.create_mpris_root (name);
- if (mpris2_root == null) return;
-
- if (previous_owner != "" && current_owner == "") {
- debug ("Client '%s' gone down", name);
- client_disappeared (name);
- }
- else if (previous_owner == "" && current_owner != "") {
- debug ("Client '%s' has appeared", name);
- bool use_playlists = this.supports_playlists ( name );
- client_appeared (mpris2_root.DesktopEntry + ".desktop", name, use_playlists);
- }
- }
-
- private MprisRoot? create_mpris_root ( string name ){
- MprisRoot mpris2_root = null;
- if ( name.has_prefix (MPRIS_PREFIX) ){
- try {
- mpris2_root = Bus.get_proxy_sync ( BusType.SESSION,
- name,
- MPRIS_MEDIA_PLAYER_PATH );
- }
- catch (IOError e){
- warning( "Mpris2watcher could not create a root interface: %s",
- e.message );
- }
- }
- return mpris2_root;
- }
-
- private bool supports_playlists ( string name )
- {
- FreeDesktopIntrospectable introspectable;
-
- try {
- /* The dbusproxy flag parameter is needed to ensure Banshee does not
- blow up. I suspect the issue is that if you
- try to instantiate a dbus object which does not have any properties
- associated with it, gdbus will attempt to fetch the properties (this is
- in the documentation) but the banshee mpris dbus object more than likely
- causes a crash because it doesn't check for the presence of properties
- before attempting to access them.
- */
- introspectable = Bus.get_proxy_sync ( BusType.SESSION,
- name,
- MPRIS_MEDIA_PLAYER_PATH,
- GLib.DBusProxyFlags.DO_NOT_LOAD_PROPERTIES);
- var results = introspectable.Introspect();
- return this.parse_interfaces (results);
- }
- catch (IOError e){
- warning( "Could not create an introspectable object: %s",
- e.message );
- }
- return false;
- }
-
- private bool parse_interfaces( string interface_info )
- {
- //parse the document from path
- bool result = false;
- Xml.Doc* xml_doc = Parser.parse_doc (interface_info);
- if (xml_doc == null) {
- warning ("Mpris2Watcher - parse-interfaces - failed to instantiate xml doc");
- return false;
- }
- //get the root node. notice the dereferencing operator -> instead of .
- Xml.Node* root_node = xml_doc->get_root_element ();
- if (root_node == null) {
- //free the document manually before throwing because the garbage collector can't work on pointers
- delete xml_doc;
- warning ("Mpris2Watcher - the interface info xml is empty");
- return false;
- }
-
- //let's parse those nodes
- for (Xml.Node* iter = root_node->children; iter != null; iter = iter->next) {
- //spaces btw. tags are also nodes, discard them
- if (iter->type != ElementType.ELEMENT_NODE){
- continue;
- }
- Xml.Attr* attributes = iter->properties; //get the node's name
- string interface_name = attributes->children->content;
- debug ( "this dbus object has interface %s ", interface_name );
- if ( interface_name == MPRIS_PREFIX.concat("Playlists")){
- result = true;
- }
- }
- delete xml_doc;
- return result;
- }
-}