D-BUS: Wie man seinen Videoplayer fernsteuert

Mysterium D-BUS, heute werfen wir ein bisschen Licht ins Dunkel 🙂

D-BUS: Wie man seinen Videoplayer fernsteuert

Ihr ahnt es sicher, der Sprachassistent aka Carola ist die Ursache für diese Forschungen 😉 Natürlich wollte ich auch so schon immer wissen, wofür der D-BUS eigentlich da ist und wie man den benutzen kann, da lag es nahe sich mal mit dem Thema zu befassen.

Der D-BUS

Der D-Bus ist ein genormter, aber flexibler, Kommunikationskanal für Programme, mit dessen Hilfe, Daten ausgetauscht und Funktionen in einem Programm von außen angestoßen werden können. Dazu bietet das Programm, das von außen gesteuert werden möchte, eine Schnittstelle via D-BUS Interface an.

Wer das alles auf Eurem Desktop tut und was da so alles angeboten wird, können wir mit dem QDBUSViewer sehen:

Serviceslist im DBUSViewer

Diese Dienste sind nur dort zu finden, wenn das Programm das Sie anbietet auch läuft. Es ist also keine statische Liste, die sich in irgendeiner Datei befindet, sondern live und dynamisch.

Wenn man diese Liste durchsieht, stößt man z.b. auf den Desktopmanager „Cinnamon“, den „Pulseaudio-Server“, „Firefox“ und jede Menge andere Programme und Serverdienste, die etwas anbieten. Am Beispiel von Celluloid schauen wir uns mal an, was wir da machen können, denn Celluloid ist der vorkonfigurierte Videoplayer für unseren Sprachassistenten:

Das MediaPlayerInterface von Celluloid

Blau hinterlegt sieht man den Servicenamen: org.mpris.MediaPlayer2.io.github.celluloid_player.Celluloid.instance-1

„org.mpris.MediaPlayer2“ indiziert ein MediaPlayer2 Interface, welches genormte Funktionen zur Verfügung stellt. Das bedeutet, daß alle Services, die so anfangen, das gleiche Interface bieten, also auf die gleiche Art angesprochen werden können. Man muß also keine Fernsteuerung nur für Celluloid bauen, sondern man baut eine Fernsteuerung für alle MediaApps 🙂

Ok, wir haben unseren Startpunkt für den Service gefunden, und wenden uns der rechten Seite zu:

Den Objekten

Die Bezeichnungen „Objekte“,“Methoden“ und „Eigenschaften“ kommen nicht ganz umsonst daher, denn es sind alles Begriffe aus der Objekt-Orientierten-Programmierung(OOP). Ein „Objekt“ bietet „Methoden“ an, die auf das Objekt angewendet werden können und die objekteigenen Daten behandeln, in welcher Form auch immer ( Lesen, Schreiben, Ändern, usw. ) . Dazu bietet das Objekt „Interfaces“ (Schnittstellen) an, das sind vereinfacht ausgedrückt, bekannte Listen von Methoden und Eigenschaften. Über wiederum genormte Basismethoden eines Objekts, kann man diese Interfaces auslesen und bestimmen, was man mit dem Objekt eigentlich machen kann.

Das Interface „org.freedesktop.DBus.Properties“

Das wohl meist verbreitete Interface dürfte org.freedesktop.DBus.Properties sein. Dies bietet die Methoden „Get“,“GetAll“ und „Set“ an. Was ein „Signal“ ist und wofür man das verwendet, kommt weiter unten, da es für uns nicht relevant ist.

Mit der Methode „Get“ kann ich einen Wert aus dem Objekt auslesen, mit „Set“ einen Wert überschreiben.

Damit wir damit etwas tun können, brauchen wir entweder ein Bibliothek für unsere Lieblingsprogrammiersprache, oder wir benutzen zu Anschauungszwecken den Konsolen Befehl: dbus-send

Beispiel

dbus-send \
–session \
–print-reply \
–type=method_call \
–dest=ZIELSERVICE \
OBJEKTPFAD \
INTERFACE.METHODE \
DATEN1 DATEN2 DATEN3DATENX

