🔒 Repository is read-only – file editing is disabled.

PaganDE/pagan-compositor/README.md main

341 linii Raw ← Powrót

pagan-compositor

Kompozytor Wayland PaganDE: floating window manager z hybrydowym CSD, zbudowany na Smithay 0.7.

  • Renderer: OpenGL (GlesRenderer, zbudowany na glow).
  • Backend deweloperski: winit (--winit).
  • Menedżer okien: pływający — kaskadowe rozmieszczanie, przeciąganie, zmiana rozmiaru, Z-order.
  • Dekoracje: client-side (wymuszamy zxdg_toplevel_decoration_v1 w trybie client), kompozytor dokłada tylko cień.

Status weryfikacji: kompiluje się (cargo build, cargo build --features udev) i jest uruchamiany na backendzie winit. Realny test wizualny potwierdził: pasek tytułu 40 px z menu i przyciskami na wspólnej osi Y, rogi 12 px, cień GPU, kursor z motywu XCursor (grim -c). Backend tty-udev kompiluje się, ale nie był uruchamiany na sprzęcie z DRM — patrz // WERYFIKUJ:.

Wymagania

  • Rust (rustup, stabilny toolchain) — curl https://sh.rustup.rs -sSf | sh
    • Potrzebne są cargo i rustc. Wiele pakietów dostarcza oba naraz (np. rust w Arch/Manjaro albo pakiet rustc w PaganOS/LFS — sprawdź footprint: obecność usr/bin/cargo). Zainstaluj cargo osobno tylko wtedy, gdy Twoje repo ma je rozdzielone.
    • Wersja: Smithay 0.7 wymaga Rust ≥ 1.80.1; cairo-rs 0.22 (toolkit) wymaga świeżego stabilnego toolchainu (edition 2024). Sprawdź rustc --version. Jeśli masz starszy — użyj rustup default stable albo zaktualizuj pakiet.
  • Podstawowe biblioteki systemowe (dla winit + EGL/GL):
    • Debian/Ubuntu: libegl1-mesa-dev libgl1-mesa-dev libwayland-dev libxkbcommon-dev
    • Fedora: mesa-libEGL-devel mesa-libGL-devel wayland-devel libxkbcommon-devel
  • pkg-config

Smithay jest używany z vendored kopii ../smithay-0.7.0 (offline, powtarzalnie). Jeśli wolisz crates.io, zmień w Cargo.toml na smithay = { version = "0.7", default-features = false, features = [...] }.

Budowanie i uruchomienie

cd pagan-compositor
cargo build
cargo run -- --winit

Po starcie na stdout pojawi się linia:

WAYLAND_DISPLAY=wayland-1

Kompozytor działa jako zwykłe okno na Twoim pulpicie. W drugim terminalu uruchom klienta (np. demo toolkitu) ustawiając ten socket. Zobacz ../pagan-toolkit/README.md.

Logi: RUST_LOG=pagan_compositor=debug cargo run -- --winit.

Architektura

src/
├── main.rs      # wejście, logowanie, wybór backendu
├── winit.rs     # backend dev: okno + pętla główna + render
├── udev.rs      # backend produkcyjny: DRM/KMS + libinput + libseat
├── state.rs     # MyCompositor: stan + handlery protokołów, floating WM
├── output.rs    # monitory: układ, hotplug, konfiguracja, ratowanie okien
├── window.rs    # PaganWindow: okno w Space + cień (per-okno, Z-order-poprawny)
├── focus.rs     # KeyboardFocusTarget / PointerFocusTarget
├── grabs.rs     # move / resize (standardowe xdg_toplevel.move/resize)
├── input.rs     # mysz, klawiatura, Z-order, surface_under, skróty
├── snap.rs      # strefy doklejania okien (krawędzie/rogi) + testy
├── cursor.rs    # kursor z motywu XCursor (cache ikon, animacja klatek)
├── theme.rs     # motyw: kolory tła/cienia/kursora + natychmiastowe przeładowanie
├── xwayland.rs  # XWayland: X11Wm, schowek X11, obsługa okien X11
├── screencopy.rs# wlr-screencopy napisany od zera
├── render.rs    # elementy renderowania, kursor, DnD, OutputDamageTracker
└── shaders/     # GLSL: rounded_corners.frag, shadow.frag (Zadanie 3)

