Integracja konsoli z menedżerem okien

Wcześniej pisałem już, że lubię mniej klikać i z tego względu używam DWM. Wspomniałem też, że cenię w nim prostotę, kod, który można w miarę szybko zrozumieć i zmodyfikować. Ostatnio uznałem, że chciałbym móc zarządzać oknami z poziomu konsoli i skryptów, bo też większość pracy wykonuję w konsoli.

Przypadki użycia

Prawdopodobnie można wymyśleć wiele przykładów użycia takiego mechanizmu. Jednak dwa okazały się na tyle istotne, że postanowiłem je dotąd zrealizować.

Linia statusu edytowana z poziomu skryptów

Najprostszy przypadek jest związany z tym, że czasem przeprowadzam jakieś działania w tle, które są dość czasochłonne (kompilacja, renderowanie filmu, itp.). Nie chcę, często zapomnę, co jakiś czas przełączać okno, żeby sprawdzić jak idą postępy, dzielenie ekranu zaś byłoby przesadą (tym bardziej, że raczej lubię małe ekrany). Najprostszy pomysł jest taki, by móc z poziomu skryptu zaktualizować linię statusu, która wyświetlałaby się obok zegarka.

Pierwsze, co przychodzi na myśl to wystawić nazwany potok, do którego można pisać co się chce i to zostaje wyświetlone w linii statusu, najlepiej dopisane do niej, jeśli jest miejsce (żeby można było wyświetlać kilka wiadomości na raz).

Tym sposobem, można łatwo zaimplementować kontrolę poziomu baterii w następujący sposób (skrypt uruchomiony w tle podczas uruchamiania środowiska):

while :
do
  acpi | awk '{print "BAT" $2 " " $4 $5;}' > /tmp/dwm.in
  sleep 5m
done

Innym razem, chcę odliczać czas:

n=0
while :
do
  echo "minęło $n" > /tmp/dwm.in
  n=$((n+1))
  sleep 1m
done

Kiedyś mogę chcieć kontrolować czas odtwarzania, np. podcastu:

mplayer cos.mp3 > /tmp/dwm.in

Przełączanie okien

Drugi przypadek jest taki, że dość często generuję strony lub piszę aplikacje internetowe, które oczywiście testuję w przeglądarce. Dlatego, przeważnie w kroku budowania projektu mam uruchomienie przeglądarki.

Tyle tylko, że jeśli mam już otwartą przeglądarkę, strona będzie otwarta w istniejącym oknie i będę musiał ręcznie się na nie przełączyć. Oczywiście, mógłbym otworzyć w nowym oknie (np. --new-window w Firefox). Jednak ja chcę, żeby strona otwarła się w istniejącym oknie, gdzie zwykle mam jakieś pomocne strony otwarte. Innym powodem dla takiego rozwiązania jest to, że wolę mieć przeglądarkę na odrębnej przestrzeni roboczej. Gdybym zwyczajnie uruchomił stronę w nowym oknie, uruchomiłoby się w tej samej.

Stąd pomysł, żebym mógł po prostu wydać, z poziomu skryptu, polecenie pokazania okna, które ostatnio zmieniło nazwę (zadziała w 99% przypadków, a jest łatwe do zaimplementowania).

Odbywa się to zwyczajnie poprzez wykonanie prostej komendy:

printf "<f" > /tmp/dwm.cmd

Pewnie, może chciałoby się pełen język skryptowy, z piękną składnią, tylko po co się przepracowywać, tym bardziej, że nie wiem pod jakim kątem rozwijać taki język. Dlatego po prostu, jeden znak to jedna komenda, jak w vimie. < Oznacza przełączenie na okno, które ostatnio zmieniło nazwę, a f ustawia je na pełen ekran, jeśli wcześniej nie było.

Jak to jest zrobione?

Cały kod można znaleźć tutaj. Obsługa nazwanych potoków znajduje się w console.c i console.h, raczej nie ma tu wiele do komentowania. Ot, ustawione są 3 potoki (do tej pory nie został wspomniany potok wyjściowy, np. dla komendy wypisującej wszystkie okna: l) i nasłuchujemy je przy pomocy funkcji poll. W zależności od potoku wejściowego, używana jest jedna z funkcji podanych przy tworzeniu obiektu konsoli (cmdact i msgact).

W funkcji run tworzony jest obiekt konsoli, a działanie sprawdzenia potoków (console_job()) jest dodane do pętli komunikatów. Na samym końcu zamykamy konsolę. Powyżej znajdujemy deklarację funkcji obsługujących zdarzenia na potokach.

Obsługa zdarzenia na strumieniu wiadomości jest prosta, zawartość wczytana ze strumienia zostaje przekopiowana do bufora wiadomości (ewentualnie, zostaje obcięta). Funkcja updatestatus() upewnia się, że zmiana zostanie wyświetlona. W nowszej wersji, wiadomości są doklejona jedna za drugą (w przypadku przepełnienia, oczywiście starsza część jest usuwana.

Druga funkcja, też bardzo prosta, wypadałoby ify zastąpić jakimś bardziej cywilizowanym mechanizmem, ale to na przyszłość. Funkcje takie jak setlayout, focus i view są raczej łatwe do pojęcia, zwłaszcza, że znajdziemy je w config.h przypisane do klawiszy, łatwo sprawdzić je doświadczalnie. Zmienna lastc, jest aktualizowana ilekroć któreś okno zmienia nazwę (jest zerowana gdy okno w nim wskazane jest zamykane).

Typy Monitor i Client odzwierciedlają odpowiednio przestrzenie robocze i okna. Stąd dla funkcji l przechodzimy przez wszystkie okna i wypisujemy ich nazwy. Prosta sprawa.