Skip to content
Snippets Groups Projects
Forked from Panel Plugins / xfce4-pulseaudio-plugin
483 commits behind the upstream repository.
  • Gaël Bonithon's avatar
    75a6a97f
    wnck: Use Libxfce4windowing when available · 75a6a97f
    Gaël Bonithon authored
    This makes the alternative raise method available on Wayland as well,
    simplifying it a bit in the process, which should introduce no
    difference in behavior on X11. The "wnck" prefix/suffix is generally
    retained in variable and function names, as it is in the Xfconf property
    name, and it doesn't seem necessary to rename everything at the cost of
    having to deal with backwards compatibility.
    75a6a97f
    History
    wnck: Use Libxfce4windowing when available
    Gaël Bonithon authored
    This makes the alternative raise method available on Wayland as well,
    simplifying it a bit in the process, which should introduce no
    difference in behavior on X11. The "wnck" prefix/suffix is generally
    retained in variable and function names, as it is in the Xfconf property
    name, and it doesn't seem necessary to rename everything at the cost of
    having to deal with backwards compatibility.
pulseaudio-mpris-player.c 32.04 KiB
/*  Copyright (c) 2017-2020 Sean Davis <bluesabre@xfce.org>
 *
 *  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., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 */



#ifdef HAVE_CONFIG_H
#include <config.h>
#endif

#include <gio/gio.h>
#include <gio/gdesktopappinfo.h>
#include <glib.h>
#include <gtk/gtk.h>

#ifdef HAVE_LIBXFCE4WINDOWING
#include <libxfce4windowing/libxfce4windowing.h>
#elif defined (HAVE_WNCK)
#define WNCK_I_KNOW_THIS_IS_UNSTABLE = 1
#include <libwnck/libwnck.h>
#include <gdk/gdkx.h>
#endif

#include "pulseaudio-mpris-player.h"



struct _PulseaudioMprisPlayer
{
  GObject          __parent__;

  GDBusConnection  *dbus_connection;
  GDBusProxy       *dbus_props_proxy;
  GDBusProxy       *dbus_player_proxy;
  GDBusProxy       *dbus_playlists_proxy;
  gchar            *dbus_name;

  gchar            *player;
  gchar            *player_label;
  gchar            *icon_name;

  gboolean          connected;

  gchar            *title;
  gchar            *artist;
  gchar            *full_path;

  gboolean          can_go_next;
  gboolean          can_go_previous;
  gboolean          can_pause;
  gboolean          can_play;
  gboolean          can_raise;
  gboolean          can_launch;

  PlaybackStatus    playback_status;

  guint             watch_id;

  GHashTable       *playlists;

#ifdef HAVE_LIBXFCE4WINDOWING
  XfwScreen        *screen;
#elif defined (HAVE_WNCK)
  gulong            xid;
#endif
};

struct _PulseaudioMprisPlayerClass
{
  GObjectClass            __parent__;
  void (*connection)      (PulseaudioMprisPlayer *player, gboolean        connected);
  void (*playback_status) (PulseaudioMprisPlayer *player, PlaybackStatus  playback_status);
  void (*metadata)        (PulseaudioMprisPlayer *player);
};



static void pulseaudio_mpris_player_finalize     (GObject               *object);
static void pulseaudio_mpris_player_dbus_connect (PulseaudioMprisPlayer *player);

static GVariant *pulseaudio_mpris_player_playlists_get_playlists  (PulseaudioMprisPlayer *player);
static void      pulseaudio_mpris_player_parse_playlists          (PulseaudioMprisPlayer *player,
                                                                   GVariant              *playlists);

enum {
  CONNECTION,
  PLAYBACK_STATUS,
  METADATA,
  VOLUME,
  LAST_SIGNAL
};
static int signals[LAST_SIGNAL] = { 0 };



G_DEFINE_TYPE (PulseaudioMprisPlayer, pulseaudio_mpris_player, G_TYPE_OBJECT)