Protokoły

Zaimplementowane globalne: wl_compositor/wl_subcompositor, wl_shm, wl_output (+xdg_output), wl_seat (+pointer/keyboard/touch), xdg_wm_base, xdg_decoration_v1 (ClientSide), wl_data_device (schowek + DnD), primary_selection, xdg_activation, fractional_scale, pointer_constraints, text_input_manager, wlr-layer-shell (panele/tła/nakładki), linux-dmabuf (bufory GPU), wp_viewporter, wp_relative_pointer, wp_pointer_gestures, wlr-screencopy (zrzuty ekranu — napisany od zera, patrz niżej), XWayland (xwayland_shell + X11Wm; gdy brak binarki Xwayland, kompozytor loguje ostrzeżenie i działa dalej).

Screencopy (zrzuty ekranu) — napisane od zera

Smithay 0.7 nie ma screencopy, więc implementujemy serwer sami w src/screencopy.rs (definicje protokołu z wayland-protocols-wlr, semantyka nasza):

  • zwlr_screencopy_manager_v1 (v4): capture_output i capture_output_region,
  • zwlr_screencopy_frame_v1: copy/copy_with_damage, kursor opcjonalnie,
  • przechwycenie: render wyjścia do tekstury offscreen (Offscreen+Bind), odczyt pikseli (ExportMem::copy_framebuffer), kopiowanie do bufora SHM klienta, ready() z timestampem (albo failed()).
  • Kolejność: żądania copy lądują w screencopy_queue, a realizuje je pętla renderowania (winit/udev), bo tylko tam jest renderer.

Test (na kompozytorze, na którym działa grim):

sudo pacman -S --needed grim slurp   # narzędzia
WAYLAND_DISPLAY=wayland-1 grim /tmp/pagan.png
WAYLAND_DISPLAY=wayland-1 grim -g "$(slurp)" /tmp/region.png

Ograniczenia: obsługiwane są bufory SHM (to, czego używa grim). Bufory dmabuf nie są jeszcze przyjmowane (nie wysyłamy zdarzenia linux_dmabuf, więc klient wybierze SHM; dmabuf dostanie failed()). Przechwycenie wyjścia innego niż o pozycji (0,0) wymaga jeszcze korekty przesunięcia — patrz // API-NOTE.

Konfiguracja (zmienne środowiskowe)

Zmienna Znaczenie
PAGAN_SCALE=1.5 skala (HiDPI) wszystkich wyjść
PAGAN_TRANSFORM=90\|180\|270\|flipped\|normal obrót wyjścia
PAGAN_OUTPUTS="DP-1:0,0;HDMI-A-1:1920,0" ręczny układ monitorów (pozycje logiczne)
PAGAN_CONFIG=/ścieżka/outputs.conf wskazanie pliku konfiguracji monitorów
PAGAN_THEME=/ścieżka/theme.conf wskazanie pliku motywu (kolorów)
PAGAN_DRM_DEVICE=/dev/dri/card1 wybór GPU dla backendu udev
PAGAN_SNAP_THRESHOLD=12 próg doklejania okna do krawędzi (px)
PAGAN_SNAP_BOTTOM=1 włącz doklejanie także do dolnej krawędzi
PAGAN_LANG (toolkit) język UI

Domyślnie (bez konfiguracji) wyjścia układane są poziomo od (0,0), bez przerw. PAGAN_SCALE/PAGAN_TRANSFORM nadpisują ustawienia backendu tylko gdy podane — winit wymaga np. Flipped180 do poprawnego rysowania.

Plik konfiguracji monitorów

