From 81a039854f7e86c19a6ce479420ba6e4e31d344e Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Andr=C3=A9=20Apitzsch?= <git@apitzsch.eu>
Date: Sun, 17 Dec 2017 01:58:42 +0100
Subject: [PATCH] Port music_track_listener to GTK dbus

---
 gajim/gui_interface.py        |  11 +-
 gajim/music_track_listener.py | 333 +++++++++++-----------------------
 gajim/roster_window.py        |   4 +-
 3 files changed, 112 insertions(+), 236 deletions(-)

diff --git a/gajim/gui_interface.py b/gajim/gui_interface.py
index 3aee96df10..aca8c573bd 100644
--- a/gajim/gui_interface.py
+++ b/gajim/gui_interface.py
@@ -56,9 +56,10 @@ from gajim.common import events
 
 from gajim.common import dbus_support
 if dbus_support.supported:
-    from gajim.music_track_listener import MusicTrackListener
     import dbus
 
+from gajim.music_track_listener import MusicTrackListener
+
 if app.HAVE_GEOCLUE:
     from gajim.common import location_listener
 
@@ -1135,7 +1136,7 @@ class Interface:
         if connected == invisible_show:
             return
         # send currently played music
-        if (obj.conn.pep_supported and dbus_support.supported and
+        if (obj.conn.pep_supported and sys.platform == 'linux' and
                 app.config.get_per('accounts', account, 'publish_tune')):
             self.enable_music_listener()
         # enable location listener
@@ -2192,8 +2193,6 @@ class Interface:
         if not self.music_track_changed_signal:
             self.music_track_changed_signal = listener.connect(
                 'music-track-changed', self.music_track_changed)
-        track = listener.get_playing_track()
-        self.music_track_changed(listener, track)
 
     def disable_music_listener(self):
         listener = MusicTrackListener.get()
@@ -2207,9 +2206,7 @@ class Interface:
         else:
             accounts = [account]
 
-        is_paused = hasattr(music_track_info, 'paused') and \
-            music_track_info.paused == 0
-        if not music_track_info or is_paused:
+        if music_track_info is None or music_track_info.paused:
             artist = title = source = ''
         else:
             artist = music_track_info.artist
diff --git a/gajim/music_track_listener.py b/gajim/music_track_listener.py
index 355025e4c6..309483204a 100644
--- a/gajim/music_track_listener.py
+++ b/gajim/music_track_listener.py
@@ -1,5 +1,5 @@
 # -*- coding: utf-8 -*-
-## src/music_track_listener.py
+## gajim/music_track_listener.py
 ##
 ## Copyright (C) 2006 Gustavo Carneiro <gjcarneiro AT gmail.com>
 ##                    Nikos Kouremenos <kourem AT gmail.com>
@@ -23,25 +23,40 @@
 ## along with Gajim. If not, see <http://www.gnu.org/licenses/>.
 ##
 
+import logging
+
 from gi.repository import GObject
-if __name__ == '__main__':
-    # install _() func before importing dbus_support
-    from gajim.common import i18n
+from gi.repository import Gio, GLib
+
+log = logging.getLogger('gajim.music_track_listener')
+
+
+def _get_music_players():
+    players = [
+        'org.mpris.MediaPlayer2.audacious',
+        'org.mpris.MediaPlayer2.bmp',
+        'org.mpris.MediaPlayer2.GnomeMusic',
+        'org.mpris.MediaPlayer2.quodlibet',
+        'org.mpris.MediaPlayer2.rhythmbox',
+        'org.mpris.MediaPlayer2.vlc',
+        'org.mpris.MediaPlayer2.xmms2'
+    ]
+
+    return players
 
-from gajim.common import dbus_support
-if dbus_support.supported:
-    import dbus
 
 class MusicTrackInfo(object):
     __slots__ = ['title', 'album', 'artist', 'duration', 'track_number',
-            'paused']
+                 'paused']
+
 
 class MusicTrackListener(GObject.GObject):
     __gsignals__ = {
-            'music-track-changed': (GObject.SignalFlags.RUN_LAST, None, (object,)),
+        'music-track-changed': (GObject.SignalFlags.RUN_LAST, None, (object,)),
     }
 
     _instance = None
+
     @classmethod
     def get(cls):
         if cls._instance is None:
@@ -51,253 +66,117 @@ class MusicTrackListener(GObject.GObject):
     def __init__(self):
         super(MusicTrackListener, self).__init__()
         self._last_playing_music = None
