MPRIS: Got everything necessary working. (I think.)
This commit is contained in:
parent
f23530628c
commit
1f149a25a3
10 changed files with 333 additions and 58 deletions
22
wobuzz/mpris/dbus_introspectable.py
Normal file
22
wobuzz/mpris/dbus_introspectable.py
Normal file
|
@ -0,0 +1,22 @@
|
|||
#!/usr/bin/python3
|
||||
|
||||
from .utils import *
|
||||
|
||||
|
||||
class DBUSIntrospectable(DBusInterface):
|
||||
def __init__(self, server, interface: str):
|
||||
self.server = server
|
||||
self.interface = interface
|
||||
|
||||
file = open(server.app.utils.wobuzz_location + "/mpris/introspection.xml")
|
||||
self.introspection_xml = file.read()
|
||||
file.close()
|
||||
|
||||
def get_all(self):
|
||||
body = ({},)
|
||||
return body
|
||||
|
||||
# ======== Methods ========
|
||||
|
||||
def Introspect(self, msg):
|
||||
return new_method_return(msg, "s", (self.introspection_xml,))
|
|
@ -1,9 +1,6 @@
|
|||
#!/usr/bin/python3
|
||||
|
||||
from jeepney import Header, MessageType, Endianness, MessageFlag, new_method_return
|
||||
from jeepney.bus_messages import message_bus
|
||||
from jeepney.io.blocking import open_dbus_connection
|
||||
from jeepney.wrappers import new_header
|
||||
from jeepney import new_signal
|
||||
|
||||
from .utils import *
|
||||
|
||||
|
@ -13,25 +10,31 @@ class DBusProperties(DBusInterface):
|
|||
body = ({},)
|
||||
return body
|
||||
|
||||
def properties_changed(self, interface: str):
|
||||
def properties_changed(self, interface: str, prop_name: str):
|
||||
body = None
|
||||
|
||||
if interface == MPRIS_ROOT_INTERFACE:
|
||||
body = (MPRIS_ROOT_INTERFACE,) + self.server.root_interface.get_all()
|
||||
prop = getattr(self.server.root_interface, prop_name)(None)
|
||||
|
||||
body = (MPRIS_ROOT_INTERFACE,) + ({prop_name: prop}, [])
|
||||
|
||||
elif interface == MPRIS_PLAYER_INTERFACE:
|
||||
body = (MPRIS_PLAYER_INTERFACE,) + self.server.player_interface.get_all()
|
||||
prop = getattr(self.server.player_interface, prop_name)(None)
|
||||
|
||||
signature = "" if body is None else "sa{sv}"
|
||||
body = (MPRIS_PLAYER_INTERFACE,) + ({prop_name: prop}, [])
|
||||
|
||||
signature = "" if body is None else "sa{sv}as"
|
||||
|
||||
msg = new_signal(
|
||||
self.server.bus_address.with_interface("org.freedesktop.DBus"),
|
||||
self.server.bus_address.with_interface(PROPERTIES_INTERFACE),
|
||||
"PropertiesChanged",
|
||||
signature,
|
||||
body
|
||||
)
|
||||
self.server.bus.send(msg)
|
||||
|
||||
# ======== Methods ========
|
||||
|
||||
def Get(self, msg: Message):
|
||||
interface_name = msg.body[0]
|
||||
|
||||
|
|
76
wobuzz/mpris/introspection.xml
Normal file
76
wobuzz/mpris/introspection.xml
Normal file
|
@ -0,0 +1,76 @@
|
|||
<node>
|
||||
<interface name="org.freedesktop.DBus.Introspectable">
|
||||
<method name="Introspect">
|
||||
<arg name="data" type="s" direction="out"/>
|
||||
</method>
|
||||
</interface>
|
||||
<interface name="org.freedesktop.DBus.Properties">
|
||||
<method name="Get">
|
||||
<arg name="interface_name" type="s" direction="in"/>
|
||||
<arg name="property_name" type="s" direction="in"/>
|
||||
<arg name="value" type="v" direction="out"/>
|
||||
</method>
|
||||
<method name="GetAll">
|
||||
<arg name="interface_name" type="s" direction="in"/>
|
||||
<arg name="properties" type="a{sv}" direction="out"/>
|
||||
</method>
|
||||
<!--
|
||||
<method name="Set">
|
||||
<arg name="interface_name" type="s" direction="in"/>
|
||||
<arg name="property_name" type="s" direction="in"/>
|
||||
<arg name="value" type="v" direction="in"/>
|
||||
</method>
|
||||
-->
|
||||
<signal name="PropertiesChanged">
|
||||
<arg name="interface_name" type="s"/>
|
||||
<arg name="changed_properties" type="a{sv}"/>
|
||||
<arg name="invalidated_properties" type="as"/>
|
||||
</signal>
|
||||
</interface>
|
||||
<interface name="org.mpris.MediaPlayer2">
|
||||
<method name="Raise"/>
|
||||
<method name="Quit"/>
|
||||
<property name="CanQuit" access="read" type="b"/>
|
||||
<property name="Fullscreen" access="read" type="b"/>
|
||||
<property name="CanSetFullscreen" access="read" type="b"/>
|
||||
<property name="CanRaise" access="read" type="b"/>
|
||||
<property name="HasTrackList" access="read" type="b"/>
|
||||
<property name="Identity" access="read" type="s"/>
|
||||
<property name="DesktopEntry" access="read" type="s"/>
|
||||
<property name="SupportedUriSchemes" access="read" type="as"/>
|
||||
<property name="SupportedMimeTypes" access="read" type="as"/>
|
||||
</interface>
|
||||
<interface name="org.mpris.MediaPlayer2.Player">
|
||||
<method name="Next"/>
|
||||
<method name="Previous"/>
|
||||
<method name="Pause"/>
|
||||
<method name="PlayPause"/>
|
||||
<method name="Stop"/>
|
||||
<method name="Play"/>
|
||||
<method name="Seek">
|
||||
<arg name="Offset" type="x" direction="in"/>
|
||||
</method>
|
||||
<method name="SetPosition">
|
||||
<arg name="TrackId" type="o" direction="in"/>
|
||||
<arg name="Position" type="x" direction="in"/>
|
||||
</method>
|
||||
<method name="OpenUri">
|
||||
<arg name="Uri" type="s" direction="in"/>
|
||||
</method>
|
||||
<property name="PlaybackStatus" access="read" type="s"/>
|
||||
<property name="LoopStatus" access="read" type="s"/>
|
||||
<property name="Rate" access="read" type="d"/>
|
||||
<property name="Shuffle" access="read" type="b"/>
|
||||
<property name="Metadata" access="read" type="a{sv}"/>
|
||||
<property name="Volume" access="read" type="d"/>
|
||||
<property name="Position" access="read" type="x"/>
|
||||
<property name="MinimumRate" access="read" type="d"/>
|
||||
<property name="MaximumRate" access="read" type="d"/>
|
||||
<property name="CanGoNext" access="read" type="b"/>
|
||||
<property name="CanGoPrevious" access="read" type="b"/>
|
||||
<property name="CanPlay" access="read" type="b"/>
|
||||
<property name="CanPause" access="read" type="b"/>
|
||||
<property name="CanSeek" access="read" type="b"/>
|
||||
<property name="CanControl" access="read" type="b"/>
|
||||
</interface>
|
||||
</node>
|
|
@ -1,7 +1,5 @@
|
|||
#!/usr/bin/python3
|
||||
|
||||
from jeepney import new_method_return
|
||||
|
||||
from .utils import *
|
||||
|
||||
|
||||
|
@ -9,26 +7,116 @@ class MPRISPlayer(DBusInterface):
|
|||
def __init__(self, server, interface):
|
||||
super().__init__(server, interface)
|
||||
|
||||
self.playback_status = "Stopped"
|
||||
self.loop_status = "None"
|
||||
self.shuffle = False
|
||||
self.metadata = {
|
||||
"mpris:trackid": ("o", "/org/mpris/MediaPlayer2/murx"), # random junk, no functionality
|
||||
"mpris:trackid": ("o", "/org/bla/gubber"), # random junk, no functionality
|
||||
"mpris:length": ("x", 0),
|
||||
"xesam:title": ("s", "Huggenburgl")
|
||||
}
|
||||
self.volume = 1.0
|
||||
|
||||
def get_all(self):
|
||||
body = ({
|
||||
"PlaybackStatus": ("s", self.playback_status),
|
||||
"LoopStatus": ("s", self.loop_status),
|
||||
"Rate": ("d", 1.0),
|
||||
"Shuffle": ("b", self.shuffle),
|
||||
"Metadata": ("a{sv}", self.metadata),
|
||||
"Volume": ("d", self.volume),
|
||||
"Position": ("x", self.server.app.player.get_progress() * 1000), # milliseconds to microseconds
|
||||
"MinimumRate": ("d", 1.0),
|
||||
"MaximumRate": ("d", 1.0),
|
||||
"CanGoNext": ("b", True),
|
||||
"CanGoPrevious": ("b", True),
|
||||
"CanPlay": ("b", True),
|
||||
"Metadata": ("a{sv}", self.metadata)
|
||||
"CanPause": ("b", True),
|
||||
"CanSeek": ("b", True),
|
||||
"CanControl": ("b", True)
|
||||
},)
|
||||
|
||||
return body
|
||||
|
||||
def Play(self, msg: Message):
|
||||
print("Play!")
|
||||
# ======== Methods ========
|
||||
|
||||
def Next(self, msg: Message):
|
||||
self.server.app.player.next_track()
|
||||
|
||||
def Previous(self, msg: Message):
|
||||
self.server.app.player.previous_track()
|
||||
|
||||
def Pause(self, msg: Message):
|
||||
return lambda: self.server.app.player.toggle_playing()
|
||||
|
||||
def PlayPause(self, msg: Message):
|
||||
print("Play/Pause!")
|
||||
return lambda: self.server.app.player.toggle_playing()
|
||||
|
||||
def Stop(self, msg: Message):
|
||||
self.server.app.player.stop()
|
||||
|
||||
def Play(self, msg: Message):
|
||||
return lambda: self.server.app.player.toggle_playing()
|
||||
|
||||
def Seek(self, msg: Message):
|
||||
seek_forward = msg.body[0] // 1000 # microseconds to milliseconds
|
||||
new_position = self.server.app.player.get_progress() + seek_forward
|
||||
self.server.app.player.seek(new_position)
|
||||
|
||||
def SetPosition(self, msg: Message):
|
||||
trackid = msg.body[0]
|
||||
position = msg.body[1] // 1000 # microseconds to milliseconds
|
||||
|
||||
self.server.app.player.seek(position)
|
||||
|
||||
def OpenUri(self, msg: Message):
|
||||
pass
|
||||
|
||||
# ======== Properties ========
|
||||
|
||||
def PlaybackStatus(self, msg: Message):
|
||||
return "s", self.playback_status
|
||||
|
||||
def LoopStatus(self, msg: Message):
|
||||
return "s", self.loop_status
|
||||
|
||||
def Rate(self, msg: Message):
|
||||
return "d", 1.0
|
||||
|
||||
def Shuffle(self, msg: Message):
|
||||
return "b", self.shuffle
|
||||
|
||||
def Metadata(self, msg: Message):
|
||||
body = (self.metadata,)
|
||||
return "a{sv}", self.metadata
|
||||
|
||||
def Volume(self, msg: Message):
|
||||
return "d", self.volume
|
||||
|
||||
def Position(self, msg: Message):
|
||||
return "x", self.server.app.player.get_progress() * 1000 # milliseconds to microseconds
|
||||
|
||||
def MinimumRate(self, msg: Message):
|
||||
return "d", 1.0
|
||||
|
||||
def MaximumRate(self, msg: Message):
|
||||
return "d", 1.0
|
||||
|
||||
def CanGoNext(self, msg: Message):
|
||||
return "b", True
|
||||
|
||||
def CanGoPrevious(self, msg: Message):
|
||||
return "b", True
|
||||
|
||||
def CanPlay(self, msg: Message):
|
||||
return "b", True
|
||||
|
||||
def CanPause(self, msg: Message):
|
||||
return "b", True
|
||||
|
||||
def CanSeek(self, msg: Message):
|
||||
return "b", True
|
||||
|
||||
def CanControl(self, msg: Message):
|
||||
return "b", True
|
||||
|
||||
|
||||
return new_method_return(msg, "a{sv}", body)
|
||||
|
|
|
@ -7,10 +7,52 @@ class MPRISRoot(DBusInterface):
|
|||
def get_all(self):
|
||||
body = ({
|
||||
"CanQuit": ("b", True),
|
||||
"CanRaise": ("b", True)
|
||||
"Fullscreen": ("b", False),
|
||||
"CanSetFullscreen": ("b", False),
|
||||
"CanRaise": ("b", True),
|
||||
"HasTrackList": ("b", False),
|
||||
"Identity": ("s", "Wobuzz"),
|
||||
"DesktopEntry": ("s", "wobuzz"),
|
||||
"SupportedUriSchemes": ("as", ["file"]),
|
||||
"SupportedMimeTypes": ("as", ["audio/mpeg"])
|
||||
},)
|
||||
|
||||
return body
|
||||
|
||||
# ======== Methods ========
|
||||
|
||||
def Raise(self, msg):
|
||||
print("Raise!")
|
||||
self.server.app.gui.window.activateWindow()
|
||||
self.server.app.gui.window.showMaximized()
|
||||
|
||||
def Quit(self, msg):
|
||||
self.server.app.gui.window.close()
|
||||
|
||||
# ======== Properties ========
|
||||
|
||||
def CanQuit(self, msg: Message):
|
||||
return "b", True
|
||||
|
||||
def Fullscreen(self, msg: Message):
|
||||
return "b", False
|
||||
|
||||
def CanSetFullscreen(self, msg: Message):
|
||||
return "b", False
|
||||
|
||||
def CanRaise(self, msg: Message):
|
||||
return "b", True
|
||||
|
||||
def HasTrackList(self, msg: Message):
|
||||
return "b", False
|
||||
|
||||
def Identity(self, msg: Message):
|
||||
return "s", "Wobuzz"
|
||||
|
||||
def DesktopEntry(self, msg: Message):
|
||||
return "s", "wobuzz"
|
||||
|
||||
def SupportedUriSchemes(self, msg: Message):
|
||||
return "as", ["file"]
|
||||
|
||||
def SupportedMimeTypes(self, msg: Message):
|
||||
return "as", ["audio/mpeg"]
|
||||
|
|
|
@ -1,11 +1,13 @@
|
|||
#!/usr/bin/python3
|
||||
|
||||
from threading import Thread
|
||||
from jeepney import DBusAddress
|
||||
from jeepney.bus_messages import message_bus
|
||||
from jeepney.io.blocking import DBusConnection, open_dbus_connection
|
||||
|
||||
from .utils import *
|
||||
from .dbus_properties import DBusProperties
|
||||
from .dbus_introspectable import DBUSIntrospectable
|
||||
from .mpris_root import MPRISRoot
|
||||
from .mpris_player import MPRISPlayer
|
||||
|
||||
|
@ -15,6 +17,7 @@ class MPRISServer:
|
|||
self.app = app
|
||||
|
||||
self.properties_interface = DBusProperties(self, PROPERTIES_INTERFACE)
|
||||
self.introspection_interface = DBUSIntrospectable(self, INTROSPECTION_INTERFACE)
|
||||
self.root_interface = MPRISRoot(self, MPRIS_ROOT_INTERFACE)
|
||||
self.player_interface = MPRISPlayer(self, MPRIS_PLAYER_INTERFACE)
|
||||
|
||||
|
@ -51,6 +54,9 @@ class MPRISServer:
|
|||
if interface == PROPERTIES_INTERFACE:
|
||||
self.properties_interface.handle_message(msg)
|
||||
|
||||
elif interface == INTROSPECTION_INTERFACE:
|
||||
self.introspection_interface.handle_message(msg)
|
||||
|
||||
elif interface == MPRIS_ROOT_INTERFACE:
|
||||
self.root_interface.handle_message(msg)
|
||||
|
||||
|
@ -58,11 +64,30 @@ class MPRISServer:
|
|||
self.player_interface.handle_message(msg)
|
||||
|
||||
def on_playstate_update(self):
|
||||
current_playlist = self.app.player.current_playlist
|
||||
player = self.app.player
|
||||
current_playlist = player.current_playlist
|
||||
|
||||
if current_playlist is not None and current_playlist.current_track is not None:
|
||||
current_track = current_playlist.current_track
|
||||
|
||||
self.player_interface.metadata["xesam:title"] = ("s", current_track.metadata.title)
|
||||
art_path = self.app.utils.tmp_path + "/cover_cache/" + current_track.metadata.path.split("/")[-1][:-4]
|
||||
|
||||
self.properties_interface.properties_changed(MPRIS_PLAYER_INTERFACE)
|
||||
# metadata milli to microseconds --↓
|
||||
self.player_interface.metadata["mpris:length"] = ("x", current_track.duration * 1000)
|
||||
self.player_interface.metadata["mpris:artUrl"] = ("s", "file://" + art_path)
|
||||
self.player_interface.metadata["xesam:title"] = ("s", current_track.metadata.title)
|
||||
self.properties_interface.properties_changed(MPRIS_PLAYER_INTERFACE, "Metadata")
|
||||
|
||||
if player.playing:
|
||||
if player.paused:
|
||||
playback_status = "Paused"
|
||||
|
||||
else:
|
||||
playback_status = "Playing"
|
||||
|
||||
else:
|
||||
playback_status = "Stopped"
|
||||
|
||||
self.player_interface.playback_status = playback_status
|
||||
|
||||
self.properties_interface.properties_changed(MPRIS_PLAYER_INTERFACE, "PlaybackStatus")
|
||||
|
|
|
@ -1,11 +1,11 @@
|
|||
#!/usr/bin/python3
|
||||
|
||||
from jeepney import DBusAddress, Message, MessageType, HeaderFields, new_error, new_signal
|
||||
|
||||
from jeepney import Message, MessageType, HeaderFields, new_error, new_method_return
|
||||
|
||||
SERVICE_NAME = "org.mpris.MediaPlayer2.wobuzz"
|
||||
OBJECT_PATH = "/org/mpris/MediaPlayer2"
|
||||
PROPERTIES_INTERFACE = "org.freedesktop.DBus.Properties"
|
||||
INTROSPECTION_INTERFACE = "org.freedesktop.DBus.Introspectable"
|
||||
MPRIS_ROOT_INTERFACE = "org.mpris.MediaPlayer2"
|
||||
MPRIS_PLAYER_INTERFACE = "org.mpris.MediaPlayer2.Player"
|
||||
|
||||
|
@ -46,18 +46,9 @@ class DBusInterface:
|
|||
self.server = server
|
||||
self.interface = interface
|
||||
|
||||
def __setattr__(self, key: str, value) -> None:
|
||||
super().__setattr__(key, value)
|
||||
|
||||
if not key[0].isupper() and not callable(value) and hasattr(self, key.title()):
|
||||
getter = getattr(self, key.title())
|
||||
|
||||
if callable(getter):
|
||||
if hasattr(self.server, "bus"):
|
||||
self.server.properties_interface.properties_changed(self.interface)
|
||||
|
||||
def handle_message(self, msg: Message):
|
||||
return_msg = None
|
||||
post_action = None # function that gets called after return_msg was set
|
||||
|
||||
match msg.header.message_type:
|
||||
case MessageType.method_call:
|
||||
|
@ -67,7 +58,17 @@ class DBusInterface:
|
|||
method = getattr(self, msg.header.fields[HeaderFields.member])
|
||||
|
||||
if callable(method):
|
||||
return_msg = method(msg)
|
||||
method_data = method(msg)
|
||||
|
||||
if isinstance(method_data, tuple):
|
||||
post_action, return_msg = method_data
|
||||
|
||||
else:
|
||||
if callable(method_data):
|
||||
post_action = method_data
|
||||
|
||||
else:
|
||||
return_msg = method_data
|
||||
|
||||
else:
|
||||
return_msg = new_error(msg, *DBusErrors.unknownMethod(method_name))
|
||||
|
@ -75,8 +76,13 @@ class DBusInterface:
|
|||
else:
|
||||
return_msg = new_error(msg, *DBusErrors.unknownMethod(method_name))
|
||||
|
||||
if return_msg is not None:
|
||||
self.server.bus.send_message(return_msg)
|
||||
if return_msg is None:
|
||||
return_msg = new_method_return(msg)
|
||||
|
||||
self.server.bus.send_message(return_msg)
|
||||
|
||||
if not post_action is None:
|
||||
post_action()
|
||||
|
||||
def get(self, msg: Message):
|
||||
prop_name: str = msg.body[1]
|
||||
|
@ -85,7 +91,13 @@ class DBusInterface:
|
|||
prop = getattr(self, prop_name)
|
||||
|
||||
if callable(prop):
|
||||
return prop()
|
||||
prop_value = prop(msg)
|
||||
signature = prop_value[0]
|
||||
value = prop_value[1]
|
||||
|
||||
return_msg = new_method_return(msg, "v", (prop_value,))
|
||||
|
||||
return return_msg
|
||||
|
||||
return new_error(msg, *DBusErrors.unknownProperty(prop_name))
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue