Simon Szustkowski

Ein Blog über alles, was mir gerade so durch den Kopf geht

Nov 9, 2017

i3: Dynamisches Monitorsetup

Bei meinem i3-Windowmanager-Setup wollte ich gerne die verschiedenen Workspaces auf verschiedene Monitore legen. In der Standardeinstellung erscheint der erste Workspace nämlich auf dem Monitor ganz links, der zweite Workspace weiter rechts, und so weiter. Man kann zwar in der i3 Config die Zuweisung von Workspaces auf Monitore relativ einfach einstellen, aber mein Problem ist, dass ich die gleiche Config auf zwei verschiedenen Computern benutze, und dort nicht die gleiche Anzahl von Monitoren existiert, bzw. deren Bezeichner nicht übereinstimmen.

Ich habe hierfür eine Lösung mittels dynamisch generierter Config erarbeitet, die ich hier einmal kurz vorstellen möchte. Zunächst habe ich mir einige Variablen überlegt, mit denen ich die Monitore bezeichnen kann. Die Anzahl der Variablen muss hierbei der Anzahl der Monitore an dem Computer mit den meisten angeschlossenen Monitoren entsprechen. In meinem Fall waren das $lmon und $rmon, da an meinem Desktop-Computer zwei Monitore, die nebeneinander stehen, angeordnet sind.

Nun kann man mit workspace $space1 output $lmon die Workspaces schon mal diesen Variablen zuweisen. Da diese Variablen aber noch nicht definiert sind, bringt das noch rein gar nichts.

Ich habe eine Lösung aus einem dynamisch generierten Configfile gewählt. In meinem ~/.config/i3/ habe ich einen neuen Unterordner namens snippets angelegt, und dort zum einen ein globales Config-Snippet angelegt, in dem die Computer-unabhängigen Config-Direktiven stehen. Weiterhin habe ich dort pro Computer eine Snippet-Datei liegen, die den Monitor-Variablen einen real existierenden Ausgang (der z.B. in der Liste erscheint, wenn man xrandr ohne Argumente ausführt) zuweist, z.B. set $lmon LVDS1. In dem Snippet für meinen Desktop mit zwei Monitoren hat jede Monitor-Variable einen anderen Wert, in dem Snippet für mein Notebook, an dem normalerweise keine weiteren Bildschirme angeschlossen sind, hat jede Monitor-Variable den gleichen Wert, nämlich den Ausgang, den das Notebookdisplay hat.

Nun geht es darum, die Snippets zur Runtime des i3 zu einer normalen Config-Datei zusammenzubauen, die dann in ~/.config/i3/config liegt. Hierzu habe ich im i3-Configfolder einen Unterordner scripts angelegt, und dort eine generate-config.sh mit folgendem Inhalt angelegt:

#! /bin/bash
echo "# DO NOT EDIT THIS FILE! EDIT THE SNIPPETS IN ./snippets INSTEAD" > $HOME/.config/i3/config
echo "# AND CALL ./scripts/generate-config.sh OR PRESS $MOD+SHIFT+C" >> $HOME/.config/i3/config
echo "" >> $HOME/.config/i3/config
cat $HOME/.config/i3/snippets/general >> $HOME/.config/i3/config
if [[ $(hostname) == "thavron" ]]
then
  cat $HOME/.config/i3/snippets/thavron-monitor >> $HOME/.config/i3/config
elif [[ $(hostname) == "nostor" ]]
then
  cat $HOME/.config/i3/snippets/nostor-monitor >> $HOME/.config/i3/config
elif [[ $(hostname) == "archvm" ]]
then
  cat $HOME/.config/i3/snippets/archvm-monitor >> $HOME/.config/i3/config
fi

Neben des Outputs einer Warnung, dass die generierte Configdatei nicht von Hand editiert werden soll, wird also zum einen das globale Config-Snippet in die Configdatei geschrieben, und zum anderen, basierend auf dem Hostnamen der einzelnen PCs die Monitor-Config des jeweiligen PCs.

Dieses Script muss nun immer dann ausgeführt werden, bevor i3 (neu)gestartet wird. Zum einen passiert dies beim Start des X11 selbst, also kommt folgendes Snippet in die .xinitrc, bzw. .xprofile, je nachdem, welcher Login-Manager benutzt wird:

# Merge Monitor Assignments for i3
if [[ -f ~/.config/i3/scripts/generate-config.sh ]]
then
    ~/.config/i3/scripts/generate-config.sh
fi

Natürlich sollte man darauf achten, dass dieses Snippet in der .xinitrc vor dem Aufruf von i3 selbst steht.

Der andere Ort, wo die Config neu gebaut werden sollte, ist in i3 selbst, der in der Standardkonfiguration mittels $mod+Shift+c neu gestartet werden kann, um beispielsweise Config-Änderungen einzulesen.

Hierzu habe ich im Ordner scripts ein Wrapper-Script i3-reload.sh angelegt mit folgendem Inhalt:

#!/bin/bash
~/.config/i3/scripts/generate-config.sh
i3-msg reload

Nichts wildes, es ruft nur das o.g. Script auf, was die Config dynamisch generiert, und schickt dann ein Reload-Signal an die laufende i3-Instanz. Damit dieses Script nun auch ausgeführt wird, wenn man o.g. Tastenkombination drückt, kommt noch diese Zeile in die i3-Config: bindsym $mod+shift+c exec --no-startup-id $HOME/.config/i3/scripts/i3-reload.sh

Et voila, es funktioniert. Nun kann man den gesamten ~/.config/i3 Ordner in ein Git-Repository packen, oder sonstige Dotfiles-Management-Tools zu Rate ziehen, und alles schön synchronisieren. Man muss nur bei neuen Computern darauf achten, für sie ein Snippet anzulegen, und sie in den Generator aufnehmen.

Bestimmt lässt sich das Generatorscript irgendwie noch verschönern, mit einer switch-case-Abfrage beispielsweise, und dann auch noch sowas wie eine Fallback-Config erzeugen. Ich glaube nämlich, dass i3 bei nicht vollständig definierten Monitor-Assignments den Workspace gar nicht erst irgendwo anzeigt - was auch der Grund ist, wieso ich diesen Aufwand überhaupt betreibe. Einfach Assignments auf Monitore machen, die dann nicht angeschlossen sind, oder nicht vorhanden sind, geht nämlich nicht.

Falls ihr Verbesserungsideen habt, immer her damit. Ich freu mich drauf.