Menadżer okien (ang. window manager, WM) – brzmi dumnie i bynajmniej nie wydaje się programem prostym do napisania. Okazuje się jednak, że stworzenie bardzo prostej aplikacji tego typu to nie żadne rocket science. Przy wykorzystaniu biblioteki Xlib, odpowiadającej za komunikację z serwerem X, implementacja nieskomplikowanego menadżera okien zajmie mniej niż 100 linii kodu.

Cel

Można oczywiście zadać pytanie „po co?”. Wiadomo, że do napisania jakiegokolwiek użytecznego (nawet do własnych zastosowań) WM droga jest dosyć długa, ale osobiście dostrzegam sens również w tworzeniu takich zabawkowych i skrajnie uproszczonych form różnych aplikacji. Nawet jeśli nie podchodzimy do nich z myślą, że staną się zaczątkiem czegoś bardziej rozbudowanego, to i tak zyskujemy pewną wiedzę. Co ważne, jest to wiedza praktyczna, a nie tylko teoretyczna i może przydać się w najrozmaitszych sytuacjach, począwszy od rozwiązywania nowych problemów programistycznych, aż po większe zrozumienie błędów zwracanych przez aplikacje zewnętrzne (np. po dzisiejszej przygodzie z menadżerem okien komunikat „cannot open display”, który można napotkać w różnych programach, stanie się mniej zagadkowy.

Założenia

Przechodząc do meritum, chciałbym zaznaczyć, że inspiracją do niniejszego artykułu stało się dla mnie spotkaniem z TinyWM – minimalistycznym menadżerem okien stworzonym przez Nicka Welcha [1]. Na swojej stronie Nick prezentuje dwie bliźniacze wersje tej aplikacji zaimplementowane w dwóch różnych językach: C oraz Pythonie. Oczywiście obie używają wspomnianej już biblioteki Xlib, stąd różnice między nimi są jedynie kosmetyczne i obie wersje zawierają tę samą logikę. Obejmuje ona przesuwanie okien oraz zmienianie ich rozmiaru. Świetny punkt wyjścia, żeby pobawić się w dorobienie we własnym zakresie chociaż jednej dodatkowej funkcjonalności. Jakiej? Menadżer okien, jak sama nazwa wskazuje, ma za zadanie przede wszystkim zarządzać oknami. Nie jest odpowiedzialny za ich rysowanie ani tworzenie, ale powinien obsługiwać wszelkie operacje na nich, takie jak powiększanie, minimalizowanie i zmienianie ich pozycji. Może też dekorować je różnymi elementami, np. paskiem tytułu, oraz dodawać efekty graficzne.

X Window System

Zanim zajmiemy się kwestiami praktycznymi, warto poświęcić chwilę na zapoznanie się z podstawami X Window System (znanego też pod nazwami X11 oraz po prostu X). Jest to bowiem system okien, dla którego będziemy tworzyć nasz menadżer. Początki „X-ów” sięgają roku 1984, a więc nie jest to już najnowsza technologia, jednak póki co wciąż obecna w większości dystrybucji GNU/Linux. Wprawdzie niektórzy wieszczą już koniec X11 i całkowite zastąpienie go przez nowe rozwiązania, takie jak np. Wayland, to raczej nie zapowiada się, by stare dobre iksy zniknęły z dnia na dzień [2].

Czym zatem jest ów X Window System? Systemem okien, czyli, najprościej mówiąc, komponentem graficznego interfejsu użytkownika, odpowiedzialnym za obsługę urządzeń wejścia/wyjścia (klawiatury, myszy) oraz grafiki [3][4]. Tworzy on okna i pozwala aplikacjom na rysowanie w nich (czym zajmują się biblioteki widżetów, takie jak Qt), zaś menadżerom okien na zarządzanie nim (obsługę przesuwania, zmiany rozmiaru itd.). Można powiedzieć, że system okien jest warstwą, która oddziela menadżera okien od sprzętu, obsługując podstawowe operacje graficzne – rysowanie linii, czy wyświetlanie napisów.

logo serwera X.Org - jednej z implementacji X Window System
Logo serwera X.Org – najpopularniejszej implementacji X Window System

System X implementuje architekturę klient-serwer – składa się z serwera, realizującego opisane powyżej funkcje związane z tworzeniem podstawowych elementów graficznych i obsługą sprzętu, oraz klienta, który może wysyłać wiadomości z żądaniem wykonania operacji na oknach (stworzeniem, zamknięciem, zmienieniem rozmiaru itp.). Takim klientem jest dowolna aplikacja, posiadająca interfejs graficzny – np. przeglądarka internetowa, edytor tekstu, emulator terminala. Nie tylko przesyła ona żądania do serwera, ale też otrzymuje od niego różne powiadomienia, m.in. o naciśniętych lub puszczonych klawiaszach oraz przyciskach (zdarzenia KeyPressKeyRelease, ButtonPress, ButtonRelease).

A teraz najciekawsze – menadżer okien jest w X11 zwykłym klientem. W kontekście całego systemu nie on posiada żadnych wyjątkowych uprawnień, jedyne co go wyróżnia, to dostęp do specjalnego API, przy pomocy którego komunikuje się on z serwerem X. Dostęp ten jest przyznawany pierwszemu klientowi, który o niego poprosi, a każdy kolejny, który próbowałby to zrobić, otrzyma odmowę (no bo kto to widział, żeby w jednym systemie współpracowały dwa niezależne menadżery okien). [5]

Kod początkowy

Kod, od którego zaczniemy naszą zabawę z menadżerami okien liczy zaledwie 50 linii w C lub nieco ponad 30 jeśli weźmiemy pod uwagę implementację stworzoną w Pythonie. W niniejszym wpisie będę bazował na tym napisanym w języku C. Dostępny jest on na stronie Nicka Welcha [1] w dwóch wersjach – zwykłej oraz zawierającej dokumentację w postaci komentarzy autora, wyjaśniających poszczególne fragmenty kodu. Uważam, że znajduje się tam kilka przydatnych informacji, dlatego dla kompletności tego artykułu kokusiłem się o ich przetłumaczenie. Jeśli dostrzegasz gdzieś błąd w tłumaczeniu, zachęcam do dokonania poprawki, tworząc issue lub pull request na GitHubie:

Uruchamianie

Środowisko, w którym postanowiłem testować tworzony menadżer okien to dystrybucja Xubuntu (działająca wewnątrz maszyny wirtualnej), w związku z czym kolejne kroki pokażę na przykładzie tego właśnie systemu. Wszystkie pliki, o których będę pisał, można znaleźć w moim repozytorium na GitHubie.

Pierwszym pytaniem, które nasuwa się na myśl po zobaczeniu kodu źródłowego tinyWM to jak w ogóle uruchomić taką aplikację? Zacznijmy od skomplikowania – ponieważ wykorzystujemy bibliotekę X11, musimy poinformować linker o tym fakcie, w związku z czym użyjemy komendy:

Jeśli w Waszym systemie nie istnieje wyżej wspomniana biblioteka, należy ją doinstalować – w przypadku (X)Ubuntu zrobimy to poprzez sudo apt install libx11-dev (aczkolwiek nazwa paczki może różnić się w zależności od dystrybucji, np. w Fedorze występuje ona jako libX11-devel).

Oprócz samego pliku wykonywalnego (który powinniśmy skopiować do katalogu /usr/bin) potrzebujemy jeszcze skryptów związanych z sesją menadżera okien [6]:

  • /usr/bin/polydev-wm-session
  • /usr/share/xsessions/polydev-wm-session.desktop

W pierwszym z nich definiujemy m.in. to, jakie pliki mają być wczytywane oraz jakie aplikacje włączane przy uruchamianiu sesji. W naszym przykładzie menadżer okien przywita nas jednym okienkiem terminala, zaś dzięki przekierowaniu strumieni wyjściowych do pliku, będziemy mogli sprawdzić np. komunikaty błędów jeśli coś w trakcie działania WM pójdzie nie tak.

Drugi plik opisuje skrót, który pojawi się na ekranie logowania, i którego wybranie pozwoli nam uruchomić naszego menadżera okien (zob. rys. poniżej). Po przygotowaniu wszystkiego co powyżej pozostaje nam wylogować się z aktualnie używanego środowiska graficznego, przełączyć WM, a następnie zalogować.

menu wyboru menadżera okien
Menu wyboru menadżera okien

Po zalogowaniu naszym oczom ukazuje się prawie całkowita pustka. Brak belek tytułowych nad oknami, brak jakichkolwiek pasków itp. Warto więc wiedzieć, że aby wylogować się z tak prostego menadżera okien, możemy po prostu zabić jego proces:

Testując działanie WM, pamiętajmy, że zmiana rozmiaru oraz położenia okien, która zaimplementowana jest w tinyWM działa z wciśniętym przyciskiem myszy oraz klawiszem ALT. Samemu zdarzyło mi się o tym zapomnieć pewnego razu i spędziłem trochę czasu na debugowaniu nieistniejącego problemu. 😉

Więcej skrótów

Moim pomysłem na krótką zabawę z tinyWM było po prostu dorzucenie obsługi kilku dodatkowych skrótów klawiaturowych. Na pierwszy ogień poszło ALT+F4, a poszukiwań informacji na temat potencjalnych rozwiązań rozpocząłem na stronie Tronche.com, zawierającej przygotowaną przez Christopha Tronche wersję The Xlib Manual.

Początkowo spróbowałem użyć funkcji XDestroyWindow – okno rzeczywiście zamykało się, ale oprócz tego pojawiały się komunikaty błędów. Dlaczego? Ponieważ w ten sposób w dość brutalny sposób (jak zresztą wskazuje sama nazwa – destroy) wyłączamy okno, ale nie wyłączamy klienta (czyli programu), który to okno utworzył.

Znacznie bardziej eleganckim rozwiązaniem jest wysłanie do klienta wiadomości WM_DELETE_WINDOW, która stanowi uprzejmą prosbę o zamknięcie okna [7]:

Oczywiście istnieje ewentualność, że program nie wspiera takich próśb i w takim wypadku porządny menadżer okien powinien dotrzeć do niego w jakiś inny sposób, np. funkcją XKillClient, która zamknie klienta i wyczyści jego zasoby (takie jak otwarte okna). Przykładowy WM, który sprawdza taką sytuację, można znaleźć pod tym linkiem.

Skoro mamy już zaimplementowane zamykanie okien, to przydałoby się zrobić również coś odwrotnego – użyć skrótu klawiszowego do dokonania dzieła kreacji zamiast destrukcji. W tym celu postanowiłem zaimplementować obsługę popularnej w wielu środowiskach kombinacji CTRL+ALT+t, służącej do uruchamiania terminala.

Aby powyższa kombinacja była przechwytywana dodałem takie oto wywołanie:

U mnie działa, ale… jak słusznie zauważa Steve Kemp w swoim fragmencie kodu, taki kod nie zadziała jeśli dodatkowo będzie na naszej klawiaturze uaktywniony np. NumLock albo CapsLock, więc aby w pełni obsługiwać nasze CTRL+ALT+t, trzeba dodać jeszcze parę linijek.

I to byłoby na tyle w kwestii mojego „udoskonalania” TinyWM. Jeśli Wy nie planujecie w tym momencie zakończyć swojej przygody z Xlib, to pozwolę sobie polecić program o nazwie XEV – event tester, który jest niezwykle pomocny przy developmencie X-owych aplikacji.

Dekorowanie okien i inne ficzery

Co jeszcze można dołożyć do tak prostego WM? Widząc okna pozbawione znajomej nam belki tytułowej zapewne większość osób, jako o jednej z pierwszych funkcjonalności w kolejce do zaimplementowania pomyśli o dekoracji okien – dodaniu do nich paska z tytułem, może również z paroma przyciskami (minimalizacja, zamykanie, powiększanie/pomniejszanie). Niestety okazuje się, że nie jest to już takie trywialne zadanie.

Sposoby są różne, a jednym z nich jest tzw. reparenting, polegający na tworzeniu dla każdego okna dodatkowego okienka – zawierającego właśnie belkę tytułową lub inne elementy ramki  – i uczynienie z niego rodzica pierwotnego okna. Parę informacji na ten temat można znaleźc np. w tej dyskusji. Na szczęście nie jest to jedyny rozwiązanie, jednym z alternatywnych jest tworzenie dodatkowych okien z paskami tytułowymi, ale bez dokonywania reparentingu. Takie podejście zaimplementowane zostało w menadżerze KatriaWM, a autor tego oprogramowania opisał swoje przejścia na blogu (polecam!).

Garść pomysłów jeśli chodzi o funkcjonalności do zaimplementowania w WM można również znaleźć w podręczniku na Wikibooks, poświęconemu X11 [8].

Zastosowanie

Oprócz niewątpliwego zastosowania edukacyjnego, TinyWM można użyć np. w kiosku internetowym, co proponuje w swoim wpisie jeden z blogerów [9]. Co ambitniejsi mogą oczywiście pokusić się o stworzenie menadżera okien do własnego codziennego użytku, ale trzeba liczyć się z tym, że wymagany wkład czasowy będzie naprawdę spory.

Źródła

  1. incise.org: tinywm, Nick Welch.
  2. X.Org a Wayland — czyli stary król i młody następca tronu.
  3. Wikipedia: System okien.
  4. Wikipedia: X Window System.
  5. How X Window Managers Work, And How To Write One (Part I), Chuan Ji.
  6. Debian.org: TinyWM.
  7. StackExchange: Is closing the window of a X client application process necessarily followed by terminating the process?
  8. Wikibooks: Features and Facilities of Window Managers.
  9. Tinywm – najlżejszy menadżer okien na świecie, mati75, 2010.
  10. Possum – simple window manager based on TinyWM.