+        self.con = {}
+
+        players = _get_music_players()
+        for name in players:
+            Gio.bus_watch_name(
+                Gio.BusType.SESSION,
+                name,
+                Gio.BusNameWatcherFlags.NONE,
+                self._appeared,
+                self._vanished)
+
+    def _appeared(self, connection, name, name_owner, *user_data):
+        '''Set up a listener for music player signals'''
+        log.info('%s appeared', name)
+        self.con[name] = connection.signal_subscribe(
+            name,
+            'org.freedesktop.DBus.Properties',
+            'PropertiesChanged',
+            '/org/mpris/MediaPlayer2',
+            None,
+            Gio.DBusSignalFlags.NONE,
+            self._signal_received,
+            name)
+
+        info = self.get_playing_track(name)
+        if info is not None:
+            self._last_playing_music = info
+            self.emit('music-track-changed', info)
 
-        bus = dbus.SessionBus()
-
-        ## MPRIS
-        bus.add_signal_receiver(self._mpris_music_track_change_cb, 'TrackChange',
-                'org.freedesktop.MediaPlayer')
-        bus.add_signal_receiver(self._mpris_playing_changed_cb, 'StatusChange',
-                'org.freedesktop.MediaPlayer')
-        bus.add_signal_receiver(self._player_name_owner_changed,
-                'NameOwnerChanged', 'org.freedesktop.DBus',
-                arg0='org.freedesktop.MediaPlayer')
-
-        ## Muine
-        bus.add_signal_receiver(self._muine_music_track_change_cb, 'SongChanged',
-                'org.gnome.Muine.Player')
-        bus.add_signal_receiver(self._player_name_owner_changed,
-                'NameOwnerChanged', 'org.freedesktop.DBus', arg0='org.gnome.Muine')
-        bus.add_signal_receiver(self._player_playing_changed_cb, 'StateChanged',
-                'org.gnome.Muine.Player')
-
-        ## Rhythmbox
-        bus.add_signal_receiver(self._player_name_owner_changed,
-                'NameOwnerChanged', 'org.freedesktop.DBus', arg0='org.gnome.Rhythmbox')
-        bus.add_signal_receiver(self._rhythmbox_playing_changed_cb,
-                'playingChanged', 'org.gnome.Rhythmbox.Player')
-        bus.add_signal_receiver(self._player_playing_song_property_changed_cb,
-                'playingSongPropertyChanged', 'org.gnome.Rhythmbox.Player')
-
-        ## Banshee
-        bus.add_signal_receiver(self._banshee_state_changed_cb,
-                'StateChanged', 'org.bansheeproject.Banshee.PlayerEngine')
-        bus.add_signal_receiver(self._player_name_owner_changed,
-                'NameOwnerChanged', 'org.freedesktop.DBus',
-                arg0='org.bansheeproject.Banshee')
-
-        ## Quod Libet
-        bus.add_signal_receiver(self._quodlibet_state_change_cb,
-                'SongStarted', 'net.sacredchao.QuodLibet')
-        bus.add_signal_receiver(self._quodlibet_state_change_cb,
-                'Paused', 'net.sacredchao.QuodLibet')
-        bus.add_signal_receiver(self._quodlibet_state_change_cb,
-                'Unpaused', 'net.sacredchao.QuodLibet')
-        bus.add_signal_receiver(self._player_name_owner_changed,
-                'NameOwnerChanged', 'org.freedesktop.DBus',
-                arg0='net.sacredchao.QuodLibet')
-
-    def _player_name_owner_changed(self, name, old, new):
-        if not new:
-            self.emit('music-track-changed', None)
+    def _vanished(self, connection, name, *user_data):
+        log.info('%s vanished', name)
+        if name in self.con:
+            connection.signal_unsubscribe(
+                self.con[name])
+            self.con.pop(name)
 
-    def _player_playing_changed_cb(self, playing):
-        if playing:
-            self.emit('music-track-changed', self._last_playing_music)
-        else:
             self.emit('music-track-changed', None)
 
-    def _player_playing_song_property_changed_cb(self, a, b, c, d):
-        if b == 'rb:stream-song-title':
-            self.emit('music-track-changed', self._last_playing_music)
+    def _signal_received(self, connection, sender_name, object_path,
+                         interface_name, signal_name, parameters, *user_data):
+        '''Signal handler for PropertiesChanged event'''
 
-    def _mpris_properties_extract(self, song):
-        info = MusicTrackInfo()
-        info.title = song.get('title', '')
-        info.album = song.get('album', '')
-        info.artist = song.get('artist', '')
-        info.duration = int(song.get('length', 0))
-        return info
-
-    def _mpris_playing_changed_cb(self, playing):
-        if type(playing) is dbus.Struct:
-            if playing[0]:
-                self.emit('music-track-changed', None)
-            else:
-                self.emit('music-track-changed', self._last_playing_music)
-        else: # Workaround for e.g. Audacious
-            if playing:
-                self.emit('music-track-changed', None)
-            else:
-                self.emit('music-track-changed', self._last_playing_music)
+        if 'PlaybackStatus' not in parameters[1]:
+            return
 
