Es stand die Aufgabe, meinen Laptop etwas alltagtauglicher zu machen.
Es handelt sich um einen ASUS-eeepc, auf dem ich ein SLAX-6.1.2 installiert habe und auf dem sich meine Präsentationen und Demonstrationsprogramme befinden.
Sehr oft muss dieser mit unterschiedlichen Beamern zusammenarbeiten und die können mit der Bildschirmauflösung des eeepc (800x480) meist wenig anfangen, sodass oftmals nichts zu sehen wäre.
Es bestand also der Wunsch, den X-Server während des Betriebs auf möglichst einfache Weise und ohne root-Zugriff auf eine verfügbare Beamer-Auflösung umzustellen.
Auch ein langes suchen einer passenden Auflösung mit <strg><alt><+> wollte ich mir ersparen, da einige Beamermodelle da ganz allergisch reagieren.
Manche schalten dann ganz auf stur und man sieht an der Wand nur noch den Hinweis "Unbekannte Auflösung".
Meine Idee war schon lange, die xrand-Schnittstelle zu nutzen, um die Auflösungen des Beamers oder externen Monitors direkt auszulesen und dann eine Tabelle der verfügbaren Modi zu generieren, aus der ich dann eine geeignete Auflösung auswählen kann.
Was soll das Script können?
Die Auflösungen des X-Servers soll zu jedem beliebigen Zeitpunkt an einen Beamer/externen Monitor angepasst werden können. Die Anpassung soll ohne Neustart des X-Servers und ohne root-Rechte erfolgen.
Die vom Beamer angebotenen Auflösungen sollen automatisch ausgelesen werden und übersichtlich in einer Tabelle dargestellt werden.
Durch einfache Auswahl aus einem Menü soll es möglich sein, die gewünschte Auflösung auszuwählen und zu aktivieren. Zur Menüdarstellung soll zwischen Xdialog oder als Alternative dialog gewählt werden. (siehe Bild rechts, die Darstellung mit Xdialog)
Ein Wechsel auf einen anderen Beamer soll während des Betriebs möglich sein.
Die Abschaltung der externen VGA-Schnittstelle und die Wiederherstellung der ursprünglichen Bildschirmauflösung soll möglich sein.
Das Script benutzt im Wesentlichen die beiden Kommandos xrandr und dialog/Xdialog. Mit xrandr werden die entsprechenden Informationen zu den verfügbaren Video-Schnittstellen aus dem X-Server gelesen und entsprechende Steuerbefehle zur Modifizierung der Auflösung an diesen gesendet. Mit dialog oder Xdialog (ist schöner) wird die komplette Menüsteuerung realisiert. Der Rest ist bash-Script. Hier nun der Quellcode der Scriptes videoswitch:
Nr. | Quelltext des Scripts | Erläuterung |
1 | #!/bin/bash | Shebang - zum Aufruf der bash |
2 | SCREEN=(); | leeres Array SCREEN deklarieren |
3 | SCREENLEN=0; | Variable SCREENLEN deklarieren |
4 | ||
5 | if ! XRANDR="$( which xrandr 2>/dev/null )"; then | Test, ob xrandr installiert ist |
6 | echo "Das Tool xrandr ist nicht installiert!" | |
7 | exit 1 | |
8 | fi | |
9 | if ! TDIALOG="$( which Xdialog 2> /dev/null )"; then | Test, ob Xdialog installiert ist |
10 | if ! TDIALOG="$( which dialog 2> /dev/null )"; then | Test, ob dialog installiert ist |
11 | echo "Kein dialog gefunden!" | |
12 | exit 1 | |
13 | fi | |
14 | fi | |
15 | ||
16 | function scan(){ | Definition der Funktion scan |
17 | local DUMMY DEVICE SCREENNR RETURN LINE; | locale Variablen der Funktion declarieren |
18 | RETURN=0 | Variablen initialisieren |
19 | SCREEN=() | |
20 | SCREENLEN=0 | |
21 | while read LINE | die Ausgabe von xrandr zeilenweise einlesen |
22 | do | |
23 | case "$LINE" in | Test des Zeileninhalts |
24 | Can\'t\ open\ display*) | xrandr findet kein X-Window |
25 | RETURN=1 | |
26 | ;; | |
27 | Screen\ [0-9]:\ *) | xrandr hat einen Screen im X-Window gefunden |
28 | DUMMY="${LINE#Screen\ }" | |
29 | SCREENNR="${DUMMY%%:*}" | die Screennummer wird abgespeichert |
30 | ;; | |
31 | ?*\ connected\ *) | xrandr hat ein nutzbares Video-Device gefunden |
32 | DEVICE="${LINE%%\ connected*}" | der Devicename wird abgespeichert |
33 | # Das DEVICE ist on" | |
34 | SCREEN[${SCREENLEN}]="SCREEN:${SCREENNR}_DEVICE:${DEVICE}_off" | |
35 | ((SCREENLEN++)) | |
36 | SCREEN[${SCREENLEN}]=" --output ${DEVICE} --off --screen ${SCREENNR}" | die xrandr-Option zum Abschalten des Devices wird im Array SCREEN gespeichert |
37 | ((SCREENLEN++)) | |
38 | ;; | |
39 | ?*\ disconnected\ *) | |
40 | DEVICE="${LINE%%\ disconnected*}" | xrand hat ein nicht nutzbares Video-Device gefunden |
41 | # Das DEVICE ist off" | |
42 | ;; | |
43 | [0-9][0-9][0-9]*x[0-9][0-9][0-9]*[\ ]**[0-9]*\.[0-9]\**) | das angeschlossene Gerät meldet eine Auflösung mit der es momentan benutzt wird |
44 | MODE="${LINE%%\ *}" | |
45 | SCREEN[${SCREENLEN}]="SCREEN:${SCREENNR}_DEVICE:${DEVICE}_MODE:${MODE}_aktiv" | |
46 | ((SCREENLEN++)) | |
47 | SCREEN[${SCREENLEN}]=" --output ${DEVICE} --mode ${MODE} --screen ${SCREENNR}" | die xrandr-Option zum Einschalten dieser Auflösung wird im Array SCREEN abgespeichert |
48 | ((SCREENLEN++)) | |
49 | # ModeLine in das Array SCREEN eingetragen | |
50 | ;; | |
51 | [0-9][0-9][0-9]*x[0-9][0-9][0-9]*) | das angeschlossene Gerät meldet eine unterstützte Auflösung |
52 | MODE="${LINE%%\ *}" | |
53 | SCREEN[${SCREENLEN}]="SCREEN:${SCREENNR}_DEVICE:${DEVICE}_MODE:${MODE}" | |
54 | ((SCREENLEN++)) | |
55 | SCREEN[${SCREENLEN}]=" --output ${DEVICE} --mode ${MODE} --screen ${SCREENNR}" | die xrandr-Option zum Einschalten dieser Auflösung wird im Array SCREEN abgespeichert |
56 | ((SCREENLEN++)) | |
57 | # ModeLine in das Array SCREEN eingetragen | |
58 | ;; | |
59 | *) | |
60 | # unbekannte xrandr-Zeile | unbekannte xrandr-Meldung |
61 | echo "Zeile $LINE unbekannt!" | Ausgabe einer Fehlermeldung im Terminal |
62 | ;; | |
63 | esac | |
64 | done < <( ${XRANDR} 2>&1 ) | Übergabe der Meldungen von xrandr an die while-Schleife |
65 | return $RETURN | Übergabe des return-Status |
66 | } | |
67 | Hauptprogramm | |
68 | if scan ; then | |
69 | EXIT=0 | |
70 | while [ ${EXIT} -eq 0 ] | |
71 | do | |
72 | DSTRING="" | |
73 | X=0 | |
74 | COUNT=0 | |
75 | for X in $( seq 0 2 $((SCREENLEN-1)) ) | |
76 | do | |
77 | DSTRING="${DSTRING} ${COUNT} ${SCREEN[$X]}" | |
78 | ((COUNT++)) | |
79 | done | |
80 | NEWSCAN="${COUNT}" | |
81 | DSTRING="${DSTRING} ${NEWSCAN} Neuscan " | |
82 | if RESULT="$( ${TDIALOG} --title 'Videoswitch V1.1' \ --cancel-label 'Beenden' \ --menu 'F.Häßler (C)2011' \ 20 70 15 ${DSTRING} 3>&1 1>&2 2>&3 3>&- )"; then | |
83 | if [ "${RESULT}" = "${NEWSCAN}" ]; then | |
84 | scan | |
85 | EXIT="$?" | |
86 | else | |
87 | ((RESULT=2*RESULT+1)) | |
88 | XRANDROPT="${SCREEN[$RESULT]}" | |
89 | ${XRANDR} ${XRANDROPT} | |
90 | scan | |
91 | fi | |
92 | else | |
93 | EXIT=1 | |
94 | fi | |
95 | done | |
96 | else | |
97 | echo "Kein X-Window gefunden!" | |
98 | fi |
Schauen wir uns die Einzelheiten des Scripts nun genauer an.
Zeile 1 bis 3
Nr. | Quelltext des Scripts | |
1 | #!/bin/bash | |
2 | SCREEN=(); | |
3 | SCREENLEN=0; | |
In den Zeilen 1 bis 3 beginnt das Ganze recht unspektakulär.
In Zeile 1 ist der sogenannte Shebang-Operator angegeben.
Dieser legt fest, dass das Script mit der bash als Kommandointerpreter ausgeführt werden soll.
Das ist auch wichtig, denn eine Reihe von Expansionen in diesem Script werden z.B. von der sh nicht unterstützt. |
||
4 |
Zeile 5 bis 14
In diesem Abschnitt wird getestet, ob die notwendigen Kommandos xrandr und Xdialog oder dialog auf dem System installiert sind.
Dazu wird das Kommando which verwendet.
Dem Kommando which wird der Name des zu suchenden Tools übergeben, nach dem dann in den Suchpfaden der Shell gesucht wird.
Wenn das Kommando gefunden wird, wird es mit seinem kompletten Pfad auf stdout ausgegeben und der exit-Status 0 (true) erzeugt.
Wird das Kommando nicht gefunden, so erzeugt which eine Fehlermeldung auf stderr und den exit-Status 1 (false).
Schauen wir uns das im Terminal mal an und geben den exit-Status mit der Parametersubstitution $? aus.
bash-3.1$ which xrandr /usr/bin/xrandr bash-3.1$ echo $? 0 bash-3.1$ which kasperletheater which: no kasperletheater in (/usr/local/bin:/usr/bin:/bin:/usr/games:/usr/lib/java/bin:/usr/lib/java/jre/bin:/usr/share/texmf/bin:.) bash-3.1$ echo $? 1 bash-3.1$ |
bash-3.1$ XRANDR="$( which xrandr )" bash-3.1$ echo $XRANDR /usr/bin/xrandr bash-3.1$ XRANDR="$( which kasperletheater )" which: no kasperletheater in (/usr/local/bin:/usr/bin:/bin:/usr/games:/usr/lib/java/bin:/usr/lib/java/jre/bin:/usr/share/texmf/bin:.) bash-3.1$ echo $XRANDR bash-3.1$ |
bash-3.1$ XRANDR="$( which xrandr 2>/dev/null )" bash-3.1$ echo $? 0 bash-3.1$ echo $XRANDR /usr/bin/xrandr bash-3.1$ XRANDR="$( which kasperletheater 2>/dev/null )" bash-3.1$ echo $? 1 bash-3.1$ echo $XRANDR bash-3.1$ |
Da es nun keinen Sinn macht das Script weiter auszuführen, wenn xrandr nicht installiert ist, wird die Suche nach dem Tool nun mit einer if..then..else..fi-Verzweigung verknüpft.
Eine solche Anweisung hat die Form
if Kommando1 ; then
Kommando2
else
Kommando3
fi
Das Kommando1 wird ausgeführt und wenn der exit-Status des Kommandos1 gleich 0 (true) ist, wird das Kommando2 ausgeführt und danach die Programmausführung nach fi fortgesetzt.
Ist der exit-Status des Kommandos1 gleich 1 (false) wird das Kommando3 ausgeführt und dann nach dem fi weiter gemacht.
Es handelt sich also um eine WENN-DANN-SONST-Verzweigung, die durch den exit-Status eines Kommandos gesteuert wird.
Mit dem Kommando ! (boolsche Negation) kann man eine boolsche Negation des exit-Status bewirken, sodass eine Umkehrung des Verhaltens erzeugt wird.
Allgemein sieht das dann so aus:
if ! Kommando1 ; then
Kommando2
else
Kommando3
fi
Das Kommando1 wird ausgeführt und der exit-Status negiert.
Wenn der exit-Status des Kommandos1 also gleich 0 (true) ist, wird das Kommando3 ausgeführt und wenn der exit-Status des Kommandos1 gleich 1 (false) ist, wird das Kommando2 ausgeführt.
Wenn also das Kommando xrandr existiert, gibt ja der Aufruf von
XRANDR="$( which xrandr 2>/dev/null )"
den exit-Status 0 zurück, ansonsten 1.
Für den Code in den Zeilen 5 bis 8 bdeutet das also:
Nr. | Quelltext des Scripts | |
5 | if ! XRANDR="$( which xrandr 2>/dev/null )"; then | |
6 | echo "Das Tool xrandr ist nicht installiert!" | |
7 | exit 1 | |
8 | fi | |
Es wird mit which getestet, ob das Kommando xrandr existiert, der Pfad zu dem Kommando in der Variable XRANDR abgelegt und danach der exit-Status von which negiert. Wenn xrandr also nicht gefunden wurde, wird der Code nach then ausgeführt. Wenn xrandr nicht existiert, wird eine Fehlermeldung ausgegeben und danach das Script mit dem exit-Status 1 sofort beendet. Wenn xrandr installiert ist, wird die Programmausführung nach dem fi fortgesetzt. |
||
9 | if ! TDIALOG="$( which Xdialog 2> /dev/null )"; then | |
10 | if ! TDIALOG="$( which dialog 2> /dev/null )"; then | |
11 | echo "Kein dialog gefunden!" | |
12 | exit 1 | |
13 | fi | |
14 | fi | |
Der Code in den Zeilen 9 bis 14 arbeitet ähnlich.
Zuerst wird getestet, ob das Kommando Xdialog existiert.
Wenn ja, dann wird der Pfad zum Kommando Xdialog in der Variable TDIALOG abgespeichert und das Programm bei Zeile 15 fortgesetzt.
Existiert Xdialog nicht, dann wird getestet, ob wenigstens dialog als Alternative installiert ist.
Wenn ja, dann wird der Pfad zu dialog in der Variablen TDIALOG abgespeichert, ansonsten wird das Script mit einer Fehlermeldung und dem exit-Status 1 (false) sofort beendet. |
||
15 | ||
Wenn das Script also bei Zeile 15 angekommen ist, dann sind folgende Bedingungen erfüllt:
|
Zeile 16 bis 66
Die Aufgabe der Funktion scan besteht darin, das Kommando xrandr aufzurufen und seine Ausgabe nach gültigen Einträgen zu scannen, die Angaben über die unterstützten Auflösungen der angeschlossenen Beamer und Monitore machen.
Eine Ausgabe des Kommandos xrandr im Terminal sieht z.B. so aus:
bash-3.1$ xrandr Screen 0: minimum 320 x 200, current 800 x 480, maximum 800 x 800 VGA connected (normal left inverted right x axis y axis) 800x600 72.2 75.0 60.3 56.2 640x480 75.0 72.8 66.7 60.0 720x400 70.1 640x350 70.1 LVDS connected 800x480+0+0 (normal left inverted right x axis y axis) 0mm x 0mm 800x480 60.0*+ 640x480 85.0 72.8 75.0 59.9 720x400 85.0 640x400 85.1 640x350 85.1 TV disconnected (normal left inverted right x axis y axis) |
Es ist zu erkennen, dass die Grafikkarte über einen externen VGA-Anschluss verfügt, an dem ein Monitor/Beamer angeschlossen ist, der die Auflösungen 800x600, 640x480, 720x40 und 640x350 unterstützt.
An der LVDS-Schnittstelle (Digitalschnittstelle für LCD-Monitore) ist das interne LCD-Display des eeepc angeschlossen, das die Auflösungen 800x480, 640x480, 720x400, 640x400 und 640x350 unterstützt.
Weiterhin verfügt die Grafikkarte über einen TV-Anschluss, der nicht nutzbar ist.
In der Konfiguration des X-Servers ist nur Screen 0 definiert, in dem momentan die beiden Schnittstellen VGA/extern und LVDS/intern nutzbar sind.
An dem * in der Ausgabezeile "800x480 60.0*+" kann man erkennen, das dieser Modus momentan aktiv ist.
Die Funktion scan liest diese Ausgabe nun zweilenweise ein und erzeugt aus allen Angaben zu verfügbaren Auflösungen der Geräte jeweils zwei Einträge im Array SCREEN.
Unter den geraden Indexnummern (0, 2, 4, ...) des Arrays wird der Wortlaut des Menüeintrages für dialog hinterlegt, unter den ungeraden Indexnummern (1, 3, 5, ...) die entsprechende xrandr-Option um diesen Modus im X-Server zu aktivieren.
Der Quellcode der Funktion ist schon recht komplex und ich werde nun versuchen, die einzelnen Anweisungen und deren Funktionsweise zu erläutern.
Shellfunktionen sind im Prinzip Unterprogramme, denen beim Aufruf genau wie Kommandos eine Reihe von Parametern übergeben werden kann und die mit return einen exit-Status erzeugen können.
Nr. | Quelltext des Scripts | |
16 | function scan(){ | |
In Zeile 16 wird die Funktion scan deklariert. Durch das Konstrukt function scan(){ ...... } wird die Funktion definiert. In den geschweiften Klammern werden die Kommandos abgelegt, die beim Aufruf der Funktion ausgeführt werden sollen. Die Definition der Funktion scan endet in der Zeile 66 mit der schließenden geschweiften Klammer. In den Zeilen 17 bis 20 befindet sich nun ein Deklarationsteil in der Funktion. |
||
17 | local DUMMY DEVICE SCREENNR RETURN LINE; | |
18 | RETURN=0 | |
19 | SCREEN=() | |
20 | SCREENLEN=0 | |
In der Zeile 17 werden die Variablen DUMMY, DEVICE, SCREENNR, RETURN und LINE als locale Variablen der Funktion definiert. Diese Variablen sind nur innerhalb der Funktion gültig und existieren außerhalb der Funktion nicht. In Zeile 18 wird die locale Variable RETURN mit dem Startwert 0 belegt. Der Wert dieser Variable wird den exit-Status der Funktion enthalten und so anzeigen, ob die Funktion ordnungsgemä&szli; ausgeführt wurde (0 true) oder nicht (1 false). In den Zeilen 19 und 20 werden die globalen Variablen SCREEN und SCREENLEN wieder mit den Startwerten (leeres Array und 0) belegt, um eventuell vorhandene Einträge eines vorhergehenden Aufrufs der Funktion scan zu löschen. |
||
Achtung!, jetzt wird es etwas knifflig.
Die Ausgaben des Kommandos xrandr müssen nun Zeile für Zeile gelesen und interpretiert werden, um dann die entsprechenden Einträge im Array SCREEN zu erzeugen.
Das zeilenweise Einlesen erledigt eine while-Schleife zusammen mit dem Kommando read.
Das Kommando read liest jeweils eine Zeile (bis zum Enter) von stdin und hinterlegt sie in einer Variable, in unserem Fall die Variable LINE.
Wenn read Daten lesen konnte, gibt es den exit-Status 0 (true) zurück.
Konnten keine Daten mehr gelesen werden (Timeout oder EOF-end of file), so wird der exit-Status 1 (false) zurückgegeben.
Deshalb wird read einfach als Abbruchbedingung für die while-Schleife eingefügt, so dass die while-Schleife-Schleife so lange ausgeführt wird, wie read Zeilen von stdin lesen kann.
Ist der Datenstrom an stdin beendet, so beendet sich auch die while-Schleife.
Das sieht dann also so aus |
||
21 | while read LINE | |
22 | do | |
Im Schleifenkörper der while-Schleife haben wir jetzt also die jeweils nächste Ausgabezeile des Kommandos xrandr in der Variable LINE zur Verfügung und können diese jetzt nacheinander nach interessanten Mustern scannen und die Einträge im Array SCREEN erzeugen. |
||
23 | case "$LINE" in | |
Das scannen der Zeileninhalte übernimmt die Mehrfachverzweigung case, die relativ komfortable Möglichkeiten des Stringvergleichs durch reguläre Ausdrücke bietet und Mehrfachverzweigungen im Gegensatz zur if-Verzweigung viel übersichtlicher darstellt.
Die case-Verzweigung hat die folgende Struktur:
Die case-Verzweigung in der Funktion scan erstreckt sich über die Zeilen 23 bis 63. |
||
24 | Can\'t\ open\ display*) | |
25 | RETURN=1 | |
26 | ;; | |
In diesem Abschnitt wird nach einem Text "Can't open display", gefolgt von beliebig vielen oder keinem weiteren Zeichen (Jokerzeichen*) in der Variablen LINE gesucht. xrandr gibt diese Meldung aus, wenn kein X-Window-System erreichbar ist. Wenn das zutrifft, wird der Wert der Variable RETURN die den exit-Status der Funktion enthält, auf 1 (false) gesetzt und das Script nach esac fortgesetzt. |
||
27 | Screen\ [0-9]:\ *) | |
28 | DUMMY="${LINE#Screen\ }" | |
29 | SCREENNR="${DUMMY%%:*}" | |
30 | ;; | |
Hier wird der Inhalt der Variable LINE mit dem Suchmuster "Screen\ [0-9]:\ *" verglichen.
Gesucht wird also nach dem Wort Screen, gefolgt von einem Leerzeichen und einer Ziffer im Bereich von 0 bis 9, einem Doppelpunkt, einem weiteren Leerzeichen und beliebig vielen oder keinem weiteren Zeichen (Jokerzeichen*).
Das entspricht der Ausgabe des definierten Screens im X-Server durch xrandr.
Bei Übereinstimmung wird dieser String nun zerlegt, um die Nummer des Screens zu ermitteln.
Dazu werden hier die Parametersubstitutionen der bash verwendet.
Prinzipiell ginge das auch mit dem Kommando cut, der an dieser Stelle aber keinerlei Vorteile bringen würde, im Gegenteil.
Da cut ein externes Kommando ist, müsste die bash erst eine eigene Subshell starten und das kostet Zeit, RAM und Performance.
Die hier verwendete Substitution hat die Form ${parameter#word}. |
||
31 | ?*\ connected\ *) | |
32 | DEVICE="${LINE%%\ connected*}" | |
33 | # Das DEVICE ist on" | |
34 | SCREEN[${SCREENLEN}]="SCREEN:${SCREENNR}_DEVICE:${DEVICE}_off" | |
35 | ((SCREENLEN++)) | |
36 | SCREEN[${SCREENLEN}]=" --output ${DEVICE} --off --screen ${SCREENNR}" | |
37 | ((SCREENLEN++)) | |
38 | ;; | |
Der Inhalt der Variable LINE wird nun dem Suchmuster "?*\ connected\ *" verglichen.
Gesucht wird also nach Mindestens einem beliebigen Zeichen, gefolgt von beliebig vielen oder keinem weiteren Zeichen (das ist der Name der Videoschnittstelle), auf die ein Leerzeichen, das Wort connected, ein weiteres Leerzeichen und beliebig viele oder keine weiteren Zeichen folgen.
Das entspricht der Ausgabe einer benutzten Schnittstelle der Grafikkarte durch xrandr.
Das Wort vor " connected" ist der Name der Schnittstelle, das wieder durch Parametersubstitutionen der bash herausgetrennt wird.
Die verwendete Substitution hat wieder die Form ${parameter%%word}.
In der Zeile 32 steht dazu der Code: DEVICE="${LINE%%\ connected*}"
In der Zeile 35 wird nun der Inhalt der Variablen SCREENLEN nun durch eine arithmetische Substitution um 1 erhöht und zeigt damit auf das nächste freie Feld des Arrays SCREEN. |
||
39 | ?*\ disconnected\ *) | |
40 | DEVICE="${LINE%%\ disconnected*}" | |
41 | # Das DEVICE ist off" | |
42 | ;; | |
Ähnlich wie in Zeile 31 wird nun nach Einträgen über nicht kontaktierte oder unbenutzbare Videoschnittstellen gesucht. Der Name des Devices wird zwar herausgetrennt und in DEVICE gespeichert, aber Menüeinträge werden nicht erzeugt. Vielleicht fällt mir dazu später noch etwas ein, was man sinnvolles tun könnte. |
||
43 | [0-9][0-9][0-9]*x[0-9][0-9][0-9]*[\ ]**[0-9]*\.[0-9]\**) | |
44 | MODE="${LINE%%\ *}" | |
45 | SCREEN[${SCREENLEN}]="SCREEN:${SCREENNR}_DEVICE:${DEVICE}_MODE:${MODE}_aktiv" | |
46 | ((SCREENLEN++)) | |
47 | SCREEN[${SCREENLEN}]=" --output ${DEVICE} --mode ${MODE} --screen ${SCREENNR}" | |
48 | ((SCREENLEN++)) | |
49 | # ModeLine in das Array SCREEN eingetragen | |
50 | ;; | |
Nun wird das Suchmuster schon etwas komplexer. Gesucht wird nach den Zeilen der xrandr-Ausgabe, in denen eine Auflösung des angeschlossenen Gerätes angegeben wird, die momentan auch aktiv ist. Dazu wird das Suchmuster "[0-9][0-9][0-9]*x[0-9][0-9][0-9]*[\ ]**[0-9]*\.[0-9]\**" verwendet, was im Einzelnen bedeutet, dass die Variable LINE einen Text enthalten muss, der aus drei Ziffern im Bereich von 0 bis 9, gefolgt von beliebig vielen oder keinem weiteren Zeichen (eventuell weitere Ziffern bei vierstelligen Auflösungen oder höher), dann muss das x folgen und es müssen mindestens drei weitere Ziffern folgen. Eventuell am Zeilenanfang auftretende Leerzeichen müssen nicht baechtet werden, da sie bereits durch read entfernt wurden. Das wäre also das Format, mit dem eine aufgelistete Auflösung erkannt wird. Jetzt muss noch ein Freiraum aus mindestens einem Leerzeichen folgen und dann eine Zahl mit Dezimalpunkt (vertikale Refreshrate des Gerätes), auf die ein Stern * folgt. Damit handelt es sich beim Inhalt der Variable LINE um eine momentan aktive Auflösung. Nun wird wieder die aus Zeile 32 bekannte Parametersubstitutionen der Form ${parameter%%word} angewendet, um den Inhalt der Variablen LINE ohne den rechtsseitigen Teil nach der Angabe der Auflösung zu expandieren. In der Variable MODE steht dann also die verfügbare und momentan aktive Auflösung in der Form "<horizontale Auflösung in Pixeln>x<vertikale Auflösung in Pixeln>"". Daraus wird nun, wie in den Zeilen 34 bis 37 gesehen, ein Menüeintrag und ein Eintrag mit der xrandr-option für diesen Modus in den nächsten freien Feldern des Arrays SCREEN hinterlegt. |
||
51 | [0-9][0-9][0-9]*x[0-9][0-9][0-9]*) | |
52 | MODE="${LINE%%\ *}" | |
53 | SCREEN[${SCREENLEN}]="SCREEN:${SCREENNR}_DEVICE:${DEVICE}_MODE:${MODE}" | |
54 | ((SCREENLEN++)) | |
55 | SCREEN[${SCREENLEN}]=" --output ${DEVICE} --mode ${MODE} --screen ${SCREENNR}" | |
56 | ((SCREENLEN++)) | |
57 | # ModeLine in das Array SCREEN eingetragen | |
58 | ;; | |
Diese Scriptzeilen unterscheiden sich von den Zeilen 43 bis 50 nur durch den leicht veränderten Suchstring. Hier werden nun alle Zeilen gefunden, die eine Auflösung liefern, egal ob aktiv oder nicht. Da die aktiven Zeilen aber schon mit dem vorhergehenden Suchmuster herausgefiltert werden, werden sie hier nicht nochmals bearbeitet, denn case bricht nach der Bearbeitung eines Suchtreffers die Arbeit und setzt die Programmabarbeitung nach esac fort. |
||
59 | *) | |
60 | # unbekannte xrandr-Zeile | |
61 | echo "Zeile $LINE unbekannt!" | |
62 | ;; | |
Nun erfolgt noch eine Suchanfrage für alle Zeilen, die bisher nicht erkannt wurden. Das Suchmuster "*" bedeutet im Prinzip, dass LINE irgendetwas enthalten kann und diese Suchanfrage erzeugt nur dann einen Treffer, wenn keines der anderen Suchmuster erfolg gebracht hat. In diesem Fall wird einfach eine kleine Fehlermeldung auf dem Terminal ausgegeben, das etwas nicht erkannt wurde. |
||
63 | esac | |
64 | done < <( ${XRANDR} 2>&1 ) | |
In diese Zeile habe ich nun nicht, wie oben beschrieben, den Code done < <( xrandr ) eingetragen, sondern ein etwas komplizierteres Konstrukt. Das hat zwei Gründe:
|
||
65 | return $RETURN | |
66 | } | |
Durch die while-Schleife durchläuft das Script diese case-Verzweigung mit dem Inhalt jeder Ausgabezeile von xrandr in der Variablen LINE. Nachdem alle Zeilen abgearbeitet wurden, beendet sich die Funktion scan und gibt den Inhalt der Variablen RETURN als exit-Status zurück. |
||
67 |
Zeile 68 bis 98
Nr. | Quelltext des Scripts | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
68 | if scan ; then | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
Zuerst einmal wird zum Programmstart die Funktion scan aufgerufen und die Ausgabe von xrandr gescannt. Sollte dabei ein Fehler auftreten, weil xrandr kein X-Window-System kontaktieren kann, wird der exit-Status 1 (false) zurückgegeben und if führt sofort den else-Zweig ab Zeile 96 aus, erzeugt eine Fehlermeldung und das Script ist beendet. Lief der scan ohne Probleme, geht es weiter mit der Erzeugungdes Menüs. |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
69 | EXIT=0 | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
Die Variable EXIT ist ein Flag das angibt, ob die while-Schleife, die das Menü erzeugt, verlassen werden soll oder nicht. Sie wird hier auf den Wert 0 gesetzt. |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
70 | while [ ${EXIT} -eq 0 ] | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
71 | do | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
Hier testet while nun den Zustand der Variable EXIT. Dazu erwartet while ein Kommando und dessen exit-Status entscheidet, ob der Schleifenkörper abgearbeitet wird (bei 0-true) oder ob die Schleife verlassen wird (bei 1-false). Hier wird für den Test das Kommando [ ... ] verwendet. Ist der Wert der Variable EXIT gleich 0, wird der Schleifenkörper ausgeführt, wenn nicht, wird die die while-Schleife bei Zeile 95 verlassen. Die Variable EXIT wird im Schleifenkörper auf 1 gesetzt, wenn ein Aufruf der scan-Funktion den exit-Status 1 (false) erzeugt oder (X)dialog mit der Taste Beenden oder Exit geschlossen wird, also wenn der User das Programm offensichtlich beenden möchte. |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
72 | DSTRING="" | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
73 | X=0 | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
74 | COUNT=0 | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
Hier werden nur ein paar Variablen initialisiert. In der Variablen DSTRING werden die Menüeinträge als dialog-Option zusammengesetzt. In Ihr wird ein String mit Null Zeichen gespeichert Mit COUNT wird gezählt, wie viele Menüeinträge es gibt und X ist eine einfache Zählvariable für die folgende for-Schleife. Beide werden auf Null gesetzt. |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
75 | for X in $( seq 0 2 $((SCREENLEN-1)) ) | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
76 | do | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
Die for-Schleife nutzt X als Zählvariable. Das Kommando seq 0 2 $((SCREENLEN-1)) erzeugt eine Werteliste von Null bis zum Wert der Variablen SCREENLEN minus 1 mit einer Schrittweite von 2, also 0, 2, 4, 6, ..... Da die Variable SCREENLEN nach dem Ausführen der scan-Funktion auf das erste freie Feld des Arrays SCREEN zeigt, erzeugt seq damit eine Liste aller geraden Indexnummern des Arrays SCREEN. In den Feldern mit geraden Indexnummern wurden ja durch die scan-Funktion bereits die Einträge für die Menüpunkte abgespeichert. Bei jedem Durchlauf der for-Schleife nimt X nun jeweils den nächsten Wert aus der Werteliste an, durchläft also die geraden Indexnummern des Arrays SCREEN. |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
77 | DSTRING="${DSTRING} ${COUNT} ${SCREEN[$X]}" | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
78 | ((COUNT++)) | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
Hier werden nun die Einträge für die dialog-Optionsliste vorgenommen. Jedem Menüpunkt ist eine Nummer voranzustellen, die dann auch als Kurzwahl verwendet werden kann und die von (X)dialog auf stdout ausgegeben wird, wenn der entsprechende Menüpunkt durch den User ausgewählt wurde. Danach folgt der Wortlaut des Menüeintrags. Das passiert hier durch den Aufruf DSTRING="${DSTRING} ${COUNT} ${SCREEN[$X]}". Die Variable DSTRING soll ja als Speicher für die Optionsliste dienen und wurde bei der Initialisierung in Zeile 72 mit einem Nullstring geladen. Hier wird der Variable DSTRING nun ein Wert zugewiesen, der sich aus dem ursprünglichen Inhalt von DSTRING, einem Leerzeichen, dem Inhalt der Variable COUNT, einem weiteren Leerzeichen und dem Inhalt des Feldes aus dem Array SCREEN, das die Indexnummer besitzt, die in der Variablen X gespeichert ist. Danach wird der Inhalt der Variable COUNT um 1 erhöht. |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
79 | done | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
Diese Anweisungen werden so oft abgearbeitet, bis die ganze von seq erzeugte Liste abgearbeitet ist. Das bedeutet, dass in der Variable DSTRING alle Einträge des Arrays SCREEN mit gerader Indexnummer (von seq erzeugt) mit einer fortlaufenden Nummer (COUNT-Zählvariable) versehen, aufgelistet werden. |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
80 | NEWSCAN="${COUNT}" | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
81 | DSTRING="${DSTRING} ${NEWSCAN} Neuscan " | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
Hier wird der (X)dialog-Optionsliste noch ein weiterer Menüpunkt "Neuscan" hinzugefügt. Wenn der User diesen Menüpunkt anwählt, soll die scan-Funktion nochmals aufgerufen werden, so dass erneut nach externen Geräten gesucht wird. In der Variable NEWSCAN wird einfach die Nummer dieses Menüeintrages abgespeichert. |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
82 | if RESULT="$( ${TDIALOG} --title 'Videoswitch V1.1' \ --cancel-label 'Beenden' \ --menu 'F.Häßler (C)2011' \ 20 70 15 ${DSTRING} 3>&1 1>&2 2>&3 3>&- )"; then | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
So, diese Zeile ist der Hammer und das aus mehreren Gründen:
Insgesamt bewirkt diese Zeile, dass das Kommando (X)dialog mit all seinen Optionen aufgerufen wird und wenn der User einen Menüpunkt auswählt, dessen Nummer in der Variable RESULT abgespeichert wird. Außerdem gibt der exit-Status 1 dieser Zuweisung an, ob der User das Kommando (X)dialog abgebrochen hat (Beenden- oder Exit-Taste gedrückt). In diesem Fall wird sofort zu Zeile 92 gesprungen. War der exit-Status jedoch 0, so bedeutet dies, dass ein Menüpunkt ausgewählt wurde und das Programm wird in Zeile 83 fortgesetzt. |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
83 | if [ "${RESULT}" = "${NEWSCAN}" ]; then | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
Es findet einfach nur ein Test statt, ob die Nummer des ausgewählten Menüpunktes mit dem Inhalt der Variable NEWSCAN übereinstimmt, also ob der User den Menüpunkt "Neuscan" ausgewählt hat oder nicht. |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
84 | scan | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
85 | EXIT="$?" | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
Wenn der Menüpunkt "Neuscan" ausgewählt wurde, wird die scan-Funktion erneut gestartet und mit der Parametersubstitutionen der Form $? der exit-Status der scan-Funktion in der Variablen EXIT gespeichert. Wenn also xrandr in der scan-Funktion einen Fehler festgestellt hat, wird EXIT auf 1 gesetzt und dadurch die while-Schleife in Zeile 70 beim nächsten Test der Variable EXIT die Schleife verlassen und das Script sich beenden. Wurde durch xrandr kein Fehler ausgelöst, wird in EXIT eine 0 gespeichert und die while-Schleife wird erneut durchlaufen, d.h. das Menü wird mit den neuen Ergebnissen der scan-Funktion erstellt. |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
86 | else | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
Wenn ein anderer Menüpunkt als "Neuscan" ausgewählt wurde, dann geht es hier weiter. |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
87 | ((RESULT=2*RESULT+1)) | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
88 | XRANDROPT="${SCREEN[$RESULT]}" | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
89 | ${XRANDR} ${XRANDROPT} | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
90 | scan | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
In der Variable RESULT befindet sich die Nummer des vom User ausgewählten Menüeintrags.
Jetzt muss nur die Indexnummer des dazu passenden Eintrags der xrandr-Optionen im Array SCRENN berechnet werden.
Die Nummer in RESULT muss dazu einfach verdoppelt werden und um 1 erhöht werden.
Das wird in Zeile 87 durch den arithmetischen Ausdruck ((RESULT=2*RESULT+1)) erreicht.
Der Inhalt der Variablen RESULT wird mit 2 multipliziert und 1 dazu addiert.
Das Ergebnis wird wieder der Variablen RESULT zugewiesen. |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
91 | fi | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
92 | else | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
Wenn das (X)dialog-Fenster beendet wurde (Beenden- oder Exit-Button), dann geht es hier weiter. Es wird lediglich die Variable EXIT auf den Wert 1 gesetzt, so dass die while-Schleife in Zeile 70 beim nächsten Test der Variable EXIT die Schleife verlässt und das Script sich beendet. nicht der Menüpunkt "Neuscan" ausgewählt wurde, dann geht es hier weiter. |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
93 | EXIT=1 | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
94 | fi | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
95 | done | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
Mit done gehts zurück zu Zeile 70 und der nächste Durchlauf der while-Schleife beginnt. |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
96 | else | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
97 | echo "Kein X-Window gefunden!" | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
98 | fi |