Oprócz zmiennych środowiskowych układ można zapisać trwale w pliku $XDG_CONFIG_HOME/pagan/outputs.conf (domyślnie ~/.config/pagan/outputs.conf). Jeden monitor = jeden wiersz NAZWA X Y [SKALA] [OBROT]:

# nazwa   x     y    skala  obrot
DP-1      0     0    1.0    normal
HDMI-A-1  2560  0    1.5    90

Pola SKALA i OBROT są opcjonalne; # zaczyna komentarz. Nazwy monitorów Nazwy monitorów zobaczysz w logu startowym (układ wyjść zaktualizowany ... DP-1=...). Kolejność nadpisywania: plik → PAGAN_OUTPUTS/PAGAN_SCALE/PAGAN_TRANSFORM (zmienne wygrywają, więc łatwo je przetestować bez ruszania pliku).

Motyw kompozytora (kolory) — theme.conf

Kompozytor sam maluje tylko kilka rzeczy (tło pulpitu, cień pod oknami, awaryjny kursor), więc motyw steruje właśnie nimi. Plik: ~/.config/pagan/theme.conf (albo $PAGAN_THEME). Przy pierwszym uruchomieniu powstaje z komentowanym szablonem, więc nie trzeba zgadywać nazw kluczy.

background      = #0e0f14   # tło pulpitu
shadow_color    = #000000   # kolor cienia (RGB)
shadow_opacity  = 0.42      # maksymalna nieprzezroczystość (0..1)
shadow_blur     = 24        # rozmycie w px (większe = bardziej rozlane)
shadow_offset_y = 6         # przesunięcie cienia w dół
shadow_radius   = 12        # promień rogów sylwetki cienia
cursor_fill     = #f2f2f7   # awaryjny kursor (gdy brak motywu XCursor)
cursor_border   = #14141a

Kolory zapisujesz jako #RRGGBB lub #RRGGBBAA; komentarz to cały wiersz zaczynający się od #.

Przeładowanie jest natychmiastowe: kompozytor obserwuje plik przez inotify (katalog, nie sam plik — edytory często zapisują przez rename) i po każdej zmianie wczytuje motyw od nowa. Nie trzeba restartu ani żadnego klawisza. Dodatkowo Ctrl+Alt+R wymusza przeładowanie ręcznie (przydatne, gdy edytor nadpisuje plik w nietypowy sposób albo gdy oglądasz zmiany „na żądanie”).

Zmiana motywu jest widoczna od następnej klatki: kolor tła idzie do czyszczenia bufora, a parametry cienia są uniformami shadera. Zrzuty ekranu (wlr-screencopy) też od razu używają nowych kolorów.

Instalacja zależności (Manjaro/Arch)

# Rust (jeśli brak):
sudo pacman -S --needed rustup && rustup default stable

# Backend dev (winit) + toolkit:
sudo pacman -S --needed mesa wayland libxkbcommon cairo pkgconf

# Backend tty-udev (produkcja):
sudo pacman -S --needed libdrm libgbm libinput libseat libxkbcommon \
    mesa vulkan-icd-loader

# Klienci X11 (XWayland) — opcjonalnie, ale bez tego aplikacje X11 nie działają:
sudo pacman -S --needed xorg-xwayland

# Narzędzia do TESTU screencopy (NIE są częścią Rusta — nie instalują cargo):
sudo pacman -S --needed grim slurp

Uwaga: cargo/rustc pochodzą z pakietu rust (albo rustup) i są potrzebne do budowania projektów. grim/slurp to niezależne narzędzia-klienty Waylanda, używane tylko do przetestowania zrzutów ekranu.

Na Manjaro XFCE sesja jest X11 — kompozytor odpalasz albo jako okno (--winit), albo po przełączeniu na inny TTY (--tty-udev, przez logind/libseat lub root).

Skróty globalne kompozytora

Przechwytywane zanim trafią do klienta (patrz input.rs::compositor_shortcut):

