blog · git · desktop · images · contact


katriawm: Vom Abenteuer, einen Window Manager zu schreiben

2016-01-05

Ich schreibe einen Window Manager. Okay, bitte nicht wegrennen. Das hier soll keine Werbeveranstaltung werden, die erzählt, wie großartig mein Projekt ist. Window Manager gibt es da draußen zuhauf und das ist uns allen bewusst. Stattdessen will ich von den Erfahrungen berichten, die ich gemacht habe. Warum habe ich das überhaupt gemacht? Wie war das so und was habe ich dabei gelernt?

Was mich dazu gebracht hat

Mein erster Kontakt mit „Tiling Window Managern“ war in den 1990ern. Das war so eine Art „Manual Tiling WM“. Genauer gesagt, war das Windows 3.0. Windows hatte damals dieses Feature, das es einem erlaubte, alle sichtbaren Fenster nebeneinander anzuzeigen. Ich habe das relativ selten benutzt und so wirklich relevant ist es für die Geschichte auch nicht, aber es zeigt, dass die Idee schon älter ist. Warum erzähle ich überhaupt etwas von Tilern? Weil sie mich dazu gebracht haben, mich im Detail mit Customization zu befassen. Das wird gleich klarer.

Später, als dann GNU/Linux schon mein Hauptsystem war, kam Windows 7 irgendwann heraus. Es brachte ein Feature namens „Aero Snap“ mit: Man kann ein Fenster auf die linke oder rechte Seite schieben und dann schnappt es in einer Bildschirmhälfte ein. Wieder so eine Art Manual Tiling. Das Feature hat mir gefallen und so habe ich dann ein kleines Python-Skript zusammengehackt, das etwas ähnliches in Xfce ermöglichte. Das war ungefähr 2008 oder 2009.

Ebenfalls in 2009 habe ich wmii ausprobiert. Da das leider ein bisschen langsam war, bin ich 2010 schließlich zu awesome gewechselt. Bei awesome sind persönliche Anpassungen sehr einfach, also habe ich angefangen, damit herumzuspielen. Awesome erlaubt es einem auch nicht nur, das Aussehen anzupassen, sondern tief in die Routinen zur Fensterverwaltung einzugreifen. Obwohl ich das Modul mit meinen Anpassungen und auch awesome dann später hinter mir gelassen habe, lebt heute ein Teil von davon noch in lain weiter.

2012 erfolgte der Wechsel zu dwm. Damit einher ging der Wechsel auf eine ganz andere Ebene von Customization: Bei dwm besteht die einzige Möglichkeit darin, den C-Quellcode zu bearbeiten. So lernt man dann zwangsläufig ein bisschen etwas darüber, was da hinter den Kulissen eigentlich passiert. Einfache Xlib-Aufrufe, einfaches Zeichnen, derlei Dinge.

Leider ist es immer schwierig, Code zu verstehen, den man nicht selbst geschrieben hat. Zudem ist dwms Quellcode ziemlich kompakt und manchmal nicht einfach zu verstehen. Es gibt wenige Kommentare. Daher fiel es mir auch nach einigen Jahren dwm-Benutzung und -Anpassung immer noch schwer, zu verstehen, was da eigentlich vor sich geht.

In 2014 habe ich dann angefangen, meinen eigenen Window Manager zu schreiben. Ich wollte einfach wissen, wie so ein Ding funktioniert. Wie kommunizieren Clients mit dem WM? Was zum Kuckuck sind „Transient Windows“? Was ist ICCCM und was EWMH? Das und viele weitere Fragen.

Im Prinzip ist das auch genau der Grund, weshalb es sich lohnt, selbst einen WM zu schreiben: Man lernt, wie Dinge funktioniert. Manchmal lernt man auch, wieso sie so funktionieren.

Gründe für mein Scheitern in 2014

Leider hatte ich mich damals entschieden, einen Reparenting Window Manager zu schreiben. Überall kann man lesen, dass nahezu alle Window Manager dieses Modell umsetzen, also muss das doch der richtige Weg sein, oder nicht? Außerdem wollte ich endlich richtige Fensterdekorationen haben statt der einfachen einfarbigen dwm-Rahmen. Insbesondere musste eine Titelleiste her, die mit dwm nicht umzusetzen ist, zumindest nicht in der von mir gewünschten Form. Wo man auch hinschaut, die Leute erzählen, man brauche Reparenting für Dekorationen.

Es hat sich dann aber herausgestellt, dass das gar nicht so einfach umzusetzen ist. Reparenting sorgt für viel zusätzliche Komplexität.

Die Krönung ist, dass Reparenting die typischen Probleme nicht auf magische Art und Weise löst. Man nehme Java als Beispiel. Wenn das unter einem Non-Reparenting WM läuft, dann hat man nur ein graues Fenster. Schreibe ich also einen Reparenting WM, dann muss das doch gelöst sein, oder? Nein, ist es nicht. Ich hatte entweder Java-Fenster, die zur Hälfte grau waren, oder völlig fehlplatzierte Menüs.

Das war reichlich frustrierend und ich habe das Projekt dann zügig wieder aufgegeben.

Neuer Versuch im Dezember 2015

Eineinhalb Jahre später habe ich immer noch meine stark angepasste Version von dwm benutzt. Ich war damit auch eigentlich zufrieden. Trotzdem nagte es weiter an mir, dass ich das Programm nicht vollständig verstand.

Drei Wochen Urlaub. Das ist wichtig zu erwähnen, weil ich es sonst nicht geschafft hätte. Seinen ersten WM zu schreiben, ist ein schwieriges Unterfangen, man muss viel lesen und viel denken. Man muss die Freiheit haben, den Rechner mal stehen zu lassen und stattdessen einen langen Spaziergang zu machen, um seinen Kopf wieder freizukriegen. Ich schätze, sowas kriegt man einfach nicht hin, wenn man jeden Tag zur Arbeit gehen muss – ganz egal, wie sehr man seinen Job mögen mag. Wenn man nach Hause kommt, ist man müde und will sich definitiv nicht mehr durch endlose Xlib-Dokus aus den 1990ern kämpfen.

Mein ursprüngliches Projekt ist in die Hose gegangen und die Tatsache, dass ich einen Reparenting WM schreiben wollte, war dafür ausschlaggebend. In der Zwischenzeit hatte jemand auf der suckless-Mailingliste vorgeschlagen, das einfach sein zu lassen. Warum malt man die Dekorationen nicht einfach in eigens dafür erzeugte Fenster und fertig? Warum sich überhaupt mit Reparenting rumschlagen?

Also habe ich mich dazu entschlossen, das nun einfach mal zu versuchen. Einen Non-Reparenting WM mit Dekorationen schreiben und dann schauen, was passiert.

Lange Rede, kurzer Sinn: Das ist überhaupt kein Problem.

Mein Punkt ist auch, dass Reparenting eine schlechte Idee ist, wenn man seinen ersten WM schreibt. Man weiß da einfach noch nichts über Xlib und X11. Lässt man Reparenting weg, wird plötzlich alles viel einfacher. Jetzt kann man die Aufgabe stemmen. Für Anfänger ist Reparenting also definitiv gar nicht zu empfehlen. Später, wenn man mal weiß, was überhaupt so im X11-Land los ist, kann man immer noch von vorne anfangen und das alles mit Reparenting nochmal neu schreiben.

Das Ergebnis: katriawm

katriawm habe ich auf GitHub gestellt. Hier ist ein Screenshot und noch einer. Kurz zusammengefasst die aktuellen Features aus der README:

- Tiling and floating
- Title bars and theming
- Per-screen workspaces, per-workspace layouts
- Application rules, layout save slots (during runtime)
- No sloppy focus
- Basic ICCCM and EWMH support
- Non-reparenting

Wer sich fragt, wieso das Ding „katriawm“ heißt, der Möge einen Blick in die volle README werfen.

xpointerbarrier ist ein Projekt, das mit dem Thema zu tun hat. Vorher war diese Funktionalität in meinem dwm fest drin: Man kann Barrieren um den Arbeitsbereich herum erstellen, die der Mauszeiger nicht überqueren kann. Das ist nützlich, um die Usability zu erhöhen, da der Mauszeiger nicht mehr plötzlich zu einem anderen Monitor springen kann.

Lessons learned: X11 und dwm

Zuallererst, Xephyr war äußerst nützlich in der frühen Entwicklungsphase.

Wie ich oben geschrieben habe, war eine Motivation, dwm besser zu verstehen. Jawoll, dieses Ziel habe ich erreicht. Viele Dinge ergeben plötzlich Sinn. Rückblickend sind auch einige meiner dwm-Patches ganz fürchterlich. Außerdem ist dwm gar nicht so kompliziert, wie ich eingangs dachte. Der Code ist zwar kompakt, aber das ergibt sich einfach automatisch, falls man weiß, wie sich ein WM denn zu verhalten hat.

Man mag jetzt den dwm-Devs Faulheit vorwerfen. :-) Es könnten mehr Kommentare da im Code sein. Auf der anderen Seite: Man dokumentiert auch nicht, wie man eine Datei öffnet, oder? Mit Dateideskriptoren hat man als Programmierer einfach vertraut zu sein. Analog gibt es dann in dwm auch keine Erklärung, was eine „Client Message“ sein könnte.

Dokumentation ist äußerst teuer. Ich kann nachvollziehen, dass ein Projekt, das sich an „elitäre“ Benutzer richtet, nicht bei Adam und Eva mit den Erklärungen anfängt. Auf der anderen Seite weiß ich jetzt die i3wm-Doku viel mehr zu schätzen.

Apropos „Client Messages“: Über diese Dinge habe ich viel gelernt. Wie können Clients mittels X11 miteinander kommunizieren? Was sind „Properties“, was sind „Atoms“? Diese Fragen wurden beantwortet.

katriawm hat einen Client, katriac, da sich der Hauptprozess überhaupt gar nicht um Benutzereingaben kümmert (mehr dazu in der README und in der INTERNALS.md). Dieser Client spricht nur via X11 mit dem Window Manager. Keine Named Pipes, keine UNIX Sockets, kein DBus. Es wird einfach nur eine X11 Client Message an den WM geschickt. Das ist sogar ziemlich nahe an dem, was normale X11-Clients tun, um EWMH-Nachrichten an den WM zu schicken. katriac ist also nur ein ganz normaler X11-Client. Ich finde das ziemlich schick, weil man viel Code und Komplexität einspart, da X11 die ganze Authorisierung und dreckige Arbeit für einen übernimmt. X11-Forwarding über SSH? Kein Problem. Ist X11 über TCP auf einer anderen Maschine erreichbar? Kein Problem. Mehrere X-Sessions und laufende Instanzen des WMs? Klar. Das ist vielleicht im Alltag nur begrenzt nützlich, aber diese Client-Form fügt sich einfach auf natürliche Art und Weise in X11 ein.

Herbstluftwm hat einen ähnlichen Client, der aber ein bisschen anders funktioniert, weil X11 Client Messages recht eingeschränkt sind. Man kann nur 20 Bytes an Payload übermitteln. Für katriac reicht das aber locker. bspwm auf der anderen Seite nutzt dann schon UNIX Sockets.

Was sind Client Messages nun? X11 denkt in „Events“ und schickt diese zu einem Client oder von Client zu Client. Wenn ein Client zum Beispiel ein Fenster neu zeichnen muss, dann schickt ihm der Server ein Expose-Event. ClientMessages sind ein bestimmter Typ von Event, sozusagen universelle Nachrichten für beliebige Zwecke. Ein Beispiel: Der Benutzer möchte ein Fenster schließen. Der WM schickt dann eine Client Message an dieses Fenster und bittet es darum, dass es sich doch schließen möge. Das ist übrigens Teil der ICCCM-Konventionen, der nahezu alle X11-Clients folgen. Die sind sogar schon so alt, dass es viele ICCCM-spezifische Hilfsfunktionen in der Xlib gibt. Der deutlich neuere Satz an Konventionen, EWMH, ist allerdings gar nicht in der Xlib enthalten. Das macht aber auch nichts, da der X-Server Client Messages ohnehin nicht interpretiert – das ist alles „oben drauf“ implementiert. Man kann daher auch ganz einfach seine eigenen Nachrichtentypen einsetzen und genau das tut katriac.

X11-Properties sind jetzt auch keine nebulösen Dinge mehr. Das sind, einfach gesagt, beliebige Daten, die an ein Window geheftet werden können. Wichtig auch hier: X11 interpretiert diese Daten nicht. An der Shell kann man die Properties mit xprop auslesen. Da sieht man dann zum Beispiel, dass der Fenstertitel nur eine Property ist. X11 hat keine Ahnung von Fenstertiteln – die sind Teil von ICCCM und teilweise EWMH. Ich kann jetzt auch dwms Statusbar viel mehr schätzen, da diese nur eine Property vom root-Window (ein besonderes Fenster, das immer da ist und leicht gefunden werden kann) liest. Das ist noch nicht einmal eine dwm-spezifische Lösung, sondern könnte allgemein eingesetzt werden. Leider kenne ich keine externe Bar, die so funktioniert.