„–session“ bedeutet, der Service ist in der Desktopsession beheimatet , die Alternative wäre „–system“

„–print-reply“ bedeutet, daß wir sehen wollen, was der Aufruf zurück gibt, was im Fall von „Get“ sehr wichtig ist, sonst sieht man nämlich nichts 😉 Bei Set oder einem anderen Methodenaufruf müssen wir das nicht zwangsläufig sehen.

„–type=method_call“ , meint, wir rufen eine Methode auf. Hier müßte man etwas anderes angeben, wenn es um ein Signal geht. „method_call“ ist auch der Default, daher könnte man es auch weglassen.

„–dest=ZIELSERVICEZielservice ist natürlich der Service, den wir ansprechen wollen. In unserem Fall also „org.mpris.MediaPlayer2.io.github.celluloid_player.Celluloid.instance-1

Der OBJEKTPFAD ist der Name des Objekts ( aka. Klassenname ). Da es als URL geschrieben ist, bezeichnen es die Schöpfer auch als Path oder Pfad. Für uns wäre das /org/mpris/MediaPlayer2 .

Die INTERFACE.METHODE ist was wir ausführen wollen, hier Get. Es muß aber genau angegeben werden, aus welchem Interface man welche Methode anspricht. Das macht man, in dem die Methode mit einem „.“ an den Interfacenamen angefügt wird. Also: org.freedesktop.DBus.Properties.Get

Dann kommen noch die Information die Get braucht : Name des Interfaces und Eigenschaft, die man abfragen möchte. Diese Daten werden zwar als Texte angegeben, sind aber in Zielprogramm ggf. keine Zeichenketten. Daher muß man einen Type mit angeben. Das wird bei Set nachher deutlicher.

Am Ende sieht das Ganze so aus:

dbus-send –session \
–print-reply \
–type=method_call \
–dest=org.mpris.MediaPlayer2.io.github.celluloid_player.Celluloid.instance-1 \
/org/mpris/MediaPlayer2 \
org.freedesktop.DBus.Properties.Get \
string:‘org.mpris.MediaPlayer2.Player‚ \
string:‘Volume

Ich möchte also mit dem Befehl die Eigenschaft(Property) Lautstärke(Volume) aus dem Interface  „org.mpris.MediaPlayer2.Player“ haben.

In der Praxis

In der Konsole sieht das dann so aus:

[eve ~]$ dbus-send –session –print-reply –type=method_call –dest=org.mpris.MediaPlayer2.io.github.celluloid_player.Celluloid.instance-1 /org/mpris/MediaPlayer2 org.freedesktop.DBus.Properties.Get string:’org.mpris.MediaPlayer2.Player‘ string:’Volume‘
method return time=1641987861.200759 sender=:1.256 -> destination=:1.267 serial=81 reply_serial=2
variant double 1
[eve ~]$

Der graue Teil ist für Euch uninteressant.

variant double 1“ ist das Ergebnis der Anfrage. „Double“ bezeichnet hier eine doppeltgenaue Fließkommazahl. Ihr braucht Euch nur merken, daß es Bruchzahlen sind, im Gegensatz zu „Integer“ was Ganzzahlen wären. Jetzt eine Eigenart der Amis, die das normiert haben : Anstatt Komma, benutzen die einen Punkt. „0,5“ ist dann also „0.5“

Jetzt steht oben aber nur „1“, was in Wirklichkeit aber „1.00“ meint. Bei ganzen Zahlen, werden die Nachkommastellen einfach nicht angezeigt. Nun müssen wir speziell für dies Interface noch wissen, das die Lautstärke Werte von 0… x % haben kann. „1“ meint hier jetzt also „100%“ .

Spannenderweise kann man mit Set die Lautstärke auch auf über 100% setzen, was der Player auch mitmacht, aber z.b. über sein Interface nicht geht 😀

Setzen wir doch mal die Lautstärke auf 50% und lesen dies dann aus:

[eve ~]$ dbus-send –session –print-reply –type=method_call –dest=org.mpris.MediaPlayer2.io.github.celluloid_player.Celluloid.instance-1 /org/mpris/MediaPlayer2 org.freedesktop.DBus.Properties.Set string:’org.mpris.MediaPlayer2.Player‘ string:’Volume‘ variant:double:0.5
method return time=1641988564.513863 sender=:1.256 -> destination=:1.268 serial=83 reply_serial=2
[eve ~]$ dbus-send –session –print-reply –type=method_call –dest=org.mpris.MediaPlayer2.io.github.celluloid_player.Celluloid.instance-1 /org/mpris/MediaPlayer2 org.freedesktop.DBus.Properties.Get string:’org.mpris.MediaPlayer2.Player‘ string:’Volume‘
method return time=1641988571.005362 sender=:1.256 -> destination=:1.269 serial=87 reply_serial=2
variant double 0.5

Hat also geklappt. Jetzt ist das Setzen der Laustärke nicht das Einzige was man damit machen kann:

[eve ~]$ dbus-send –session –print-reply –type=method_call –dest=org.mpris.MediaPlayer2.io.github.celluloid_player.Celluloid.instance-1 /org/mpris/MediaPlayer2 org.freedesktop.DBus.Properties.Get string:’org.mpris.MediaPlayer2.Player‘ string:‘PlaybackStatus
method return time=1641988850.220984 sender=:1.256 -> destination=:1.270 serial=88 reply_serial=2
variant string „Paused“

Der Medienplayer ist also angehalten. Wie man in der Übersicht sieht:

können wir die Position im Film setzen (SetPosition), die Wiedergabegeschwindigkeit setzen (Rate), Vor- und Zurückspulen(Seek), den Player anweisen eine andere Datei zu spielen ( OpenUri ) und vieles mehr.

Die Wiedergabefunktionen sind im Interface „org.mpris.MediaPlayer2.Player“ untergebracht, deswegen sehen die Beispiele unten anders aus, als unsere Anweisungen für die Lautstärkekontrolle.

Ein paar nützliche Beispiele

Die Wiedergabe starten oder fortsetzen:

dbus-send –session –type=method_call –print-reply –dest=org.mpris.MediaPlayer2.io.github.celluloid_player.Celluloid.instance-1 /org/mpris/MediaPlayer2 org.mpris.MediaPlayer2.Player.Play

Die Wiedergabe pausieren

dbus-send –session –type=method_call –print-reply –dest=org.mpris.MediaPlayer2.io.github.celluloid_player.Celluloid.instance-1 /org/mpris/MediaPlayer2 org.mpris.MediaPlayer2.Player.Pause

Die Wiedergabe beenden ( was bei NetFlix zu einem kuriosen Umstand führt 😀 )

dbus-send –session –type=method_call –print-reply –dest=org.mpris.MediaPlayer2.io.github.celluloid_player.Celluloid.instance-1 /org/mpris/MediaPlayer2 org.mpris.MediaPlayer2.Player.Stop

In der Wiedergabeliste ein Video weiterspringen:

dbus-send –session –type=method_call –print-reply –dest=org.mpris.MediaPlayer2.io.github.celluloid_player.Celluloid.instance-1 /org/mpris/MediaPlayer2 org.mpris.MediaPlayer2.Player.Next

In der Wiedergabeliste ein Video zurückspringen:

dbus-send –session –type=method_call –print-reply –dest=org.mpris.MediaPlayer2.io.github.celluloid_player.Celluloid.instance-1 /org/mpris/MediaPlayer2 org.mpris.MediaPlayer2.Player.Previous

Wiedergabe fortsetzen oder pausieren, ist abhängig vom PlayStatus:

dbus-send –session –type=method_call –print-reply –dest=org.mpris.MediaPlayer2.io.github.celluloid_player.Celluloid.instance-1 /org/mpris/MediaPlayer2 org.mpris.MediaPlayer2.Player.PlayPause

Mit dem DBUSViewer könnt Ihr direkt ausprobieren, was in der Anwendung mit Eurem Befehl ausgelöst wird. Ein Doppelklick auf Play reicht da schon. Bei Eigenschaften muß man die rechte Maustaste klicken und dann Get oder Set aufrufen.

Was sind Signale?

