Merge pull request #514 from ncoop/mpris-api
Add DBus callbacks to now_playing
This commit is contained in:
commit
08ccc58ee5
@ -1,5 +1,3 @@
|
|||||||
|
|
||||||
import functools
|
|
||||||
from os.path import basename
|
from os.path import basename
|
||||||
|
|
||||||
import dbus
|
import dbus
|
||||||
@ -8,28 +6,69 @@ from i3pystatus import IntervalModule, formatp
|
|||||||
from i3pystatus.core.util import TimeWrapper
|
from i3pystatus.core.util import TimeWrapper
|
||||||
|
|
||||||
|
|
||||||
|
class Dbus:
|
||||||
|
obj_dbus = "org.freedesktop.DBus"
|
||||||
|
path_dbus = "/org/freedesktop/DBus"
|
||||||
|
obj_player = "org.mpris.MediaPlayer2"
|
||||||
|
path_player = "/org/mpris/MediaPlayer2"
|
||||||
|
intf_props = obj_dbus + ".Properties"
|
||||||
|
intf_player = obj_player + ".Player"
|
||||||
|
|
||||||
|
|
||||||
class NoPlayerException(Exception):
|
class NoPlayerException(Exception):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
class NowPlaying(IntervalModule):
|
class NowPlaying(IntervalModule):
|
||||||
"""
|
"""
|
||||||
Shows currently playing track information, supports most media players
|
Shows currently playing track information. Supports media players that \
|
||||||
|
conform to the Media Player Remote Interfacing Specification.
|
||||||
|
|
||||||
* Requires python-dbus available from every distros' package manager.
|
* Requires ``python-dbus`` from your distro package manager, or \
|
||||||
|
``dbus-python`` from PyPI.
|
||||||
|
|
||||||
Left click on the module play/pauses, right click goes to the next track.
|
Left click on the module to play/pause, and right click to go to the next \
|
||||||
|
track.
|
||||||
|
|
||||||
.. rubric:: Available formatters (uses :ref:`formatp`)
|
.. rubric:: Available formatters (uses :ref:`formatp`)
|
||||||
|
|
||||||
* `{title}` — (the title of the current song)
|
* `{title}` — (the title of the current song)
|
||||||
* `{album}` — (the album of the current song, can be an empty string (e.g. for online streams))
|
* `{album}` — (the album of the current song, can be an empty string \
|
||||||
|
(e.g. for online streams))
|
||||||
* `{artist}` — (can be empty, too)
|
* `{artist}` — (can be empty, too)
|
||||||
* `{filename}` — (file name with out extension and path; empty unless title is empty)
|
* `{filename}` — (file name with out extension and path; empty unless \
|
||||||
* `{song_elapsed}` — (position in the currently playing song, uses :ref:`TimeWrapper`, default is `%m:%S`)
|
title is empty)
|
||||||
|
* `{song_elapsed}` — (position in the currently playing song, uses \
|
||||||
|
:ref:`TimeWrapper`, default is `%m:%S`)
|
||||||
* `{song_length}` — (length of the current song, same as song_elapsed)
|
* `{song_length}` — (length of the current song, same as song_elapsed)
|
||||||
* `{status}` — (play, pause, stop mapped through the `status` dictionary)
|
* `{status}` — (play, pause, stop mapped through the `status` dictionary)
|
||||||
* `{volume}` — (volume)
|
* `{volume}` — (volume)
|
||||||
|
|
||||||
|
.. rubric:: Available callbacks
|
||||||
|
|
||||||
|
* ``playpause`` — Plays if paused or stopped, otherwise pauses.
|
||||||
|
* ``next_song`` — Goes to next track in the playlist.
|
||||||
|
* ``player_command`` — Invoke a command with the `MediaPlayer2.Player` \
|
||||||
|
interface. The method name and its arguments are appended as list elements.
|
||||||
|
* ``player_prop`` — Get or set a property of the `MediaPlayer2.Player` \
|
||||||
|
interface. Append the property name to get, or the name and a value to set.
|
||||||
|
|
||||||
|
`MediaPlayer2.Player` methods and properties are documented at \
|
||||||
|
https://specifications.freedesktop.org/mpris-spec/latest/Player_Interface.html
|
||||||
|
|
||||||
|
Your player may not support the full interface.
|
||||||
|
|
||||||
|
Example module registration with callbacks:
|
||||||
|
|
||||||
|
::
|
||||||
|
|
||||||
|
status.register("now_playing",
|
||||||
|
on_leftclick=["player_command", "PlayPause"],
|
||||||
|
on_rightclick=["player_command", "Stop"],
|
||||||
|
on_middleclick=["player_prop", "Shuffle", True],
|
||||||
|
on_upscroll=["player_command", "Seek", -10000000],
|
||||||
|
on_downscroll=["player_command", "Seek", +10000000])
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
interval = 1
|
interval = 1
|
||||||
@ -69,11 +108,11 @@ class NowPlaying(IntervalModule):
|
|||||||
old_player = None
|
old_player = None
|
||||||
|
|
||||||
def find_player(self):
|
def find_player(self):
|
||||||
obj = dbus.SessionBus().get_object("org.freedesktop.DBus", "/org/freedesktop/DBus")
|
obj = dbus.SessionBus().get_object(Dbus.obj_dbus, Dbus.path_dbus)
|
||||||
|
|
||||||
def get_players(methodname):
|
def get_players(methodname):
|
||||||
method = obj.get_dbus_method(methodname, 'org.freedesktop.DBus')
|
method = obj.get_dbus_method(methodname, Dbus.obj_dbus)
|
||||||
return [a for a in method() if a.startswith("org.mpris.MediaPlayer2.")]
|
return [a for a in method() if a.startswith(Dbus.obj_player + ".")]
|
||||||
|
|
||||||
players = get_players('ListNames')
|
players = get_players('ListNames')
|
||||||
if not players:
|
if not players:
|
||||||
@ -87,45 +126,51 @@ class NowPlaying(IntervalModule):
|
|||||||
|
|
||||||
def get_player(self):
|
def get_player(self):
|
||||||
if self.player:
|
if self.player:
|
||||||
player = "org.mpris.MediaPlayer2." + self.player
|
player = Dbus.obj_player + "." + self.player
|
||||||
try:
|
try:
|
||||||
return dbus.SessionBus().get_object(player, "/org/mpris/MediaPlayer2")
|
return dbus.SessionBus().get_object(player, Dbus.path_player)
|
||||||
except dbus.exceptions.DBusException:
|
except dbus.exceptions.DBusException:
|
||||||
raise NoPlayerException()
|
raise NoPlayerException()
|
||||||
else:
|
else:
|
||||||
player = self.find_player()
|
player = self.find_player()
|
||||||
return dbus.SessionBus().get_object(player, "/org/mpris/MediaPlayer2")
|
return dbus.SessionBus().get_object(player, Dbus.path_player)
|
||||||
|
|
||||||
def run(self):
|
def run(self):
|
||||||
try:
|
try:
|
||||||
player = self.get_player()
|
player = self.get_player()
|
||||||
properties = dbus.Interface(player, "org.freedesktop.DBus.Properties")
|
properties = dbus.Interface(player, Dbus.intf_props)
|
||||||
|
|
||||||
def get_prop(name, default=None):
|
def get_prop(name, default=None):
|
||||||
try:
|
try:
|
||||||
return properties.Get("org.mpris.MediaPlayer2.Player", name)
|
return properties.Get(Dbus.intf_player, name)
|
||||||
except dbus.exceptions.DBusException:
|
except dbus.exceptions.DBusException:
|
||||||
return default
|
return default
|
||||||
|
|
||||||
currentsong = get_prop("Metadata")
|
currentsong = get_prop("Metadata")
|
||||||
|
|
||||||
fdict = {
|
fdict = {
|
||||||
"status": self.status[self.statusmap[get_prop("PlaybackStatus")]],
|
"status": self.status[self.statusmap[
|
||||||
"len": 0, # TODO: Use optional(!) TrackList interface for this to gain 100 % mpd<->now_playing compat
|
get_prop("PlaybackStatus")]],
|
||||||
|
# TODO: Use optional(!) TrackList interface for this to
|
||||||
|
# gain 100 % mpd<->now_playing compat
|
||||||
|
"len": 0,
|
||||||
"pos": 0,
|
"pos": 0,
|
||||||
"volume": int(get_prop("Volume", 0) * 100),
|
"volume": int(get_prop("Volume", 0) * 100),
|
||||||
|
|
||||||
"title": currentsong.get("xesam:title", ""),
|
"title": currentsong.get("xesam:title", ""),
|
||||||
"album": currentsong.get("xesam:album", ""),
|
"album": currentsong.get("xesam:album", ""),
|
||||||
"artist": ", ".join(currentsong.get("xesam:artist", "")),
|
"artist": ", ".join(currentsong.get("xesam:artist", "")),
|
||||||
"song_length": TimeWrapper((currentsong.get("mpris:length") or 0) / 1000 ** 2),
|
"oong_length": TimeWrapper((currentsong.get("mpris:length") or
|
||||||
"song_elapsed": TimeWrapper((get_prop("Position") or 0) / 1000 ** 2),
|
0) / 1000 ** 2),
|
||||||
|
"song_elapsed": TimeWrapper((get_prop("Position") or 0) /
|
||||||
|
1000 ** 2),
|
||||||
"filename": "",
|
"filename": "",
|
||||||
}
|
}
|
||||||
|
|
||||||
if not fdict["title"]:
|
if not fdict["title"]:
|
||||||
fdict["filename"] = '.'.join(
|
fdict["filename"] = '.'.join(
|
||||||
basename((currentsong.get("xesam:url") or "")).split('.')[:-1])
|
basename((currentsong.get("xesam:url") or "")).
|
||||||
|
split('.')[:-1])
|
||||||
|
|
||||||
self.data = fdict
|
self.data = fdict
|
||||||
self.output = {
|
self.output = {
|
||||||
@ -159,7 +204,7 @@ class NowPlaying(IntervalModule):
|
|||||||
|
|
||||||
def playpause(self):
|
def playpause(self):
|
||||||
try:
|
try:
|
||||||
dbus.Interface(self.get_player(), "org.mpris.MediaPlayer2.Player").PlayPause()
|
dbus.Interface(self.get_player(), Dbus.intf_player).PlayPause()
|
||||||
except NoPlayerException:
|
except NoPlayerException:
|
||||||
return
|
return
|
||||||
except dbus.exceptions.DBusException:
|
except dbus.exceptions.DBusException:
|
||||||
@ -167,7 +212,29 @@ class NowPlaying(IntervalModule):
|
|||||||
|
|
||||||
def next_song(self):
|
def next_song(self):
|
||||||
try:
|
try:
|
||||||
dbus.Interface(self.get_player(), "org.mpris.MediaPlayer2.Player").Next()
|
dbus.Interface(self.get_player(), Dbus.intf_player).Next()
|
||||||
|
except NoPlayerException:
|
||||||
|
return
|
||||||
|
except dbus.exceptions.DBusException:
|
||||||
|
return
|
||||||
|
|
||||||
|
def player_command(self, command, *args):
|
||||||
|
try:
|
||||||
|
interface = dbus.Interface(self.get_player(), Dbus.intf_player)
|
||||||
|
getattr(interface, command)(*args)
|
||||||
|
except NoPlayerException:
|
||||||
|
return
|
||||||
|
except dbus.exceptions.DBusException:
|
||||||
|
return
|
||||||
|
|
||||||
|
def player_prop(self, name, value=None):
|
||||||
|
try:
|
||||||
|
properties = dbus.Interface(self.get_player(), Dbus.intf_props)
|
||||||
|
# None/null/nil implies get because it's not a valid DBus datatype.
|
||||||
|
if value is None:
|
||||||
|
return properties.Get(Dbus.intf_player, name)
|
||||||
|
else:
|
||||||
|
properties.Set(Dbus.intf_player, name, value)
|
||||||
except NoPlayerException:
|
except NoPlayerException:
|
||||||
return
|
return
|
||||||
except dbus.exceptions.DBusException:
|
except dbus.exceptions.DBusException:
|
||||||
|
Loading…
Reference in New Issue
Block a user