static void
pulseaudio_mpris_player_class_init (PulseaudioMprisPlayerClass *klass)
{
  GObjectClass      *gobject_class;

  gobject_class = G_OBJECT_CLASS (klass);
  gobject_class->finalize = pulseaudio_mpris_player_finalize;

  signals[CONNECTION] =
    g_signal_new ("connection",
                  G_TYPE_FROM_CLASS (gobject_class),
                  G_SIGNAL_RUN_LAST,
                  G_STRUCT_OFFSET (PulseaudioMprisPlayerClass, connection),
                  NULL, NULL,
                  g_cclosure_marshal_VOID__BOOLEAN,
                  G_TYPE_NONE, 1, G_TYPE_BOOLEAN);

  signals[PLAYBACK_STATUS] =
    g_signal_new ("playback-status",
                  G_TYPE_FROM_CLASS (gobject_class),
                  G_SIGNAL_RUN_LAST,
                  G_STRUCT_OFFSET (PulseaudioMprisPlayerClass, playback_status),
                  NULL, NULL,
                  g_cclosure_marshal_VOID__ENUM,
                  G_TYPE_NONE, 1, G_TYPE_INT);

  signals[METADATA] =
    g_signal_new ("metadata",
                  G_TYPE_FROM_CLASS (gobject_class),
                  G_SIGNAL_RUN_LAST,
                  G_STRUCT_OFFSET (PulseaudioMprisPlayerClass, metadata),
                  NULL, NULL,
                  g_cclosure_marshal_VOID__VOID,
                  G_TYPE_NONE, 0);
}



static gboolean
pulseaudio_mpris_player_can_condition_track_info (PulseaudioMprisPlayer *player)
{
  if (player->title == NULL || player->artist == NULL)
    return FALSE;

  if (!g_utf8_validate (player->title, -1, NULL) || !g_utf8_validate (player->artist, -1, NULL))
    return FALSE;

  if (g_utf8_strlen (player->title, -1) <= 0 || g_utf8_strlen (player->artist, -1) <= 0)
    return FALSE;

  return TRUE;
}


static gboolean
pulseaudio_mpris_player_condition_track_info (PulseaudioMprisPlayer *player,
                                              const gchar           *delimiter)
{
  gchar    *lookup = NULL;
  gchar    *replace = NULL;
  gboolean  found = FALSE;

  lookup = g_strconcat (player->artist, delimiter, NULL);
  if (g_str_has_prefix (player->title, lookup))
    {
      replace = g_utf8_substring (player->title, g_utf8_strlen (lookup, -1), g_utf8_strlen (player->title, -1));
      g_free (player->title);
      player->title = replace;
      found = TRUE;
    }
  g_free (lookup);

  if (found)
    return TRUE;

  // Track titles match ARTIST - TITLE
  if (g_str_has_suffix (player->artist, "VEVO"))
    {
      gchar **components = g_strsplit (player->title, delimiter, 2);

      if (g_strv_length (components) == 2)
        {
          g_free (player->artist);
          player->artist = g_strdup (components[0]);

          g_free (player->title);
          player->title = g_strdup (components[1]);

          found = TRUE;
        }

      g_strfreev (components);
    }

  return found;
}


static void
pulseaudio_mpris_player_parse_metadata (PulseaudioMprisPlayer *player,
                                        GVariant              *dictionary)
{
  GVariantIter iter;
  GVariant *value;
  gchar *key;

  gchar **artists;

  if (player->title != NULL)
    {
      g_free(player->title);
      player->title = NULL;
    }

  if (player->artist != NULL)
    {
      g_free(player->artist);
      player->artist = NULL;
    }

  g_variant_iter_init (&iter, dictionary);
  while (g_variant_iter_loop (&iter, "{sv}", &key, &value))
    {
      if (0 == g_ascii_strcasecmp (key, "xesam:title"))
        {
          player->title = g_strdup(g_variant_get_string(value, NULL));
        }
      else if (0 == g_ascii_strcasecmp (key, "xesam:artist"))
        {
          if (g_variant_is_of_type (value, G_VARIANT_TYPE_STRING))
            {
              player->artist = g_strdup (g_variant_get_string(value, NULL));
            }
          else
            {
              artists = g_variant_dup_strv (value, NULL);
              if (artists != NULL)
                {
                  if (g_strv_length (artists) > 0)
                    {
                      player->artist = g_strdup (artists[0]);
                    }
                  else
                    {
                      player->artist = g_strdup ("");
                    }

                  g_strfreev (artists);
                }
            }
        }
    }

  if (!pulseaudio_mpris_player_can_condition_track_info (player))
    return;

  if (pulseaudio_mpris_player_condition_track_info (player, " - "))
    return;

  if (pulseaudio_mpris_player_condition_track_info (player, ": "))
    return;
}



