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

PaganDE/README.md main

322 linii Raw ← Powrót

PaganDE

Środowisko graficzne Wayland budowane od zera w Rust:

  • pagan-compositor — kompozytor (serwer) na Smithay 0.7: floating window manager, renderer OpenGL (glow), wymuszone dekoracje serwerowe (SSD) z menu aplikacji w pasku, płaski firmowy styl, doklejanie okien (Aero Snap).
  • pagan-toolkit — własna biblioteka UI dla klientów: wayland-client + memmap2 (SHM) + cairo-rs. Bez Qt/GTK/SCTK/iced. Antyaliasing, zaokrąglone rogi, wspólny pasek tytułu, lekka lokalizacja PL/EN.
  • smithay-0.7.0 — vendored źródła Smithay (build offline, powtarzalny).

Filozofia: spójne dekoracje serwerowe (SSD)

Kompozytor rysuje pasek tytułu dla okien, które oddają mu dekoracje, i wymusza tryb serwerowy (xdg-decorationServerSide). Dotyczy to naszego toolkitu, Qt, GTK4 oraz WSZYSTKICH aplikacji X11 (X11 nie zna CSD). Wyjątkiem są klienty, które tego protokołu nie znają (GTK3) — one rysują własne dekoracje, więc nie dokładamy drugiego paska. Szczegóły i obejście dla GTK: Dekoracje serwerowe (SSD).

Menu aplikacji jest jedynym elementem, którego kompozytor nie zna z góry — aplikacje mają różne menu. Rozwiązuje to własny protokół pagan_app_menu_v1: klient publikuje etykiety górnego poziomu, kompozytor rysuje je w pasku i odsyła kliknięcia. Szczegóły: Dekoracje serwerowe (SSD).

Aplikacje bez tego protokołu dostają w pasku przycisk „burger” (zamiast etykiet).

Wymagania

  • Rust. Wystarczy rustc z PaganOS — zawiera cargo (sprawdzone: cargo 1.96.0). cairo-rs 0.22 wymaga świeżego stabilnego toolchainu (edition 2024), więc ≥ 1.85.
  • Biblioteki systemowe: EGL/GL, Wayland, xkbcommon, Cairo, glib (dla cairo-rs).

PaganOS (repo.paganlinux.eu)

# toolchain + biblioteki do OBU projektów (backend winit) i do toolkitu
sudo pag install rustc pkgconf wayland wayland-protocols libxkbcommon cairo \
    glib2 pixman mesa libglvnd egl-wayland fontconfig dejavu-ttf

# backend PRODUKCYJNY (tty-udev) + testy narzędziami Waylanda
sudo pag install xorg-libdrm libinput seatd systemd libdisplay-info \
    xorg-libxcursor grim slurp

# aplikacje X11 (opcjonalnie) — bez tego działają tylko klienty Wayland
sudo pag install xorg-xwayland

Uwaga: systemd dostarcza libudev, a seatdlibseat; mesalibgbm. Jeśli któraś nazwa nie istnieje w repo, sprawdź ją przez wyszukiwanie w pag. grim/slurp nie instalują cargo — to niezależne klienty Wayland (cargo jest w paczce rustc).

Uruchomienie (development, backend winit)

Terminal 1 — kompozytor:

cd pagan-compositor
cargo run -- --winit
# wypisze m.in.:
#   WAYLAND_DISPLAY=wayland-1

Terminal 2 — klient demo (skopiuj socket z terminala 1):

cd pagan-toolkit
WAYLAND_DISPLAY=wayland-1 PAGAN_LANG=pl cargo run --example demo

Powinno pojawić się okno 800×500 z ciemnym paskiem tytułu: „hamburger” (Menu) po lewej i trzy okrągłe przyciski (minimalizuj / maksymalizuj / zamknij) po prawej — wszystkie w jednej linii (TITLEBAR_CONTROL_CENTER_Y = 20.0), z zaokrąglonymi rogami.

