読むWM "tinyWM" をちゃんと読む

by ぺるき @PerukiFUN

TOGATTA SERVER LT Vol.4 - 2023/9/30

読むWM "tinyWM" をちゃんと読む TOGATTA SERVER LT Vol.4 (2023/9/30)

自己紹介

多田 瑛貴 ただ てるき

公立はこだて未来大学 システム情報科学部 2年

X @PerukiFUN
GitHub TadaTeruki
HP portfolio.peruki.dev

読むWM "tinyWM" をちゃんと読む TOGATTA SERVER LT Vol.4 (2023/9/30)

自己紹介

地図とパソコンが好きです

主な興味

  • LTサークル Mariners' Conference 部長
    • 最近の活動: #函館市電LT
  • 手続き的生成 (CG)
    • 特に地形生成
  • 地理情報システム
  • Linuxデスクトップ
読むWM "tinyWM" をちゃんと読む TOGATTA SERVER LT Vol.4 (2023/9/30)

今回のお話

Linux系のGUIの中身のお話

X11における
「WM(ウインドウマネージャ)」
を、実際に読んで理解しよう

読むWM "tinyWM" をちゃんと読む TOGATTA SERVER LT Vol.4 (2023/9/30)

X11 (X Window System) とは

Unix、Linux系OSでよく使われる
ウィンドウシステムの一つ

  • GUI環境の基礎を提供
    • ウインドウの機能など
  • 入力デバイスの管理
  • ディスプレイ(出力デバイス)への出力

"X11"そのものは何らかのソフトウェアを指さない

読むWM "tinyWM" をちゃんと読む TOGATTA SERVER LT Vol.4 (2023/9/30)

X11の構造

クライアントサーバーモデルに基づいている

  • Xサーバー
    • ディスプレイへの出力
    • キーボードやマウスの入力の受け取り
  • Xクライアント
    • 主に、各GUIアプリケーション

これらソフトウェアがXプロトコルを通してやりとりする

読むWM "tinyWM" をちゃんと読む TOGATTA SERVER LT Vol.4 (2023/9/30)

Xサーバーについて

  • Xクライアントをディスプレイに描画
  • キーボードやマウスから入力を受け取り、Xクライアントに送る

一方で、ウインドウの位置関係・前後関係などを
よしなに設定してくれる機能は持たない

GUIの実現には、ユーザーの操作をそれらに反映する
橋渡し的な存在が必要 → WM(ウインドウマネージャ) が用いられる

読むWM "tinyWM" をちゃんと読む TOGATTA SERVER LT Vol.4 (2023/9/30)

WM (ウインドウマネージャ)

特別なXクライアントで、主に以下を担当

  • ウインドウの位置関係・前後関係の管理
    • 受け取った入出力を反映させる
  • ウインドウの装飾
    • ウインドウのタイトルバーなど

自作できるとめちゃめちゃロマンがある

読むWM "tinyWM" をちゃんと読む TOGATTA SERVER LT Vol.4 (2023/9/30)

一般的に利用されるWM

基本的にはOS(Windows, MacOS)や

デスクトップ環境(GNOME, KDEなど)に
紐付いている

一方で、Linuxでは
i3awesomeicewmswayなど
独立したWMも使われている

画像は自分の環境のi3

読むWM "tinyWM" をちゃんと読む TOGATTA SERVER LT Vol.4 (2023/9/30)

tinyWMとは

https://github.com/mackstann/tinywm

このWMを50行のCコード
ミニマムに実装したもの
実用には耐え難いが、ちゃんと動く!

読むWM "tinyWM" をちゃんと読む TOGATTA SERVER LT Vol.4 (2023/9/30)

tinyWMとは

実装されている機能は以下の3つ:

  • ウインドウの移動
    • 縦横移動 (掴んで動かす)
    • 最前面への移動
  • ウインドウの拡大縮小

タイトルバーなどの装飾は実装しない
画像: https://qiita.com/ai56go/items/dec1307f634181d923f5

読むWM "tinyWM" をちゃんと読む TOGATTA SERVER LT Vol.4 (2023/9/30)

LTの動機

tinyWMのコードリーディングは
WM開発に入門するアプローチの一つ

自分自身もWM開発には興味があるが
こういったのをしっかり精読したかというとそうでもない

X11のウインドウマネージャがどう動いてるのか眺めてみる

読むWM "tinyWM" をちゃんと読む TOGATTA SERVER LT Vol.4 (2023/9/30)

ソースコードを見てみよう

https://github.com/mackstann/tinywm/blob/master/tinywm.c

読むWM "tinyWM" をちゃんと読む TOGATTA SERVER LT Vol.4 (2023/9/30)

基本的な流れ

  1. Xサーバーへの接続
  2. 受け取る入力イベントの「グラブ」
  3. メインループ
    • ユーザーの入力の受け取る
    • 入力の内容をディスプレイに反映