#ifdef HAVE_LIBXFCE4WINDOWING
/**
 * Alternative "Raise" method.
 * Some media players (e.g. Spotify) do not support the "Raise" method.
 * This workaround utilizes libxfce4windowing to find the correct window and raise it.
 */
static void
pulseaudio_mpris_player_raise_wnck (PulseaudioMprisPlayer *player)
{
  for (GList *lp = xfw_screen_get_windows (player->screen); lp != NULL; lp = lp->next)
    {
      if (g_strcmp0 (player->player_label, xfw_window_get_name (lp->data)) == 0)
        {
          xfw_window_activate (lp->data, g_get_monotonic_time () / 1000, NULL);
          break;
        }
    }
}

#elif defined (HAVE_WNCK)

static void
pulseaudio_mpris_player_get_xid (PulseaudioMprisPlayer *player)
{
  WnckScreen           *screen = NULL;
  GList                *window = NULL;

  if (player->xid > 0L || ! GDK_IS_X11_DISPLAY (gdk_display_get_default ()))
    return;

  screen = wnck_screen_get_default ();
  if (screen != NULL)
    {
      wnck_screen_force_update (screen);
      if (player->xid == 0L)
        {
          for (window = wnck_screen_get_windows (screen); window != NULL; window = window->next)
            {
              if (0 == g_strcmp0 (player->player_label, wnck_window_get_name (WNCK_WINDOW (window->data))))
                {
                  player->xid = wnck_window_get_xid (WNCK_WINDOW (window->data));
                  if (player->xid > 0L)
                    return;
                }
            }
        }
    }
}



/**
 * Alternative "Raise" method.
 * Some media players (e.g. Spotify) do not support the "Raise" method.
 * This workaround utilizes libwnck to find the correct window and raise it.
 */
static void
pulseaudio_mpris_player_raise_wnck (PulseaudioMprisPlayer *player)
{
  WnckWindow           *window = NULL;

  pulseaudio_mpris_player_get_xid (player);

  if (player->xid == 0L)
    return;

  window = wnck_window_get (player->xid);
  if (window != NULL)
    wnck_window_activate (window, g_get_monotonic_time () / 1000);
}
#endif


void
pulseaudio_mpris_player_call_player_method (PulseaudioMprisPlayer *player,
                                            const gchar           *method)
{
  GDBusMessage *message;
  GError       *error = NULL;
  gchar        *iface = NULL;

  if (g_strcmp0 (method, "Raise") == 0)
    iface = "org.mpris.MediaPlayer2";
#if defined (HAVE_WNCK) || defined (HAVE_LIBXFCE4WINDOWING)
  else if (g_strcmp0 (method, "RaiseWnck") == 0)
    return pulseaudio_mpris_player_raise_wnck (player);
#endif
  else if (g_strcmp0 (method, "Quit") == 0)
    iface = "org.mpris.MediaPlayer2";
  else
    iface = "org.mpris.MediaPlayer2.Player";

  message = g_dbus_message_new_method_call (player->dbus_name,
                                            "/org/mpris/MediaPlayer2",
                                            iface,
                                            method);

  g_dbus_connection_send_message (player->dbus_connection,
                                  message,
                                  G_DBUS_SEND_MESSAGE_FLAGS_NONE,
                                  NULL,
                                  &error);
  if (error != NULL)
    {
      g_warning ("unable to send message: %s", error->message);
      g_clear_error (&error);
      error = NULL;
    }

  g_dbus_connection_flush_sync (player->dbus_connection, NULL, &error);
  if (error != NULL)
    {
      g_warning ("unable to flush message queue: %s", error->message);
      g_clear_error (&error);
    }

  g_object_unref (message);
}



GList *
pulseaudio_mpris_player_get_playlists (PulseaudioMprisPlayer *player)
{
  return g_hash_table_get_keys (player->playlists);
}



void pulseaudio_mpris_player_activate_playlist (PulseaudioMprisPlayer *player,
                                                const gchar           *playlist)
{
  gchar *path = g_hash_table_lookup (player->playlists, playlist);

  if (path != NULL)
    {
      g_dbus_connection_call (player->dbus_connection,
                              player->dbus_name,
                              "/org/mpris/MediaPlayer2",
                              "org.mpris.MediaPlayer2.Playlists",
                              "ActivatePlaylist",
                              g_variant_new("(o)", path),
                              NULL,
                              G_DBUS_CALL_FLAGS_NONE,
                              -1,
                              NULL,
                              NULL,
                              NULL);
    }
}