Skrót Działanie
Ctrl+Alt+Backspace zakończ kompozytor
Ctrl+Alt+Left / Right fokus na poprzednie / następne okno
Ctrl+Alt+Delete zamknij okno z fokusem (xdg_toplevel.close)
Ctrl+Alt+M zminimalizuj okno z fokusem
Ctrl+Alt+U przywróć ostatnio zminimalizowane okno
Ctrl+Alt+R przeładuj motyw z pliku

Hybrydowe CSD — jak to działa

  1. Klient (toolkit) rysuje całe okno: zaokrąglone tło, pasek tytułu, menu i przyciski. Kompozytor nie zna ich położenia.
  2. Klik w pasek tytułu → klient wysyła xdg_toplevel.move(seat, serial, x, y). Ciągnięcie krawędzi → xdg_toplevel.resize(seat, serial, edges, w, h).
  3. Kompozytor ustanawia grab (grabs.rs) i przesuwa/rozciąga okno.
  4. xdg_decoration_manager_v1 w trybie client sprawia, że klienci nie oczekują dekoracji od nas (a jeśli proszą o server, respektujemy to).

Zaleta: menu i przyciski są zawsze idealnie wyrównane (rysuje je ten sam kod), a kompozytor nie wymaga żadnego niestandardowego protokołu.

Dlaczego Space<PaganWindow>, a nie Space<Window>

W specyfikacji była propozycja pub space: Space<Window>. Użyliśmy cienkiego wrappera PaganWindow(pub Window), bo cień musi być rysowany pod konkretnym oknem, ale nad oknami niżej. Smithay pozwala dołączyć własne elementy tylko do typu implementującego AsRenderElements. Wrapper deleguje cały kontrakt SpaceElement/WaylandFocus do Window, więc różnica jest minimalna, a cień ma poprawny Z-order. Jeśli cienie nie są potrzebne, wystarczy podmienić typ.

Cień (własny shader GLSL — GPU)

Cień jest liczony na GPU własnym pixel shaderem shaders/shadow.frag (kompilowany przez GlesRenderer::compile_custom_pixel_shader, rysowany jako PixelShaderElement):

  • sylwetka to zaokrąglony prostokąt liczony odległością ze znakiem (SDF) — cień pasuje do 12-pikselowych rogów rysowanych przez toolkit,
  • miękkość to exp(-d/blur) (tanie przybliżenie rozmycia Gaussa),
  • cień jest przesunięty w dół i rysowany pod oknem (w tej samej grupie elementów co okno → poprawny Z-order).

Konsekwencja: typy renderowania są specjalizowane do GlesRenderer (PixelShaderElement działa tylko z konkretnym rendererem). Używamy GlesRenderer w obu backendach, więc to nie ogranicza.

shaders/rounded_corners.frag to gotowa maska rogów (pixel shader) na przyszły tryb SSD / okna bez własnego CSD. W obecnej konfiguracji (CSD) nie jest nakładana, bo klient rysuje rogi sam, a pixel shader nie potrafi "wymazać" już narysowanych pikseli (szczegóły w nagłówku pliku).

Kluczowe decyzje

  • xdg_decoration = ClientSide jako domyślne. To fundament hybrydowego CSD.
  • Move/resize przez standardowe żądania klienta, nie przez własny protokół.
  • Kaskada 20 px (CASCADE_STEP) z zawijaniem do początku przy krawędzi ekranu.
  • Fokus = podniesienie w Z-order: kliknięcie okna aktywuje je i wysuwa.
  • Renderer glow (GlesRenderer) — najprostsza ścieżka dev; Vulkan (wgpu) to przyszły feature.
  • Bez animacji w tym kroku (zgodnie z ustaleniem). Miejsce na nie to input.rs/window.rs; springs dojdą jako osobny moduł.

Monitory (liczba, rozmiary, hotplug)

