[Xfce4-commits] [panel-plugins/xfce4-pulseaudio-plugin] 01/01: Add support for controlling media players via MPRIS2
noreply at xfce.org
noreply at xfce.org
Sun Aug 27 17:59:29 CEST 2017
This is an automated email from the git hooks/post-receive script.
b l u e s a b r e p u s h e d a c o m m i t t o b r a n c h m a s t e r
in repository panel-plugins/xfce4-pulseaudio-plugin.
commit d3d8cf91e2bd326113e57b57215e8306d9c92e64
Author: Sean Davis <smd.seandavis at gmail.com>
Date: Sun Aug 27 11:58:25 2017 -0400
Add support for controlling media players via MPRIS2
Squashed commit of the following:
commit f417887baba9da6df599de05de8eed8654465ddb
Author: Sean Davis <smd.seandavis at gmail.com>
Date: Sun Aug 27 05:57:31 2017 -0400
Add myself to AUTHORS
commit c520c520d8824f1c4f3a8c2c151b03a7440f40a8
Author: Sean Davis <smd.seandavis at gmail.com>
Date: Wed Aug 9 22:39:41 2017 -0400
Add ~ files to .gitignore
commit a5b8fb79ef52eb351552ff280c91c00b373fcb7e
Author: Sean Davis <smd.seandavis at gmail.com>
Date: Wed Aug 9 22:38:46 2017 -0400
Consistent spacing
commit e4c2db8f2337a8a4c10f98ff73f27e2ad1f52489
Author: Sean Davis <smd.seandavis at gmail.com>
Date: Wed Aug 9 22:31:32 2017 -0400
Port glade file to GTK 3.6
commit 9c4fee2ca0b04d771a2c3d6e7ca0db484d5d8fe3
Author: Sean Davis <smd.seandavis at gmail.com>
Date: Wed Aug 9 22:04:49 2017 -0400
GTK+ support through 3.22, ignore deprecations on essential functionality
commit b1f526ef5def96e4ea1e41b04feb59e1b52049c8
Author: Sean Davis <smd.seandavis at gmail.com>
Date: Wed Aug 9 21:58:20 2017 -0400
Silently discard const qualifier with pulseaudio-volume
commit b6eed1262a088c0c4e6008b66e7b2b68adaaba29
Author: Sean Davis <smd.seandavis at gmail.com>
Date: Wed Aug 9 20:48:57 2017 -0400
Remove default player, depend on broadcasting MPRIS2 players
commit 7aa7d42e98a2d469e77928c4697e31e6baaaf3e8
Author: Sean Davis <smd.seandavis at gmail.com>
Date: Wed Aug 9 20:36:09 2017 -0400
Fix intended icon size loading: GTK_ICON_SIZE_LARGE_TOOLBAR / 24px
commit f90850b2292a61483e8fdd89cc2557704a2d8e8f
Author: Sean Davis <smd.seandavis at gmail.com>
Date: Wed Aug 9 20:15:21 2017 -0400
Add mprismenuitem.c to POTFILES.in
commit 1760ea626025de6c853a5e8ae4b87324a5c01928
Author: Sean Davis <smd.seandavis at gmail.com>
Date: Tue Aug 8 21:41:14 2017 -0400
Better handling of stopped state, consistent status tracking
commit 11b9875bb6717b67b79a915809193e23b6f6b3e2
Author: Sean Davis <smd.seandavis at gmail.com>
Date: Tue Aug 8 19:18:19 2017 -0400
Drop unused properties and signals
commit b3b59527d5c7164a6246ac1623c4667aef9fd582
Author: Sean Davis <smd.seandavis at gmail.com>
Date: Tue Aug 8 06:21:57 2017 -0400
Sort players
commit a5b26216c3036d3f3e8525a73633b796b436a33b
Author: Sean Davis <smd.seandavis at gmail.com>
Date: Tue Aug 8 05:34:28 2017 -0400
Disconnect from MPRIS when menu items are destroyed, fixes another crash
commit a5268c8cf16805530e85c3751e90c797a19c4dec
Author: Sean Davis <smd.seandavis at gmail.com>
Date: Sun Aug 6 12:10:46 2017 -0400
Fix several crashes and build cleaner with debug enabled
commit a9248cd3ca2dd337c208f6dbbc6848c2e10df127
Author: Sean Davis <smd.seandavis at gmail.com>
Date: Sun Aug 6 07:37:26 2017 -0400
Consistent whitespace and const usage
commit 798f6101261eef6cc7b1545ed6cc5c73b98dcba6
Author: Sean Davis <smd.seandavis at gmail.com>
Date: Sun Aug 6 06:54:04 2017 -0400
Handle null string array
commit f5bd2e38082308854b4fe5e0fb68b3a297814b51
Author: Sean Davis <smd.seandavis at gmail.com>
Date: Sat Aug 5 13:20:56 2017 -0400
Add ability to raise or launch (launch crashes plugin) players
commit 358f862c332ee4e255cbc4309b78203be126112b
Author: Sean Davis <smd.seandavis at gmail.com>
Date: Sat Aug 5 11:35:10 2017 -0400
Fix crash on player exit
commit f856d67def5b6e81845ef35585d39bd2103d0b60
Author: Sean Davis <smd.seandavis at gmail.com>
Date: Sat Aug 5 08:18:18 2017 -0400
Add support for reverse domain name formatted launchers (Fixes integration with Parole)
commit 3797f7b37c885eab58da40a7799cc50128cb463e
Author: Sean Davis <smd.seandavis at gmail.com>
Date: Thu Aug 3 23:50:55 2017 -0400
Fix a few defaults, add storage for known players
commit 96c336661fb6832c63fba06c46abc46a4ba83b86
Author: Sean Davis <smd.seandavis at gmail.com>
Date: Wed Aug 2 06:41:08 2017 -0400
Add .gitignore
commit ce61a7725ad3eb362e9d0bdaf65ad9b0b66b0c7e
Author: Sean Davis <smd.seandavis at gmail.com>
Date: Wed Aug 2 06:40:06 2017 -0400
Add initial MPRIS2 support.
This still requires some cleanup and a bit of refactoring, but should be at a usable point.
---
.gitignore | 29 ++
AUTHORS | 1 +
configure.ac.in | 2 +
panel-plugin/Makefile.am | 8 +-
panel-plugin/mprismenuitem.c | 746 ++++++++++++++++++++++++++++++++
panel-plugin/mprismenuitem.h | 103 +++++
panel-plugin/pulseaudio-button.c | 16 +-
panel-plugin/pulseaudio-button.h | 2 +
panel-plugin/pulseaudio-config.c | 108 +++++
panel-plugin/pulseaudio-config.h | 6 +
panel-plugin/pulseaudio-dialog.glade | 129 +++---
panel-plugin/pulseaudio-menu.c | 181 +++++++-
panel-plugin/pulseaudio-menu.h | 2 +
panel-plugin/pulseaudio-mpris-player.c | 759 +++++++++++++++++++++++++++++++++
panel-plugin/pulseaudio-mpris-player.h | 79 ++++
panel-plugin/pulseaudio-mpris.c | 308 +++++++++++++
panel-plugin/pulseaudio-mpris.h | 62 +++
panel-plugin/pulseaudio-plugin.c | 14 +-
panel-plugin/pulseaudio-volume.c | 8 +-
panel-plugin/scalemenuitem.c | 46 +-
panel-plugin/scalemenuitem.h | 7 +-
po/POTFILES.in | 1 +
22 files changed, 2509 insertions(+), 108 deletions(-)
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..88c93fb
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,29 @@
+*~
+*.la
+*.lo
+*_ui.h
+.deps
+.libs
+Makefile
+Makefile.in
+Makefile.in.in
+aclocal.m4
+autom4te.cache/
+compile
+config.*
+configure
+configure.ac
+depcomp
+install-sh
+intltool*
+libtool
+ltmain.sh
+missing
+panel-plugin/pulseaudio.desktop
+panel-plugin/pulseaudio.desktop.in
+po/*.gmo
+po/.intltool-merge-cache
+po/Makefile.in.in
+po/POTFILES
+po/stamp-it
+stamp-h1
diff --git a/AUTHORS b/AUTHORS
index 373891d..840e31f 100644
--- a/AUTHORS
+++ b/AUTHORS
@@ -3,3 +3,4 @@ Guido Berhoerster <guido+xfce at berhoerster.name>
Simon Steinbeiss <ochosi at xfce.org>
Viktor Odintsev <zakhams at gmail.com>
Matthieu Mota <matthieumota at gmail.com>
+Sean Davis <bluesabre at xfce.org>
diff --git a/configure.ac.in b/configure.ac.in
index e079f25..c737c8d 100644
--- a/configure.ac.in
+++ b/configure.ac.in
@@ -84,6 +84,8 @@ XDT_CHECK_PACKAGE([LIBXFCE4UI], [libxfce4ui-2], [4.11.0])
XDT_CHECK_PACKAGE([LIBXFCE4PANEL], [libxfce4panel-${LIBXFCE4PANEL_VERSION_API}], [4.11.0])
XDT_CHECK_PACKAGE([XFCONF], [libxfconf-0], [4.6.0])
+XDT_CHECK_PACKAGE([GIO], [gio-2.0], [2.30])
+
dnl **********************************
dnl *** Optional keybinder Support ***
dnl **********************************
diff --git a/panel-plugin/Makefile.am b/panel-plugin/Makefile.am
index 360f4d3..286d80f 100644
--- a/panel-plugin/Makefile.am
+++ b/panel-plugin/Makefile.am
@@ -35,10 +35,16 @@ libpulseaudio_plugin_la_SOURCES = \
pulseaudio-dialog.h \
pulseaudio-menu.c \
pulseaudio-menu.h \
+ pulseaudio-mpris.c \
+ pulseaudio-mpris.h \
+ pulseaudio-mpris-player.c \
+ pulseaudio-mpris-player.h \
pulseaudio-notify.c \
pulseaudio-notify.h \
scalemenuitem.c \
- scalemenuitem.h
+ scalemenuitem.h \
+ mprismenuitem.c \
+ mprismenuitem.h
libpulseaudio_plugin_la_CFLAGS = \
diff --git a/panel-plugin/mprismenuitem.c b/panel-plugin/mprismenuitem.c
new file mode 100644
index 0000000..938d8cf
--- /dev/null
+++ b/panel-plugin/mprismenuitem.c
@@ -0,0 +1,746 @@
+/* -*- c-basic-offset: 2 -*- vi:set ts=2 sts=2 sw=2:
+ * * 2017 Sean Davis <bluesabre at xfce.org>
+ *
+ * Licensed under the GNU General Public License Version 2
+ *
+ * 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
+ */
+/*
+ * Based on the scale menu item implementation of the indicator applet:
+ * Authors:
+ * Cody Russell <crussell at canonical.com>
+ * http://bazaar.launchpad.net/~indicator-applet-developers/ido/trunk.14.10/view/head:/src/idoscalemenuitem.h
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include "mprismenuitem.h"
+
+#include <gdk/gdk.h>
+#include <gdk/gdkkeysyms.h>
+#include <gio/gdesktopappinfo.h>
+
+/* for DBG/TRACE */
+#include <libxfce4util/libxfce4util.h>
+
+
+static gboolean mpris_menu_item_button_press_event (GtkWidget *menuitem,
+ GdkEventButton *event);
+static gboolean mpris_menu_item_button_release_event (GtkWidget *menuitem,
+ GdkEventButton *event);
+static void update_packing (MprisMenuItem *self);
+
+
+
+struct _MprisMenuItemPrivate {
+ GtkWidget *title_label;
+ GtkWidget *artist_label;
+
+ GtkWidget *go_previous;
+ GtkWidget *play_pause;
+ GtkWidget *go_next;
+
+ gboolean can_go_previous;
+ gboolean can_play;
+ gboolean can_pause;
+ gboolean can_go_next;
+ gboolean can_raise;
+
+ gboolean is_running;
+ gboolean is_playing;
+ gboolean is_stopped;
+
+ gchar *player;
+ gchar *title;
+ gchar *filename;
+
+ GtkWidget *vbox;
+ GtkWidget *hbox;
+ GtkWidget *button_box;
+};
+
+enum {
+ MEDIA_NOTIFY,
+ LAST_SIGNAL
+};
+
+static guint signals[LAST_SIGNAL] = { 0 };
+
+G_GNUC_BEGIN_IGNORE_DEPRECATIONS
+G_DEFINE_TYPE (MprisMenuItem, mpris_menu_item, GTK_TYPE_IMAGE_MENU_ITEM)
+G_GNUC_END_IGNORE_DEPRECATIONS
+
+#define GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), TYPE_MPRIS_MENU_ITEM, MprisMenuItemPrivate))
+
+
+
+static void
+mpris_menu_item_class_init (MprisMenuItemClass *item_class)
+{
+ GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (item_class);
+
+ widget_class->button_press_event = mpris_menu_item_button_press_event;
+ widget_class->button_release_event = mpris_menu_item_button_release_event;
+
+ /**
+ * MprisMenuItem::media-notify:
+ * @menuitem: the #MprisMenuItem for which the value changed
+ * @value: the mpris signal to emit
+ *
+ * Emitted whenever the a media button is clicked.
+ */
+ signals[MEDIA_NOTIFY] = g_signal_new ("media-notify",
+ TYPE_MPRIS_MENU_ITEM,
+ G_SIGNAL_RUN_LAST,
+ 0, NULL, NULL,
+ g_cclosure_marshal_VOID__STRING,
+ G_TYPE_NONE,
+ 1, G_TYPE_STRING);
+
+ g_type_class_add_private (item_class, sizeof (MprisMenuItemPrivate));
+}
+
+static void
+remove_children (GtkContainer *container)
+{
+ GList * children;
+ GList * l;
+
+ g_return_if_fail (GTK_IS_CONTAINER (container));
+
+ children = gtk_container_get_children (container);
+
+ for (l=children; l!=NULL; l=l->next)
+ gtk_container_remove (container, l->data);
+ g_list_free (children);
+}
+
+static void
+gtk_label_set_markup_printf_escaped (GtkLabel *label,
+ const gchar *format,
+ ...)
+{
+ va_list args;
+ gchar *str;
+
+ va_start (args, format);
+ str = g_markup_vprintf_escaped (format, args);
+ gtk_label_set_markup (label, str);
+ va_end (args);
+
+ g_free (str);
+}
+
+static void
+media_notify (MprisMenuItem *item, gchar *message)
+{
+ g_signal_emit (item, signals[MEDIA_NOTIFY], 0, message);
+}
+
+static void
+media_play_pause_clicked_event (GtkButton *button, gpointer user_data)
+{
+ MprisMenuItem *item = user_data;
+ media_notify (item, "PlayPause");
+}
+
+static void
+media_go_previous_clicked_event (GtkButton *button, gpointer user_data)
+{
+ MprisMenuItem *item = user_data;
+ media_notify (item, "Previous");
+}
+
+static void
+media_go_next_clicked_event (GtkButton *button, gpointer user_data)
+{
+ MprisMenuItem *item = user_data;
+ media_notify (item, "Next");
+}
+
+static void
+mpris_menu_item_raise (MprisMenuItem *item)
+{
+ MprisMenuItemPrivate *priv;
+
+ g_return_if_fail (IS_MPRIS_MENU_ITEM (item));
+
+ priv = GET_PRIVATE (item);
+
+ if (priv->is_running)
+ {
+ if (priv->can_raise)
+ {
+ media_notify (item, "Raise");
+ }
+ }
+}
+
+static void
+mpris_menu_item_launch (MprisMenuItem *item)
+{
+ MprisMenuItemPrivate *priv;
+ GAppInfo *app_info;
+
+ g_return_if_fail (IS_MPRIS_MENU_ITEM (item));
+
+ priv = GET_PRIVATE (item);
+
+ if (priv->is_running)
+ return;
+
+ app_info = (GAppInfo*)g_desktop_app_info_new_from_filename (priv->filename);
+ if (app_info != NULL)
+ {
+ g_app_info_launch (app_info, NULL, NULL, NULL);
+ g_object_unref (app_info);
+ }
+}
+
+static void
+mpris_menu_item_raise_or_launch (MprisMenuItem *item)
+{
+ MprisMenuItemPrivate *priv;
+
+ g_return_if_fail (IS_MPRIS_MENU_ITEM (item));
+
+ priv = GET_PRIVATE (item);
+
+ if (priv->is_running)
+ mpris_menu_item_raise (item);
+ else
+ mpris_menu_item_launch (item);
+}
+
+static void
+menu_item_activate_event (GtkMenuItem *mi, gpointer user_data)
+{
+ mpris_menu_item_raise_or_launch (MPRIS_MENU_ITEM (mi));
+}
+
+static GtkWidget *
+track_info_label_new (void)
+{
+ GtkWidget *label;
+
+ label = gtk_label_new (NULL);
+ gtk_label_set_width_chars (GTK_LABEL (label), 25);
+ gtk_label_set_max_width_chars (GTK_LABEL (label), 25);
+ gtk_label_set_ellipsize (GTK_LABEL (label), PANGO_ELLIPSIZE_MIDDLE);
+
+ #if GTK_CHECK_VERSION (3, 16, 0)
+ gtk_label_set_xalign (GTK_LABEL (label), 0.0);
+ gtk_widget_set_halign (label, GTK_ALIGN_START);
+ #else
+ gtk_misc_set_alignment (GTK_MISC(label), 0, 0);
+ #endif
+
+ return label;
+}
+
+static void
+update_packing (MprisMenuItem *self)
+{
+ MprisMenuItemPrivate *priv;
+ GtkBox *hbox;
+ GtkBox *vbox;
+ GtkBox *button_box;
+ GtkStyleContext *ctx;
+
+ g_return_if_fail (IS_MPRIS_MENU_ITEM (self));
+
+ priv = GET_PRIVATE (self);
+ hbox = GTK_BOX (gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0));
+ vbox = GTK_BOX (gtk_box_new (GTK_ORIENTATION_VERTICAL, 0));
+
+ button_box = GTK_BOX (gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0));
+ ctx = gtk_widget_get_style_context (GTK_WIDGET (button_box));
+ gtk_style_context_add_class (ctx, "linked");
+
+ TRACE("entering");
+
+ if (priv->vbox)
+ remove_children (GTK_CONTAINER (priv->vbox));
+ if (priv->hbox)
+ {
+ remove_children (GTK_CONTAINER (priv->hbox));
+ gtk_container_remove (GTK_CONTAINER (self), priv->hbox);
+ }
+
+ priv->hbox = GTK_WIDGET(hbox);
+ priv->vbox = GTK_WIDGET(vbox);
+ priv->button_box = GTK_WIDGET(button_box);
+
+ /* add the new layout */
+ /* [IC] Title [ CON ]
+ * [ON] Artist [ TROLS ]
+ */
+ priv->go_previous = gtk_button_new_from_icon_name ("media-skip-backward-symbolic", GTK_ICON_SIZE_MENU);
+ priv->play_pause = gtk_button_new_from_icon_name ("media-playback-start-symbolic", GTK_ICON_SIZE_MENU);
+ priv->go_next = gtk_button_new_from_icon_name ("media-skip-forward-symbolic", GTK_ICON_SIZE_MENU);
+
+ g_signal_connect (priv->play_pause, "clicked", G_CALLBACK (media_play_pause_clicked_event), self);
+ g_signal_connect (priv->go_previous, "clicked", G_CALLBACK (media_go_previous_clicked_event), self);
+ g_signal_connect (priv->go_next, "clicked", G_CALLBACK (media_go_next_clicked_event), self);
+ g_signal_connect (self, "activate", G_CALLBACK (menu_item_activate_event), self);
+
+ priv->title_label = track_info_label_new ();
+ priv->artist_label = track_info_label_new ();
+
+ gtk_box_pack_start (button_box, priv->go_previous, FALSE, FALSE, 0);
+ gtk_box_pack_start (button_box, priv->play_pause, FALSE, FALSE, 0);
+ gtk_box_pack_start (button_box, priv->go_next, FALSE, FALSE, 0);
+
+ gtk_box_pack_start (vbox, priv->title_label, FALSE, FALSE, 0);
+ gtk_box_pack_start (vbox, priv->artist_label, FALSE, FALSE, 0);
+
+ gtk_box_pack_start (hbox, GTK_WIDGET (vbox), TRUE, TRUE, 0);
+ gtk_box_pack_start (hbox, GTK_WIDGET (button_box), FALSE, FALSE, 0);
+
+ mpris_menu_item_set_title (self, priv->player);
+ mpris_menu_item_set_artist (self, _("Not currently playing"));
+
+ gtk_widget_show_all (priv->button_box);
+ gtk_widget_show_all (priv->hbox);
+ gtk_widget_show_all (priv->vbox);
+
+ gtk_container_add (GTK_CONTAINER (self), priv->hbox);
+}
+
+static void
+mpris_menu_item_init (MprisMenuItem *self)
+{
+}
+
+static GtkWidget *
+mpris_menu_item_get_widget_at_event (MprisMenuItem *menuitem,
+ GdkEventButton *event)
+{
+ MprisMenuItemPrivate *priv;
+ GtkAllocation alloc;
+ gint x, y;
+
+ g_return_val_if_fail (IS_MPRIS_MENU_ITEM (menuitem), NULL);
+
+ priv = GET_PRIVATE (menuitem);
+
+ gtk_widget_get_allocation (priv->button_box, &alloc);
+ gtk_widget_translate_coordinates (GTK_WIDGET (menuitem), priv->button_box, event->x, event->y, &x, &y);
+
+ if (x > 0 && x < alloc.width && y > 0 && y < alloc.height)
+ {
+ gtk_widget_get_allocation (priv->go_previous, &alloc);
+ gtk_widget_translate_coordinates (GTK_WIDGET (menuitem), priv->go_previous, event->x, event->y, &x, &y);
+
+ if (x > 0 && x < alloc.width && y > 0 && y < alloc.height)
+ return GTK_WIDGET (priv->go_previous);
+
+ gtk_widget_get_allocation (priv->play_pause, &alloc);
+ gtk_widget_translate_coordinates (GTK_WIDGET (menuitem), priv->play_pause, event->x, event->y, &x, &y);
+
+ if (x > 0 && x < alloc.width && y > 0 && y < alloc.height)
+ return GTK_WIDGET (priv->play_pause);
+
+ gtk_widget_get_allocation (priv->go_next, &alloc);
+ gtk_widget_translate_coordinates (GTK_WIDGET (menuitem), priv->go_next, event->x, event->y, &x, &y);
+
+ if (x > 0 && x < alloc.width && y > 0 && y < alloc.height)
+ return GTK_WIDGET (priv->go_next);
+ }
+
+ return GTK_WIDGET (menuitem);
+}
+
+static gboolean
+mpris_menu_item_button_press_event (GtkWidget *menuitem,
+ GdkEventButton *event)
+{
+ GtkWidget *widget;
+
+ g_return_val_if_fail (IS_MPRIS_MENU_ITEM (menuitem), FALSE);
+
+ widget = mpris_menu_item_get_widget_at_event (MPRIS_MENU_ITEM (menuitem), event);
+
+ if (widget == NULL)
+ return FALSE;
+
+ if (widget == menuitem)
+ return FALSE;
+
+ gtk_widget_event (widget, (GdkEvent*) event);
+ return TRUE;
+}
+
+static gboolean
+mpris_menu_item_button_release_event (GtkWidget *menuitem,
+ GdkEventButton *event)
+{
+ GtkWidget *widget;
+
+ g_return_val_if_fail (IS_MPRIS_MENU_ITEM (menuitem), FALSE);
+
+ widget = mpris_menu_item_get_widget_at_event (MPRIS_MENU_ITEM (menuitem), event);
+
+ if (widget == NULL)
+ return FALSE;
+
+ if (widget == menuitem)
+ return FALSE;
+
+ gtk_widget_event (widget, (GdkEvent*) event);
+ return TRUE;
+}
+
+const gchar *
+mpris_menu_item_get_player (MprisMenuItem *mi)
+{
+ MprisMenuItemPrivate *priv;
+
+ g_return_val_if_fail (IS_MPRIS_MENU_ITEM (mi), NULL);
+
+ priv = GET_PRIVATE (mi);
+
+ return priv->player;
+}
+
+void
+mpris_menu_item_set_title (MprisMenuItem *item,
+ const gchar *title)
+{
+ MprisMenuItemPrivate *priv;
+
+ g_return_if_fail (IS_MPRIS_MENU_ITEM (item));
+
+ priv = GET_PRIVATE (item);
+
+ if (title == NULL || *title == '\0')
+ gtk_label_set_markup_printf_escaped (GTK_LABEL (priv->title_label), "<b>%s</b>", priv->title);
+ else
+ gtk_label_set_markup_printf_escaped (GTK_LABEL (priv->title_label), "<b>%s</b>", title);
+}
+
+void
+mpris_menu_item_set_artist (MprisMenuItem *item,
+ const gchar *artist)
+{
+ MprisMenuItemPrivate *priv;
+
+ g_return_if_fail (IS_MPRIS_MENU_ITEM (item));
+
+ priv = GET_PRIVATE (item);
+
+ if (artist == NULL || *artist == '\0')
+ gtk_label_set_label (GTK_LABEL (priv->artist_label), NULL);
+ else
+ gtk_label_set_label (GTK_LABEL (priv->artist_label), artist);
+}
+
+void
+mpris_menu_item_set_can_go_previous (MprisMenuItem *item,
+ gboolean enabled)
+{
+ MprisMenuItemPrivate *priv;
+
+ g_return_if_fail (IS_MPRIS_MENU_ITEM (item));
+
+ priv = GET_PRIVATE (item);
+
+ priv->can_go_previous = enabled;
+
+ if (priv->is_running)
+ gtk_widget_set_sensitive (priv->go_previous, priv->can_go_previous);
+ else
+ gtk_widget_set_sensitive (priv->go_previous, FALSE);
+}
+
+void
+mpris_menu_item_set_can_play (MprisMenuItem *item,
+ gboolean enabled)
+{
+ MprisMenuItemPrivate *priv;
+
+ g_return_if_fail (IS_MPRIS_MENU_ITEM (item));
+
+ priv = GET_PRIVATE (item);
+
+ priv->can_play = enabled;
+
+ if (priv->is_running)
+ gtk_widget_set_sensitive (priv->play_pause, priv->can_play);
+ else
+ gtk_widget_set_sensitive (priv->play_pause, FALSE);
+}
+
+void
+mpris_menu_item_set_can_pause (MprisMenuItem *item,
+ gboolean enabled)
+{
+ MprisMenuItemPrivate *priv;
+
+ g_return_if_fail (IS_MPRIS_MENU_ITEM (item));
+
+ priv = GET_PRIVATE (item);
+
+ priv->can_pause = enabled;
+
+ if (priv->is_running)
+ {
+ if (priv->is_playing)
+ {
+ gtk_widget_set_sensitive (priv->play_pause, priv->can_pause);
+ }
+ }
+ else
+ gtk_widget_set_sensitive (priv->play_pause, FALSE);
+}
+
+void
+mpris_menu_item_set_can_go_next (MprisMenuItem *item,
+ gboolean enabled)
+{
+ MprisMenuItemPrivate *priv;
+
+ g_return_if_fail (IS_MPRIS_MENU_ITEM (item));
+
+ priv = GET_PRIVATE (item);
+
+ priv->can_go_next = enabled;
+
+ if (priv->is_running)
+ gtk_widget_set_sensitive (priv->go_next, priv->can_go_next);
+ else
+ gtk_widget_set_sensitive (priv->go_next, FALSE);
+}
+
+void
+mpris_menu_item_set_is_playing (MprisMenuItem *item,
+ gboolean playing)
+{
+ MprisMenuItemPrivate *priv;
+
+ g_return_if_fail (IS_MPRIS_MENU_ITEM (item));
+
+ priv = GET_PRIVATE (item);
+
+ priv->is_playing = playing;
+
+ if (priv->is_playing)
+ {
+ gtk_image_set_from_icon_name (GTK_IMAGE (gtk_button_get_image (GTK_BUTTON (priv->play_pause))), "media-playback-pause-symbolic", GTK_ICON_SIZE_LARGE_TOOLBAR);
+ gtk_widget_set_sensitive (priv->play_pause, priv->can_pause);
+ priv->is_stopped = FALSE;
+ }
+ else
+ {
+ gtk_image_set_from_icon_name (GTK_IMAGE (gtk_button_get_image (GTK_BUTTON (priv->play_pause))), "media-playback-start-symbolic", GTK_ICON_SIZE_LARGE_TOOLBAR);
+ gtk_widget_set_sensitive (priv->play_pause, priv->can_play);
+ }
+
+ if (!priv->is_running)
+ {
+ gtk_widget_set_sensitive (priv->play_pause, FALSE);
+ }
+}
+
+void
+mpris_menu_item_set_is_stopped (MprisMenuItem *item,
+ gboolean stopped)
+{
+ MprisMenuItemPrivate *priv;
+
+ g_return_if_fail (IS_MPRIS_MENU_ITEM (item));
+
+ priv = GET_PRIVATE (item);
+
+ priv->is_stopped = stopped;
+
+ if (priv->is_stopped)
+ {
+ if (priv->is_playing)
+ {
+ mpris_menu_item_set_is_playing (item, FALSE);
+ }
+
+ mpris_menu_item_set_title (item, NULL);
+ mpris_menu_item_set_artist (item, _("Not currently playing"));
+ }
+}
+
+void
+mpris_menu_item_set_is_running (MprisMenuItem *item,
+ gboolean running)
+{
+ MprisMenuItemPrivate *priv;
+
+ g_return_if_fail (IS_MPRIS_MENU_ITEM (item));
+
+ priv = GET_PRIVATE (item);
+
+ priv->is_running = running;
+
+ if (!priv->is_running)
+ {
+ mpris_menu_item_set_title (item, NULL);
+ mpris_menu_item_set_artist (item, _("Not currently playing"));
+ mpris_menu_item_set_can_play (item, FALSE);
+ mpris_menu_item_set_can_pause (item, FALSE);
+ mpris_menu_item_set_can_go_previous (item, FALSE);
+ mpris_menu_item_set_can_go_next (item, FALSE);
+ mpris_menu_item_set_is_playing (item, FALSE);
+ mpris_menu_item_set_is_stopped (item, TRUE);
+ }
+ else
+ {
+ mpris_menu_item_set_can_play (item, priv->can_play);
+ mpris_menu_item_set_can_pause (item, priv->can_pause);
+ mpris_menu_item_set_can_go_next (item, priv->can_go_next);
+ mpris_menu_item_set_can_go_previous (item, priv->can_go_previous);
+ mpris_menu_item_set_is_playing (item, priv->is_playing);
+ mpris_menu_item_set_is_stopped (item, priv->is_stopped);
+ }
+}
+
+void
+mpris_menu_item_set_can_raise (MprisMenuItem *item,
+ gboolean can_raise)
+{
+ MprisMenuItemPrivate *priv;
+
+ g_return_if_fail (IS_MPRIS_MENU_ITEM (item));
+
+ priv = GET_PRIVATE (item);
+
+ priv->can_raise = can_raise;
+}
+
+GtkWidget*
+mpris_menu_item_new_with_player (const gchar *player,
+ const gchar *title,
+ const gchar *icon_name,
+ const gchar *filename)
+{
+ MprisMenuItem *menu_item;
+ MprisMenuItemPrivate *priv;
+ GtkWidget *img;
+
+ TRACE("entering");
+
+ menu_item = MPRIS_MENU_ITEM (g_object_new (TYPE_MPRIS_MENU_ITEM, NULL));
+
+ priv = GET_PRIVATE (menu_item);
+
+ priv->player = g_strdup(player);
+ if (title != NULL)
+ priv->title = g_strdup(title);
+ else
+ priv->title = g_strdup(player);
+ priv->filename = g_strdup(filename);
+
+ priv->vbox = NULL;
+ priv->hbox = NULL;
+ priv->button_box = NULL;
+
+ update_packing (menu_item);
+
+ gtk_widget_add_events (GTK_WIDGET(menu_item), GDK_SCROLL_MASK|GDK_POINTER_MOTION_MASK|GDK_BUTTON_MOTION_MASK);
+
+ img = gtk_image_new_from_icon_name (icon_name, GTK_ICON_SIZE_LARGE_TOOLBAR);
+G_GNUC_BEGIN_IGNORE_DEPRECATIONS
+ gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (menu_item), img);
+G_GNUC_END_IGNORE_DEPRECATIONS
+ gtk_image_set_pixel_size (GTK_IMAGE (img), 24);
+
+ return GTK_WIDGET(menu_item);
+}
+
+static gchar *
+find_desktop_entry (const gchar *player_name)
+{
+ GKeyFile *key_file;
+ gchar *file;
+ gchar *filename = NULL;
+ gchar *full_path;
+
+ 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, &full_path, 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);
+ g_free (file);
+
+ return filename;
+}
+
+GtkWidget*
+mpris_menu_item_new_from_player_name (const gchar *player)
+{
+ GtkWidget *widget = NULL;
+ GKeyFile *key_file;
+ gchar *file;
+ gchar *filename;
+ gchar *full_path;
+
+ filename = find_desktop_entry (player);
+ if (filename == NULL)
+ {
+ g_free (filename);
+ return NULL;
+ }
+
+ 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_string (key_file, "Desktop Entry", "Name", NULL);
+ gchar *icon_name = g_key_file_get_string (key_file, "Desktop Entry", "Icon", NULL);
+
+ widget = mpris_menu_item_new_with_player (player, name, icon_name, full_path);
+
+ g_free (name);
+ g_free (icon_name);
+ }
+
+ g_key_file_free (key_file);
+ g_free (file);
+
+ return widget;
+}
diff --git a/panel-plugin/mprismenuitem.h b/panel-plugin/mprismenuitem.h
new file mode 100644
index 0000000..80b1c12
--- /dev/null
+++ b/panel-plugin/mprismenuitem.h
@@ -0,0 +1,103 @@
+/* -*- c-basic-offset: 2 -*- vi:set ts=2 sts=2 sw=2:
+ * * Copyright (C) 2017 Sean Davis <bluesabre at xfce.org>
+ *
+ * Licensed under the GNU General Public License Version 2
+ *
+ * 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
+ */
+/*
+ * Based on the scale menu item implementation of the indicator applet:
+ * Authors:
+ * Cody Russell <crussell at canonical.com>
+ * http://bazaar.launchpad.net/~indicator-applet-developers/ido/trunk.14.10/view/head:/src/idoscalemenuitem.h
+ */
+
+
+#ifndef _MPRIS_MENU_ITEM_H_
+#define _MPRIS_MENU_ITEM_H_
+
+#include <gtk/gtk.h>
+
+G_BEGIN_DECLS
+
+#define TYPE_MPRIS_MENU_ITEM (mpris_menu_item_get_type ())
+#define MPRIS_MENU_ITEM(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), TYPE_MPRIS_MENU_ITEM, MprisMenuItem))
+#define MPRIS_MENU_ITEM_CLASS(c) (G_TYPE_CHECK_CLASS_CAST ((c), TYPE_MPRIS_MENU_ITEM, MprisMenuItemClass))
+#define IS_MPRIS_MENU_ITEM(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), TYPE_MPRIS_MENU_ITEM))
+#define IS_MPRIS_MENU_ITEM_CLASS(c) (G_TYPE_CHECK_CLASS_TYPE ((c), TYPE_MPRIS_MENU_ITEM))
+#define MPRIS_MENU_ITEM_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), TYPE_MPRIS_MENU_ITEM, MprisMenuItemClass))
+
+
+typedef struct _MprisMenuItem MprisMenuItem;
+typedef struct _MprisMenuItemClass MprisMenuItemClass;
+typedef struct _MprisMenuItemPrivate MprisMenuItemPrivate;
+
+struct _MprisMenuItem
+{
+ GtkImageMenuItem parent_instance;
+
+ MprisMenuItemPrivate *priv;
+};
+
+struct _MprisMenuItemClass
+{
+ GtkImageMenuItemClass parent_class;
+};
+
+
+GType mpris_menu_item_get_type (void) G_GNUC_CONST;
+
+GtkWidget *mpris_menu_item_new_with_player (const gchar *player,
+ const gchar *title,
+ const gchar *icon_name,
+ const gchar *filename);
+
+GtkWidget *mpris_menu_item_new_from_player_name (const gchar *player);
+
+const gchar *mpris_menu_item_get_player (MprisMenuItem *mi);
+
+void mpris_menu_item_set_title (MprisMenuItem *mi,
+ const gchar *title);
+
+void mpris_menu_item_set_artist (MprisMenuItem *mi,
+ const gchar *artist);
+
+void mpris_menu_item_set_can_go_previous (MprisMenuItem *mi,
+ gboolean enabled);
+
+void mpris_menu_item_set_can_play (MprisMenuItem *mi,
+ gboolean enabled);
+
+void mpris_menu_item_set_can_pause (MprisMenuItem *mi,
+ gboolean enabled);
+
+void mpris_menu_item_set_can_go_next (MprisMenuItem *mi,
+ gboolean enabled);
+
+void mpris_menu_item_set_can_raise (MprisMenuItem *mi,
+ gboolean can_raise);
+
+void mpris_menu_item_set_is_running (MprisMenuItem *mi,
+ gboolean running);
+
+void mpris_menu_item_set_is_playing (MprisMenuItem *mi,
+ gboolean playing);
+
+void mpris_menu_item_set_is_stopped (MprisMenuItem *mi,
+ gboolean stopped);
+
+G_END_DECLS
+
+#endif /* _MPRIS_MENU_ITEM_H_ */
diff --git a/panel-plugin/pulseaudio-button.c b/panel-plugin/pulseaudio-button.c
index 8dd1639..692a651 100644
--- a/panel-plugin/pulseaudio-button.c
+++ b/panel-plugin/pulseaudio-button.c
@@ -43,6 +43,7 @@
#include "pulseaudio-plugin.h"
#include "pulseaudio-config.h"
#include "pulseaudio-menu.h"
+#include "pulseaudio-mpris.h"
#include "pulseaudio-button.h"
#define V_MUTED 0
@@ -80,6 +81,7 @@ struct _PulseaudioButton
PulseaudioPlugin *plugin;
PulseaudioConfig *config;
+ PulseaudioMpris *mpris;
PulseaudioVolume *volume;
GtkWidget *image;
@@ -129,7 +131,11 @@ pulseaudio_button_init (PulseaudioButton *button)
gtk_widget_set_can_default (GTK_WIDGET (button), FALSE);
gtk_button_set_relief (GTK_BUTTON (button), GTK_RELIEF_NONE);
gtk_button_set_use_underline (GTK_BUTTON (button),TRUE);
+#if GTK_CHECK_VERSION (3, 20, 0)
+ gtk_widget_set_focus_on_click (GTK_WIDGET (button), FALSE);
+#else
gtk_button_set_focus_on_click (GTK_BUTTON (button), FALSE);
+#endif
gtk_widget_set_name (GTK_WIDGET (button), "pulseaudio-button");
/* Preload icons */
@@ -193,7 +199,7 @@ pulseaudio_button_button_press (GtkWidget *widget,
if(event->button == 1 && button->menu == NULL) /* left button */
{
gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (widget), TRUE);
- button->menu = pulseaudio_menu_new (button->volume, button->config, widget);
+ button->menu = pulseaudio_menu_new (button->volume, button->config, button->mpris, widget);
gtk_menu_attach_to_widget (GTK_MENU (button->menu), widget, NULL);
if (button->deactivate_id == 0)
@@ -203,12 +209,15 @@ pulseaudio_button_button_press (GtkWidget *widget,
G_CALLBACK (pulseaudio_button_menu_deactivate), button);
}
+G_GNUC_BEGIN_IGNORE_DEPRECATIONS
gtk_menu_popup (GTK_MENU (button->menu),
NULL, NULL,
xfce_panel_plugin_position_menu, button->plugin,
//NULL, NULL,
1,
event->time);
+G_GNUC_END_IGNORE_DEPRECATIONS
+
return TRUE;
}
@@ -364,12 +373,14 @@ pulseaudio_button_volume_changed (PulseaudioButton *button,
PulseaudioButton *
pulseaudio_button_new (PulseaudioPlugin *plugin,
PulseaudioConfig *config,
+ PulseaudioMpris *mpris,
PulseaudioVolume *volume)
{
PulseaudioButton *button;
g_return_val_if_fail (IS_PULSEAUDIO_PLUGIN (plugin), NULL);
g_return_val_if_fail (IS_PULSEAUDIO_CONFIG (config), NULL);
+ g_return_val_if_fail (IS_PULSEAUDIO_MPRIS (mpris), NULL);
g_return_val_if_fail (IS_PULSEAUDIO_VOLUME (volume), NULL);
button = g_object_new (TYPE_PULSEAUDIO_BUTTON, NULL);
@@ -377,6 +388,7 @@ pulseaudio_button_new (PulseaudioPlugin *plugin,
button->plugin = plugin;
button->volume = volume;
button->config = config;
+ button->mpris = mpris;
button->volume_changed_id =
g_signal_connect_swapped (G_OBJECT (button->volume), "volume-changed",
G_CALLBACK (pulseaudio_button_volume_changed), button);
@@ -385,5 +397,3 @@ pulseaudio_button_new (PulseaudioPlugin *plugin,
return button;
}
-
-
diff --git a/panel-plugin/pulseaudio-button.h b/panel-plugin/pulseaudio-button.h
index b393eb2..7e16f70 100644
--- a/panel-plugin/pulseaudio-button.h
+++ b/panel-plugin/pulseaudio-button.h
@@ -25,6 +25,7 @@
#include "pulseaudio-config.h"
#include "pulseaudio-volume.h"
#include "pulseaudio-menu.h"
+#include "pulseaudio-mpris.h"
G_BEGIN_DECLS
@@ -42,6 +43,7 @@ typedef struct _PulseaudioButtonClass PulseaudioButtonClass;
PulseaudioButton *pulseaudio_button_new (PulseaudioPlugin *plugin,
PulseaudioConfig *config,
+ PulseaudioMpris *mpris,
PulseaudioVolume *volume);
void pulseaudio_button_set_size (PulseaudioButton *button,
diff --git a/panel-plugin/pulseaudio-config.c b/panel-plugin/pulseaudio-config.c
index faeb378..70e81c7 100644
--- a/panel-plugin/pulseaudio-config.c
+++ b/panel-plugin/pulseaudio-config.c
@@ -49,6 +49,7 @@
#define DEFAULT_SHOW_NOTIFICATIONS TRUE
#define DEFAULT_VOLUME_STEP 6
#define DEFAULT_VOLUME_MAX 153
+#define DEFAULT_MPRIS_PLAYERS ""
@@ -78,6 +79,7 @@ struct _PulseaudioConfig
guint volume_step;
guint volume_max;
gchar *mixer_command;
+ gchar *mpris_players;
};
@@ -90,6 +92,7 @@ enum
PROP_VOLUME_STEP,
PROP_VOLUME_MAX,
PROP_MIXER_COMMAND,
+ PROP_MPRIS_PLAYERS,
N_PROPERTIES,
};
@@ -161,6 +164,17 @@ pulseaudio_config_class_init (PulseaudioConfigClass *klass)
G_PARAM_STATIC_STRINGS));
+
+ g_object_class_install_property (gobject_class,
+ PROP_MPRIS_PLAYERS,
+ g_param_spec_string ("mpris-players",
+ NULL, NULL,
+ DEFAULT_MPRIS_PLAYERS,
+ G_PARAM_READWRITE |
+ G_PARAM_STATIC_STRINGS));
+
+
+
pulseaudio_config_signals[CONFIGURATION_CHANGED] =
g_signal_new (g_intern_static_string ("configuration-changed"),
G_TYPE_FROM_CLASS (gobject_class),
@@ -180,6 +194,7 @@ pulseaudio_config_init (PulseaudioConfig *config)
config->volume_step = DEFAULT_VOLUME_STEP;
config->volume_max = DEFAULT_VOLUME_MAX;
config->mixer_command = g_strdup (DEFAULT_MIXER_COMMAND);
+ config->mpris_players = g_strdup (DEFAULT_MPRIS_PLAYERS);
}
@@ -227,6 +242,10 @@ pulseaudio_config_get_property (GObject *object,
g_value_set_string (value, config->mixer_command);
break;
+ case PROP_MPRIS_PLAYERS:
+ g_value_set_string (value, config->mpris_players);
+ break;
+
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
break;
@@ -292,6 +311,13 @@ pulseaudio_config_set_property (GObject *object,
config->mixer_command = g_value_dup_string (value);
break;
+ case PROP_MPRIS_PLAYERS:
+ g_free (config->mpris_players);
+ config->mpris_players = g_value_dup_string (value);
+ g_object_notify (G_OBJECT (config), "mpris-players");
+ g_signal_emit (G_OBJECT (config), pulseaudio_config_signals [CONFIGURATION_CHANGED], 0);
+ break;
+
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
break;
@@ -356,6 +382,84 @@ pulseaudio_config_get_mixer_command (PulseaudioConfig *config)
+gchar **
+pulseaudio_config_get_mpris_players (PulseaudioConfig *config)
+{
+ if (!IS_PULSEAUDIO_CONFIG (config))
+ {
+ return g_strsplit (DEFAULT_MPRIS_PLAYERS, ";", 1);
+ }
+
+ return g_strsplit (config->mpris_players, ";", 0);
+}
+
+static gint
+compare_players (gconstpointer item1, gconstpointer item2)
+{
+ return g_ascii_strcasecmp (item1, item2);
+}
+
+void
+pulseaudio_config_set_mpris_players (PulseaudioConfig *config,
+ gchar **players)
+{
+ GSList *player_array;
+ gchar *player_string;
+ GValue src = { 0, };
+ guint index = 0;
+
+ g_return_if_fail (IS_PULSEAUDIO_CONFIG (config));
+
+ player_array = NULL;
+ for (guint i = 0; i < g_strv_length (players); i++) {
+ player_array = g_slist_prepend (player_array, players[i]);
+ }
+ player_array = g_slist_sort (player_array, (GCompareFunc) compare_players);
+
+ for (GSList *list = player_array; list != NULL; list = g_slist_next (list)) {
+ players[index] = list->data;
+ index++;
+ }
+
+ g_slist_free (player_array);
+
+ player_string = g_strjoinv (";", players);
+
+ g_value_init(&src, G_TYPE_STRING);
+ g_value_set_static_string(&src, player_string);
+
+ pulseaudio_config_set_property (G_OBJECT (config), PROP_MPRIS_PLAYERS, &src, NULL);
+}
+
+void
+pulseaudio_config_add_mpris_player (PulseaudioConfig *config,
+ gchar *player)
+{
+ gchar **players;
+ gchar **player_list;
+ gchar *players_string;
+ gchar *player_string;
+
+ players = pulseaudio_config_get_mpris_players (config);
+ if (g_strv_contains ((const char * const *) players, player)) {
+ return;
+ }
+
+ players_string = g_strjoinv (";", players);
+ player_string = g_strjoin (";", players_string, player, NULL);
+ player_list = g_strsplit(player_string, ";", 0);
+
+ pulseaudio_config_set_mpris_players (config, player_list);
+
+ g_strfreev (player_list);
+ g_free (player_string);
+ g_free (players_string);
+ g_strfreev (players);
+}
+
+
+
+
PulseaudioConfig *
pulseaudio_config_new (const gchar *property_base)
{
@@ -389,6 +493,10 @@ pulseaudio_config_new (const gchar *property_base)
xfconf_g_property_bind (channel, property, G_TYPE_STRING, config, "mixer-command");
g_free (property);
+ property = g_strconcat (property_base, "/mpris-players", NULL);
+ xfconf_g_property_bind (channel, property, G_TYPE_STRING, config, "mpris-players");
+ g_free (property);
+
g_object_notify (G_OBJECT (config), "enable-keyboard-shortcuts");
g_signal_emit (G_OBJECT (config), pulseaudio_config_signals [CONFIGURATION_CHANGED], 0);
}
diff --git a/panel-plugin/pulseaudio-config.h b/panel-plugin/pulseaudio-config.h
index b00b48d..cad1054 100644
--- a/panel-plugin/pulseaudio-config.h
+++ b/panel-plugin/pulseaudio-config.h
@@ -43,6 +43,12 @@ gboolean pulseaudio_config_get_show_notifications (PulseaudioC
guint pulseaudio_config_get_volume_step (PulseaudioConfig *config);
guint pulseaudio_config_get_volume_max (PulseaudioConfig *config);
const gchar *pulseaudio_config_get_mixer_command (PulseaudioConfig *config);
+gchar **pulseaudio_config_get_mpris_players (PulseaudioConfig *config);
+
+void pulseaudio_config_set_mpris_players (PulseaudioConfig *config,
+ gchar **players);
+void pulseaudio_config_add_mpris_player (PulseaudioConfig *config,
+ gchar *player);
G_END_DECLS
diff --git a/panel-plugin/pulseaudio-dialog.glade b/panel-plugin/pulseaudio-dialog.glade
index ad7d014..8adbde0 100644
--- a/panel-plugin/pulseaudio-dialog.glade
+++ b/panel-plugin/pulseaudio-dialog.glade
@@ -1,8 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
+<!-- Generated with glade 3.20.0 -->
<interface>
- <!-- interface-requires libxfce4ui 0.0 -->
- <requires lib="gtk+" version="2.14"/>
- <!-- interface-naming-policy toplevel-contextual -->
+ <requires lib="gtk+" version="3.6"/>
+ <requires lib="libxfce4ui-2" version="4.11"/>
<object class="GtkImage" id="image1">
<property name="visible">True</property>
<property name="can_focus">False</property>
@@ -16,12 +16,13 @@
<property name="icon_name">gtk-properties</property>
<property name="type_hint">normal</property>
<child internal-child="vbox">
- <object class="GtkVBox" id="dialog-vbox2">
+ <object class="GtkBox" id="dialog-vbox2">
<property name="visible">True</property>
<property name="can_focus">False</property>
+ <property name="orientation">vertical</property>
<property name="spacing">2</property>
<child internal-child="action_area">
- <object class="GtkHButtonBox" id="dialog-action_area2">
+ <object class="GtkButtonBox" id="dialog-action_area2">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="layout_style">end</property>
@@ -63,11 +64,12 @@
</packing>
</child>
<child>
- <object class="GtkVBox" id="vbox1">
+ <object class="GtkBox" id="vbox1">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="border_width">6</property>
- <property name="spacing">6</property>
+ <property name="orientation">vertical</property>
+ <property name="spacing">12</property>
<child>
<object class="GtkFrame" id="frame1">
<property name="visible">True</property>
@@ -78,12 +80,14 @@
<object class="GtkAlignment" id="alignment1">
<property name="visible">True</property>
<property name="can_focus">False</property>
+ <property name="top_padding">6</property>
<property name="left_padding">12</property>
+ <property name="right_padding">12</property>
<child>
- <object class="GtkVBox" id="vbox2">
+ <object class="GtkBox" id="vbox2">
<property name="visible">True</property>
<property name="can_focus">False</property>
- <property name="border_width">6</property>
+ <property name="orientation">vertical</property>
<property name="spacing">6</property>
<child>
<object class="GtkCheckButton" id="checkbutton-keyboard-shortcuts">
@@ -92,6 +96,7 @@
<property name="can_focus">True</property>
<property name="receives_default">False</property>
<property name="tooltip_text" translatable="yes">Enables volume control using multimedia keys. Make sure no other application that listens to these keys (e.g. xfce4-volumed) is running in the background.</property>
+ <property name="halign">start</property>
<property name="use_underline">True</property>
<property name="draw_indicator">True</property>
</object>
@@ -108,6 +113,7 @@
<property name="can_focus">True</property>
<property name="receives_default">False</property>
<property name="tooltip_text" translatable="yes">Enables on-screen volume notifications.</property>
+ <property name="halign">start</property>
<property name="use_underline">True</property>
<property name="draw_indicator">True</property>
</object>
@@ -148,79 +154,56 @@
<object class="GtkAlignment" id="alignment2">
<property name="visible">True</property>
<property name="can_focus">False</property>
+ <property name="top_padding">6</property>
<property name="left_padding">12</property>
+ <property name="right_padding">12</property>
<child>
- <object class="GtkVBox" id="vbox4">
+ <object class="GtkGrid">
<property name="visible">True</property>
<property name="can_focus">False</property>
- <property name="border_width">6</property>
- <property name="spacing">6</property>
+ <property name="row_spacing">6</property>
+ <property name="column_spacing">12</property>
<child>
- <object class="GtkTable" id="table1">
+ <object class="GtkLabel" id="label2">
<property name="visible">True</property>
<property name="can_focus">False</property>
- <property name="n_rows">2</property>
- <property name="n_columns">2</property>
- <property name="column_spacing">12</property>
- <property name="row_spacing">6</property>
- <child>
- <object class="GtkLabel" id="label2">
- <property name="visible">True</property>
- <property name="can_focus">False</property>
- <property name="label" translatable="yes">Audio _Mixer</property>
- <property name="use_underline">True</property>
- </object>
- <packing>
- <property name="x_options">GTK_FILL</property>
- <property name="y_options">GTK_FILL</property>
- </packing>
- </child>
- <child>
- <object class="GtkEntry" id="entry-mixer-command">
- <property name="visible">True</property>
- <property name="can_focus">True</property>
- <property name="tooltip_text" translatable="yes">Audio mixer command that can be executed from the context menu, e.g. "pavucontrol", "unity-control-center sound".</property>
- <property name="invisible_char">•</property>
- <property name="primary_icon_activatable">False</property>
- <property name="secondary_icon_activatable">False</property>
- <property name="primary_icon_sensitive">True</property>
- <property name="secondary_icon_sensitive">True</property>
- </object>
- <packing>
- <property name="left_attach">1</property>
- <property name="right_attach">2</property>
- <property name="y_options">GTK_FILL</property>
- </packing>
- </child>
- <child>
- <object class="GtkAlignment" id="alignment3">
- <property name="visible">True</property>
- <property name="can_focus">False</property>
- <property name="xalign">0</property>
- <property name="xscale">0</property>
- <child>
- <object class="GtkButton" id="button-run-mixer">
- <property name="label" translatable="yes">_Run Audio Mixer...</property>
- <property name="visible">True</property>
- <property name="can_focus">True</property>
- <property name="receives_default">True</property>
- <property name="image">image1</property>
- <property name="use_underline">True</property>
- </object>
- </child>
- </object>
- <packing>
- <property name="right_attach">2</property>
- <property name="top_attach">1</property>
- <property name="bottom_attach">2</property>
- <property name="y_options">GTK_FILL</property>
- </packing>
- </child>
+ <property name="label" translatable="yes">Audio _Mixer</property>
+ <property name="use_underline">True</property>
</object>
<packing>
- <property name="expand">False</property>
- <property name="fill">True</property>
- <property name="position">0</property>
+ <property name="left_attach">0</property>
+ <property name="top_attach">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkEntry" id="entry-mixer-command">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="tooltip_text" translatable="yes">Audio mixer command that can be executed from the context menu, e.g. "pavucontrol", "unity-control-center sound".</property>
+ <property name="hexpand">True</property>
+ <property name="invisible_char">•</property>
+ <property name="primary_icon_activatable">False</property>
+ <property name="secondary_icon_activatable">False</property>
+ </object>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="top_attach">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkButton" id="button-run-mixer">
+ <property name="label" translatable="yes">_Run Audio Mixer...</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">True</property>
+ <property name="halign">start</property>
+ <property name="image">image1</property>
+ <property name="use_underline">True</property>
+ </object>
+ <packing>
+ <property name="left_attach">0</property>
+ <property name="top_attach">1</property>
+ <property name="width">2</property>
</packing>
</child>
</object>
diff --git a/panel-plugin/pulseaudio-menu.c b/panel-plugin/pulseaudio-menu.c
index e0a8c21..3f0982d 100644
--- a/panel-plugin/pulseaudio-menu.c
+++ b/panel-plugin/pulseaudio-menu.c
@@ -35,6 +35,8 @@
#include <libxfce4ui/libxfce4ui.h>
#include "pulseaudio-menu.h"
+#include "pulseaudio-mpris.h"
+#include "mprismenuitem.h"
#include "scalemenuitem.h"
@@ -44,6 +46,7 @@ struct _PulseaudioMenu
PulseaudioVolume *volume;
PulseaudioConfig *config;
+ PulseaudioMpris *mpris;
GtkWidget *button;
GtkWidget *range_output;
GtkWidget *mute_output_item;
@@ -81,6 +84,7 @@ pulseaudio_menu_init (PulseaudioMenu *menu)
{
menu->volume = NULL;
menu->config = NULL;
+ menu->mpris = NULL;
menu->button = NULL;
menu->range_output = NULL;
menu->mute_output_item = NULL;
@@ -106,6 +110,7 @@ pulseaudio_menu_finalize (GObject *object)
menu->volume = NULL;
menu->config = NULL;
+ menu->mpris = NULL;
menu->button = NULL;
menu->range_output = NULL;
menu->mute_output_item = NULL;
@@ -279,19 +284,114 @@ pulseaudio_menu_volume_changed (PulseaudioMenu *menu,
gtk_range_set_value (GTK_RANGE (menu->range_input), pulseaudio_volume_get_volume_mic (menu->volume) * 100.0);
}
+static void
+media_notify_cb (GtkWidget *widget,
+ gchar *message,
+ gpointer user_data)
+{
+ PulseaudioMenu *menu = user_data;
+ g_return_if_fail (IS_PULSEAUDIO_MENU (menu));
+ g_return_if_fail (IS_MPRIS_MENU_ITEM (widget));
+
+ pulseaudio_mpris_notify_player (menu->mpris, mpris_menu_item_get_player (MPRIS_MENU_ITEM (widget)), message);
+}
+
+static void
+mpris_update_cb (PulseaudioMpris *mpris,
+ gchar *player,
+ gpointer user_data)
+{
+ MprisMenuItem *menu_item = user_data;
+
+ gchar *title;
+ gchar *artist;
+ gboolean is_playing;
+ gboolean is_stopped;
+ gboolean can_play;
+ gboolean can_pause;
+ gboolean can_go_previous;
+ gboolean can_go_next;
+ gboolean can_raise;
+
+ g_return_if_fail (IS_PULSEAUDIO_MPRIS (mpris));
+ g_return_if_fail (IS_MPRIS_MENU_ITEM (menu_item));
+
+ if (mpris_menu_item_get_player (menu_item) == NULL)
+ {
+ return;
+ }
+
+ if (g_strcmp0 (player, mpris_menu_item_get_player (menu_item)) == 0)
+ {
+ if (pulseaudio_mpris_get_player_snapshot (mpris,
+ player,
+ &title,
+ &artist,
+ &is_playing,
+ &is_stopped,
+ &can_play,
+ &can_pause,
+ &can_go_previous,
+ &can_go_next,
+ &can_raise))
+ {
+ mpris_menu_item_set_is_running (menu_item, TRUE);
+ mpris_menu_item_set_title (menu_item, title);
+ mpris_menu_item_set_artist (menu_item, artist);
+
+ mpris_menu_item_set_can_play (menu_item, can_play);
+ mpris_menu_item_set_can_pause (menu_item, can_pause);
+
+ mpris_menu_item_set_can_go_previous (menu_item, can_go_previous);
+ mpris_menu_item_set_can_go_next (menu_item, can_go_next);
+
+ mpris_menu_item_set_is_playing (menu_item, is_playing);
+ mpris_menu_item_set_is_stopped (menu_item, is_stopped);
+ }
+
+ if (title != NULL)
+ g_free (title);
+ if (artist != NULL)
+ g_free (artist);
+ }
+}
+
+static void
+item_destroy_cb (GtkWidget *widget,
+ gpointer user_data)
+{
+ PulseaudioMenu *menu = user_data;
+
+ g_return_if_fail (IS_PULSEAUDIO_MENU (menu));
+ g_return_if_fail (IS_MPRIS_MENU_ITEM (widget));
+
+ g_signal_handlers_disconnect_by_func (G_OBJECT (menu->mpris), G_CALLBACK (mpris_update_cb), widget);
+}
PulseaudioMenu *
pulseaudio_menu_new (PulseaudioVolume *volume,
PulseaudioConfig *config,
+ PulseaudioMpris *mpris,
GtkWidget *widget)
{
PulseaudioMenu *menu;
GdkScreen *gscreen;
GtkWidget *mi;
- GtkWidget *img = NULL;
gdouble volume_max;
+ gchar **players;
+
+ gchar *title = NULL;
+ gchar *artist = NULL;
+ gboolean is_playing;
+ gboolean is_stopped;
+ gboolean can_play;
+ gboolean can_pause;
+ gboolean can_go_previous;
+ gboolean can_go_next;
+ gboolean can_raise;
+
g_return_val_if_fail (IS_PULSEAUDIO_VOLUME (volume), NULL);
g_return_val_if_fail (IS_PULSEAUDIO_CONFIG (config), NULL);
g_return_val_if_fail (GTK_IS_WIDGET (widget), NULL);
@@ -301,12 +401,12 @@ pulseaudio_menu_new (PulseaudioVolume *volume,
else
gscreen = gdk_display_get_default_screen (gdk_display_get_default ());
-
menu = g_object_new (TYPE_PULSEAUDIO_MENU, NULL);
gtk_menu_set_screen (GTK_MENU (menu), gscreen);
menu->volume = volume;
menu->config = config;
+ menu->mpris = mpris;
menu->button = widget;
menu->volume_changed_id =
g_signal_connect_swapped (G_OBJECT (menu->volume), "volume-changed",
@@ -319,11 +419,7 @@ pulseaudio_menu_new (PulseaudioVolume *volume,
/* output volume slider */
mi = scale_menu_item_new_with_range (0.0, volume_max, 1.0);
-
- img = gtk_image_new_from_icon_name ("audio-volume-high-symbolic", GTK_ICON_SIZE_DND);
- gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (mi), img);
- gtk_image_set_pixel_size (GTK_IMAGE (img), 24);
-
+ scale_menu_item_set_image_from_icon_name (SCALE_MENU_ITEM (mi), "audio-volume-high-symbolic");
scale_menu_item_set_description_label (SCALE_MENU_ITEM (mi), _("<b>Audio output volume</b>"));
/* range slider */
@@ -347,11 +443,7 @@ pulseaudio_menu_new (PulseaudioVolume *volume,
/* input volume slider */
mi = scale_menu_item_new_with_range (0.0, volume_max, 1.0);
-
- img = gtk_image_new_from_icon_name ("microphone-sensitivity-high-symbolic", GTK_ICON_SIZE_DND);
- gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (mi), img);
- gtk_image_set_pixel_size (GTK_IMAGE (img), 24);
-
+ scale_menu_item_set_image_from_icon_name (SCALE_MENU_ITEM (mi), "microphone-sensitivity-high-symbolic");
scale_menu_item_set_description_label (SCALE_MENU_ITEM (mi), _("<b>Audio input volume</b>"));
/* range slider */
@@ -373,6 +465,68 @@ pulseaudio_menu_new (PulseaudioVolume *volume,
gtk_widget_show (mi);
gtk_menu_shell_append (GTK_MENU_SHELL (menu), mi);
+ /* MPRIS2 */
+ players = pulseaudio_config_get_mpris_players (menu->config);
+ if (players != NULL)
+ {
+ for (guint i = 0; i < g_strv_length (players); i++)
+ {
+ mi = mpris_menu_item_new_from_player_name (players[i]);
+ if (mi != NULL)
+ {
+ if (pulseaudio_mpris_get_player_snapshot (menu->mpris,
+ players[i],
+ &title,
+ &artist,
+ &is_playing,
+ &is_stopped,
+ &can_play,
+ &can_pause,
+ &can_go_previous,
+ &can_go_next,
+ &can_raise))
+ {
+ mpris_menu_item_set_is_running (MPRIS_MENU_ITEM (mi), TRUE);
+ mpris_menu_item_set_title (MPRIS_MENU_ITEM (mi), title);
+ mpris_menu_item_set_artist (MPRIS_MENU_ITEM (mi), artist);
+
+ mpris_menu_item_set_can_raise (MPRIS_MENU_ITEM (mi), can_raise);
+
+ mpris_menu_item_set_can_play (MPRIS_MENU_ITEM (mi), can_play);
+ mpris_menu_item_set_can_pause (MPRIS_MENU_ITEM (mi), can_pause);
+
+ mpris_menu_item_set_can_go_previous (MPRIS_MENU_ITEM (mi), can_go_previous);
+ mpris_menu_item_set_can_go_next (MPRIS_MENU_ITEM (mi), can_go_next);
+
+ mpris_menu_item_set_is_playing (MPRIS_MENU_ITEM (mi), is_playing);
+ mpris_menu_item_set_is_stopped (MPRIS_MENU_ITEM (mi), is_stopped);
+
+ if (title != NULL)
+ g_free (title);
+ if (artist != NULL)
+ g_free (artist);
+ }
+ else
+ {
+ mpris_menu_item_set_is_running (MPRIS_MENU_ITEM (mi), FALSE);
+ mpris_menu_item_set_is_stopped (MPRIS_MENU_ITEM (mi), TRUE);
+ }
+
+ g_signal_connect (mi, "media-notify", G_CALLBACK (media_notify_cb), menu);
+ g_signal_connect (menu->mpris, "update", G_CALLBACK (mpris_update_cb), mi);
+ g_signal_connect (mi, "destroy", G_CALLBACK(item_destroy_cb), menu);
+
+ gtk_widget_show (mi);
+ gtk_menu_shell_append (GTK_MENU_SHELL (menu), mi);
+
+ /* separator */
+ mi = gtk_separator_menu_item_new ();
+ gtk_widget_show (mi);
+ gtk_menu_shell_append (GTK_MENU_SHELL (menu), mi);
+ }
+ }
+ }
+
/* Audio mixers */
mi = gtk_menu_item_new_with_mnemonic (_("_Audio mixer..."));
gtk_widget_show (mi);
@@ -381,8 +535,5 @@ pulseaudio_menu_new (PulseaudioVolume *volume,
pulseaudio_menu_volume_changed (menu, FALSE, menu->volume);
-
return menu;
}
-
-
diff --git a/panel-plugin/pulseaudio-menu.h b/panel-plugin/pulseaudio-menu.h
index 724e82a..3849b79 100644
--- a/panel-plugin/pulseaudio-menu.h
+++ b/panel-plugin/pulseaudio-menu.h
@@ -23,6 +23,7 @@
#include "pulseaudio-volume.h"
#include "pulseaudio-config.h"
+#include "pulseaudio-mpris.h"
G_BEGIN_DECLS
@@ -40,6 +41,7 @@ typedef struct _PulseaudioMenuClass PulseaudioMenuClass;
PulseaudioMenu *pulseaudio_menu_new (PulseaudioVolume *volume,
PulseaudioConfig *config,
+ PulseaudioMpris *mpris,
GtkWidget *widget);
G_END_DECLS
diff --git a/panel-plugin/pulseaudio-mpris-player.c b/panel-plugin/pulseaudio-mpris-player.c
new file mode 100644
index 0000000..c07eb37
--- /dev/null
+++ b/panel-plugin/pulseaudio-mpris-player.c
@@ -0,0 +1,759 @@
+/* Copyright (c) 2017 Sean Davis <bluesabre at 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>
+
+#include "pulseaudio-mpris-player.h"
+
+struct _PulseaudioMprisPlayer
+{
+ GObject __parent__;
+
+ GDBusConnection *dbus_connection;
+ GDBusProxy *dbus_props_proxy;
+ GDBusProxy *dbus_player_proxy;
+ gchar *dbus_name;
+
+ gchar *player;
+ gchar *player_label;
+ gchar *icon_name;
+
+ gboolean connected;
+
+ gchar *title;
+ gchar *artist;
+
+ gboolean can_go_next;
+ gboolean can_go_previous;
+ gboolean can_pause;
+ gboolean can_play;
+ gboolean can_raise;
+
+ PlaybackStatus playback_status;
+
+ guint watch_id;
+};
+
+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);
+
+
+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 void
+pulseaudio_mpris_player_parse_metadata (PulseaudioMprisPlayer *player,
+ GVariant *dictionary)
+{
+ GVariantIter iter;
+ GVariant *value;
+ gchar *key;
+
+ gchar **artists;
+
+ 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"))
+ {
+ 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);
+ }
+ }
+ }
+}
+
+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";
+ 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);
+}
+
+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 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_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);
+ }
+ }
+
+ 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;
+
+ 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);
+ }
+ }
+}
+
+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. */
+ g_variant_unref (child);
+
+ child = g_variant_iter_next_value (&iter); /* Property name. */
+ 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);
+ pulseaudio_mpris_player_parse_player_properties (player, reply);
+ g_variant_unref (reply);
+
+ reply = pulseaudio_mpris_player_get_all_media_player_properties (player);
+ pulseaudio_mpris_player_parse_media_player_properties (player, reply);
+ g_variant_unref (reply);
+}
+
+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;
+
+ g_signal_emit (player, signals[CONNECTION], 0, player->connected);
+}
+
+static gchar *
+find_desktop_entry (const gchar *player_name)
+{
+ GKeyFile *key_file;
+ gchar *file;
+ gchar *filename = NULL;
+ gchar *full_path;
+
+ 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, &full_path, 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);
+ g_free (file);
+
+ return filename;
+}
+
+static void
+pulseaudio_mpris_player_set_details_from_desktop (PulseaudioMprisPlayer *player,
+ const gchar *player_name)
+{
+ GKeyFile *key_file;
+ gchar *file;
+ gchar *full_path;
+ gchar *filename;
+
+ filename = find_desktop_entry (player_name);
+
+ if (filename == NULL)
+ {
+ player->player_label = g_strdup(player->player);
+ player->icon_name = "applications-multimedia";
+
+ g_free (filename);
+ 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_string (key_file, "Desktop Entry", "Name", 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 = "applications-multimedia";
+ }
+
+ 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;
+ }
+
+ /* Clean player */
+ if (player->player != NULL)
+ {
+ g_free (player->player);
+ player->player = NULL;
+ }
+
+ /* Set new player and connect again */
+ if (player != NULL)
+ {
+ player->player = g_strdup(player_name);
+
+ pulseaudio_mpris_player_set_details_from_desktop (player, player_name);
+ pulseaudio_mpris_player_dbus_connect (player);
+ }
+}
+
+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_OWNER_FLAGS_REPLACE,
+ 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;
+ }
+
+ 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;
+}
+
+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_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->connected = FALSE;
+
+ player->title = NULL;
+ player->artist = 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;
+}
+
+
+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->connected = FALSE;
+
+ player->title = NULL;
+ player->artist = 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;
+
+ (*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);
+
+ return player;
+}
diff --git a/panel-plugin/pulseaudio-mpris-player.h b/panel-plugin/pulseaudio-mpris-player.h
new file mode 100644
index 0000000..b4e8b8a
--- /dev/null
+++ b/panel-plugin/pulseaudio-mpris-player.h
@@ -0,0 +1,79 @@
+/* Copyright (c) 2017 Sean Davis <bluesabre at 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.
+ */
+
+#ifndef __PULSEAUDIO_MPRIS_PLAYER_PLAYER_H__
+#define __PULSEAUDIO_MPRIS_PLAYER_PLAYER_H__
+
+#include <glib.h>
+#include <gtk/gtk.h>
+
+G_BEGIN_DECLS
+
+/**
+ * PlaybackStatus:
+ * @PLAYING: A track is currently playing.
+ * @PAUSED: A track is currently paused.
+ * @STOPPED: There is no track currently playing.
+ *
+ * The current playback status:
+ * See mpris2 specification <ulink url="http://specifications.freedesktop.org/mpris-spec/latest/Player_Interface.html#Property:PlaybackStatus">PlaybackStatus</ulink>
+ */
+typedef enum {
+ PLAYING = 1,
+ PAUSED,
+ STOPPED
+} PlaybackStatus;
+
+GType pulseaudio_mpris_player_get_type (void);
+
+#define TYPE_PULSEAUDIO_MPRIS_PLAYER (pulseaudio_mpris_player_get_type())
+#define PULSEAUDIO_MPRIS_PLAYER(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), TYPE_PULSEAUDIO_MPRIS_PLAYER, PulseaudioMprisPlayer))
+#define PULSEAUDIO_MPRIS_PLAYER_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass), TYPE_PULSEAUDIO_MPRIS_PLAYER, PulseaudioMprisPlayerClass))
+#define IS_PULSEAUDIO_MPRIS_PLAYER(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), TYPE_PULSEAUDIO_MPRIS_PLAYER))
+#define IS_PULSEAUDIO_MPRIS_PLAYER_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass), TYPE_PULSEAUDIO_MPRIS_PLAYER))
+#define PULSEAUDIO_MPRIS_PLAYER_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS((obj), TYPE_PULSEAUDIO_MPRIS_PLAYER, PulseaudioMprisPlayerClass))
+
+typedef struct _PulseaudioMprisPlayer PulseaudioMprisPlayer;
+typedef struct _PulseaudioMprisPlayerClass PulseaudioMprisPlayerClass;
+
+PulseaudioMprisPlayer *pulseaudio_mpris_player_new (gchar *name);
+
+const gchar *pulseaudio_mpris_player_get_player (PulseaudioMprisPlayer *player);
+const gchar *pulseaudio_mpris_player_get_player_title (PulseaudioMprisPlayer *player);
+const gchar *pulseaudio_mpris_player_get_icon_name (PulseaudioMprisPlayer *player);
+const gchar *pulseaudio_mpris_player_get_title (PulseaudioMprisPlayer *player);
+const gchar *pulseaudio_mpris_player_get_artist (PulseaudioMprisPlayer *player);
+
+gboolean pulseaudio_mpris_player_is_connected (PulseaudioMprisPlayer *player);
+gboolean pulseaudio_mpris_player_is_playing (PulseaudioMprisPlayer *player);
+gboolean pulseaudio_mpris_player_is_stopped (PulseaudioMprisPlayer *player);
+
+gboolean pulseaudio_mpris_player_can_play (PulseaudioMprisPlayer *player);
+gboolean pulseaudio_mpris_player_can_pause (PulseaudioMprisPlayer *player);
+gboolean pulseaudio_mpris_player_can_go_previous (PulseaudioMprisPlayer *player);
+gboolean pulseaudio_mpris_player_can_go_next (PulseaudioMprisPlayer *player);
+gboolean pulseaudio_mpris_player_can_raise (PulseaudioMprisPlayer *player);
+
+gboolean pulseaudio_mpris_player_is_equal (PulseaudioMprisPlayer *a,
+ PulseaudioMprisPlayer *b);
+
+void pulseaudio_mpris_player_call_player_method (PulseaudioMprisPlayer *player,
+ const gchar *method);
+
+G_END_DECLS
+
+#endif /* !__PULSEAUDIO_MPRIS_PLAYER_H__ */
diff --git a/panel-plugin/pulseaudio-mpris.c b/panel-plugin/pulseaudio-mpris.c
new file mode 100644
index 0000000..4ceac95
--- /dev/null
+++ b/panel-plugin/pulseaudio-mpris.c
@@ -0,0 +1,308 @@
+/* Copyright (c) 2017 Sean Davis <bluesabre at 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 <glib.h>
+#include <gtk/gtk.h>
+
+#include "pulseaudio-mpris.h"
+#include "pulseaudio-mpris-player.h"
+
+struct _PulseaudioMpris
+{
+ GObject __parent__;
+ PulseaudioConfig *config;
+
+ GDBusConnection *dbus_connection;
+ GHashTable *players;
+
+ guint player_timer_id;
+};
+
+struct _PulseaudioMprisClass
+{
+ GObjectClass __parent__;
+ void (*update) (PulseaudioMpris *mpris);
+};
+
+
+static void pulseaudio_mpris_finalize (GObject *object);
+
+
+enum
+{
+ UPDATE,
+ LAST_SIGNAL
+};
+static int signals[LAST_SIGNAL] = { 0 };
+
+
+G_DEFINE_TYPE (PulseaudioMpris, pulseaudio_mpris, G_TYPE_OBJECT)
+
+static void
+pulseaudio_mpris_class_init (PulseaudioMprisClass *klass)
+{
+ GObjectClass *gobject_class;
+
+ gobject_class = G_OBJECT_CLASS (klass);
+ gobject_class->finalize = pulseaudio_mpris_finalize;
+
+ signals[UPDATE] =
+ g_signal_new ("update",
+ G_TYPE_FROM_CLASS (gobject_class),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (PulseaudioMprisClass, update),
+ NULL, NULL,
+ g_cclosure_marshal_VOID__STRING,
+ G_TYPE_NONE, 1, G_TYPE_STRING);
+
+}
+
+
+gchar **
+pulseaudio_mpris_get_available_players (PulseaudioMpris *mpris)
+{
+ GError *error = NULL;
+ GVariant *v;
+ GVariantIter *iter;
+ const gchar *str = NULL;
+ gchar **res = NULL;
+ guint items = 0;
+
+ v = g_dbus_connection_call_sync (mpris->dbus_connection,
+ "org.freedesktop.DBus",
+ "/org/freedesktop/DBus",
+ "org.freedesktop.DBus",
+ "ListNames",
+ NULL,
+ G_VARIANT_TYPE ("(as)"),
+ G_DBUS_CALL_FLAGS_NONE,
+ -1,
+ NULL,
+ &error);
+ if (error)
+ {
+ g_critical ("Could not get a list of names registered on the session bus, %s",
+ error ? error->message : "no error given");
+ g_clear_error (&error);
+ return NULL;
+ }
+
+ g_variant_get (v, "(as)", &iter);
+ while (g_variant_iter_loop (iter, "&s", &str))
+ {
+ if (g_str_has_prefix(str, "org.mpris.MediaPlayer2."))
+ {
+ res = (gchar**)g_realloc(res, (items + 1) * sizeof(gchar*));
+ res[items] = g_strdup(str + 23);
+ items++;
+ }
+ }
+
+ /* Add NULL termination to the res vector */
+ if (items > 0)
+ {
+ res = g_realloc(res, (items + 1) * sizeof(gchar*));
+ res[items] = NULL;
+ }
+
+ g_variant_iter_free (iter);
+ g_variant_unref (v);
+
+ return res;
+}
+
+static void
+pulseaudio_mpris_player_update_cb (PulseaudioMprisPlayer *player,
+ gchar *sender_name,
+ gpointer user_data)
+{
+ PulseaudioMpris *mpris = user_data;
+
+ g_return_if_fail (IS_PULSEAUDIO_MPRIS (mpris));
+
+ g_signal_emit (mpris, signals[UPDATE], 0, pulseaudio_mpris_player_get_player (player));
+}
+
+static void
+pulseaudio_mpris_player_metadata_cb (PulseaudioMprisPlayer *player,
+ gpointer user_data)
+{
+ PulseaudioMpris *mpris = user_data;
+
+ g_return_if_fail (IS_PULSEAUDIO_MPRIS (mpris));
+
+ g_signal_emit (mpris, signals[UPDATE], 0, pulseaudio_mpris_player_get_player (player));
+}
+
+static gboolean
+pulseaudio_mpris_tick_cb (gpointer user_data)
+{
+ PulseaudioMpris *mpris = user_data;
+ PulseaudioMprisPlayer *player;
+ gchar **players;
+
+ players = pulseaudio_mpris_get_available_players (mpris);
+ if (players == NULL)
+ return TRUE;
+
+ for (guint i = 0; i < g_strv_length (players); i++)
+ {
+ if (!g_hash_table_contains (mpris->players, players[i]))
+ {
+ player = pulseaudio_mpris_player_new (players[i]);
+
+ g_signal_connect (player, "connection", G_CALLBACK (pulseaudio_mpris_player_update_cb), mpris);
+ g_signal_connect (player, "playback-status", G_CALLBACK (pulseaudio_mpris_player_update_cb), mpris);
+ g_signal_connect (player, "metadata", G_CALLBACK (pulseaudio_mpris_player_metadata_cb), mpris);
+
+ g_hash_table_insert (mpris->players, players[i], player);
+
+ pulseaudio_config_add_mpris_player (mpris->config, players[i]);
+ }
+ }
+
+ return TRUE;
+}
+
+gboolean
+pulseaudio_mpris_get_player_snapshot (PulseaudioMpris *mpris,
+ const gchar *name,
+ gchar **title,
+ gchar **artist,
+ gboolean *is_playing,
+ gboolean *is_stopped,
+ gboolean *can_play,
+ gboolean *can_pause,
+ gboolean *can_go_previous,
+ gboolean *can_go_next,
+ gboolean *can_raise)
+{
+ PulseaudioMprisPlayer *player;
+ player = PULSEAUDIO_MPRIS_PLAYER (g_hash_table_lookup (mpris->players, name));
+
+ if (player != NULL)
+ {
+ if (pulseaudio_mpris_player_is_connected (player))
+ {
+ *title = g_strdup(pulseaudio_mpris_player_get_title (player));
+ *artist = g_strdup(pulseaudio_mpris_player_get_artist (player));
+
+ *is_playing = pulseaudio_mpris_player_is_playing (player);
+ *is_stopped = pulseaudio_mpris_player_is_stopped (player);
+ *can_play = pulseaudio_mpris_player_can_play (player);
+ *can_pause = pulseaudio_mpris_player_can_pause (player);
+ *can_go_previous = pulseaudio_mpris_player_can_go_previous (player);
+ *can_go_next = pulseaudio_mpris_player_can_go_next (player);
+ *can_raise = pulseaudio_mpris_player_can_raise (player);
+ }
+ else
+ {
+ *title = g_strdup(pulseaudio_mpris_player_get_player_title (player));
+ *artist = g_strdup("Not currently playing");
+
+ *is_playing = FALSE;
+ *is_stopped = TRUE;
+ *can_play = FALSE;
+ *can_pause = FALSE;
+ *can_go_previous = FALSE;
+ *can_go_next = FALSE;
+ *can_raise = FALSE;
+ }
+ if (*title == NULL || g_strcmp0 (*title, "") == 0)
+ *title = g_strdup(pulseaudio_mpris_player_get_player_title (player));
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+gboolean
+pulseaudio_mpris_notify_player (PulseaudioMpris *mpris,
+ const gchar *name,
+ const gchar *message)
+{
+ PulseaudioMprisPlayer *player;
+
+ g_return_val_if_fail (IS_PULSEAUDIO_MPRIS (mpris), FALSE);
+
+ player = g_hash_table_lookup (mpris->players, name);
+
+ if (player != NULL)
+ {
+ if (pulseaudio_mpris_player_is_connected (player))
+ {
+ pulseaudio_mpris_player_call_player_method (player, message);
+ return TRUE;
+ }
+ }
+
+ return FALSE;
+}
+
+static void
+pulseaudio_mpris_init (PulseaudioMpris *mpris)
+{
+ mpris->config = NULL;
+ mpris->dbus_connection = NULL;
+}
+
+static void
+pulseaudio_mpris_finalize (GObject *object)
+{
+ PulseaudioMpris *mpris;
+
+ mpris = PULSEAUDIO_MPRIS (object);
+
+ mpris->config = NULL;
+ mpris->dbus_connection = NULL;
+
+ (*G_OBJECT_CLASS (pulseaudio_mpris_parent_class)->finalize) (object);
+}
+
+PulseaudioMpris *
+pulseaudio_mpris_new (PulseaudioConfig *config)
+{
+ PulseaudioMpris *mpris;
+ GDBusConnection *gconnection;
+ GError *gerror = NULL;
+
+ g_return_val_if_fail (IS_PULSEAUDIO_CONFIG (config), 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;
+ }
+
+ mpris = g_object_new (TYPE_PULSEAUDIO_MPRIS, NULL);
+
+ mpris->config = config;
+ mpris->dbus_connection = gconnection;
+ mpris->players = g_hash_table_new (g_str_hash, g_str_equal);
+ mpris->player_timer_id = g_timeout_add_seconds (1, pulseaudio_mpris_tick_cb, mpris);
+
+ return mpris;
+}
diff --git a/panel-plugin/pulseaudio-mpris.h b/panel-plugin/pulseaudio-mpris.h
new file mode 100644
index 0000000..c3aeabc
--- /dev/null
+++ b/panel-plugin/pulseaudio-mpris.h
@@ -0,0 +1,62 @@
+/* Copyright (c) 2017 Sean Davis <bluesabre at 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.
+ */
+
+#ifndef __PULSEAUDIO_MPRIS_H__
+#define __PULSEAUDIO_MPRIS_H__
+
+#include <glib.h>
+#include <gtk/gtk.h>
+
+#include "pulseaudio-config.h"
+
+G_BEGIN_DECLS
+
+GType pulseaudio_mpris_get_type (void);
+
+#define TYPE_PULSEAUDIO_MPRIS (pulseaudio_mpris_get_type())
+#define PULSEAUDIO_MPRIS(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), TYPE_PULSEAUDIO_MPRIS, PulseaudioMpris))
+#define PULSEAUDIO_MPRIS_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass), TYPE_PULSEAUDIO_MPRIS, PulseaudioMprisClass))
+#define IS_PULSEAUDIO_MPRIS(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), TYPE_PULSEAUDIO_MPRIS))
+#define IS_PULSEAUDIO_MPRIS_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass), TYPE_PULSEAUDIO_MPRIS))
+#define PULSEAUDIO_MPRIS_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS((obj), TYPE_PULSEAUDIO_MPRIS, PulseaudioMprisClass))
+
+typedef struct _PulseaudioMpris PulseaudioMpris;
+typedef struct _PulseaudioMprisClass PulseaudioMprisClass;
+
+PulseaudioMpris *pulseaudio_mpris_new (PulseaudioConfig *config);
+
+gchar **pulseaudio_mpris_get_available_players (PulseaudioMpris *mpris);
+
+gboolean pulseaudio_mpris_get_player_snapshot (PulseaudioMpris *mpris,
+ const gchar *name,
+ gchar **title,
+ gchar **artist,
+ gboolean *is_playing,
+ gboolean *is_stopped,
+ gboolean *can_play,
+ gboolean *can_pause,
+ gboolean *can_go_previous,
+ gboolean *can_go_next,
+ gboolean *can_raise);
+
+gboolean pulseaudio_mpris_notify_player (PulseaudioMpris *mpris,
+ const gchar *name,
+ const gchar *message);
+
+G_END_DECLS
+
+#endif /* !__PULSEAUDIO_MPRIS_H__ */
diff --git a/panel-plugin/pulseaudio-plugin.c b/panel-plugin/pulseaudio-plugin.c
index 2ef8eb4..6d6b550 100644
--- a/panel-plugin/pulseaudio-plugin.c
+++ b/panel-plugin/pulseaudio-plugin.c
@@ -44,6 +44,7 @@
#include "pulseaudio-button.h"
#include "pulseaudio-dialog.h"
#include "pulseaudio-notify.h"
+#include "pulseaudio-mpris.h"
#ifdef HAVE_IDO
#include <libido/libido.h>
@@ -104,6 +105,10 @@ struct _PulseaudioPlugin
/* config dialog builder */
PulseaudioDialog *dialog;
+
+ /* mpris */
+ PulseaudioMpris *mpris;
+ gchar **players;
};
@@ -142,6 +147,8 @@ pulseaudio_plugin_init (PulseaudioPlugin *pulseaudio_plugin)
#ifdef HAVE_LIBNOTIFY
pulseaudio_plugin->notify = NULL;
#endif
+
+ pulseaudio_plugin->mpris = NULL;
}
@@ -413,10 +420,16 @@ pulseaudio_plugin_construct (XfcePanelPlugin *plugin)
/* volume controller */
pulseaudio_plugin->volume = pulseaudio_volume_new (pulseaudio_plugin->config);
+ /* initialize mpris support */
+ pulseaudio_plugin->mpris = pulseaudio_mpris_new (pulseaudio_plugin->config);
+ pulseaudio_plugin->players = pulseaudio_mpris_get_available_players (pulseaudio_plugin->mpris);
+
/* instantiate a button box */
pulseaudio_plugin->button = pulseaudio_button_new (pulseaudio_plugin,
pulseaudio_plugin->config,
+ pulseaudio_plugin->mpris,
pulseaudio_plugin->volume);
+
/* initialize notify wrapper */
#ifdef HAVE_LIBNOTIFY
pulseaudio_plugin->notify = pulseaudio_notify_new (pulseaudio_plugin->config,
@@ -429,4 +442,3 @@ pulseaudio_plugin_construct (XfcePanelPlugin *plugin)
}
-
diff --git a/panel-plugin/pulseaudio-volume.c b/panel-plugin/pulseaudio-volume.c
index c263599..acdbfdd 100644
--- a/panel-plugin/pulseaudio-volume.c
+++ b/panel-plugin/pulseaudio-volume.c
@@ -213,7 +213,7 @@ pulseaudio_volume_source_info_cb (pa_context *context,
volume->volume_mic = vol_mic;
g_signal_emit (G_OBJECT (volume), pulseaudio_volume_signals [VOLUME_MIC_CHANGED], 0, FALSE);
}
- pulseaudio_debug ("volume mic: %d, muted mic: %d", vol_mic, muted_mic);
+ pulseaudio_debug ("volume mic: %f, muted mic: %d", vol_mic, muted_mic);
}
@@ -597,7 +597,7 @@ pulseaudio_volume_set_volume_cb2 (pa_context *context,
if (i == NULL) return;
//pulseaudio_debug ("*** %s", pa_cvolume_snprint (st, sizeof (st), &i->volume));
- pa_cvolume_set (&i->volume, 1, pulseaudio_volume_d2v (volume, volume->volume));
+ pa_cvolume_set ((pa_cvolume *)&i->volume, 1, pulseaudio_volume_d2v (volume, volume->volume));
pa_context_set_sink_volume_by_index (context, i->index, &i->volume, pulseaudio_volume_sink_volume_changed, volume);
}
@@ -664,7 +664,7 @@ pulseaudio_volume_set_volume_mic_cb2 (pa_context *context,
if (i == NULL) return;
//pulseaudio_debug ("*** %s", pa_cvolume_snprint (st, sizeof (st), &i->volume));
- pa_cvolume_set (&i->volume, 1, pulseaudio_volume_d2v (volume, volume->volume_mic));
+ pa_cvolume_set ((pa_cvolume *)&i->volume, 1, pulseaudio_volume_d2v (volume, volume->volume_mic));
pa_context_set_source_volume_by_index (context, i->index, &i->volume, pulseaudio_volume_source_volume_changed, volume);
}
@@ -719,5 +719,3 @@ pulseaudio_volume_new (PulseaudioConfig *config)
return volume;
}
-
-
diff --git a/panel-plugin/scalemenuitem.c b/panel-plugin/scalemenuitem.c
index 2755075..ee09ca8 100644
--- a/panel-plugin/scalemenuitem.c
+++ b/panel-plugin/scalemenuitem.c
@@ -75,7 +75,9 @@ enum {
static guint signals[LAST_SIGNAL] = { 0 };
+G_GNUC_BEGIN_IGNORE_DEPRECATIONS
G_DEFINE_TYPE (ScaleMenuItem, scale_menu_item, GTK_TYPE_IMAGE_MENU_ITEM)
+G_GNUC_END_IGNORE_DEPRECATIONS
#define GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), TYPE_SCALE_MENU_ITEM, ScaleMenuItemPrivate))
@@ -111,7 +113,7 @@ scale_menu_item_class_init (ScaleMenuItemClass *item_class)
* ScaleMenuItem::slider-grabbed:
* @menuitem: The #ScaleMenuItem emitting the signal.
*
- * The ::slider-grabbed signal is emitted when the pointer selects the slider.
+ * The ::slider-grabbed signal is emitted when the pointer selects the slider.
*/
signals[SLIDER_GRABBED] = g_signal_new ("slider-grabbed",
G_OBJECT_CLASS_TYPE (gobject_class),
@@ -284,7 +286,9 @@ scale_menu_item_button_release_event (GtkWidget *menuitem,
GdkEventButton *event)
{
ScaleMenuItemPrivate *priv;
+#if !(GTK_CHECK_VERSION (3, 14, 0))
gint x, y;
+#endif
TRACE("entering");
@@ -472,6 +476,22 @@ scale_menu_item_get_percentage_label (ScaleMenuItem *menuitem)
return gtk_label_get_text (GTK_LABEL (priv->percentage_label));
}
+static GtkWidget *
+scale_menu_item_label_new (const gchar *str)
+{
+ GtkWidget *label = gtk_label_new (str);
+
+ /* align left */
+#if GTK_CHECK_VERSION (3, 16, 0)
+ gtk_label_set_xalign (GTK_LABEL (label), 0.0);
+ gtk_widget_set_halign (label, GTK_ALIGN_START);
+#else
+ gtk_misc_set_alignment (GTK_MISC(label), 0, 0);
+#endif
+
+ return label;
+}
+
/**
* scale_menu_item_set_description_label:
* @menuitem: The #ScaleMenuItem
@@ -505,11 +525,8 @@ scale_menu_item_set_description_label (ScaleMenuItem *menuitem,
else if(label)
{
/* create label */
- priv->description_label = gtk_label_new (NULL);
+ priv->description_label = scale_menu_item_label_new (NULL);
gtk_label_set_markup (GTK_LABEL (priv->description_label), label);
-
- /* align left */
- gtk_misc_set_alignment (GTK_MISC(priv->description_label), 0, 0);
}
update_packing (menuitem);
@@ -549,9 +566,7 @@ scale_menu_item_set_percentage_label (ScaleMenuItem *menuitem,
else if(label)
{
/* create label */
- priv->percentage_label = gtk_label_new (label);
- /* align left */
- gtk_misc_set_alignment (GTK_MISC(priv->percentage_label), 0, 0);
+ priv->percentage_label = scale_menu_item_label_new (label);
}
update_packing (menuitem);
@@ -582,3 +597,18 @@ scale_menu_item_set_value (ScaleMenuItem *item,
gtk_range_set_value (GTK_RANGE (priv->scale), value);
priv->ignore_value_changed = FALSE;
}
+
+void
+scale_menu_item_set_image_from_icon_name (ScaleMenuItem *item,
+ const gchar *icon_name)
+{
+ GtkWidget *img = NULL;
+
+ g_return_if_fail (IS_SCALE_MENU_ITEM (item));
+
+ img = gtk_image_new_from_icon_name (icon_name, GTK_ICON_SIZE_LARGE_TOOLBAR);
+G_GNUC_BEGIN_IGNORE_DEPRECATIONS
+ gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (item), img);
+G_GNUC_END_IGNORE_DEPRECATIONS
+ gtk_image_set_pixel_size (GTK_IMAGE (img), 24);
+}
diff --git a/panel-plugin/scalemenuitem.h b/panel-plugin/scalemenuitem.h
index 772419a..2d793e2 100644
--- a/panel-plugin/scalemenuitem.h
+++ b/panel-plugin/scalemenuitem.h
@@ -73,8 +73,11 @@ void scale_menu_item_set_description_label (ScaleMenuItem *menuitem,
void scale_menu_item_set_percentage_label (ScaleMenuItem *menuitem,
const gchar *label);
-void scale_menu_item_set_value (ScaleMenuItem *item,
- gdouble value);
+void scale_menu_item_set_value (ScaleMenuItem *item,
+ gdouble value);
+
+void scale_menu_item_set_image_from_icon_name (ScaleMenuItem *menuitem,
+ const gchar *icon_name);
G_END_DECLS
diff --git a/po/POTFILES.in b/po/POTFILES.in
index 75a84e8..0cae865 100644
--- a/po/POTFILES.in
+++ b/po/POTFILES.in
@@ -7,3 +7,4 @@ panel-plugin/pulseaudio-button.c
panel-plugin/pulseaudio-volume.c
panel-plugin/pulseaudio-menu.c
panel-plugin/pulseaudio-notify.c
+panel-plugin/mprismenuitem.c
--
To stop receiving notification emails like this one, please contact
the administrator of this repository.
More information about the Xfce4-commits
mailing list