Doklejanie i kafelkowanie (Aero Snap)

Kompozytor obsługuje „doklejanie” okien do krawędzi i rogów ekranu — po stronie serwera, więc działa dla każdej aplikacji (nie tylko naszego toolkitu).

  ┌─────────┬─────────┐     krawędź lewa/prawa → połowa ekranu
  │ TL      │      TR │     krawędź górna     → pełny ekran
  ├─────────┼─────────┤     róg (2 krawędzie)  → ćwiartka (siatka 2×2)
  │ BL      │      BR │     krawędź dolna     → bez doklejania
  └─────────┴─────────┘
  • Podczas przeciągania okna (za pasek tytułu) kursor wjeżdżający w strefę (domyślnie 12 px od krawędzi) natychmiast ustawia okno w docelowej geometrii; wyjazd ze strefy przywraca rozmiar sprzed doklejenia.
  • Dwa okna doklejone do przeciwległych połówek (lub rogów) same układają się w kafelki — to całe „stackowanie”, nie potrzeba osobnego menedżera kafelkowego.
  • Kolejne przeciągnięcie okna doklejonego wyskakuje do rozmiaru sprzed snapa (rozmiar „swobodny” pamiętamy w user_data okna).
  • Resize myszką: złapanie okna w odległości 8 px od krawędzi/rogu uruchamia xdg_toplevel.resize (toolkit) → grab resize (kompozytor).

Konfiguracja przez zmienne środowiskowe (czytane raz przy starcie):

Zmienna Domyślnie Znaczenie
PAGAN_SNAP_THRESHOLD 12 Odległość od krawędzi włączająca doklejanie (px)
PAGAN_SNAP_BOTTOM 0 1 włącza doklejanie do dolnej krawędzi (dolna połowa)

Logika geometrii to czysta funkcja w pagan-compositor/src/snap.rs z testami: cargo test --bins.

Motyw kompozytora (kolory) — natychmiastowe przeładowanie

Kompozytor sam rysuje tylko tło pulpitu, pasek/menu SSD, „ducha” doklejania i awaryjny kursor — dlatego motyw steruje nimi. Konfiguracja: ~/.config/pagan/theme.conf (lub PAGAN_THEME), tworzona z komentowanym szablonem przy pierwszym starcie:

background   = #0e0f14   # tło pulpitu
cursor_fill  = #f2f2f7   # awaryjny kursor (gdy brak motywu XCursor)
cursor_border = #14141a

# Dekoracje serwerowe (SSD) — płaski, firmowy pasek tytułu
ssd_bar            = #1c202b # pasek okna AKTYWNEGO
ssd_unfocused      = #14161c # pasek okna NIEAKTYWNEGO
ssd_border         = #0d0e13 # 1 px linia pod paskiem
ssd_text           = #d6dae4 # tytuł + etykiety menu
ssd_text_unfocused = #868b96
ssd_icon_close     = #e06c6c # glif „zamknij”
ssd_radius         = 12
ssd_font_px        = 13
ssd_menu_min_width = 560     # węższe okno zwija menu do burgera

Zmiany działają od razu — kompozytor obserwuje plik przez inotify i wczytuje go od nowa po każdym zapisie (bez restartu). Ctrl+Alt+R wymusza przeładowanie ręcznie. Kolory to #RRGGBB / #RRGGBBAA. Implementacja: src/theme.rs (7 testów jednostkowych).

Dekoracje serwerowe (SSD) i menu aplikacji w pasku

Kompozytor wymusza tryb dekoracji serwerowych (xdg-decorationServerSide) dla okien najwyższego poziomu, które ten protokół znają (nasz toolkit, Qt, GTK4). Pasek jest płaski (bez gradientów), z zaokrąglonymi górnymi rogami.