Kompozytor obsługuje wiele wyjść tak, jak wypada:

  • Liczba i rozmiar — backend udev tworzy jedno wyjście per podłączony konektor/CRTC, w trybie preferowanym monitora (WlMode::from(drm_mode)), z fizycznym rozmiarem panelu (mm) w PhysicalProperties. Backend winit ma z definicji jedno wyjście; jego rozmiar śledzi WinitEvent::Resized.
  • Układoutput.rs::relayout_outputs ustawia wyjścia poziomo od (0,0), bez przerw (po odłączeniu środkowego monitora reszta się dosuwa).
  • HotplugDrmScanEvent::Connected/Disconnected dodaje/usuwa wyjścia oraz przełącza sesję; po każdej zmianie wołany jest re-layout.
  • Ratowanie okienensure_windows_on_screen przenosi okna, które po zmianie układu wypadły poza wszystkie wyjścia (np. stały na odłączonym ekranie).
  • Nowe okna — lądują na monitorze pod kursorem (kaskada w obrębie tego wyjścia), co jest standardem w pływających menedżerach okien.
  • Kursor — przy ruchu względnym (libinput) jest przyciągany do najbliższego wyjścia, więc nie „zapuszcza się” w martwe pole przy układzie w kształcie L.

Stan wyjść logujemy po każdej zmianie: układ wyjść zaktualizowany count=2 outputs=DP-1=2560x1440+0+0, HDMI-A-1=1920x1080+2560+0.

Czego jeszcze nie ma: przeliczania układu przy zmianie skali w locie. Skalowanie (HiDPI) i obrót są już wspierane — globalnie (PAGAN_SCALE, PAGAN_TRANSFORM) oraz per monitor w outputs.conf.

Backend tty-udev (produkcja)

Zaimplementowany w src/udev.rs, włączany feature'em udev:

# Zależności systemowe (Debian/Ubuntu):
#   libdrm-dev libgbm-dev libinput-dev libudev-dev libseat-dev
cargo build --features udev
sudo ./target/debug/pagan-compositor --tty-udev   # albo przez logind/libseat bez sudo

Zakres: sesja libseat (pauza/wznowienie VT), libinput (ruch względny, przyciski, scroll, klawiatura), wybór GPU (PAGAN_DRM_DEVICE opcjonalnie), per-urządzenie DrmOutputManager + GbmAllocator + GbmFramebufferExporter, tworzenie wyjść per konektor/CRTC, pętla VBlank i hotplug. Logika okien/protokołów jest w 100% współdzielona z backendem winit (MyCompositor::init).

✅ Ten kod kompiluje się (cargo build --features udev, zweryfikowane), ale nie był uruchamiany na sprzęcie z DRM (brak dostępu do TTY/GPU w środowisku budowania). Miejsca wskazane // WERYFIKUJ: warto sprawdzić przy pierwszym uruchomieniu na prawdziwym TTY.

Znane ograniczenia / możliwe korekty przy pierwszym uruchomieniu na TTY

  • Backend udev jest niezweryfikowany na sprzęcie (patrz sekcja wyżej i // WERYFIKUJ:).
  • Obsługa subsurface'ów w commit jest uproszczona (bierzemy root drzewa).
  • XWayland zaimplementowany; wymaga obecności binarki Xwayland (sudo pag install xorg-xwayland w PaganOS, sudo pacman -S xorg-xwayland w Arch/Manjaro). Bez niej kompozytor działa, ale bez aplikacji X11.
  • wlr-screencopy napisaliśmy od zera (src/screencopy.rs) — działa z grim na backendzie winit (kursor: grim -c).
  • Minimalizacja chowa okno z Space i przywraca przez Ctrl+Alt+U. Docelowo trafi na pasek zadań / wlr-foreign-toplevel.
  • Ikona DnD jest już rysowana pod kursorem (render.rs::output_elements).
  • Kursor pochodzi z motywu XCursor (XCURSOR_THEME/XCURSOR_SIZE); gdy motywu brak, rysowany jest czytelny prostokąt zapasowy.
  • pointer_gestures/relative_pointer są zarejestrowane; pełne przekazywanie zdarzeń gestów zależy od backendu (udev przekazuje ruch względny).