static GVariant *
pulseaudio_mpris_player_get_all_player_properties(PulseaudioMprisPlayer *player)
{
  GVariantIter iter;
  GVariant *result, *child = NULL;

  result = g_dbus_connection_call_sync (player->dbus_connection,
                                        player->dbus_name,
                                        "/org/mpris/MediaPlayer2",
                                        "org.freedesktop.DBus.Properties",
                                        "GetAll",
                                        g_variant_new ("(s)", "org.mpris.MediaPlayer2.Player"),
                                        G_VARIANT_TYPE ("(a{sv})"),
                                        G_DBUS_CALL_FLAGS_NONE,
                                        -1,
                                        NULL,
                                        NULL);

  if (result)
    {
      g_variant_iter_init (&iter, result);
      child = g_variant_iter_next_value (&iter);
    }

  return child;
}



static GVariant *
pulseaudio_mpris_player_get_all_media_player_properties (PulseaudioMprisPlayer *player)
{
  GVariantIter iter;
  GVariant *result, *child = NULL;

  result = g_dbus_connection_call_sync (player->dbus_connection,
                                        player->dbus_name,
                                        "/org/mpris/MediaPlayer2",
                                        "org.freedesktop.DBus.Properties",
                                        "GetAll",
                                        g_variant_new ("(s)", "org.mpris.MediaPlayer2"),
                                        G_VARIANT_TYPE ("(a{sv})"),
                                        G_DBUS_CALL_FLAGS_NONE,
                                        -1,
                                        NULL,
                                        NULL);

  if (result)
    {
      g_variant_iter_init (&iter, result);
      child = g_variant_iter_next_value (&iter);
    }

  return child;
}


static GVariant *
pulseaudio_mpris_player_playlists_get_playlists (PulseaudioMprisPlayer *player)
{
  GVariantIter iter;
  GVariant *result, *child = NULL;

  result = g_dbus_connection_call_sync(player->dbus_connection,
                                       player->dbus_name,
                                       "/org/mpris/MediaPlayer2",
                                       "org.mpris.MediaPlayer2.Playlists",
                                       "GetPlaylists",
                                       g_variant_new("(uusb)", (guint32)0, (guint32)5, "Played", TRUE),
                                       G_VARIANT_TYPE("(a(oss))"),
                                       G_DBUS_CALL_FLAGS_NONE,
                                       -1,
                                       NULL,
                                       NULL);

  if (result)
  {
    g_variant_iter_init(&iter, result);
    child = g_variant_iter_next_value(&iter);
  }

  return child;
}



static void
pulseaudio_mpris_player_parse_playback_status (PulseaudioMprisPlayer *player,
                                               const gchar           *playback_status)
{
  if (0 == g_ascii_strcasecmp(playback_status, "Playing"))
    player->playback_status = PLAYING;
  else if (0 == g_ascii_strcasecmp(playback_status, "Paused"))
    player->playback_status = PAUSED;
  else
    player->playback_status = STOPPED;
}



static void
pulseaudio_mpris_player_set_details_from_desktop (PulseaudioMprisPlayer *player,
                                                  const gchar           *player_name);



