1# Copyright 2006 Federico Pelloni <federico.pelloni@gmail.com> 2# 2013 Christoph Reiter 3# 2017 Nick Boultbee 4# 5# This program is free software; you can redistribute it and/or modify 6# it under the terms of the GNU General Public License as published by 7# the Free Software Foundation; either version 2 of the License, or 8# (at your option) any later version. 9 10from gi.repository import GLib 11from gi.repository import Gio 12 13from quodlibet.util import dbusutils 14from quodlibet.query import Query 15from quodlibet.qltk.songlist import SongList 16from quodlibet.formats import decode_value 17 18 19class DBusHandler: 20 """ 21 <!DOCTYPE node PUBLIC 22 "-//freedesktop//DTD D-BUS Object Introspection 1.0//EN" 23 "http://www.freedesktop.org/standards/dbus/1.0/introspect.dtd"> 24 <node name="/net/sacredchao/QuodLibet"> 25 <interface name="org.freedesktop.DBus.Introspectable"> 26 <method name="Introspect"> 27 <arg direction="out" type="s" /> 28 </method> 29 </interface> 30 <interface name="net.sacredchao.QuodLibet"> 31 <signal name="SongStarted"> 32 <arg type="v" name="song" /> 33 </signal> 34 <signal name="SongEnded"> 35 <arg type="v" name="song" /> 36 <arg type="v" name="skipped" /> 37 </signal> 38 <signal name="Paused"></signal> 39 <signal name="Unpaused"></signal> 40 <method name="GetPosition"> 41 <arg direction="out" type="u" /> 42 </method> 43 <method name="IsPlaying"> 44 <arg direction="out" type="b" /> 45 </method> 46 <method name="CurrentSong"> 47 <arg direction="out" type="a{ss}" /> 48 </method> 49 <method name="Next"></method> 50 <method name="Previous"></method> 51 <method name="Pause"></method> 52 <method name="Play"></method> 53 <method name="PlayPause"> 54 <arg direction="out" type="b" /> 55 </method> 56 <method name="Query"> 57 <arg direction="in" type="s" name="text" /> 58 <arg direction="out" type="aa{ss}" /> 59 </method> 60 </interface> 61 </node> 62 """ 63 64 BUS_NAME = 'net.sacredchao.QuodLibet' 65 PATH = '/net/sacredchao/QuodLibet' 66 67 def __init__(self, player, library): 68 try: 69 self._registered_ids = [] 70 self._method_outargs = {} 71 self.library = library 72 self.conn = Gio.bus_get_sync(Gio.BusType.SESSION, None) 73 Gio.bus_own_name_on_connection(self.conn, self.BUS_NAME, 74 Gio.BusNameOwnerFlags.NONE, 75 None, self.__on_name_lost) 76 self.__register_ifaces() 77 except GLib.Error: 78 pass 79 else: 80 player.connect('song-started', self.__song_started) 81 player.connect('song-ended', self.__song_ended) 82 player.connect('paused', lambda player: self.Paused()) 83 player.connect('unpaused', lambda player: self.Unpaused()) 84 self._player = player 85 86 def __on_name_lost(self, connection, name): 87 for _id in self._registered_ids: 88 connection.unregister_object(_id) 89 90 def __register_ifaces(self): 91 info = Gio.DBusNodeInfo.new_for_xml(self.__doc__) 92 for interface in info.interfaces: 93 for method in interface.methods: 94 self._method_outargs[method.name] = '({})'.format( 95 ''.join([arg.signature for arg in method.out_args])) 96 97 _id = self.conn.register_object( 98 object_path=self.PATH, 99 interface_info=interface, 100 method_call_closure=self.__on_method_call) 101 self._registered_ids.append(_id) 102 103 def __on_method_call(self, connection, sender, object_path, interface_name, 104 method_name, parameters, invocation): 105 args = list(parameters.unpack()) 106 result = getattr(self, method_name)(*args) 107 if not isinstance(result, tuple): 108 result = (result,) 109 110 out_args = self._method_outargs[method_name] 111 if out_args != '()': 112 variant = GLib.Variant(out_args, result) 113 invocation.return_value(variant) 114 else: 115 invocation.return_value(None) 116 117 @staticmethod 118 def __dict(song): 119 dict = {} 120 for key, value in (song or {}).items(): 121 value = decode_value(key, value) 122 dict[key] = dbusutils.dbus_unicode_validate(value) 123 if song: 124 dict["~uri"] = song("~uri") 125 return dict 126 127 def __song_started(self, player, song): 128 if song is not None: 129 song = self.__dict(song) 130 self.SongStarted(song) 131 132 def __song_ended(self, player, song, skipped): 133 if song is not None: 134 song = self.__dict(song) 135 self.SongEnded(song, skipped) 136 137 def Introspect(self): 138 return self.__doc__ 139 140 def SongStarted(self, song): 141 self.conn.emit_signal(None, self.PATH, 'net.sacredchao.QuodLibet', 142 'SongStarted', GLib.Variant('(a{ss})', (song,))) 143 144 def SongEnded(self, song, skipped): 145 self.conn.emit_signal(None, self.PATH, 'net.sacredchao.QuodLibet', 146 'SongEnded', GLib.Variant('(a{ss}b)', 147 (song, skipped))) 148 149 def Paused(self): 150 self.conn.emit_signal(None, self.PATH, 'net.sacredchao.QuodLibet', 151 'Paused', None) 152 153 def Unpaused(self): 154 self.conn.emit_signal(None, self.PATH, 'net.sacredchao.QuodLibet', 155 'Unpaused', None) 156 157 def GetPosition(self): 158 return self._player.get_position() 159 160 def IsPlaying(self): 161 return not self._player.paused 162 163 def CurrentSong(self): 164 return self.__dict(self._player.song) 165 166 def Next(self): 167 self._player.next() 168 169 def Previous(self): 170 self._player.previous() 171 172 def Pause(self): 173 self._player.paused = True 174 175 def Play(self): 176 self._player.play() 177 178 def PlayPause(self): 179 self._player.playpause() 180 return self._player.paused 181 182 def Query(self, text): 183 if text is not None: 184 query = Query(text, star=SongList.star) 185 if query.is_parsable: 186 return [self.__dict(s) for s in self.library.values() 187 if query.search(s)] 188 return None 189