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 😉