-    def _mpris_music_track_change_cb(self, arg):
-        self._last_playing_music = self._mpris_properties_extract(arg)
-        self.emit('music-track-changed', self._last_playing_music)
+        log.info('Signal received: %s - %s', interface_name, parameters)
 
-    def _muine_properties_extract(self, song_string):
-        d = dict((x.strip() for x in  s1.split(':', 1)) for s1 in \
-                song_string.split('\n'))
-        info = MusicTrackInfo()
-        info.title = d['title']
-        info.album = d['album']
-        info.artist = d['artist']
-        info.duration = int(d['duration'])
-        info.track_number = int(d['track_number'])
-        return info
+        info = self.get_playing_track(user_data[0])
+        self._last_playing_music = info
 
-    def _muine_music_track_change_cb(self, arg):
-        info = self._muine_properties_extract(arg)
         self.emit('music-track-changed', info)
 
-    def _rhythmbox_playing_changed_cb(self, playing):
-        if playing:
-            info = self.get_playing_track()
-            self.emit('music-track-changed', info)
-        else:
-            self.emit('music-track-changed', None)
-
-    def _rhythmbox_properties_extract(self, props):
-        info = MusicTrackInfo()
-        info.title = props.get('title', None)
-        info.album = props.get('album', None)
-        info.artist = props.get('artist', None)
-        info.duration = int(props.get('duration', 0))
-        info.track_number = int(props.get('track-number', 0))
-        return info
-
-    def _banshee_state_changed_cb(self, state):
-        if state == 'playing':
-            bus = dbus.SessionBus()
-            banshee = bus.get_object('org.bansheeproject.Banshee',
-                    '/org/bansheeproject/Banshee/PlayerEngine')
-            currentTrack = banshee.GetCurrentTrack()
-            self._last_playing_music = self._banshee_properties_extract(
-                    currentTrack)
-            self.emit('music-track-changed', self._last_playing_music)
-        elif state == 'paused':
-            self.emit('music-track-changed', None)
+    def _properties_extract(self, properties):
+        meta = properties.get('Metadata')
+        if meta is None or not meta:
+            return None
 
-    def _banshee_properties_extract(self, props):
         info = MusicTrackInfo()
-        info.title = props.get('name', None)
-        info.album = props.get('album', None)
-        info.artist = props.get('artist', None)
-        info.duration = int(props.get('length', 0))
-        return info
-
-    def _quodlibet_state_change_cb(self, state=None):
-        info = self.get_playing_track()
-        if info:
-            self.emit('music-track-changed', info)
+        info.title = meta.get('xesam:title')
+        info.album = meta.get('xesam:album')
+        artist = meta.get('xesam:artist')
+        if artist is not None and len(artist):
+            info.artist = artist[0]
         else:
-            self.emit('music-track-changed', None)
+            info.artist = None
+        info.duration = float(meta.get('mpris:length', 0))
+        info.track_number = meta.get('xesam:trackNumber', 0)
+
+        status = properties.get('PlaybackStatus')
+        info.paused = status is not None and status == 'Paused'
 
-    def _quodlibet_properties_extract(self, props):
-        info = MusicTrackInfo()
-        info.title = props.get('title', None)
-        info.album = props.get('album', None)
-        info.artist = props.get('artist', None)
-        info.duration = float(props.get('~#length', 0))
         return info
 
-    def get_playing_track(self):
+    def get_playing_track(self, name):
         '''Return a MusicTrackInfo for the currently playing
         song, or None if no song is playing'''