static void
pulseaudio_mpris_player_parse_player_properties (PulseaudioMprisPlayer *player,
                                                 GVariant              *properties)
{
  GVariantIter  iter;
  GVariant     *value;
  const gchar  *key;
  const gchar  *playback_status = NULL;

  g_variant_iter_init (&iter, properties);

  while (g_variant_iter_loop (&iter, "{sv}", &key, &value))
    {
      if (0 == g_ascii_strcasecmp (key, "PlaybackStatus"))
        {
          playback_status = g_variant_get_string(value, NULL);
        }
      else if (0 == g_ascii_strcasecmp (key, "CanGoNext"))
        {
          player->can_go_next = g_variant_get_boolean(value);
        }
      else if (0 == g_ascii_strcasecmp (key, "CanGoPrevious"))
        {
          player->can_go_previous = g_variant_get_boolean(value);
        }
      else if (0 == g_ascii_strcasecmp (key, "CanPlay"))
        {
          player->can_play = g_variant_get_boolean(value);
        }
      else if (0 == g_ascii_strcasecmp (key, "CanPause"))
        {
          player->can_pause = g_variant_get_boolean(value);
        }
      else if (0 == g_ascii_strcasecmp (key, "Metadata"))
        {
          pulseaudio_mpris_player_parse_metadata (player, value);
          g_signal_emit (player, signals[METADATA], 0, NULL);
        }
      else if (0 == g_ascii_strcasecmp(key, "ActivePlaylist") || 0 == g_ascii_strcasecmp(key, "PlaylistCount"))
        {
          /* Playlists */
          GVariant *reply = pulseaudio_mpris_player_playlists_get_playlists(player);
          if (reply)
          {
            pulseaudio_mpris_player_parse_playlists(player, reply);
            g_variant_unref(reply);
          }
        }
    }

  if (playback_status != NULL)
    {
      pulseaudio_mpris_player_parse_playback_status (player, playback_status);
      g_signal_emit (player, signals[PLAYBACK_STATUS], 0, player->playback_status);
    }
}



static void
pulseaudio_mpris_player_parse_media_player_properties (PulseaudioMprisPlayer *player,
                                                       GVariant              *properties)
{
  GVariantIter  iter;
  GVariant     *value;
  const gchar  *key;
  const gchar  *filename = NULL;

  g_variant_iter_init (&iter, properties);

  while (g_variant_iter_loop (&iter, "{sv}", &key, &value))
    {
      if (0 == g_ascii_strcasecmp (key, "CanRaise"))
        {
          player->can_raise = g_variant_get_boolean(value);
        }
      else if (0 == g_ascii_strcasecmp (key, "DesktopEntry"))
        {
          filename = g_variant_get_string(value, NULL);
          pulseaudio_mpris_player_set_details_from_desktop (player, filename);
        }
    }
}



static void
pulseaudio_mpris_player_parse_playlists (PulseaudioMprisPlayer *player,
                                         GVariant *playlists)
{
  GVariantIter iter;

  gchar *path;
  const gchar *name;
  const gchar *icon;

  g_hash_table_remove_all (player->playlists);

  g_variant_iter_init(&iter, playlists);

  while (g_variant_iter_loop(&iter, "(oss)", &path, &name, &icon))
  {
    g_hash_table_insert (player->playlists, g_strdup (name), g_strdup (path));
  }
}



static void
pulseaudio_mpris_player_on_dbus_property_signal (GDBusProxy *proxy,
                                                 gchar      *sender_name,
                                                 gchar      *signal_name,
                                                 GVariant   *parameters,
                                                 gpointer    user_data)
{
  GVariantIter iter;
  GVariant *child;

  PulseaudioMprisPlayer *player = user_data;

  if (g_ascii_strcasecmp (signal_name, "PropertiesChanged"))
    return;

  g_variant_iter_init (&iter, parameters);

  child = g_variant_iter_next_value (&iter); /* Interface name. */
  if (child)
    {
      g_variant_unref (child);
    }

  child = g_variant_iter_next_value (&iter); /* Property name. */
  if (child)
    {
      pulseaudio_mpris_player_parse_player_properties (player, child);
      g_variant_unref(child);
    }
}



static void
pulseaudio_mpris_player_on_dbus_connected (GDBusConnection *connection,
                                           const gchar     *name,
                                           const gchar     *name_owner,
                                           gpointer         user_data)
{
  GVariant *reply;

  PulseaudioMprisPlayer *player = user_data;

  player->connected = TRUE;

  /* Notify that connect to a player.*/
  g_signal_emit (player, signals[CONNECTION], 0, player->connected);

  /* And informs the current status of the player */
  reply = pulseaudio_mpris_player_get_all_player_properties (player);
  if (reply)
    {
      pulseaudio_mpris_player_parse_player_properties (player, reply);
      g_variant_unref (reply);
    }


  /* Media player properties */
  reply = pulseaudio_mpris_player_get_all_media_player_properties (player);
  if (reply)
    {
      pulseaudio_mpris_player_parse_media_player_properties (player, reply);
      g_variant_unref (reply);
    }


  /* Playlists */
  reply = pulseaudio_mpris_player_playlists_get_playlists (player);
  if (reply)
  {
    pulseaudio_mpris_player_parse_playlists (player, reply);
    g_variant_unref (reply);
  }

#ifdef HAVE_WNCK
  pulseaudio_mpris_player_get_xid (player);
#endif
}