Signale sind ein Weg, wie ich beachrichtigt werde, wenn sich im Programme ( Objekt ) etwas ändert.

Bleiben wir mal bei einer Videoplayerfernsteuerung.In diesem Kontext sollte die Fernsteuerung anzeigen, wo sich der Film gerade befindet, wie laut der Player eingestellt ist usw. . Damit das Programm das kann, melde ich ein Signal bei dem Service (Player) an und sage so, daß ich Updates zu dem Status X haben möchte.

Jeder der KDE Connect schon einmal als Fernsteuerung benutzt hat, kennt das Feature.

Das funktioniert natürlich nur bei laufenden Programmen, weswegen man das in der Konsole so nicht zeigen kann. Dafür gibt es den DBUS-Monitor, der zeigt Euch so etwas an:

Beispiel:

$ dbus-monitor
method call time=1641989991.978864 sender=:1.93 -> destination=org.mpris.MediaPlayer2.io.github.celluloid_player.Celluloid.instance-1 serial=325 path=/org/mpris/MediaPlayer2; interface=org.freedesktop.DBus.Properties; member=Get
string „org.mpris.MediaPlayer2.Player“
string „CanSeek“
method return time=1641989991.979023 sender=:1.274 -> destination=:1.93 serial=72 reply_serial=325
variant boolean true
method call time=1641989991.979110 sender=:1.93 -> destination=org.mpris.MediaPlayer2.io.github.celluloid_player.Celluloid.instance-1 serial=326 path=/org/mpris/MediaPlayer2; interface=org.freedesktop.DBus.Properties; member=Get
string „org.mpris.MediaPlayer2.Player“
string „Position“
error time=1641989991.979292 sender=:1.274 -> destination=:1.93 error_name=org.gtk.GDBus.UnmappedGError.Quark._celluloid_2dmpris_2derror_2dquark.Code1 reply_serial=326
string „Failed to get value of unknown property „Position““
signal time=1641989992.711142 sender=:1.274 -> destination=(null destination) serial=74 path=/org/mpris/MediaPlayer2; interface=org.freedesktop.DBus.Properties; member=PropertiesChanged
string „org.mpris.MediaPlayer2.Player“
array [
dict entry(
string „Volume“
variant double 0.5
)
]
array [
]
method call time=1641989992.711327 sender=:1.93 -> destination=org.mpris.MediaPlayer2.io.github.celluloid_player.Celluloid.instance-1 serial=327 path=/org/mpris/MediaPlayer2; interface=org.freedesktop.DBus.Properties; member=Get
string „org.mpris.MediaPlayer2.Player“
string „CanSeek“
method return time=1641989992.741618 sender=:1.274 -> destination=:1.93 serial=75 reply_serial=327
variant boolean true
method call time=1641989992.741806 sender=:1.93 -> destination=org.mpris.MediaPlayer2.io.github.celluloid_player.Celluloid.instance-1 serial=328 path=/org/mpris/MediaPlayer2; interface=org.freedesktop.DBus.Properties; member=Get
string „org.mpris.MediaPlayer2.Player“
string „Position“
error time=1641989992.741967 sender=:1.274 -> destination=:1.93 error_name=org.gtk.GDBus.UnmappedGError.Quark._celluloid_2dmpris_2derror_2dquark.Code1 reply_serial=328
string „Failed to get value of unknown property „Position““

Ja, da geht was ab auf dem D-BUS, nicht wundern wenn Ihr überwältigt seid. Aber mit dieser kleinen Einführung kann man viel mehr davon verstehen, als Ihr jetzt vielleicht noch glaubt.

Beispiel oben:

signal time=1641989992.711142 sender=:1.274 -> destination=(null destination) serial=74 path=/org/mpris/MediaPlayer2; interface=org.freedesktop.DBus.Properties; member=PropertiesChanged
string „org.mpris.MediaPlayer2.Player
array [
dict entry(
string „Volume
variant double 0.5
)
]
array [
]

Der Sender ist die ID des Prozesses, der die Nachricht losgeschickt hat. „PropertiesChanged“ meint „Eigenschaft geändert“. Der Rest dürfte für Euch jetzt kein Problem mehr sein 😉