Dwa świadome wyjątki:

  • Klienty CSD (np. GTK3). GTK3 na Waylandzie ZAWSZE rysuje własne dekoracje i nie tworzy obiektu zxdg_toplevel_decoration_v1. Nie da się na nim wymusić naszego paska, więc kompozytor NIE dokłada drugiego — inaczej oba paski nakładałyby się na siebie (wyglądało to jak „przesunięta dekoracja”). Aby GTK dostało NASZ pasek, launcher uruchamia je przez XWayland (patrz niżej).
  • Okna override-redirect (menu, podpowiedzi X11) to elementy aplikacji, a nie samodzielne okna — pasek nad nimi wyglądałby jak przyklejony do menu.

Wysokości panelu nie zgadujemy: kompozytor czyta obszar roboczy wyjścia (LayerMap::non_exclusive_zone), więc kaskada nowych okien i maksymalizacja lądują POD panelem, a nie za nim.

W pasku, w jednym rzędzie, leżą: menu aplikacji (po lewej), tytuł okna (wyśrodkowany, o ile się mieści) oraz przyciski minimalizuj/maksymalizuj/zamknij (po prawej). Okno nieaktywne jest przygaszone i pozbawione tytułu z akcentem — dzięki temu od razu widać, gdzie trafi klawiatura.

Własny protokół: pagan_app_menu_v1

Każda aplikacja ma inne menu, więc kompozytor nie może znać jego zawartości. Rozdzielamy odpowiedzialność:

  • klient publikuje same TYTUŁY górnego poziomu (set_labels),
  • kompozytor rysuje je w firmowym pasku i odsyła zdarzenie clicked(index, x) (z pozycją etykiety, żeby klient wiedział, gdzie rozwinąć),
  • klient rysuje ROZWINIĘCIE we własnej powierzchni (OpenMenu) i wykonuje akcję wybranej pozycji (WindowAction::MenuItem).

Pełny łańcuch działania (z logu demo):

otwarto menu z paska tytułu index=0
wybrano pozycję menu aplikacji menu_index=0 item_index=3 label="Zamknij"
Zamykanie okna na życzenie użytkownika.

Opis protokołu: protocols/pagan-app-menu-v1.xml. Kod generuje wayland-scanner po obu stronach (te same etykiety na serwerze w pagan-compositor/src/app_menu.rs i u klienta w pagan-toolkit/src/app_menu.rs). Użycie w aplikacji:

let mut menu = pagan_toolkit::AppMenu::new();
menu.add("Plik", &["Nowe okno", "Otwórz…", "Zapisz", "Zamknij"])
    .add("Edycja", &["Wytnij", "Kopiuj", "Wklej"]);
pagan_toolkit::publish_app_menu(&mut state, &menu)?;

Aplikacje bez tego protokołu dostają w pasku przycisk „burger”. Gdy okno jest za wąskie na etykiety (ssd_menu_min_width), etykiety zwijają się do burgera — a jego rozwinięcie to menu aplikacji (wiersze = etykiety, klik odsyłamy klientowi). Dopiero gdy klient nie ma menu (np. terminale X11), burger rozwija awaryjne menu okna (minimalizuj/maksymalizuj/zamknij).

Tekst na pasku (tytuł i etykiety menu) rysuje kompozytor: rasteryzuje go Cairo i wysyła na GPU jako teksturę (pagan-compositor/src/text.rs, cache per napis).

Ikony przycisków (wektorowo)

Ikony minimalizuj/maksymalizuj/zamknij oraz „burger” są rysowane wektorowo Cairo (pagan-compositor/src/icons.rs) — prawdziwe ścieżki z zaokrąglonymi końcami (LineCap::Round), a nie aproksymacja prostokątami. Świadomie NIE ładujemy plików .svg: renderowanie SVG wymaga librsvg, które ciągnie stos GTK/GDK — wbrew założeniu „bez gotowych frameworków UI”. Kształty są te same, co miałyby w SVG, a rozmiar i kolor steruje motyw (ssd_icon_px, ssd_text).

Launcher do testów (panel)