static void
pulseaudio_mpris_player_on_dbus_lost (GDBusConnection *connection,
                                      const gchar     *name,
                                      gpointer         user_data)
{
  PulseaudioMprisPlayer *player = user_data;

  /* Interface MediaPlayer2.Player */
  player->playback_status = STOPPED;
  player->can_go_next     = FALSE;
  player->can_go_previous = FALSE;
  player->can_play        = FALSE;
  player->can_pause       = FALSE;
  player->can_raise       = FALSE;
  player->connected       = FALSE;

  if (player->title != NULL)
    g_free (player->title);
  if (player->artist != NULL)
    g_free (player->artist);

  player->title           = NULL;
  player->artist          = NULL;
#ifdef HAVE_WNCK
  player->xid             = 0L;
#endif

  g_signal_emit (player, signals[CONNECTION], 0, player->connected);
}



static gchar *
find_desktop_entry (const gchar *player_name)
{
  GKeyFile  *key_file;
  gchar     *file = NULL;
  gchar     *filename = NULL;

  file = g_strconcat ("applications/", player_name, ".desktop", NULL);

  key_file = g_key_file_new();
  if (g_key_file_load_from_data_dirs (key_file, file, NULL, G_KEY_FILE_NONE, NULL))
    {
      filename = g_strconcat (player_name, ".desktop", NULL);
    }
  else
    {
      /* Support reverse domain name (RDN) formatted launchers. */
      gchar ***results = g_desktop_app_info_search (player_name);
      gint i, j;

      for (i = 0; results[i]; i++)
        {
          for (j = 0; results[i][j]; j++)
            {
              if (filename == NULL)
                {
                  filename = g_strdup (results[i][j]);
                }
            }
          g_strfreev (results[i]);
        }
      g_free (results);
    }

  g_key_file_free (key_file);

  if (file)
    g_free (file);

  return filename;
}



static void
pulseaudio_mpris_player_set_details_from_desktop (PulseaudioMprisPlayer *player,
                                                  const gchar           *player_name)
{
  GKeyFile  *key_file = NULL;
  gchar     *file = NULL;
  gchar     *full_path = NULL;
  gchar     *filename = NULL;

  filename = find_desktop_entry (player_name);

  if (player->player_label != NULL)
    g_free (player->player_label);
  if (player->icon_name != NULL)
    g_free (player->icon_name);

  if (filename == NULL)
    {
      player->player_label = g_strdup (player->player);
      player->icon_name = g_strdup ("applications-multimedia");
      return;
    }

  file = g_strconcat("applications/", filename, NULL);
  g_free (filename);

  key_file = g_key_file_new();
  if (g_key_file_load_from_data_dirs (key_file, file, &full_path, G_KEY_FILE_NONE, NULL))
    {
      gchar *name = g_key_file_get_locale_string (key_file, "Desktop Entry", "Name", NULL, NULL);
      gchar *icon_name = g_key_file_get_string (key_file, "Desktop Entry", "Icon", NULL);

      player->player_label = g_strdup (name);
      player->icon_name = g_strdup (icon_name);

      g_free (name);
      g_free (icon_name);
    }
  else
    {
      player->player_label = g_strdup (player->player);
      player->icon_name = g_strdup ("applications-multimedia");
    }

  if (full_path != NULL) {
    player->full_path = g_strdup (full_path);
    g_free (full_path);
  }

  g_key_file_free (key_file);
  g_free (file);
}



static void
pulseaudio_mpris_player_set_player (PulseaudioMprisPlayer *player,
                                    const gchar           *player_name)
{
  /* Disconnect dbus */
  if (player->watch_id)
    {
      g_bus_unwatch_name (player->watch_id);
      player->watch_id = 0;
    }
  if (player->dbus_props_proxy != NULL)
    {
      g_object_unref (player->dbus_props_proxy);
      player->dbus_props_proxy = NULL;
    }
  if (player->dbus_player_proxy != NULL)
    {
      g_object_unref (player->dbus_player_proxy);
      player->dbus_player_proxy = NULL;
    }
  if (player->dbus_playlists_proxy != NULL)
    {
      g_object_unref (player->dbus_playlists_proxy);
      player->dbus_playlists_proxy = NULL;
    }

  /* Clean player */
  if (player->player != NULL)
    {
      g_free (player->player);
      player->player = NULL;
    }

  /* Set new player and connect again */
  player->player = g_strdup(player_name);

  pulseaudio_mpris_player_set_details_from_desktop (player, player_name);
  pulseaudio_mpris_player_dbus_connect (player);

  player->can_launch = player->full_path != NULL;
}



