Desktop-Notification von Systemservice mittels dbus
Review: Dieser Artikel ist für das Review freigegeben.
|
Getestet: Dieser Hinweis soll dir dabei helfen, zu entscheiden, ob dieser Artikel auf deinem System funktionieren wird oder nicht. Solltest du feststellen, dass dieser Artikel bei einer ungetestet Version funktioniert, kannst du das gerne hier korrigieren oder im Forum anmerken.
|
Problemstellung
Startet man als User einen Prozess auf der Shell und schickt ihn in den Hintergrund und möchte über das Fertigwerden benachrichtigt werden, löst man das am elegantesten über eine Desktop-Notification. Das sind diese "Bubbles" mit Textinhalt, die bei Desktopenvironments (Gnome, KDE, XFCE) gelegentlich aufpoppen um den User zu Benachrichtigen. Damit dies grundsätzlich funktioniert, muss in der graphischen Sitzung ein sogenannter notification-daemon laufen. Der wird bei Gnome, XFCE oder KDE automatisch gestartet, bei Windowmanagern (openbox, fvwm, fluxbox,...) muss man sich selber um den Start eines Notification-Daemon kümmern.
Damit man diesem Notification-Daemon eine Nachricht senden kann, kann man z.B. das Programm notify-send verwenden. Dies funktioniert aber nur, wenn das Programm in der graphischen Sitzung des Users - als dieser User - gestartet wird. Als root, oder aus einem Hintergrundprozess (wie z.B. mit systemd oder cron gestartet) klappt das nicht. Damit man von einem Hintergrundprozess aus an ALLE graphisch eingeloggten User auf deren Desktop eine Nachricht schicken kann, gibt es eine Lösung mit dbus, welche dieser Artikel behandelt.
Benachrichtigung mit notify-send und dbus-send
So gut wie alle Lösungen für die Benachrichtigung am Desktop des Users von einem System-Dienst aus laufen nach einem ähnlichen Schema ab. Es werden die aktuell eingeloggten User gesucht, dann deren graphischen Sessions und dann zu diesen Sessions die Variablen DISPLAY und $DBUS_SESSION_BUS_ADDRESS. Dann werden in einer Schleife durch die graphischen Sessions mit sudo oder su die Variabeln exportiert und als der jeweilige User notify-send mit der Meldung ausgeführt.
Die Methoden um die graphischen Sessions ausfindig zu machen und aus den Environments die beiden Variablen zu extrahieren hängen stark vom Loginmanager ab. Wird logind, consolekit oder anderes verwendet, muss man immer anders die angemeldeten User ermitteln, was den Codeaufwand enorm erhöht und verkompliziert.
Diese Lösungen funktionieren prinzipiell, sind aber immer wieder fehleranfällig und ungenau.
Stabile Lösung zur Benachrichtigung mit dbus
Prinzip
dbus läuft einerseits mit einem systemweiten Systembus und andererseits läuft in jeder graphischen Sitzung ein weiterer dbus-Service nur für diesen User und diese Sitzung. Über diese Busse können nun Programme untereinander kommunizieren.
Ein Service oder Skript wird z. B. mit systemd oder cron als root (oder ein anderer Systemuser) gestartet. Dieses Programm sendet dann eine Message an den System-Bus von dbus. In der graphischen Session lauscht ein Programm ("Lauschdienst") am Systembus von dbus ob eine Message ankommt. Der Lauschdienst leitet diese Message dann an den Sessionbus von dbus an die Schnittstelle des Notification-Daemons weiter. Der Notification-Daemon stellt dann die Benachrichtigung am Desktop dar.
Notification-Daemon
Desktop-Notifications benötigen eine Notification-Daemon und funktionieren - wie der Name schon sagt - nur am Desktop. Also in Gnome, KDE oder in einem Windowmanager. Gnome bringt seinen eigenen Notification-Daemon mit, Xfce4 ebenfalls. Verwendet man openbox oder fvwm, so muss man einen separaten Notification-Daemon (z. B. notify-osd oder xfce4-notifyd ) installieren und starten. Dieser Notification-Daemon nimmt über dbus Meldungen entgegen und stellt sie am Desktop in einer "Bubble" dar.
Notification-Daemon testen
Der notification-Daemon kann über notify-send oder dbus-send aus der betreffenden Session und nur für diese Session direkt angesprochen werden. Zum Test einfach einmal in einem Terminalfenster folgenden Befehl ausführen:
user@debian:~$ notify-send Testmessage Body
(dazu ist das Paket libnotify-bin notwendig).
Umsetzung
Die Dokumentation auf freedesktop.org ist zwar umfangreich, aber geht - so wie die meisten anderen Dokumentationen im Netz, die ich gefunden habe, nicht weit genug oder viel zu detailliert in die Tiefe. Das dürfte auch der Grund sein, warum ich bisher nirgendwo Codebeispiele oder Todos gefunden habe, die so eine Notification beschreiben und die System-Bus und Session-Bus nutzen. Den entscheidenden Hinweis fand ich im Arch-Linux Forum.
"Lauschdienst" für die graphische Sitzung
Der Lauschdienst ist in diesem Beispiel in Python implementiert und wird über xdg-autostart mit dem Start der graphischen Sitzung gestartet.
Dazu legt man eine Datei für das Skript in /usr/local/bin an und macht sie ausführbar.
root@debian:~# editor /usr/local/bin/desktop-notifyd.py
#!/usr/bin/env python
import dbus, dbus.glib
import gtk
import datetime
systembus = dbus.SystemBus()
sessionbus = dbus.SessionBus()
notifications = dbus.Interface(sessionbus.get_object('org.freedesktop.Notifications',
'/org/freedesktop/Notifications'), 'org.freedesktop.Notifications')
def notify_updates(*args):
"""
format is defined here: https://developer.gnome.org/notification-spec/
convert arguments from dbus.String to normal string with str() to avoid errors
"""
icon = "insert-image" # Siehe https://standards.freedesktop.org/icon-naming-spec/icon-naming-spec-latest.html#names
notifications.Notify("Notification", 0, icon, str(args[0]),
str(' '.join(args[1:])),
"", {}, 0)
systembus.add_signal_receiver(notify_updates, 'Notification', 'my.desktop')
gtk.main()
Die Erkennung des Icons anhand des Default-Themes von gtk funktioniert leider nicht. gtk.icon_theme_get_default scheint in python nicht mehr zu existieren. Kennt jemand eine alternative Funktion? Das Icon wird in diesem Fall im "Lauschdienst" in die Message eingefügt. Man kann natürlich auch das Icon über die Notification aus dem Systemdienst oder Skript mitschicken. Dann müsste der Lauschdienst anders programmiert werden.
Der Aufbau dieser Message in notifications.Notify findet man hier Siehe unter Basic-Design
root@debian:~# chmod +x /usr/local/bin/desktop-notifyd.py
Damit dieses Skript auch beim Login ausgeführt wird, muss man noch eine weitere Datei anlegen
root@debian:~# editor /etc/xdg/autostart/Desktop-notification.desktop
[Desktop Entry]
Version=1.0
Name=Notifications background-services
Name[de]=Benachrichtigungen für Hintergrund-Dienste und Skripte
Exec=/usr/local/bin/desktop-notifyd.py
TryExec=/usr/local/bin/desktop-notifyd.py
Icon=xfce4-notifyd
Terminal=false
StartupNotify=false
Type=Application
Categories=GTK;Settings;DesktopSettings;
Senden einer Benachrichtigung an den Systembus
Es gibt mehrere Möglichkeiten an den Systembus eine Benachrichtigung zu schicken. Im Folgenden werden einige dieser Möglichkeiten behandelt, die je nach Einsatzzweck zu verwenden sind.
von der Shell
Startet man per cron ein Shell-Skript, dann ist es am einfachsten, dbus-send zu vwerwenden. Dazu ist an geeigneter Stelle im Skript folgende Zeile einzufügen.
root@debian:~# /usr/bin/dbus-send --system /my/desktop my.desktop.Notification string:test string:bla
Damit wird sofort eine Desktop-Notificakon mit der Überschrift "test" und dem Body "bla" angezeigt. Es werden in der Message zwei Texte übergeben, die als string codiert werden. Man kann hier auch andere Typen festlegen (Verschiedene Typen auf freedesktop.org) Die Anzahl der übergebenen Message-Argumente hängt vom User-Skript von oben ab, wie die Argumente weiterverarbeitet werden. In diesem Beispiel wird der erste String als "Zusammenfassung" oder "Überschrift" für die Benachrichtigung verwendet, alle weiteren Strings werden zusammengefasst und als Body der Benachrichtigung dargestellt. Das Icon wird der Benachrichtigung wird in diesem Beispiel vom "Lauschdienst" eingesetzt. Man kann den Lauschdienst natürlich auch so schreiben, dass er das Icon (den Pfad zum Icon) aus der Notification selber bekommt!
aus Python3
Dies ist ein Beispielprogramm, welches alleine lauffähig ist und nur eine Benachrichtigung sendet. Die entsprechenden Funktionen müssen in der Praxis dann direkt in ein Python-Programm eingebaut werden.
ohne Erkennung der Variablentypen in der Message
Hier das einfachste Beispiel. Die Erkennung der "signatur" also um welchen Variablentyp es sich bei den einzelnen Messagebestandteilen handelt, erfolgt automatisch von dbus. Diese Erkennung ist nicht immer zuverlässig, daher ist im zweiten Beispiel eine einfache Typerkennung eingebaut, welche eine signatur "zusammenbaut".
root@debian:~# editor /usr/local/bin/desktop-notify.py
#!/usr/bin/python3 -u
import dbus
class desktop_notification:
def __init__(self, tag, signal_name='Notification'):
self.tag = tag
self.dbus_path = "/my/desktop"
self.dbus_iface = "my.desktop"
self.dbus_busname = "my.desktop"
self.bus = dbus.SystemBus()
self.signal_name = signal_name
def send_signal(self, *args):
"""Send a signal on the bus."""
msg = [self.tag, ' '.join(args)]
message = dbus.lowlevel.SignalMessage(self.dbus_path, self.dbus_iface, self.signal_name)
message.append(*msg)
self.bus.send_message(message)
if __name__ == '__main__':
notify = desktop_notification('test')
notify.send_signal('a', 'b')
mit Erkennung der Variablentypen in der Message
Das nächste Beispiel ist um die erwähnte einfache Erkennung und Zusammensetzung der Signatur anhand der Variablentypen laut freedesktop.org. Zur signatur ist zu sagen, dass für jedes Element von msg ein Kennbuchstabe in der richtigen Reihenfolge angeführt werden muss. Sind die Elemente von msg 3 Strings, dann sieht die signatur 'sss' aus. Sind es ein String, ein Integer und nochmal ein String, dann so 'sis'
root@debian:~# editor /usr/local/bin/desktop-notify.py
#!/usr/bin/python3 -u
import dbus
class desktop_notification:
def __init__(self, tag, signal_name='Notification'):
self.tag = tag
self.dbus_path = "/my/desktop"
self.dbus_iface = "my.desktop"
self.dbus_busname = "my.desktop"
self.bus = dbus.SystemBus()
self.signal_name = signal_name
def send_signal(self, *args):
"""Send a signal on the bus."""
signature=''
msg = [self.tag, ' '.join(args)]
for i in msg:
if type(i) is float:
signature += 'd'
elif type(i) is int:
signature += 'i'
elif type(i) is str:
signature += 's'
else:
signature += 'v'
message = dbus.lowlevel.SignalMessage(self.dbus_path, self.dbus_iface, self.signal_name)
message.append(signature=signature, *msg)
self.bus.send_message(message)
if __name__ == '__main__':
notify = desktop_notification('test')
notify.send_signal('a', 'b')
In der Regel erkennt dbus die Signatur selber - damit ist dieses Beispiel nur eine Fingerübung um zu Erklären, wie die Signatur aussehen muss. In Python kann man sich sogar erkennen lassen.
weitere Programmiersprachen
Es gibt sicher auch für andere Programmiersprachen Bindings für dbus, und es wäre fein, wenn jemand der dies kann, Codebeispiele für weitere Programmiersprachen (Perl, C, Ruby,...) hier ergänzt.
Download als Debian-paket
Um einen allgemeinen Lauschdienst per Paket zu installieren habe ich ein Debian-Paket geschnürt und den Quelltext auf Github gestellt. https://github.com/xundeenergie/system-notification Das Paket system-notification_current_all.deb ist immer ein Link auf die aktuell gültige Version. Da dieses Paket momentan (Jänner 2018) stark in Entwicklung ist, ändert sich immer wieder etwas an der Syntax, wie man eine Notification absetzen kann. Außerdem werden mehrere Konfigurationsmöglichkeiten angeboten und die Beispiele sind viel ausführlicher als dieser Wikiartikel.
Die Interfaces, mit denen dieser Lauschdienst angesprochen werden kann lauten beispielhaft am Shell-Aufruf mit dbus-send so:
root@debian:~# /usr/bin/dbus-send --system /at/xundeenergie/notifications at.xundeenergie.notifications.Notification string:"Test Header" string:Testbody