「グラブ」という謎多き操作を説明する日本語の文献が全く見当たらない

読むWM "tinyWM" をちゃんと読む TOGATTA SERVER LT Vol.4 (2023/9/30)

tinyWMの概観

#include <X11/Xlib.h>

int main(void)
{
    ((変数の定義))

    [ ] Xサーバーへの接続
    [ ] 入力イベントのグラブ

    for(;;)
    {
        [ ] ユーザーの入力の受け取る
        [ ] 入力の内容ごとにディスプレイに反映
    }
}
読むWM "tinyWM" をちゃんと読む TOGATTA SERVER LT Vol.4 (2023/9/30)

Xサーバーへの接続

Display * dpy;
if(!(dpy = XOpenDisplay(0x0))) return 1;

XOpenDisplayで初めてXサーバーとの通信が確立される
dpyには、Xサーバーの情報が挿入される NULLだったら異常終了

dpyを他のいろんな関数に引き回しすことで、Xサーバーとのやり取りを行う
Xサーバーとのあらゆるやり取りはDisplay型のメソッドとして提供されると考えるとわかりやすいかも

読むWM "tinyWM" をちゃんと読む TOGATTA SERVER LT Vol.4 (2023/9/30)

tinyWMの概観

#include <X11/Xlib.h>

int main(void)
{
    ((変数の定義))

    [x] Xサーバーへの接続
    [ ] 入力イベントのグラブ

    for(;;)
    {
        [ ] ユーザーの入力の受け取る
        [ ] 入力の内容ごとにディスプレイに反映
    }
}
読むWM "tinyWM" をちゃんと読む TOGATTA SERVER LT Vol.4 (2023/9/30)

入力イベントのグラブ

発生する入力イベントは、基本的にウインドウ側で受理される

しかしWM開発においては、入力の情報をウインドウではなく
WM側で受け取りたいときがある

例えば、ウインドウの移動やリサイズなどのGUIの操作

このとき、WM側で入力を受け取るようにするための操作がグラブ

読むWM "tinyWM" をちゃんと読む TOGATTA SERVER LT Vol.4 (2023/9/30)

入力イベントのグラブ

XGrabKey(dpy, XKeysymToKeycode(dpy, XStringToKeysym("F1")), Mod1Mask,
        DefaultRootWindow(dpy), True, GrabModeAsync, GrabModeAsync);

XGrabKeyAlt+F1をグラブ
ウインドウの最前面移動に使われる

読むWM "tinyWM" をちゃんと読む TOGATTA SERVER LT Vol.4 (2023/9/30)

入力イベントのグラブ

XGrabButton(dpy, 1, Mod1Mask, DefaultRootWindow(dpy), True,
        ButtonPressMask|ButtonReleaseMask|PointerMotionMask, 
        GrabModeAsync, GrabModeAsync, None, None);
XGrabButton(dpy, 3, Mod1Mask, DefaultRootWindow(dpy), True,
        ButtonPressMask|ButtonReleaseMask|PointerMotionMask, 
        GrabModeAsync, GrabModeAsync, None, None);

XGrabButtonAlt+右/左クリックをグラブ

ウインドウの移動とリサイズに使われる

読むWM "tinyWM" をちゃんと読む TOGATTA SERVER LT Vol.4 (2023/9/30)

tinyWMの概観

#include <X11/Xlib.h>

int main(void)
{
    ((変数の定義))

    [x] Xサーバーへの接続
    [x] 入力イベントのグラブ

    for(;;)
    {
        [ ] ユーザーの入力の受け取る
        [ ] 入力の内容ごとにディスプレイに反映
    }
}
読むWM "tinyWM" をちゃんと読む TOGATTA SERVER LT Vol.4 (2023/9/30)

ユーザーの入力の受け取る

XEvent ev;

XEventは様々なイベントを表現する共用体
(このあとイベントの種類によってXButtonEventなどにキャストされる)

    XNextEvent(dpy, &ev);

ユーザーの入力の内容は、イベントとしてXサーバー内にキューされる
XNextEventでイベントをデキューしevに格納する

読むWM "tinyWM" をちゃんと読む TOGATTA SERVER LT Vol.4 (2023/9/30)

tinyWMの概観

#include <X11/Xlib.h>

int main(void)
{
    ((変数の定義))

    [x] Xサーバーへの接続
    [x] 入力イベントのグラブ

    for(;;)
    {
        [x] ユーザーの入力の受け取る
        [ ] 入力の内容ごとにディスプレイに反映
    }
}
読むWM "tinyWM" をちゃんと読む TOGATTA SERVER LT Vol.4 (2023/9/30)

入力の内容ごとにディスプレイに反映

XEvent ev;

ev.typeには入力の種類が格納される (KeyPressButtonPressなど)

これを元に条件分岐

    if(ev.type == ***) ...
    else if(ev.type == ***) ...
    else if(ev.type == ***) ...

入力の種類に応じて、ディスプレイ上のウインドウに対して操作を行う