static void
pulseaudio_mpris_player_dbus_connect (PulseaudioMprisPlayer *player)
{
  GDBusProxy *proxy;
  GError     *gerror = NULL;
  guint       watch_id;
  if (player->player == NULL)
    return;

  g_free(player->dbus_name);
  player->dbus_name = g_strdup_printf("org.mpris.MediaPlayer2.%s", player->player);

  watch_id = g_bus_watch_name_on_connection(player->dbus_connection,
                                            player->dbus_name,
                                            G_BUS_NAME_WATCHER_FLAGS_AUTO_START,
                                            pulseaudio_mpris_player_on_dbus_connected,
                                            pulseaudio_mpris_player_on_dbus_lost,
                                            player,
                                            NULL);

  proxy = g_dbus_proxy_new_sync (player->dbus_connection,
                                 G_DBUS_PROXY_FLAGS_NONE,
                                 NULL,
                                 player->dbus_name,
                                 "/org/mpris/MediaPlayer2",
                                 "org.freedesktop.DBus.Properties",
                                 NULL, /* GCancellable */
                                 &gerror);

  if (proxy == NULL)
    {
      g_printerr ("Error creating proxy: %s\n", gerror->message);
      g_error_free (gerror);
      gerror = NULL;
    }
  else
    {
      g_signal_connect (proxy, "g-signal",
                        G_CALLBACK (pulseaudio_mpris_player_on_dbus_property_signal), player);
      player->dbus_props_proxy = proxy;
    }

  /* interface=org.mpris.MediaPlayer2.Player */
  proxy = g_dbus_proxy_new_sync (player->dbus_connection,
                                 G_DBUS_PROXY_FLAGS_NONE,
                                 NULL,
                                 player->dbus_name,
                                 "/org/mpris/MediaPlayer2",
                                 "org.mpris.MediaPlayer2.Player",
                                 NULL, /* GCancellable */
                                 &gerror);

  if (proxy == NULL)
    {
      g_printerr ("Error creating proxy: %s\n", gerror->message);
      g_error_free (gerror);
      gerror = NULL;
    }
  else
    {
      player->dbus_player_proxy = proxy;
    }

  /* interface=org.mpris.MediaPlayer2.Player */
  proxy = g_dbus_proxy_new_sync(player->dbus_connection,
                                G_DBUS_PROXY_FLAGS_NONE,
                                NULL,
                                player->dbus_name,
                                "/org/mpris/MediaPlayer2",
                                "org.mpris.MediaPlayer2.Playlists",
                                NULL, /* GCancellable */
                                &gerror);

  if (proxy == NULL)
    {
      g_printerr("Error creating proxy: %s\n", gerror->message);
      g_error_free(gerror);
      gerror = NULL;
    }
  else
    {
      player->dbus_playlists_proxy = proxy;
    }

  player->watch_id = watch_id;
}



const gchar *
pulseaudio_mpris_player_get_player (PulseaudioMprisPlayer *player)
{
  return player->player;
}



const gchar *
pulseaudio_mpris_player_get_player_title (PulseaudioMprisPlayer *player)
{
  return player->player_label;
}



const gchar *
pulseaudio_mpris_player_get_icon_name (PulseaudioMprisPlayer *player)
{
  return player->icon_name;
}



const gchar *
pulseaudio_mpris_player_get_title (PulseaudioMprisPlayer *player)
{
  return player->title;
}



const gchar *
pulseaudio_mpris_player_get_artist (PulseaudioMprisPlayer *player)
{
  return player->artist;
}



const gchar *
pulseaudio_mpris_player_get_full_path (PulseaudioMprisPlayer *player)
{
  return player->full_path;
}



gboolean
pulseaudio_mpris_player_is_connected (PulseaudioMprisPlayer *player)
{
  return player->connected;
}