Apropos Bar: katriawm selbst zeichnet keine Bar. Stattdessen setzt es eine Property am root-Window, die dann von externen Tools ausgelesen werden kann. Mein aktuelles Bash-Skript zum Auslesen benötigt zwar noch Liebe oder einen Rewrite, aber vom Prinzip her gefällt mir diese Idee sehr gut, weil es eine sehr lose Kopplung ist.

Mit dwm hatte ich irgendwann akzeptiert, dass es keine Titelleisten an den Fenstern gibt (eigentlich schon zu awesome-Zeiten). Damit kann man schon leben. Trotzdem sind die Dinger äußerst hilfreich. Mit katriawm habe ich die nun wieder und ich möchte sie nicht mehr missen. Man braucht zwar ein bisschen Code dafür, klar, aber ich denke, dass es das wert ist.

Größere Hürden gab es bei Focus und der Focus History. Wann muss der WM welches Fenster fokussieren? Da gibt es sehr viele Sonderfälle und Ausnahmen. Man muss sich sogar mit Anwendungen rumschlagen, die den Fokus klauen. Fokus ist einer der wenigen Bereiche, in denen der Window Manager nicht das letzte Wort hat, wodurch es reichlich schwer wird, eine bestimmte Policy durchzusetzen. Welche Datenstruktur benutzt man, um die History zu speichern? Letztendlich ist mir aber eine, wie ich finde, nette Lösung eingefallen, die es einem erlaubt, den kompletten Focus Stack über mehrere Workspaces und Monitore hinweg zu erhalten. Sprich, wenn man auf einen anderen Bereich wechselt, dann kann man das Fenster auswählen, das zuletzt auf diesem Workspace selektiert war.

Der ominöse „Fullscreen Mode“ wurde auch enthüllt. Ich hatte das vorher nie so richtig verstanden. Es ist nämlich so, dass nicht jeder Window Manager es dem Benutzer erlaubt, beliebige Fenster in den Fullscreen Mode zu schicken – das ist doch Beweis genug dafür, dass dieser Modus etwas ganz besonderes ist, oder? Sonst könnte das doch jedes Fenster. Vielleicht muss die Anwendung das unterstützen? Vielleicht ist das ein ganz anderer Fenstertyp? Nun habe ich gelernt: Clients schicken einfach eine EWMH-Nachricht an den WM und bitten ihn darum, Fullscreen Mode zu aktivieren. Der WM geht dann hin, versteckt alle Dekorationen und verändert die Fenstergröße. Dann setzt er noch eine Property am Client-Fenster, um ihm zu sagen: „Erledigt.“ Das war’s. Das heißt im Umkehrschluss, dass es keinen (technischen) Grund gibt, nicht einfach jedes Fenster in Fullscreen setzen zu können. GIMP, xfontsel oder xeyes? Warum nicht.

Ich glaube übrigens, dass es einfacher ist, einen Window Manager zu schreiben als einen Client, der sich vernünftig verhält (sofern es kein trivialer Client ist). Das Mindset der X11-Entwickler scheint mir zu sein: „Clients haben keine Rechte. Die sind Sklaven des Window Managers.“ Clients müssen mit allem zurechtkommen, was ihnen der WM aufdrückt. Die können nach einem 300x300 Pixel großen Fenster fragen, kriegen dann aber 200x800 Pixel und zusätzlich ist das Fenster 2000 Pixel von der Stelle weg, wo sie dachten, dass es hingehöre. Clients müssen mit Non-Reparenting, Reparenting, doppeltem Reparenting und sonst allem möglichen zurechtkommen. Zu jeder beliebigen Zeit kann ein Fenster sogar „unmapped“ werden und ist dann nicht mehr sichtbar. Das muss insgesamt recht lästig sein. Ein Window Manager hingegen hat viele Freiheiten und kann quasi tun, was er möchte.

Lessons learned: Softwareentwicklung

Ich bin beruflich kein Softwareentwickler. Das mache ich nur in meiner Freizeit. Viele der Projekte, an denen ich privat arbeite, sind sehr einfach. Da muss man wenig bis gar nichts planen. Vereinfacht gesagt: Normalerweise programmiere ich einfach drauf los, sobald die Idee im Kopf hinreichend gereift ist.

katriawm ist jetzt kein gigantisches Projekt und das wusste ich auch vor Beginn schon. Im Moment hat es ungefähr 3500 Zeilen Code (wc -l). Trotzdem wollte ich diesmal etwas anderes ausprobieren. Ich habe die Arbeit in zwei Zyklen aufgeteilt: Implementierung und Wartung. Das erinnert ein bisschen an das Modell, was bei der Entwicklung des Linux-Kernels verwendet wird.