読むWM "tinyWM" をちゃんと読む TOGATTA SERVER LT Vol.4 (2023/9/30)

KeyPress: ウインドウの最前面移動

    if(ev.type == KeyPress && ev.xkey.subwindow != None)
        XRaiseWindow(dpy, ev.xkey.subwindow);

イベントがKeyPressであり、かつカーソル上にウインドウがあれば
それを最前面に移動する

Alt+F1しかグラブされていないのでこれだけでOK

読むWM "tinyWM" をちゃんと読む TOGATTA SERVER LT Vol.4 (2023/9/30)

ButtonPress: ウインドウの選択

XWindowAttributes attr;
XButtonEvent start;
    else if(ev.type == ButtonPress && ev.xbutton.subwindow != None){
        XGetWindowAttributes(dpy, ev.xbutton.subwindow, &attr);
        start = ev.xbutton;
    }

イベントがButtonPressであり、かつカーソル上にウインドウがあれば

  • ウインドウの情報をattrに挿入する
  • イベントの情報をstartで持っておく
    • いずれも、ウインドウの移動やリサイズのときに使う
読むWM "tinyWM" をちゃんと読む TOGATTA SERVER LT Vol.4 (2023/9/30)

MotionNotify: ウインドウの移動・リサイズ

    else if(ev.type == MotionNotify && start.subwindow != None)
    {
        int xdiff = ev.xbutton.x_root - start.x_root;
        int ydiff = ev.xbutton.y_root - start.y_root;
        XMoveResizeWindow(dpy, start.subwindow,
            attr.x + (start.button==1 ? xdiff : 0),
            attr.y + (start.button==1 ? ydiff : 0),
            MAX(1, attr.width + (start.button==3 ? xdiff : 0)),
            MAX(1, attr.height + (start.button==3 ? ydiff : 0)));
    }

イベントがMotionNotifyであり、かつカーソル上にウインドウがあれば

  • attr start 内の情報をもとにウインドウを移動・リサイズする
    • 移動かリサイズかは、start内に格納されたボタンの種類で判断
読むWM "tinyWM" をちゃんと読む TOGATTA SERVER LT Vol.4 (2023/9/30)

ButtonRelease: ウインドウの選択の解除

    else if(ev.type == ButtonRelease)
        start.subwindow = None;

イベントがButtonReleaseであれば、選択を解除する

読むWM "tinyWM" をちゃんと読む TOGATTA SERVER LT Vol.4 (2023/9/30)

メインループで行うこと

  • キューされた入力イベントを一つ取り出す
  • 入力イベントをもとにディスプレイ上に反映

これを繰り返している

読むWM "tinyWM" をちゃんと読む TOGATTA SERVER LT Vol.4 (2023/9/30)

tinyWMの概観

#include <X11/Xlib.h>

int main(void)
{
    ((変数の定義))

    [x] Xサーバーへの接続
    [x] 入力イベントのグラブ

    for(;;)
    {
        [x] ユーザーの入力の受け取る
        [x] 入力の内容ごとにディスプレイに反映
    }
}
読むWM "tinyWM" をちゃんと読む TOGATTA SERVER LT Vol.4 (2023/9/30)

読めた!

読むWM "tinyWM" をちゃんと読む TOGATTA SERVER LT Vol.4 (2023/9/30)

補足

  • 今回のは入力イベントのみを扱っているが
    他の様々な種類のイベントも同じ流れで処理される
    • 新しいウインドウの作成時や、非表示時、削除時など
  • X11は比較的古い技術
    • 新しく作るならWaylandを使うほうがナウいが...
      入門のハードルはまだ高い

X11は複雑な設計が問題視され、次世代にあたるWaylandに置き換わりつつある
参考: https://wayland.freedesktop.org/architecture.html

読むWM "tinyWM" をちゃんと読む TOGATTA SERVER LT Vol.4 (2023/9/30)

もっといろんなことをするには

  • タイトルバーを実装する
  • ウインドウの移動やリサイズを
    ウインドウの外枠をつまんで行うようにする
    • このあたりを実装しようとすると
      難易度が結構上がる
  • タスクバーやメニュー、ランチャーも作る
    • オレオレデスクトップ環境を作ろう
  • etc...

写真は自分が過去にsway用に作ったdock

読むWM "tinyWM" をちゃんと読む TOGATTA SERVER LT Vol.4 (2023/9/30)

結論

WM開発は意外と簡単に入門できる

みんなもやろう

読むWM "tinyWM" をちゃんと読む TOGATTA SERVER LT Vol.4 (2023/9/30)

参考資料

tinywm
https://github.com/mackstann/tinywm

Linux システムソフトウェア - グラフィックスシステムの概要
https://zenn.dev/hidenori3/articles/c2be2bd50fc8dd

読むWM "tinyWM" をちゃんと読む TOGATTA SERVER LT Vol.4 (2023/9/30)