Panel (pagan-panel) ma dwa przyciski obok siebie:

  • Pagan — uruchamia terminal (jak dotychczas),
  • Demo — uruchamia naszą aplikację testową (pagan-toolkit/examples/demo), żeby jednym kliknięciem obejrzeć dekoracje SSD i menu aplikacji.

Ścieżkę do demo można nadpisać zmienną PAGAN_DEMO_CMD. Bez niej panel szuka pagan-demo w PATH, a potem w układzie deweloperskim repozytorium.

Uruchamianie w NASZEJ sesji (ważne). Panel nie startuje programów „jak leci”: w sesji XFCE ustawione jest XDG_SESSION_TYPE=x11 i DISPLAY=:0.0, więc GUI domyślnie wybiera X11 i ląduje na pulpicie XFCE — poza naszym kompozytorem (a wtedy okno i jego dekoracja nie mają ze sobą nic wspólnego). Dlatego launcher:

  • ustawia XDG_SESSION_TYPE=wayland,
  • dla GTK wymusza GDK_BACKEND=x11 (czyli NASZ XWayland). Powód: GTK3 nie zna xdg-decoration na Waylandzie, więc tylko na X11 dostaje nasz pasek SSD; nadpisz przez PAGAN_GDK_BACKEND=wayland, jeśli wolisz natywny Wayland,
  • dla Qt wymusza QT_QPA_PLATFORM=wayland (Qt zna xdg-decoration i sam rezygnuje z CSD, więc dostaje nasz pasek bez XWayland),
  • podaje aplikacjom DISPLAY naszego XWayland, który kompozytor publikuje w $XDG_RUNTIME_DIR/pagan/xwayland-display.

Dzięki temu i aplikacje Wayland, i X11 trafiają do naszego kompozytora i dostają spójną dekorację SSD.

Testy interakcji bez machania myszką (dev)

Na backendzie winit kompozytor jest zwykłym oknem X11, więc zdarzenia myszy można wstrzykiwać przez XTest (wymaga libXtst):

DISPLAY=:1 python3 tools/x11_mouse.py drag X1 Y1 X2 Y2   # przeciągnij
DISPLAY=:1 python3 tools/x11_mouse.py click X Y          # kliknij
DISPLAY=:1 python3 tools/x11_probe.py list               # okna X + geometria
DISPLAY=:1 python3 tools/x11_probe.py find 1280 800       # origin okna klienta

Co jest zrobione (Zadania 1–3)

Zadanie Status Gdzie
1. Szkielet kompozytora (Smithay, winit, floating, move/resize, Z-order, xdg-decoration=server) ✅ winit + ✅ udev pagan-compositor/src/
1b. Doklejanie do krawędzi/rogów (Aero Snap) + „wyskakiwanie” ze snapa ✅ (snap.rs, 10 testów) pagan-compositor/src/snap.rs, grabs.rs
2. Szkielet toolkitu (Wayland, SHM/memmap2, Cairo, CSD, hit-testing) pagan-toolkit/src/
3. Shadery GLSL (zaokrąglone prostokąty: pasek, menu, duch snapu) pagan-compositor/src/shaders/, window.rs
4. Wymuszone dekoracje serwerowe (SSD) + menu aplikacji w pasku pagan-compositor/src/window.rs, text.rs, app_menu.rs
5. Własny protokół pagan_app_menu_v1 (menu per aplikacja) protocols/, pagan-compositor/src/app_menu.rs, pagan-toolkit/src/app_menu.rs
6. Wektorowe ikony paska + launcher aplikacji testowej w panelu pagan-compositor/src/icons.rs, pagan-panel/src/main.rs