-
-        bus = dbus.SessionBus()
-
-        ## Check Muine playing track
-        test = False
-        if hasattr(bus, 'name_has_owner'):
-            if bus.name_has_owner('org.gnome.Muine'):
-                test = True
-        elif dbus.dbus_bindings.bus_name_has_owner(bus.get_connection(),
-        'org.gnome.Muine'):
-            test = True
-        if test:
-            obj = bus.get_object('org.gnome.Muine', '/org/gnome/Muine/Player')
-            player = dbus.Interface(obj, 'org.gnome.Muine.Player')
-            if player.GetPlaying():
-                song_string = player.GetCurrentSong()
-                song = self._muine_properties_extract(song_string)
-                self._last_playing_music = song
-                return song
-
-        ## Check Rhythmbox playing song
-        test = False
-        if hasattr(bus, 'name_has_owner'):
-            if bus.name_has_owner('org.gnome.Rhythmbox'):
-                test = True
-        elif dbus.dbus_bindings.bus_name_has_owner(bus.get_connection(),
-        'org.gnome.Rhythmbox'):
-            test = True
-        if test:
-            rbshellobj = bus.get_object('org.gnome.Rhythmbox',
-                    '/org/gnome/Rhythmbox/Shell')
-            player = dbus.Interface(
-                    bus.get_object('org.gnome.Rhythmbox',
-                    '/org/gnome/Rhythmbox/Player'), 'org.gnome.Rhythmbox.Player')
-            rbshell = dbus.Interface(rbshellobj, 'org.gnome.Rhythmbox.Shell')
-            try:
-                uri = player.getPlayingUri()
-            except dbus.DBusException:
-                uri = None
-            if not uri:
+        proxy = Gio.DBusProxy.new_for_bus_sync(
+            Gio.BusType.SESSION,
+            Gio.DBusProxyFlags.NONE,
+            None,
+            name,
+            '/org/mpris/MediaPlayer2',
+            'org.freedesktop.DBus.Properties',
+            None)
+
+        try:
+            result = proxy.call_sync(
+                "GetAll",
+                GLib.Variant('(s)', ('org.mpris.MediaPlayer2.Player',)),
+                Gio.DBusCallFlags.NONE,
+                -1,
+                None)
+        except GLib.Error as e:
+            if e.domain == 'g-dbus-error-quark':
+                log.debug("Could not enable music listener: %s", e.message)
                 return None
-            props = rbshell.getSongProperties(uri)
-            info = self._rhythmbox_properties_extract(props)
+            else:
+                raise
+        else:
+            info = self._properties_extract(result[0])
             self._last_playing_music = info
             return info
 
-        ## Check Banshee playing track
-        test = False
-        if hasattr(bus, 'name_has_owner'):
-            if bus.name_has_owner('org.bansheeproject.Banshee'):
-                test = True
-        elif dbus.dbus_bindings.bus_name_has_owner(bus.get_connection(),
-        'org.bansheeproject.Banshee'):
-            test = True
-        if test:
-            banshee = bus.get_object('org.bansheeproject.Banshee',
-                    '/org/bansheeproject/Banshee/PlayerEngine')
-            currentTrack = banshee.GetCurrentTrack()
-            if currentTrack:
-                song = self._banshee_properties_extract(currentTrack)
-                self._last_playing_music = song
-                return song
-
-        ## Check Quod Libet playing track
-        test = False
-        if hasattr(bus, 'name_has_owner'):
-            if bus.name_has_owner('net.sacredchao.QuodLibet'):
-                test = True
-        elif dbus.dbus_bindings.bus_name_has_owner(bus.get_connection(),
-        'net.sacredchao.QuodLibet'):
-            test = True
-        if test:
-            quodlibet = bus.get_object('net.sacredchao.QuodLibet',
-                    '/net/sacredchao/QuodLibet')
-            if quodlibet.IsPlaying():
-                currentTrack = quodlibet.CurrentSong()
-                song = self._quodlibet_properties_extract(currentTrack)
-                self._last_playing_music = song
-                return song
-
-        return None
 
 # here we test :)
 if __name__ == '__main__':
     def music_track_change_cb(listener, music_track_info):
-        if music_track_info is None:
+        if music_track_info is None or music_track_info.paused:
             print('Stop!')
         else:
             print(music_track_info.title)
     listener = MusicTrackListener.get()
     listener.connect('music-track-changed', music_track_change_cb)
-    track = listener.get_playing_track()
-    if track is None:
-        print('Now not playing anything')
-    else:
-        print('Now playing: "%s" by %s' % (track.title, track.artist))
     GObject.MainLoop().run()
diff --git a/gajim/roster_window.py b/gajim/roster_window.py
index dffc7d378f..4793fdae44 100644
--- a/gajim/roster_window.py
+++ b/gajim/roster_window.py
@@ -40,6 +40,7 @@ from gi.repository import GObject
 from gi.repository import GLib
 from gi.repository import Gio
 import os
+import sys
 import time
 import locale
 import hashlib
@@ -67,7 +68,6 @@ from gajim.common import i18n
 if app.HAVE_GEOCLUE:
     from gajim.common import location_listener
 from gajim.common import ged
-from gajim.common import dbus_support
 from gajim.message_window import MessageWindowMgr
 from nbxmpp.protocol import NS_FILE, NS_ROSTERX, NS_CONFERENCE
 
@@ -4963,7 +4963,7 @@ class RosterWindow:
 
                 item = Gtk.CheckMenuItem(_('Publish Tune'))
                 pep_submenu.append(item)
-                if not dbus_support.supported:
+                if sys.platform != 'linux':
                     item.set_sensitive(False)
                 else:
                     activ = app.config.get_per('accounts', account,
-- 
GitLab