So lief das ab:

  1. Ich stelle eine Liste von Features zusammen, die ich gerne einbauen würde. Diese Liste sollte „nicht zu groß“ werden, damit ich den Überblick nicht verliere.
  2. Ich baue die Features ein.
  3. Fällt mir zwischendrin eine Idee für ein tolles neues Feature ein, dann schreibe ich das in die Liste für den nächsten Zyklus.
  4. Sind alle Features fertig, dann beginnt der Wartungszyklus. In dieser Zeit werden nur Bugs gefixt und die Codequalität verbessert.
  5. Goto 1.

Es gibt nur eine schwache zeitliche Beschränkung: Der Wartungszyklus sollte mindestens ein oder zwei Tage lang sein. Das klingt jetzt nach wenig, wenn man Urlaub hat, ist das aber recht viel. ;-)

Das ist also ein einfaches Modell und eigentlich auch ein recht offensichtliches. Funktioniert gut und fühlt sich gut an. Ähnliche Ideen findet man auch in den heutigen beliebten Philosophien zu Softwareentwicklung. Außerdem hat es einen netten Seiteneffekt: Man denkt mehr darüber nach, welche Features man umsetzen will. Ist das jetzt ein wichtiges Feature, was im nächsten Zyklus eingebaut werden sollte, oder kann das warten?

In den Wartungszyklen ist eine gute Angewohnheit, den Code einfach von oben nach unten durchzulesen. Einfach mal lesen. Man wird viele kleine Details und Unstimmigkeiten entdecken, die man mal aufräumen könnte. Und weil das gerade ein Wartungszyklus ist, „darf“ man vieles davon auch gleich beheben.

Ich habe eine TODO Datei. Dort wurde das Projekt organisiert (leider habe ich die Datei erst recht spät committed, weil ich erst meinte, dass das nicht ins Repo gehört). Ich habe keine externe Software benutzt und auch nicht den GitHub Issue Tracker (weil das Projekt noch gar nicht öffentlich war und ich keinen bezahlten Account habe). Ich finde, diese Datei hat gut funktioniert. Die TODO-Liste direkt beim Code behalten – aber nicht im Code. Früher hatte ich TODO-Items im Code in Kommentaren hinterlegt und dann TaskList.vim benutzt, um die Dinger anzuzeigen. Das ist auch meistens okay, funktioniert aber nicht mit den beiden Zyklen. Außerdem übersieht man dann doch häufig Items.

Unbekanntes am Horizont

katriawm ist Non-Reparenting, also habe ich mich nicht um diese Baustellen gekümmert. Warum bin ich bei meinem ursprünglichen Versuch gescheitert? Warum machen so viele WMs Reparenting? Sollte ich das nochmal probieren?

Außerdem ist der Support für EWMH und natürlich ICCCM recht eingeschränkt. Ich schreibe „natürlich“, weil ICCCM lang, komplex und in vielerlei Hinsicht veraltet ist. Ich werde mich vermutlich erst um die wichtigeren Teile von EWMH kümmern. (Obwohl katriawm notwendigerweise inkompatibel zu EWMH ist, siehe dazu INTERNALS.md.)

Ich hatte auch keinen Kontakt mit Colormaps oder Visuals. Vielleicht, weil es eben doch schon 2016 ist. Selections – also „die Zwischenablage“ – könnten auch sehr interessant sein.

Abschluss

So und was mache ich jetzt? Natürlich bleibe ich dran und werde mehr über X11 lernen. Aber was wird aus katriawm? Wird das mein normaler Window Manager? Wird das vielleicht sogar ein erfolgreiches Projekt, was von anderen Leuten benutzt wird? Keine Ahnung.

Man darf nicht vergessen, dass es ein Lernprojekt ist. Eines der wenigen Projekte von mir, die kein bestimmtes Problem lösen. Ich habe das einfach nur gemacht, weil es mich interessiert hat. Ich schätze, katriawm sollte man entsprechend behandeln.

Ich möchte jeden Lesenden ermutigen, selbst einen eigenen Window Manager zu Lernzwecken zu schreiben. Das war und ist eines der interessantesten Projekte, die ich je gemacht habe. :-) Klar, das braucht Zeit und viel Energie. Aber man kann es machen. Also, man kann es machen, weil alles freie Software ist. Jedes Zahnrad kann inspiziert und durch eigene Software ersetzt werden. Was wäre eine bessere Grundlage zum Lernen?

Ihr sitzt da nicht vor einer Black Box.

Comments?