Immer wieder taucht das Problem auf, dass es "nett" wäre, wenn ein Dienst/Server, der von cron oder systemd aus gestartet wird, am Desktop mittels notification bekannt macht, dass er fertig ist.
Dazu findet man zu hauf die Krücke, sich die Umgebungsvariable DBUS_SESSION_BUS_ADDRESS irgendwie zu krallen, das $DISPLAY ebenfalls, dies dann im Service/Skript zu exportieren und dann notify-send von dort aus zu starten.
Das ganze ist und bleibt aber eine Krücke, die dann mal besser, mal schlechter arbeitet.
Ich hab mich jetzt mal eingehender mit dieser Thematik und mit dbus beschäftigt, da ich gerne eine Notification am Desktop für mein Backup-Programm hätte, das von systemd aus gestartet wird. Ich hab zwar für Gnome3 eine Shell-Extension geschrieben, die aber eben nur auf Gnome3 funktioniert. In der Openbox-Session hingegen sehe ich nicht, wann das Backup fertig ist, das auf die externe Platte geschrieben wird...
Nun gut... ich habe ein Lösung gefunden, die dbus so nützt, wie es vorgesehen ist.
Der Service/Cronjob schickt eine Message an den dbus-SessionBus. In ALLEN graphischen Oberflächen aller eingeloggten User lauscht eine Anwendung am SessionBus von dbus, ob eine Message vom Dienst kommt und schickt dann im Falle des Eintreffens eine Notification - wiederum über dbus an den Notification-Daemon.
Das klingt jetzt furchtbar kompliziert, funktioniert aber wunderbar. Mit dieser Lösung muss keine Xauthority, keine Display oder andere Umgebungsvariable gesucht und exportiert werden...
Wichtig ist, dass in der Graphischen Oberfläche ein Notification-Daemon läuft. Bei Gnome ist der sowieso dabei, für Openbox habe ich xfce4-notifiyd installiert. Sollte aber mit jedem anderen (dunst, notify-osd,...) auch funktionieren.
Testen kann man, ob ein Notification-Daemon läuft, wenn man in einem Terminal einen Befehl abschickt (ggfs. noch libnotify-bin installieren)
Code: Alles auswählen
notify-send "Bla" "Blubb"
Jetzt brauchen wir den Listener, der am Systembus in der graphischen Oberfläche lauscht und die Notification auf den Session-Bus übersetzt und weiterschickt:
Dazu legt man eine Datei
Code: Alles auswählen
editor /usr/local/bin/dbus-listener.py
Code: Alles auswählen
#!/usr/bin/env python
import dbus, dbus.glib
import gtk
systembus = dbus.SystemBus()
sessionbus = dbus.SessionBus()
notifications = dbus.Interface(sessionbus.get_object('org.freedesktop.Notifications',
'/org/freedesktop/Notifications'), 'org.freedesktop.Notifications')
#Funktioniert leider nicht, daher wird das icon hart codiert
#icon = gtk.icon_theme_get_default().lookup_icon("update-none", 22,
# gtk.ICON_LOOKUP_USE_BUILTIN).get_filename()
icon = '/usr/share/icons/oxygen/base/48x48/actions/svn-update.png'
def notify_updates(*args):
notifications.Notify("Anwendungs Name", 0, icon, "Titel: %s" % (args[0]),
' '.join(args[1:]),
"", {}, -1)
systembus.add_signal_receiver(notify_updates, 'signal_name', 'ein.eindeutiger.BusName')
gtk.main()
Code: Alles auswählen
chmod +x /usr/local/bin/dbus-listener.py
Code: Alles auswählen
editor /etc/xdg/autostart/dbus-listener.desktop
Code: Alles auswählen
[Desktop Entry]
Version=1.0
Name=Notifications service for background services
Name[de]=Benachrichtigungen für Hintergrunddienste
Exec=/usr/local/bin/dbus-listener.py
TryExec=/usr/local/bin/dbus-listener.py
Icon=xfce4-notifyd
Terminal=false
StartupNotify=false
Type=Application
Categories=GTK;Settings;DesktopSettings;
Code: Alles auswählen
dbus-send --system /ein/eindeutiger/BusName ein.eindeutiger.BusName.signal_name string:"Bla" string:"Blubb"
Dies kann man als User und als root testen!
Diese Zeile kann man schon in ein Shell-Skript einbauen, welches z.B. von cron aufgerufen wird. Und das genügt schon.
Ich hab ein Python-Skript, mit welchem ich meine Backups erstelle. In Python gibt es ein dbus-Modul, welches ich verwende. Die Dokumentation dazu ist sehr bescheiden. Ich habe eigentlich nur Informationen für den Listener in Python oder eine Sender an den SessionBus gefunden, aber nicht wie ich ein einfaches Signal an den Systembus sende. Und es hat mich viel Zeit gekostet, das herauszufinden.
Und da ist die einfache Lösung:
Code: Alles auswählen
#!/usr/bin/env python3
"""Python 3.6 script. Creates a Notification pop-up bubble"""
import dbus
OPATH = "/ein/eindeutiger/BusName"
IFACE = "ein.eindeutiger.BusName"
BUS_NAME = "ein.eindeutiger.BusName"
def send_signal(bus, path, dbus_interface, signal_name, signature, *args):
"""Send a signal on the bus."""
message = dbus.lowlevel.SignalMessage(path, dbus_interface, signal_name)
message.append(signature=signature, *args)
bus.send_message(message)
if __name__ == "__main__":
bus = dbus.SystemBus()
send_signal(bus, OPATH, IFACE, 'signal_name', 'sss', "Titelergänzung", "Body text", "Noch ein Bodytext")
Es folgen auf 'sss' 3 Felder, die Strings sind. Daher besteht die Signatur aus 3x 's'. Wäre nur ein Feld mit einer Integerzahl, lautete die signatur 'i'
Wäre das erste Feld ein String, gefolgt von einem Feld das eine Integerzahl ist, schaut die Signatur 'si' aus.
Die Anzahl der Felder und das Format gibt der Listener von oben vor. Ich habe ihn so gebaut, dass das erste Feld in die Titelzeile eingebaut wird. die weiteren Felder werden zu einem String zusammengefügt und bilden den Body der Notification.
Man kann natürlich auch nur einzelne Worte und Zahlen übergeben und die Meldung im Listener daraus zusammenbauen, oder man übergibt gleich den fertigen Meldungstext.
Allgemein zu dbus ist noch zu sagen:
/ein/eindeutiger/BusName: So ist das Format des Pfades (wozu auch immer der benötigt wird)
ein.eindeutiger.BusName: So ist das Format des Busnamens. Es scheint sinnvoll zu sein, dieses dem Pfad anzupassen (mit . statt /). Der Busname kann aber auch ein einziges eindeutiges Wort sein ("alsdkföjasd" z.B)
signal_name: Das wäre die "Funktion", die angesprochen werden soll.
In meinem Fall lauten diese so:
/at/meinedomain/mkbackup bzw. at.meinedomain.mkbackup und der signal_name lautet interval
Das muss in allen Skripten (auch im Einzeiler mit dbus-send) einheitlich sein.
Für dbus-send ist zu beachten, dass der der Pfad /ein/eindeutiger/BusName lautet, beim Busnamen jedoch der signal_name angehängt wird ein.eindeutiger.BusName.signal_name!!!
Ich bin noch dabei, eine Nomenklatur zu entwickeln, dass ich einen "allgemeinen" Listener für solche Hintergrunddienste habe, den ich nicht nur vom Backupskript sinnvoll mit Namen ansprechen kann.
Ich würd mich sehr über Rückmeldungen freuen.
Leider hab ich noch nicht herausgefunden, ob und wie ich Links und Buttons ein die Notification einbauen kann. Denn für mein Backupskript wärs natürlich fein, gleich einen Button zu haben, wo ich im Fileexplorer direkt zum Backup hüpfen kann (oder die Platte auswerfen). Man kann auch sicher irgendwo noch die urgency und Sichtbarkeitsdauer einstellen. Das hab ich auch noch nicht rausgefunden.
Ich bin froh, endlich einmal bei dbus soweit hinter das Konzept gestiegen zu sein, dass ich diese Notification ohne Krücken zum Laufen gebracht habe
Anregungen sind gerne erwünscht. Soll ich auch einen Wikiartikel verfassen?
lg scientific