diff options
Diffstat (limited to 'plugins')
| -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 | 
