# pagan-compositor Kompozytor Wayland PaganDE: **floating** window manager z **hybrydowym CSD**, zbudowany na [Smithay 0.7](https://github.com/Smithay/smithay). - 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 ```bash 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`): ```bash 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]`: ```conf # 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. ```conf 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) ```bash # 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`, a nie `Space` W specyfikacji była propozycja `pub space: Space`. 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ład** — `output.rs::relayout_outputs` ustawia wyjścia poziomo od (0,0), bez przerw (po odłączeniu środkowego monitora reszta się dosuwa). - **Hotplug** — `DrmScanEvent::Connected/Disconnected` dodaje/usuwa wyjścia oraz przełącza sesję; po każdej zmianie wołany jest re-layout. - **Ratowanie okien** — `ensure_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`: ```bash # 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).