Ograniczenia i następne kroki

  • Protokoły: schowek/DnD, primary-selection, xdg-activation, fractional-scale, pointer-constraints, text-input, wlr-layer-shell, linux-dmabuf, viewporter, relative-pointer, pointer-gestures, wlr-screencopy napisany od zera (src/screencopy.rs, bufory SHM — test: grim) oraz XWayland (src/xwayland.rs; bez binarki Xwayland kompozytor działa dalej, tylko loguje WARN).
  • Monitory: liczba, rozmiary, układ poziomy bez przerw, hotplug, ratowanie okien, nowe okna na monitorze pod kursorem, skala/transformacja (globalnie przez PAGAN_SCALE/PAGAN_TRANSFORM oraz per monitor w ~/.config/pagan/outputs.conf), ręczny układ przez PAGAN_OUTPUTS.
  • Skróty: Ctrl+Alt+Backspace (wyjście), Ctrl+Alt+←/→ (fokus), Ctrl+Alt+Delete (zamknij), Ctrl+Alt+M (minimalizuj), Ctrl+Alt+U (przywróć), Ctrl+Alt+R (przeładuj motyw).
  • Backend tty-udev: kompiluje się (--features udev, --tty-udev), ale nie był uruchamiany na sprzęcie (komentarze // WERYFIKUJ:).
  • Bez cienia pod oknami (świadomie): okna są płaskie, oddziela je od tła tylko zaokrąglony pasek i 1 px linia. rounded_corners.frag rysuje zaokrąglone prostokąty (pasek SSD, tło menu, „duch” doklejania).
  • Kursor z motywu XCursor (XCURSOR_THEME/XCURSOR_SIZE), z zapasowym prostokątem. Ikona DnD rysowana pod kursorem. Minimalizacja chowa okno z Space.
  • Brak animacji (świadomie — najpierw działający system).

Status weryfikacji (uruchomione i sprawdzone)

Całość skompilowana i uruchomiona na prawdziwym sprzęcie (NVIDIA + Mesa, EGL 1.5):

Co Komenda Wynik
Build kompozytora (winit) cargo build ✅ 0 błędów
Build kompozytora (udev) cargo build --features udev ✅ 0 błędów
Testy jednostkowe cargo test --bins ✅ 43/43 (snap + monitory + motyw + SSD)
Build toolkitu cargo build --examples ✅ 0 błędów
Kompozytor ./target/debug/pagan-compositor --winit ✅ socket wayland-1, output 1280×800
Klient + CSD WAYLAND_DISPLAY=wayland-1 demo ✅ okno, klik/hover/maximize/move
Bez cienia analiza pikseli klatki (ring wokół okna) ✅ brak halo — treść styka się z pulpitem
Zrzut ekranu WAYLAND_DISPLAY=wayland-1 grim shot.png ✅ (nasz wlr-screencopy od zera)
Kursor z motywu grim -c shot.png ✅ widoczny kursor XCursor
Motyw — tło zmiana background w theme.conf ✅ nowy kolor w ~1 s (inotify)
Motyw — pasek zmiana ssd_bar w theme.conf ✅ nowy kolor w ~1 s (inotify)
XWayland log startowy bez binarki Xwayland ⚠️ miękka degradacja (WARN) — brak binarki w środowisku testowym

Zmierzona geometria z grim: menu i trzy przyciski mają środek na tej samej osi Y, odstęp dokładnie 32 px, rogi przezroczyste (promień 12 px).

grim bez -c nie rysuje kursora — do weryfikacji kursora używaj grim -c.

Dwie pułapki API, które kosztowały najwięcej czasu (na przyszłość)

  1. Kolejność elementów renderowania: Smithay oczekuje listy od przodu do tyłu (indeks 0 = na wierzchu; render_output rysuje od końca). Belka paska musi być na końcu listy (pod tekstem i glifami), a kursor i ikona DnD na samym początku — inaczej kursor chowa się POD oknem. space::render_output w Smithayu robi dokładnie tak: najpierw elementy niestandardowe, potem zawartość przestrzeni.
  2. PixelShaderElement: varying v_coords jest w UV [0..1], nie w pikselach — trzeba pomnożyć przez uniform vec2 size. Dodatkowo GLSL ES wymaga jawnego precision mediump float; (Smithay dokleja tylko #version 100).