Aufgabenstellung: beim Ende einer X11-Sitzung soll ein Programm aufgerufen werden.
Während beim Start einer Sitzung Dateien aus dem HOME-Verzeichnis des Users,
z.B.
und $HOME/.xsession
, eingebunden
werden, gibt es keinen solchen Haken für das Ende einer Sitzung.
$HOME/.xsessionrc
Die
bei Stackexchange vorgeschlagenen Lösungen
empfinde ich als unbefriedigend:
- Die global genutzte Datei
/etc/gdm/PostSession/Default
kann nur vonroot
und sollte nicht für einen einzelnen User angepasst werden; - Die Datei
.bash_logout
wird auch beim Ende einer (Bash-)Shell-Sitzung aufgerufen und ist damit zu unspezifisch.
Was passiert beim Ende einer Sitzung?
- Der Window-Manager terminiert.
Der Window-Manager wird von dem Prozess ausgeführt, der vorher die
.xsession
eingelesen hat. Man kann also aus der.xsession
ein Skript im Hintergrund starten, das auf das Ende seines Parent-Prozesses wartet und dann das gewünschte Programm aufruft. Dieses Warten ist leider nur möglich mit einer Linux-spezifischen Erweiterung. - Der X11-Server trennt alle Verbindungen.
Man kann also aus der
.xsession
ein Skript im Hintergrund starten, das sich mit der X-Server verbindet, auf das Trennen der Verbindung wartet und dann das gewünschte Programm aufruft.
Methode 1: warten auf Ende des Window-Managers
Die clib
für Linux definiert eine Funktion prctl()
(man 7 prctl),
mit der ein Prozess das Prozess-System manipulieren kann. Nach dem Aufruf von prset(PR_SET_PDEATHSIG, SIGTERM)
wird das gewünschte Signal an den aufrufenden Prozess
gesendet, sobald der Eltern-Prozess terminiert. Dieses Signal wird abgefangen und das gewünschte
Programm gestartet.
Compiliert man diesen Aufruf in eine Shared-Library, so kann man mit Hilfe der
Enviroment-Variable LD_PRELOAD
diese Funktion jedem Programm unterschieben
.
(Diese Idee ist schamlos
bei Stackoverflow geklaut.)
Und so wird's gemacht:
-
Laden Sie das kleine C-Programm prctl_set_pdeathsig_term.c herunter:
#include <sys/prctl.h> #include <sys/types.h> #include <signal.h> __attribute__((constructor)) static void on_load() { prctl(PR_SET_PDEATHSIG, SIGTERM); }
- Compilieren Sie dieses Programm:
gcc -fPIC -shared -o prctl_set_pdeathsig_term.so prctl_set_pdeathsig_term.c
- Wählen Sie ein Verzeichnis für die erzeugte dynamische Bibliothek,
und schieben sie die Datei in dieses Verzeichnis.
In meinem Beispiel nutze ich das Unterverzeichnis
in meinem HOME-Verzeichnis:bin
mv -iv prctl_set_pdeathsig_term.so $HOME/bin/
- Wählen Sie das Skript oder Programm, das bei Ende der Sitzung gestartet werden soll.
Wenn das Programm nicht in ihrem Standard-Suchpfad liegt, nutzen sie den vollen Pfad.
In meinem Beispiel heißt das Programm
, es liegt im Standard-Suchpfad.onlogout.sh
- Ergänzen Sie die Datei
oder.xsession
in ihrem.xsessionrc
$HOME
-Verzeichnis um eine Zeile, wobei Sie die blau umrahmten Teile anpassen:LD_PRELOAD=$HOME/bin/prctl_set_pdeathsig_term.so perl -e '\$SIG{"TERM"}=sub{};sleep;system"onlogout.sh&"' &
Wichtig:
LD_PRELOAD=
verlangt einen absolutenPfad.
Beim Start der Sitzung wird das Perl-Skript im Hintergrund gestartet und diesem das Signal
beim Ende des Eltern-Prozesses
, also beim Ende des Window-Managers, untergeschoben
.
Das Perl-Skript fängt dieses Signal ab und begibt sich danach in einen endlosen Schlaf, endlos bis zum gewünschten Signal. Danach ruft es das gewünschte Programm auf.
Achtung
Race-Conditions: wenn der Sitzungsprozess stirbt,
bevor prctl()
aufgerufen wurde, wird das Perl-Skript endlos schlafen;
wenn der Sitzungsprozess nach dem Aufruf von prctl()
stirbt, aber vor der Zuweisung
, wird das Perl-Skript
durch das Signal beendet, ohne dass das gewünschte Programm aufgerufen wird.
Die Verbesserung ist \$SIG{"TERM"}=sub{}
left as an exercise to the students™
.
Faulsäcke wählen stattdessen die Methode 2.
Methode 2: warten auf Trennen der Verbindung zum X-Server
Die Idee ist bestechend einfach: beim Ende der Sitzung trennt der Server alle Verbindungen. Also verbindet man sich mit dem Server und wartet auf die Trennung. Leider muss man auch hier ein kleines Programm compilieren:
-
Laden Sie das kleine C-Programm xconnect.c herunter:
#include <stdio.h> #include <stdlib.h> #include <string.h> #include <X11/Xlib.h> int ioErrorHandler(Display *display) { exit(0); } int main(int argc, char** argv) { char *prog = argv[0]; Display* display = XOpenDisplay(NULL); if (display == NULL) { fprintf(stderr, "%s: Cannot open display \\"%s\\"\\n", prog, getenv("DISPLAY")); exit(1); } XSetIOErrorHandler(&ioErrorHandler); for(;;) { XEvent event; XNextEvent(display, &event); fprintf (stderr, "%s: Unexpected XEvent (type: %d)\\n", prog, event.type); } }
Diese Programm verbindet sich mit dem Default-X-Server, beim Scheitern des Verbindungsaufbaus liefert es den Return-Code
1
.Danach wartet es in einer Schleife auf Events (es sollten keine kommen) und ignoriert diese. Beim Ende der X11-Sitzung trennt der Server alle Verbingungen, die xlib erzeugt einen XIO-Error, der vom
ioErrorHandler()
bearbeitet wird: dieser beendet das Programm mit dem Return-Code0
.Die Return-Codes sind also:
0
: X11-Sitzung wurde beendet;
1
: Programm wurde außerhalb einer X11-Sitzung gestartet;
>1
: Programm wurde durch ein Signal beendet.
(Falls man diese Funktion mit einem der Standard-X11-Programm nachbilden kann, bin ich für einen kurzen Hinweis sehr dankbar.)
- Compilieren Sie dieses Programm:
gcc -Werror -Wall -o xconnect xconnect.c -lX11
- Wählen Sie ein Verzeichnis für das erzeugte Programm, und schieben sie die Datei in dieses
Verzeichnis.
In meinem Beispiel nutze ich das Unterverzeichnis
in meinem HOME-Verzeichnis:bin
mv -iv xconnect $HOME/bin/
- Wählen Sie das Skript oder Programm, das bei Ende der Sitzung gestartet werden soll.
Wenn das Programm nicht in ihrem Standard-Suchpfad liegt, nutzen sie den vollen Pfad.
In meinem Beispiel heißt das Programm
, es liegt im Standard-Suchpfad.onlogout.sh
- Ergänzen Sie die Datei
oder.xsession
in ihrem.xsessionrc
$HOME
-Verzeichnis um eine Zeile, wobei Sie den blau umrahmten Teil anpassen:xconnect && onlogout.sh &
Beim Start der Sitzung wird dieses Sub-Skript im Hintergrund gestartet und das xconnect
versucht sich mit dem X-Server zu verbinden. Nur wenn das gelingt, terminiert xconnect
beim Auftrennen der Verbindung mit dem Return-Code 0, und nur dann wird das gewünschte Programm
gestartet.
Wenn die Verbindung fehlschlägt, oder wenn xconnect
durch ein Signal beendet wird,
liefert es einen Return-Code ungleich 0, und das gewünschte Programm wird nicht
gestartet.