gboolean
pulseaudio_mpris_player_is_playing (PulseaudioMprisPlayer *player)
{
  return player->playback_status == PLAYING;
}



gboolean
pulseaudio_mpris_player_is_stopped (PulseaudioMprisPlayer *player)
{
  return player->playback_status == STOPPED;
}



gboolean
pulseaudio_mpris_player_can_play (PulseaudioMprisPlayer *player)
{
  return player->can_play;
}



gboolean
pulseaudio_mpris_player_can_pause (PulseaudioMprisPlayer *player)
{
  return player->can_pause;
}



gboolean
pulseaudio_mpris_player_can_go_previous (PulseaudioMprisPlayer *player)
{
  return player->can_go_previous;
}



gboolean
pulseaudio_mpris_player_can_go_next (PulseaudioMprisPlayer *player)
{
  return player->can_go_next;
}



gboolean
pulseaudio_mpris_player_can_raise (PulseaudioMprisPlayer *player)
{
  return player->can_raise;
}



gboolean
pulseaudio_mpris_player_can_launch (PulseaudioMprisPlayer *player)
{
  return player->can_launch;
}



gboolean
pulseaudio_mpris_player_is_equal (PulseaudioMprisPlayer *a,
                                  PulseaudioMprisPlayer *b)
{
  return g_strcmp0 (pulseaudio_mpris_player_get_player_title (a), pulseaudio_mpris_player_get_player_title (b)) == 0;
}


static void
pulseaudio_mpris_player_init (PulseaudioMprisPlayer *player)
{
  player->dbus_connection   = NULL;
  player->dbus_name         = NULL;
  player->dbus_props_proxy  = NULL;
  player->dbus_player_proxy = NULL;
  player->dbus_playlists_proxy = NULL;
  player->connected         = FALSE;

  player->title             = NULL;
  player->artist            = NULL;
  player->full_path         = NULL;

  player->can_go_next       = FALSE;
  player->can_go_previous   = FALSE;
  player->can_pause         = FALSE;
  player->can_play          = FALSE;
  player->can_raise         = FALSE;

  player->playback_status   = STOPPED;

  player->watch_id          = 0;

#ifdef HAVE_LIBXFCE4WINDOWING
  player->screen = xfw_screen_get_default ();
#endif
}



static void
pulseaudio_mpris_player_finalize (GObject *object)
{
  PulseaudioMprisPlayer *player;

  player = PULSEAUDIO_MPRIS_PLAYER (object);

  player->dbus_connection   = NULL;
  player->dbus_name         = NULL;
  player->dbus_props_proxy  = NULL;
  player->dbus_player_proxy = NULL;
  player->dbus_playlists_proxy = NULL;
  player->connected         = FALSE;

  player->title             = NULL;
  player->artist            = NULL;
  player->full_path         = NULL;

  player->can_go_next       = FALSE;
  player->can_go_previous   = FALSE;
  player->can_pause         = FALSE;
  player->can_play          = FALSE;
  player->can_raise         = FALSE;

  player->playback_status   = STOPPED;

  player->watch_id          = 0;

  if (player->playlists != NULL)
    g_hash_table_destroy (player->playlists);

#ifdef HAVE_LIBXFCE4WINDOWING
  g_object_unref (player->screen);
#endif

  (*G_OBJECT_CLASS(pulseaudio_mpris_player_parent_class)->finalize)(object);
}



PulseaudioMprisPlayer *
pulseaudio_mpris_player_new (gchar *name)
{
  PulseaudioMprisPlayer *player;
  GDBusConnection       *gconnection;
  GError                *gerror = NULL;

  gconnection = g_bus_get_sync (G_BUS_TYPE_SESSION, NULL, &gerror);
  if (gconnection == NULL)
    {
      g_message ("Failed to get session bus: %s", gerror->message);
      g_error_free (gerror);
      gerror = NULL;
    }

  player = g_object_new (TYPE_PULSEAUDIO_MPRIS_PLAYER, NULL);

  player->dbus_connection = gconnection;

  pulseaudio_mpris_player_dbus_connect (player);
  pulseaudio_mpris_player_set_player (player, name);

  player->playlists = g_hash_table_new_full (g_str_hash, g_str_equal, (GDestroyNotify)g_free, (GDestroyNotify)g_free);

  return player;
}