Środowisko graficzne dla Zenit Linux, napisane w Nimie (+ Fidget do UI). Stack:
zde-comp-- kompozytor Wayland + XWayland (Nim FFI → libwlroots, API oparte owlr_output_state). Cały kod (backend/output/xdg-shell/ layer-shell/wejście/schowek/XWayland/sesja-VT) był realnie skompilowany i zweryfikowany wobec prawdziwych nagłówków wlroots -- pierwotnie 0.18 i 0.20, a w rundzie rozbudowy "Aurora"/XWayland dodatkowo wobec 0.17.1 (Ubuntu 24.04): zainstalowanolibwlroots-devw piaskownicy,zde-compskompilował się i wystartował, postawiono pod nim zagnieżdżony Xvfb, uruchomiono prawdziwe okno X11 (xclock) i zweryfikowano w logu poprawną sekwencjęnew_surface -> associatez odczytanym tytułem okna. Jedyna poprawka, jakiej to wymagało: brakującyimport std/sequtilswwlcomp/xwayland.nim(błąd Nim, nie API wlroots). Nie przetestowano jeszcze pełnego renderowania klatek przez GPU (headless Xvfb w piaskownicy nie ma sprzętowego OpenGL) ani wobec wlroots 0.18/0.20 -- ale sama poprawność API (nazwy pól/sygnałów, sygnatury funkcji) jest teraz potwierdzona kompilacją, nie tylko pamięcią. To jest serwer -- odpowiednik Xorg/Sway/Muttera. Uruchamia się jako pierwszy, z TTY.zde-shell-- pasek zadań / launcher / chrome okien (Fidget). To klient Wayland, łączy się z socketem, który wystawiazde-comp. Skompilowany, uruchomiony pod Xvfb i zweryfikowany realnymi zrzutami ekranu + symulowanymi kliknięciami (xdotool) -- patrz sekcja "Nowości" niżej po listę pięciu błędów renderowania, które to znalazło i naprawiło (dok był niewidoczny, wskazówki zegara niewidoczne, itd.).apps/terminal,apps/filemanager,apps/clock,apps/texteditor,apps/calculator,apps/sysmonitor-- wbudowane aplikacje shellu. Menedżer plików (apps/filemanager/files.nim) od rundy 13 obsługuje też przeciąganie plików/folderów myszą (drag & drop) -- patrz "Nowości w v0.2 (runda 13)" niżej.comp/-- logika okien używana przezzde-shell, rozbita na kilka plików (nie mylić zwlcomp/-- to osobna, dużo niższopoziomowa warstwa: prawdziwy kompozytor).
wlcomp/ -- zde-comp (kompozytor)
types.nim wspólne typy: Server/Output/Toplevel/Keyboard
output.nim wyjścia (monitory): init, klatki, odłączanie
toplevel.nim okna xdg-shell + XWayland: focus, move/resize,
hit-testing (uogólnione przez surfaceOf/geometryOf,
patrz "Nowości" niżej), Alt+Tab (cycleToPreviousToplevel)
xwayland.nim okna X11 (Xwayland): new_surface/associate/map/
unmap/destroy -- patrz "Nowości" niżej
session.nim sesja logind/seatd + przełączanie VT (Ctrl+Alt+F1-12)
-- patrz "Nowości" niżej
idle.nim DPMS (wygaszanie wyjść po bezczynności) + protokół
ext-idle-notify-v1 -- patrz "Nowości" niżej
gestures.nim gesty touchpada (swipe 3-palcowy) -- patrz "Nowości" niżej
input.nim klawiatura + kursor/mysz
main.nim tylko inicjalizacja i spięcie sygnałów
wlroots.nim bindingi FFI do libwlroots/wayland-server
shim.c/.h C glue dla funkcji `static inline` z wayland-server
shell/ -- zde-shell (pasek/launcher/chrome okien)
state.nim stan globalny (compositor, zegar, rejestry terminali/
monitora systemu/zegarów do odpytywania w tle) +
paleta kolorów "Aurora" (patrz "Nowości" niżej)
waylandlink.nim linkowanie protokołów Wayland przy -d:wayland (patrz niżej)
wallpaper.nim tło pulpitu -- gradient + poświata (Aurora), albo obraz z pliku
chrome.nim pasek tytułu / obramowanie / przyciski okna (Aurora)
taskbar.nim pływający dok + launcher z wyszukiwarką (Aurora)
launcher_apps.nim uruchamianie poszczególnych aplikacji z launchera
desktopapps.nim skanowanie/uruchamianie PRAWDZIWYCH aplikacji
systemowych (.desktop, XDG) -- patrz "Nowości" niżej
notifications.nim system powiadomień (toasty) -- patrz "Nowości" niżej
quicksettings.nim głośność/jasność (best-effort, zewn. narzędzia) --
patrz "Nowości" niżej
sound.nim dźwięk alarmu (best-effort) -- patrz "Nowości" niżej
shell.nim główna pętla rysowania + start
comp/ -- silnik okien używany przez zde-shell (focus/z-order/drag)
types.nim wspólne typy: ZdeWindow, Compositor, DragState
window.nim cykl życia okien: tworzenie/zamykanie, focus, z-order
drag.nim przeciąganie/zmiana rozmiaru myszą
comp.nim fasada re-eksportująca powyższe (import bez zmian)
apps/ -- aplikacje uruchamiane z launchera
terminal/ terminal (bash przez potoki, bez pty)
filemanager/ menedżer plików (nawigacja, tworzenie/zmiana nazwy/usuwanie, otwieranie w edytorze)
clock/ zegar analogowy + cyfrowy + data, zakładka alarmów i minutnika
texteditor/ prosty edytor plików tekstowych
calculator/ kalkulator (siatka przycisków, prosty automat stanu)
sysmonitor/ monitor systemu -- realne CPU/RAM z /proc, wykres historii
shell/wlprotocol/ (nagłówki+źródła protokołów Wayland dla waylandlink.nim,
wygenerowane przez wayland-scanner) i wlcomp/protocol/ to wygenerowane
artefakty budowania -- nie są wersjonowane w repo, build.janet tworzy je
przy każdym uruchomieniu.
build.janet samo wykrywa i instaluje brakujące zależności systemowe --
nie musisz już ręcznie szukać nazwy pakietu (apt-cache search libwlroots
itp.) ani odgadywać, czy Twoja dystrybucja nazywa go libwlroots-dev czy
libwlroots-0.20-dev. Obsługiwane menedżery pakietów: apt
(Debian/Ubuntu), dnf (Fedora/RHEL), pacman (Arch), zypper
(openSUSE), apk (Alpine). Przy pierwszym uruchomieniu janet build.janet samo:
- wykryje Twoją dystrybucję i menedżer pakietów,
- sprawdzi każdą zależność (
nim,gcc,wayland-scanner,wlroots >= 0.18,wayland-server,xkbcommon, ...), - dla brakujących -- dla wlroots dodatkowo przeszuka dostępne pakiety
(odpowiednik ręcznego
apt-cache search+ wybrania najnowszej wersjonowanej nazwy) -- i zapyta o zgodę przed jakąkolwiek instalacją wymagającą uprawnień administratora (użyjesudo, jeśli nie jesteś rootem), - zainstaluje i sprawdzi jeszcze raz, zanim przejdzie dalej.
Osobno, przed budowaniem zde-shell, build.janet sprawdza też paczki
nimble (Fidget i całe jego drzewo zależności: pixie, typography,
staticglfw, opengl, ...) -- jeśli którejś brakuje, samo odpala nimble install -y (to czyta zde.nimble z bieżącego katalogu, więc uwzględni
przypięte tam wersje). Nie musisz już ręcznie pamiętać o nimble install
przed pierwszym budowaniem.
Do automatyzacji (np. CI, obrazy Dockera) ustaw ZDE_ASSUME_YES=1 w
środowisku, żeby pominąć pytania:
ZDE_ASSUME_YES=1 janet build.janetWymagana wersja wlroots: >= 0.18. zde-comp używa API opartego o
wlr_output_state (wlr_output_state_init/set_mode/set_enabled +
wlr_output_commit_state), które zastąpiło starsze
wlr_output_set_mode/wlr_output_enable/wlr_output_commit w wlroots
0.18. Ubuntu 24.04 (noble) ma tylko wlroots 0.17 w apt -- na niej zde-comp
się nie zbuduje (auto-instalacja i tak zainstaluje to, co dostępne w
Twoich repozytoriach -- jeśli to za stara wersja, kompilacja zde-comp
zgłosi to jasnym błędem C, nie czymś tajemniczym). Sprawdź swoją wersję:
pkg-config --modversion wlroots (albo wlroots-0.XX). Ubuntu 24.10+
(oracular/plucky/questing) i Debian testing/sid mają wystarczająco nowe
wersje.
Jeśli wolisz zainstalować wszystko ręcznie z wyprzedzeniem zamiast polegać na auto-instalacji (Ubuntu/Debian; nazwy pakietów w innych dystrybucjach się różnią):
sudo apt install nim nimble gcc \
libwlroots-dev wayland-protocols libwayland-bin \
libxkbcommon-dev libinput-dev libgbm-dev libdrm-dev libseat-dev \
libx11-dev libxrandr-dev libxinerama-dev libxcursor-dev libxi-dev libxxf86vm-dev \
libglfw3-dev libgl1-mesa-dev fontconfig fonts-dejavu-core \
libdbus-1-dev pkg-configRunda 35 dodaje libdbus-1-dev (+ pkg-config) do tej listy --
w odróżnieniu od dwebp/wl-copy/xclip (opcjonalne NA URUCHOMIENIU,
brakujące narzędzie = po cichu wyłączona jedna integracja), libdbus-1
jest wymagane do SAMEJ KOMPILACJI zde-shell od tej rundy --
shell/notifications.nim bezwarunkowo importuje shell/dbusnotify.nim
(patrz "Nowości w v0.2 (runda 35)" niżej), które przez
{.passc: gorge("pkg-config --cflags dbus-1").} woła pkg-config W
TRAKCIE kompilacji. Uruchomienie samego zde-shell bez działającego
dbus-daemon --session w sesji jest za to wciąż bezpieczne (patrz
duży komentarz w dbusnotify.nim) -- integracja po prostu się nie
aktywuje, reszta zde-shell działa normalnie.
zde-shell wymaga zainstalowanego fontu (dowolnego, byle był) --
używa fontconfig (fc-match) do znalezienia najlepszego dostępnego fontu
sans-serif/monospace w systemie, więc nie dołączamy własnych plików
.ttf. Jeśli zobaczysz błąd nie znaleziono żadnego fontu przy starcie,
zainstaluj fontconfig + dowolny font (np. fonts-dejavu-core) -- na
desktopowym Linuksie to i tak niemal zawsze już jest.
build.janet wymaga interpretera Janet (sh: 1: janet: not found, jeśli
go brakuje). Ubuntu/Debian nie mają go w apt -- zbuduj ze źródeł (mały
projekt w czystym C, ~1 minutę, dokładnie tak to zweryfikowałem):
git clone --depth 1 https://github.com/janet-lang/janet.git /tmp/janet-src
cd /tmp/janet-src && make -j"$(nproc)" && sudo make installSprawdź: janet -v powinno wypisać numer wersji. Potem wróć do katalogu
projektu i buduj dalej normalnie.
Potem jedna komenda:
janet build.janet # buduje zde-comp + zde-shell (Wayland)Warianty:
janet build.janet :x11 # zde-shell na X11/GLFW zamiast Wayland
janet build.janet :comp-only # tylko kompozytor
janet build.janet :shell-only # tylko shell
janet build.janet :clean # czyści dist/Odpowiedniki jako zadania nimble (te same kroki, tylko przez nimble <task> zamiast bezpośrednio janet ...): nimble buildAll, buildX11,
buildComp, buildShell, clean.
Nie masz/nie chcesz instalować Janeta? nimble buildShellDirect
(albo wprost nimble c -d:release -d:pixieNoSimd -o:dist/zde-shell shell/shell.nim) zbuduje sam zde-shell w wariancie X11, bez Janeta i
bez generowania nagłówków protokołów. To jedyne, co da się zbudować tą
drogą -- zde-comp i wariant Wayland i tak wymagają wayland-scanner
uruchamianego przez build.janet.
Uwaga o zwykłym nimble build: na niektórych świeżych/deweloperskich
wersjach nimble (m.in. tych z "vnext"/"declarative parser") nimble build
bez argumentów potrafi zgłosić Error: Nothing to build, mimo że bin
jest w zde.nimble ustawione -- to obserwowany kwirk tego parsera przy
błędzie parsowania plików .nimble zależności (zobaczysz wtedy wcześniej
ostrzeżenie Declarative parser failed). Jeśli to Twój przypadek, użyj
nimble buildShellDirect zamiast gołego nimble build.
Wynikowe binarki lądują w dist/zde-comp i dist/zde-shell.
zde.nimble wymaga fidget >= 0.7.10 (nie >= 0.7.9!) -- to celowe: nimble
przy ograniczeniu >= domyślnie wybiera najniższą pasującą wersję, a
fidget 0.7.9 ciągnie za sobą pixie w wersji, która ma znany błąd pakowania
(pixie/fileformats/png.nim importuje moduł crunchy, którego
pixie.nimble nie deklaruje jako zależności -- nimble build wywala się
wtedy błędem Error: cannot open file: crunchy). Podniesienie dolnej
granicy do 0.7.10 (wymaga Nim >= 2.0) wymusza pociągnięcie nowszego pixie,
gdzie tego błędu nie ma; dodatkowo crunchy jest też wprost dopisany jako
zależność, na wszelki wypadek. Jeśli Twój Nim jest starszy niż 2.0, w
zde.nimble jest gotowy do odkomentowania blok z twardym przypięciem
starych wersji + crunchy.
Ważne: nimble build (bez argumentów) próbuje zbudować wariant X11
zde-shell (patrz bin w zde.nimble) -- ale jeśli zgłosi Nothing to build, zobacz sekcję "Warianty" wyżej (nimble buildShellDirect). Pełny
zestaw (Wayland + zde-comp) buduj przez nimble buildAll albo wprost
janet build.janet.
zde-comp to prawdziwy kompozytor -- musi działać z wirtualnego
terminala (TTY), nie z poziomu innej sesji graficznej (chyba że świadomie
testujesz go zagnieżdżonego pod innym Waylandem/X11 -- patrz niżej).
- Przełącz się na wolny TTY, np.
Ctrl+Alt+F3, zaloguj się. - Upewnij się, że Twój użytkownik jest w grupach potrzebnych do dostępu
do KMS/input bez roota:
sudo usermod -aG video,input,seat $USER # wyloguj się i zaloguj ponownie, żeby grupy się zaktualizowały
zde-compkorzysta zlibseat/loginddo przejęcia sesji (poprzezwlr_backend_autocreate) -- upewnij się, żesystemd-logind(alboseatd) działa:sudo systemctl status systemd-logind # zwykle już działa # alternatywa bez systemd: sudo systemctl enable --now seatd
- Uruchom:
Powinieneś zobaczyć w stderr coś w stylu:
cd dist ./zde-compTo wszystko -- jedno TTY wystarczy. Od v0.2zde-comp: uruchomiony na WAYLAND_DISPLAY=wayland-1 zde-comp: uruchomiono /.../dist/zde-shell (WAYLAND_DISPLAY=wayland-1)zde-compsam odpalazde-shelljako swój "startup command" (patrz sekcja "Nowości w v0.2" niżej) --zde-shellmusi leżeć w TYM SAMYM katalogu cozde-comp(czyli wdist/, tak jak buduje gobuild.janet/zde.nimbledomyślnie). Jeśli wolisz uruchomić powłokę ręcznie (np. do debugowania) albo Twoja binarkazde-shellleży gdzie indziej:ZDE_NO_AUTOSTART=1 ./zde-comp # w drugim TTY: WAYLAND_DISPLAY=wayland-1 dist/zde-shell # albo wskaż inną binarkę zde-shell bez wyłączania autostartu: ZDE_SHELL_PATH=/gdzie/indziej/zde-shell ./zde-comp
Jeśli masz już działającą sesję Wayland (np. GNOME/Sway) albo X11, możesz
odpalić zde-comp jako zwykłe okno wewnątrz niej -- wlroots automatycznie
wykrywa, że działa zagnieżdżony, i użyje backendu Wayland-in-Wayland albo
X11-in-X11 zamiast prawdziwego DRM/KMS:
dist/zde-compOd v0.2 to wystarczy -- zde-comp sam odpala dist/zde-shell (patrz
"Wariant A" wyżej po szczegóły i zmienne ZDE_NO_AUTOSTART/
ZDE_SHELL_PATH). Zweryfikowane realnym uruchomieniem w tej rundzie
(nie tylko przeczytane w kodzie): zde-comp postawiony zagnieżdżony pod
Xvfb sam odpalił zde-shell, który połączył się z nim jako prawdziwy
klient Wayland -- zrzut ekranu potwierdza kompletny pulpit ZDE (tapeta,
dok, zegar) wyrenderowany przez WŁASNY kompozytor ZDE, nie przez
GLFW/X11 bezpośrednio (patrz sekcja "Nowości w v0.2" niżej po pełen opis
i drugi zrzut ekranu z realnym oknem X11 przez XWayland).
To najwygodniejszy sposób na rozwijanie/debugowanie ZDE bez ciągłego przełączania TTY.
WAYLAND_DISPLAY-- nazwa socketu wystawionego przezzde-comp(zde-compsam ją ustawia dla siebie i swoich dzieci -- w tym dla autostartowanegozde-shell, patrz wyżej; klienty startowane ręcznie w innym terminalu muszą ją dostać jawnie).ZDE_NO_AUTOSTART-- wyłącza autostartzde-shellprzezzde-comp(dowolna niepusta wartość) -- patrz "Wariant A" wyżej.ZDE_SHELL_PATH-- wskazujezde-comp, SKĄD odpalićzde-shellprzy autostarcie, zamiast domyślnego "obok binarki zde-comp" -- patrz "Wariant A" wyżej.WLR_BACKENDS-- wymusza konkretny backend wlroots (drm,wayland,x11,headless) zamiast autodetekcji -- przydatne przy debugowaniu.DISPLAY-- po starcie XWaylandazde-comp(a właściwiewlr_xwayland) sam wystawia gniazdo X11 i ustawia tę zmienną dla procesów, które odpali; stare aplikacje X11 uruchomione zDISPLAYustawionym na tę wartość powinny działać przez XWayland bez zmian.
Nowości w v0.2 (runda 33) -- weryfikacja interakcji "przypnij na wierzchu" z "Pokaż pulpit" i przenoszeniem między pulpitami
Krótka runda dopełniająca rundę 32: dwie interakcje między
alwaysOnTop (runda 32) a dwiema wcześniejszymi funkcjami (toggleShowDesktop
z rundy 28, moveWindowToWorkspace z rundy 27) nie były jeszcze
sprawdzone razem. Po ośmiu rundach znajdowania błędów DOKŁADNIE w
takich miejscach ("dwie funkcje, które nigdy nie były przetestowane
obok siebie"), warto było to sprawdzić explicite, zamiast zakładać, że
"pewnie działa".
Wynik: oba przypadki działają poprawnie, bez poprawek.
- Przypięte okno + "Pokaż pulpit": przypięte okno jest minimalizowane przez "Pokaż pulpit" na równi ze zwykłymi (poprawne zachowanie -- "Pokaż pulpit" to bezwzględne "wyczyść wszystko z ekranu", nie powinno robić wyjątku dla przypiętych okien) i poprawnie przywracane, z zachowanym statusem przypięcia po całym cyklu.
- Przypięte okno + przeniesienie na inny pulpit: status przypięcia przetrwa przeniesienie, a okno poprawnie renderuje się na wierzchu również na NOWYM pulpicie, nie tylko na tym, na którym zostało przypięte.
Oba scenariusze dopisane jako stałe testy regresyjne do
test_real_comp_alwaysontop.nim (teraz 9 scenariuszy, z 7) -- na
PRAWDZIWYM, niezmodyfikowanym comp/comp.nim, tak jak reszta tej serii.
Ta runda to uczciwy przykład, że nie każde poszukiwanie błędu kończy się
jego znalezieniem -- i że to też jest wartościowy wynik, wart
udokumentowania i zabezpieczenia testem na przyszłość, nie tylko
błędy same w sobie.
Nowości w v0.2 (runda 32) -- "Przypnij na wierzchu" (always-on-top), świadomie wąski zakres po ośmiu rundach napraw
Dziewiąta runda dotycząca comp/ -- pierwsza NOWA funkcja od rundy 28,
zaprojektowana z pełną świadomością tego, co osiem poprzednich rund
(24-31) już nauczyło o tym kodzie: większość znalezionych tam błędów
brała się z DWÓCH funkcji inaczej interpretujących to samo pojęcie
"kolejności okien" (zIndex vs. minimized vs. workspace). Żeby nie
dołożyć DZIEWIĄTEGO takiego przypadku, przypinanie okna "na wierzchu"
zostało celowo ograniczone do JEDNEGO wymiaru: kolejności RYSOWANIA
(windowsInZOrder). Nie rusza zIndex samego okna ani logiki fokusu
(cycleFocus, focusNextBestOnWorkspace) -- Alt+Tab i przejmowanie
fokusu po zamknięciu/zminimalizowaniu działają DOKŁADNIE tak samo,
niezależnie od tego, czy jakiekolwiek okno jest przypięte.
- Przycisk "📌" w pasku tytułu każdego okna (
shell/chrome.nim) -- dostępny niezależnie odresizable/closable, bo przypinanie to inna oś niż zmiana rozmiaru/zamykanie. - Przypięte okno renderuje się ZAWSZE nad wszystkimi nieprzypiętymi,
niezależnie od tego, który z nich ma wyższy surowy
zIndex--windowsInZOrdersortuje teraz dwupoziomowo (najpierw status przypięcia, potemzIndexw ramach każdej z dwóch grup). - Dwa przypięte okna wciąż porządkują się między sobą normalnie wg
zIndex-- przypięcie nie zamienia ich w nierozróżnialną grupę. toggleAlwaysOnTopświadomie NIE zmienia fokusu -- przypięcie okna w tle (np. odtwarzacza muzyki) nie powinno kraść fokusu klawiatury aktywnemu oknu, w którym użytkownik akurat pracuje.
Metoda weryfikacji: test_real_comp_alwaysontop.nim -- 7
scenariuszy na PRAWDZIWYM, niezmodyfikowanym comp/comp.nim, w tym DWA
uznane za KLUCZOWE dla tej rundy: cycleFocus (Alt+Tab) daje DOKŁADNIE
tę samą sekwencję co w testach z rund 21/23, niezależnie od tego, czy
jedno z trzech okien jest przypięte; przejęcie fokusu po
minimizeWindow (runda 30) wciąż opiera się wyłącznie na surowym
zIndex, nieporuszone przez status przypięcia. Reszta: podstawowe
działanie (przypięte na wierzchu mimo niższego zIndex), przypięte
zostaje na wierzchu nawet po ogniskowaniu innego okna (cały sens tej
funkcji), dwa przypięte okna porządkują się między sobą, odpięcie
przywraca zwykłe sortowanie, brak zmiany fokusu przy samym przypinaniu.
Nowości w v0.2 (runda 31) -- naprawiona luka w kontrakcie: openWindow nie gwarantowało, że początkowy rozmiar jest co najmniej minSize
Ósmy problem znaleziony w comp/ -- tego samego, "latentnego" gatunku
co runda 29 (nie widoczny dziś w praktyce, ale realna niespójność w
publicznym kontrakcie API), znaleziony przy okazji przeglądu
openWindow po naprawie dkResize z rundy 26.
Problem: openWindow przyjmuje ZARÓWNO size (startowy rozmiar),
JAK I minSize (minimalny rozmiar) jako niezależne parametry, ale nic
nie sprawdzało, czy size >= minSize -- konstruktor po prostu wpisywał
size wprost, nawet jeśli był MNIEJSZY niż deklarowane dla tego samego
okna minimum. Potwierdzone bezpośrednim uruchomieniem PRZED naprawą:
openWindow(..., size = vec2(100, 50), minSize = vec2(280, 180))
dawało okno o rozmiarze (100, 50) -- sprzeczne samo ze sobą.
Uczciwa notatka o zasięgu: sprawdzone w shell/launcher_apps.nim --
wszystkie obecne w kodzie aplikacji wywołania openWindow przekazują
size bezpiecznie większy niż DefaultMinSize (280x180), żadne nie
przekazuje własnego minSize w ogóle. To nie jest błąd widoczny w
dzisiejszym działaniu ZDE -- to luka, którą ugryzłaby pierwsza
aplikacja (albo przyszła zmiana domyślnych rozmiarów), jaka
przekazałaby oba parametry niespójnie.
Naprawa: przycięcie size do minSize w JEDNYM miejscu
(clampedSize), zastosowane PRZED liczeniem pozycji startowej (żeby
wyśrodkowanie na ekranie też korzystało z faktycznego, przyciętego
rozmiaru, nie surowego) oraz w savedSize (żeby maksymalizacja i
przywrócenie takiego okna nie cofały go do niespójnego, zbyt małego
stanu) -- ten sam duch co przycinanie w dkResize z rundy 26, tylko
zastosowany też przy SAMYM OTWARCIU okna.
Metoda weryfikacji: test_real_comp_open_minsize.nim -- 5
scenariuszy na PRAWDZIWYM, niezmodyfikowanym comp/comp.nim: regresja
na sam znaleziony błąd, przycinanie NIEZALEŻNE per oś (nie
"wszystko-albo-nic"), regresja na typowy przypadek (size > minSize,
bez zmian), spójność savedSize przy cyklu maksymalizuj/przywróć, oraz
poprawność wyśrodkowania względem faktycznego (przyciętego) rozmiaru.
Nowości w v0.2 (runda 30) -- naprawiona asymetria: minimalizacja aktywnego okna nie oddawała fokusu następnemu widocznemu
Siódmy problem znaleziony w comp/ -- tym razem PRAWDZIWY, widoczny w
codziennym działaniu (w odróżnieniu od rundy 29), znaleziony przez
porównanie dwóch koncepcyjnie podobnych operacji obok siebie.
Błąd: closeWindow zawsze oddawał fokus najwyżej ułożonemu z
pozostałych widocznych okien na tym samym pulpicie po usunięciu okna.
minimizeWindow -- ta sama, z punktu widzenia użytkownika, kategoria
zdarzenia ("to okno znika z widoku") -- tego NIE robiło: po prostu
zerowało comp.focusedId na 0 i kończyło, nawet gdy na ekranie
zostawały inne, widoczne okna. Potwierdzone bezpośrednim uruchomieniem
PRZED naprawą: dwa okna, drugie ma fokus, zminimalizuj je --
windowsInZOrder() nadal zwraca pierwsze (widoczne), ale focusedId
zostaje 0, jakby nic w ogóle nie było widoczne -- w praktyce
użytkownik zminimalizowałby aktywne okno i nie zobaczyłby ŻADNEGO okna
podświetlonego jako aktywne, mimo że inne wciąż stoją na ekranie.
Naprawa: wydzielona wspólna procedura focusNextBestOnWorkspace
(ten sam duch co doMaximize z rundy 22) -- closeWindow i
minimizeWindow teraz DOSŁOWNIE dzielą tę samą logikę zamiast
utrzymywać dwie osobne, prawie identyczne kopie, które już raz się
rozjechały. To nie tylko naprawia dzisiejszy błąd, ale też eliminuje
ryzyko, że przyszła zmiana w jednym miejscu znowu nie trafi do
drugiego.
Metoda weryfikacji: test_real_comp_minimize_focus.nim -- 6
scenariuszy na PRAWDZIWYM, niezmodyfikowanym comp/comp.nim: regresja
na sam znaleziony błąd, minimalizacja NIE-ogniskowanego okna (fokus bez
zmian), minimalizacja jedynego okna (poprawnie focusedId = 0), fokus
trafia na NAJWYŻEJ ułożone z wielu pozostałych (nie przypadkowe),
respektowanie granic pulpitów wirtualnych, oraz regresja potwierdzająca,
że closeWindow po refaktoryzacji nadal działa dokładnie tak jak
przedtem.
Nowości w v0.2 (runda 29) -- naprawiona niespójność API: closable=false nie było wcale egzekwowane przez closeWindow
Szósty z rzędu problem znaleziony w comp/ -- ale, w odróżnieniu od
poprzednich pięciu (rundy 24-27), UCZCIWIE inny co do zasięgu: to NIE
jest błąd widoczny dziś w codziennym działaniu ZDE, tylko luka w
egzekwowaniu udokumentowanego kontraktu publicznego API, którą
znaleziono przez systematyczny przegląd, nie przez natrafienie na
zaobserwowane złe zachowanie.
Problem: closable (parametr openWindow, mówiący "tego okna nie
da się zamknąć") był sprawdzany WYŁĄCZNIE w shell/chrome.nim, przy
decyzji, czy w ogóle POKAZAĆ przycisk "X" na pasku tytułu. Samo
closeWindow (comp/window.nim) w ogóle nie sprawdzało tej flagi --
ukrycie przycisku "X" było więc czysto KOSMETYCZNE: skrót klawiszowy
(Ctrl+Alt+Q, actCloseWindow) albo jakiekolwiek inne, przyszłe
wywołanie closeWindow wprost wciąż zamykało okno, mimo
closable = false.
Uczciwa notatka o zasięgu: sprawdzone przez grep po całym repo --
ŻADNE okno w obecnym kodzie aplikacji nie jest dziś otwierane z
closable = false. To nie jest więc błąd, na który ktokolwiek mógłby
dziś trafić, używając ZDE -- to luka, która ugryzłaby PIERWSZĄ
aplikację, jaka kiedykolwiek skorzystałaby z tego parametru, ufając, że
robi to, co obiecuje. Naprawiona teraz, na zapas, egzekwowaniem w
JEDNYM, autorytatywnym miejscu (closeWindow samo odmawia, jeśli
not w.closable) -- zamiast polegać na tym, że każdy wywołujący (skrót
klawiszowy, dok, przyciski UI, przyszły kod) osobno o tym pamięta.
Metoda weryfikacji: test_real_comp_closable.nim -- 5 scenariuszy
na PRAWDZIWYM, niezmodyfikowanym comp/comp.nim: regresja na sam
znaleziony problem (closable=false faktycznie opiera się
closeWindow), regresja na zwykłe okno (closable=true, wartość
domyślna, zamyka się bez zmian), onClose NIE wywołuje się dla okna,
którego nie dało się zamknąć (ważne, żeby aplikacja nie wykonała
sprzątania dla okna, które w rzeczywistości wciąż istnieje), fokus nie
zmienia się przy nieudanej próbie, oraz regresja na nieistniejące id
jako bezpieczny no-op.
Domyka realny brak: funkcja obecna w praktycznie każdym DE (minimalizuj
wszystkie okna naraz, żeby zobaczyć pulpit, ponowne wywołanie przywraca
je wszystkie) nie istniała w ogóle. Po pięciu rundach z rzędu
poświęconych wyłącznie naprawianiu znalezionych błędów, ta runda to
znowu nowa funkcja -- comp/window.nim (toggleShowDesktop), skrót
domyślnie super+d.
Kluczowy szczegół projektowy: ponowne wywołanie przywraca WYŁĄCZNIE
okna, które SAMO zminimalizowało (Compositor.showDesktopIds) -- NIE
"wszystkie aktualnie zminimalizowane okna". Bez tego rozróżnienia okno,
które użytkownik zminimalizował RĘCZNIE PRZED wywołaniem "Pokaż
pulpit", zostałoby po cichu przywrócone razem z resztą przy drugim
naciśnięciu Super+D, mimo że użytkownik nigdy o to nie prosił --
zweryfikowane wprost testem (Test 3, "kluczowy przypadek").
- Fokus zapamiętywany i przywracany (
showDesktopPrevFocusedId) -- drugie Super+D wraca do tego samego okna, które było aktywne przed pierwszym. - Respektuje pulpity wirtualne -- dotyczy WYŁĄCZNIE bieżącego pulpitu,
okna na innych pulpitach nietknięte (ten sam wzorzec co
cycleFocus/switchWorkspacez wcześniejszych rund). - Bezpieczne na okno zamknięte W TRAKCIE pokazywania pulpitu (drugie
wywołanie nie wywala się na już-nieistniejącym
id) i na pusty pulpit bez żadnych okien.
Metoda weryfikacji: test_real_comp_showdesktop.nim -- 7
scenariuszy na PRAWDZIWYM, niezmodyfikowanym comp/comp.nim:
podstawowe minimalizowanie/przywracanie, kluczowy przypadek z ręcznie
zminimalizowanym oknem sprzed wywołania, przywracanie fokusu, izolacja
pulpitów wirtualnych, zamknięcie okna w trakcie pokazywania pulpitu,
pusty pulpit jako bezpieczny przypadek brzegowy.
Nowy skrót actToggleShowDesktop, domyślnie super+d, konfigurowalny w
Ustawieniach jak reszta (runda 21) -- trzy równoległe tablice w
shell/shortcuts.nim rozszerzone o jeden wpis w poprawnej pozycji,
przeliczone ręcznie (19 = 19 = 19 = liczba wartości enuma).
Nowości w v0.2 (runda 27) -- naprawiony błąd: przeniesienie zminimalizowanego okna na inny pulpit zostawiało je niewidocznym, mimo że "miało fokus"
Piąty z rzędu prawdziwy błąd znaleziony w comp/ -- w moveWindowToWorkspace,
kodzie nietkniętym przez żadną z poprzednich pięciu rund.
Błąd: moveWindowToWorkspace ustawiało comp.focusedId = id
bezpośrednio, z pominięciem comp.focus(id) (które normalnie
podnosi zIndex okna na wierzch ORAZ czyści minimized). Dla zwykłego
okna nie robiło to różnicy -- ale dla okna przenoszonego W STANIE
ZMINIMALIZOWANYM (łatwe do wywołania z paska zadań, bez wcześniejszego
ręcznego przywracania) zostawiało w.minimized == true nietknięte,
mimo że comp.focusedId już na nie wskazywało. Kompozytor "wierzył",
że to okno ma fokus, a jednocześnie windowsInZOrder() (używane do
rysowania) dalej je pomijało jako zminimalizowane -- okno stawało się
niewidoczne mimo rzekomego fokusu, w praktyce "znikając" po przeniesieniu
na inny pulpit z paska zadań.
Potwierdzone bezpośrednim uruchomieniem PRZED naprawą, nie domysłem:
zminimalizuj okno, przenieś je na inny pulpit -- windowsInZOrder() na
docelowym pulpicie zwracało 0 okien, mimo że focusedId wskazywało
dokładnie na to jedno, właśnie przeniesione.
Naprawa: comp.focus(id) zamiast bezpośredniego przypisania --
przeniesienie zminimalizowanego okna na inny pulpit teraz też je
PRZYWRACA, spójnie z tym, jak "przywróć i pokaż" działa wszędzie
indziej w tym module (restoreWindow robi dokładnie to samo, jednym
wywołaniem comp.focus).
Metoda weryfikacji: test_real_comp_moveworkspace.nim -- 4
scenariusze na PRAWDZIWYM, niezmodyfikowanym comp/comp.nim: regresja
na sam znaleziony błąd (zminimalizowane okno widoczne po przeniesieniu),
regresja na zwykłe okno (bez zmian), potwierdzenie że naprawa używa
PEŁNEGO focus() (zIndex też poprawnie podniesiony, nie tylko
minimized wyczyszczone ręcznie), oraz przeniesienie na ten sam
pulpit jako bezpieczny przypadek brzegowy.
Nowości w v0.2 (runda 26) -- naprawiony błąd: zmiana rozmiaru okna z lewej/górnej krawędzi poza minimalny rozmiar przesuwała przeciwległą krawędź
Czwarty z rzędu prawdziwy błąd znaleziony w comp/ dzięki temu, że
faktycznie się kompiluje i uruchamia -- tym razem w logice zmiany
rozmiaru okna (dkResize, comp/drag.nim), nietkniętej przez żadną z
poprzednich czterech rund.
Błąd: applyLeft/applyTop (szablony obliczające nową pozycję/
rozmiar przy przeciąganiu lewej/górnej krawędzi okna) liczyły newPos
wprost z NIEPRZYCIĘTEGO przesunięcia kursora, a przycinanie do
minSize działo się DOPIERO PÓŹNIEJ, bez żadnej odpowiadającej korekty
newPos. W praktyce: przeciągnięcie lewej (albo górnej) krawędzi POZA
punkt, w którym okno osiągnęłoby swój minimalny rozmiar -- codzienny,
łatwy do przypadkowego wywołania scenariusz, nie przypadek brzegowy --
powodowało, że PRZECIWLEGŁA krawędź (prawa/dolna -- ta, której
użytkownik w ogóle nie dotykał) nagle PRZESKAKIWAŁA w bok, zamiast
zostać na miejscu.
Potwierdzone bezpośrednim uruchomieniem PRZED naprawą, nie domysłem:
okno 200x150 na pozycji (100,100) -- prawa krawędź na x=300 --
przeciągnięcie lewej krawędzi o +150px (poza minSize.x = 100, surowy
wynikowy rozmiar wyszedłby 50px) dawało finalnie pos.x=250, size.x=100
-- prawa krawędź wychodziła na x=350, zamiast zostać na x=300.
Naprawa: newPos dla lewej/górnej krawędzi liczone jest teraz na
podstawie tego, o ILE rozmiar FAKTYCZNIE się zmienił WZGLĘDEM już
przyciętego minimum (newPos.x = startPos.x + startSize.x - newSize.x),
nie wprost z surowego przesunięcia kursora -- gwarantuje to, że
przeciwległa krawędź (newPos.x + newSize.x) zawsze zostaje dokładnie
tam, gdzie była, niezależnie od tego, czy przycięcie do minSize
faktycznie zadziałało. Prawa/dolna krawędź nigdy nie miały tego błędu
(nie zmieniają pozycji okna w ogóle) -- naprawa dotyczy WYŁĄCZNIE
lewej/górnej, potwierdzone osobnym testem regresyjnym.
Metoda weryfikacji: test_real_comp_resize.nim -- 5 scenariuszy na
PRAWDZIWYM, niezmodyfikowanym comp/comp.nim: regresja na sam
znaleziony błąd (lewa krawędź poza minSize, prawa musi zostać
nieruchoma), to samo dla górnej/dolnej, róg (obie osie naraz poza
minSize jednocześnie), zwykłe rozciąganie W GRANICACH minSize
(niezmienione), oraz regresja potwierdzająca, że prawy-dolny róg nigdy
nie miał tego problemu i nadal działa bez zmian.
Nowości w v0.2 (runda 25) -- naprawiony błąd: zamknięcie okna psuło kaskadę pozycji startowej kolejnych okien
Trzeci z rzędu prawdziwy błąd znaleziony dzięki testowalności comp/
(rundy 21-24) -- znowu w kodzie, który istniał od dawna, ujawnionym
dopiero przez systematyczne przeglądanie modułu w poszukiwaniu kolejnych
celów.
Błąd: openWindow kaskaduje pozycję startową nowego okna (żeby
kolejne okna nie nakładały się idealnie jedno na drugim -- klasyczny
"cascade" znany z każdego DE), ale licznik kaskady był kluczowany po
comp.windows.len -- liczbie okien otwartych AKURAT TERAZ, nie po
liczbie okien otwartych kiedykolwiek w tej sesji. W praktyce: zamknięcie
okna W ŚRODKU sekwencji otwierania (zwyczajny scenariusz codziennego
użytkowania -- otwórz kilka okien, zamknij jedno, otwórz kolejne, nie
przypadek brzegowy) cofało licznik kaskady, więc KOLEJNE nowo otwarte
okno mogło wylądować DOKŁADNIE na pozycji innego, wciąż otwartego okna
-- dokładnie ten problem, który kaskadowanie miało w ogóle zapobiegać.
Potwierdzone bezpośrednim uruchomieniem PRZED naprawą, nie domysłem:
otwórz W1/W2/W3 (każde na innej, skaskadowanej pozycji), zamknij W2,
otwórz W4 -- W4 lądowało w DOKŁADNIE tej samej pozycji co W3
(vec2(616.0, 308.0) w obu przypadkach).
Naprawa: kaskada liczona teraz wg id (unikalny, monotonicznie
rosnący identyfikator okna -- nigdy się nie cofa ani nie jest ponownie
użyty, niezależnie od tego, ile okien zamknięto po drodze) zamiast
comp.windows.len. Zawijanie po 8 "stopniach" kaskady (żeby 9. okno nie
wylądowało poza ekranem) pozostaje niezmienione -- to wciąż mod 8,
tylko liczony od stabilnego id, nie od chwiejnej liczby aktualnie
otwartych okien.
Metoda weryfikacji: test_real_comp_cascade.nim -- 4 scenariusze na
PRAWDZIWYM, niezmodyfikowanym comp/comp.nim: regresja na sam
znaleziony błąd (zamknięcie środkowego okna nie psuje pozycji
kolejnego), podstawowe kaskadowanie bez zamykania (nadal działa),
przewidywalność kaskady niezależna od "hałasu" (otwórz-i-zamknij 5
tymczasowych okien, kaskada 7. faktycznie otwartego okna liczy się od
jego prawdziwego id, nie od liczby okien widocznych w danej chwili),
oraz regresja na zawijanie po 8 oknach (niezmienione).
Nowości w v0.2 (runda 24) -- naprawiony błąd: okna przyciągnięte do połowy/ćwiartki traciły snap przy zmianie rozdzielczości ekranu
Kolejny prawdziwy błąd znaleziony dzięki temu, że comp/ faktycznie się
kompiluje i uruchamia (rundy 21-23) -- tym razem NIE w nowo dodanym
kodzie tej rundy, tylko w PRZEDTEM ISTNIEJĄCEJ procedurze
setScreenSize, ujawniony dopiero przez sprawdzenie, jak współdziała z
funkcjami z rund 21-22.
Błąd: setScreenSize (wołana przy zmianie rozdzielczości ekranu,
np. podłączeniu innego monitora) traktowała KAŻDE zmaksymalizowane okno
identycznie -- rozciągała je na CAŁY nowy ekran. To poprawne dla okna
zmaksymalizowanego W CAŁOŚCI, ale BŁĘDNE dla okna przyciągniętego do
POŁOWY albo ĆWIARTKI (w.maximized == true to JEDNA flaga używana dla
obu przypadków, rozróżnia je dopiero w.snapEdge) -- w praktyce okno
przyciągnięte do lewej połowy nagle zajmowało CAŁY nowy ekran po zmianie
rozdzielczości, mimo że snapEdge wciąż formalnie mówił seLeft.
Potwierdzone bezpośrednim uruchomieniem PRZED naprawą, nie domysłem: w
oknie 1920x1080 przyciągniętym do lewej połowy (960px szerokości) po
setScreenSize(2560x1440) szerokość wynosiła 2560 -- cały nowy
ekran, zamiast oczekiwanych 1280 (połowa nowego). Ten sam błąd dotyczył
też ćwiartek z rundy 21 -- w praktyce prawdopodobnie NAJCZĘŚCIEJ
widoczny scenariusz (laptop z oknem przyciągniętym do połowy, podłączony
do zewnętrznego monitora o innej rozdzielczości).
Naprawa: gdy snapEdge != seNone, setScreenSize przelicza
geometrię przez snapGeometry (tę samą funkcję, której już używa
snapWindow z rundy 21) względem NOWEGO rozmiaru ekranu, zamiast na
sztywno rozciągać na pełny ekran -- prawdziwa pełna maksymalizacja
(snapEdge == seNone) zachowuje dotychczasowe, poprawne zachowanie bez
zmian.
Metoda weryfikacji: test_real_comp_screensize.nim -- 5
scenariuszy na PRAWDZIWYM, niezmodyfikowanym comp/comp.nim: regresja
na sam znaleziony błąd (lewa połowa pozostaje połową NOWEGO ekranu),
prawa połowa (pozycja X też poprawnie przeliczona, nie tylko rozmiar),
ĆWIARTKA (kluczowy przypadek, który w praktyce najbardziej ujawnia ten
błąd -- geometria poprawnie przeliczona względem nowego ekranu), oraz
dwa testy regresyjne potwierdzające, że naprawa NIE zepsuła istniejącego
zachowania: prawdziwa pełna maksymalizacja nadal poprawnie rozciąga się
na cały nowy ekran, okno niezmaksymalizowane wciąż tylko przycinane
(clampToScreen), bez zmiany rozmiaru.
Nowości w v0.2 (runda 23) -- cykl fokusu wstecz (Shift+Alt+Tab); prawdziwy błąd znaleziony i naprawiony metodą "uruchom i zobacz"
Domyka realny brak: Alt+Tab działał tylko w jedną stronę -- Shift+Alt+Tab (cykl WSTECZ) nie istniał w ogóle, mimo że to standard w praktycznie każdym menedżerze okien.
To była runda z najbardziej pouczającym, realnym błędem w całej tej
serii. Pierwsza, intuicyjna implementacja reverse w cycleFocus
(comp/window.nim) wyglądała poprawnie na papierze: znajdź bieżący
indeks ogniskowanego okna w posortowanej wg z-order liście, odejmij 1
zamiast dodać. Napisany od razu do niej test (oparty na ZAŁOŻONEJ,
prostej sekwencji) faktycznie nie przechodził -- ale dopiero
bezpośrednia obserwacja (echo po każdym kroku w osobnym skrypcie
diagnostycznym) ujawniła PRAWDZIWY charakter problemu: comp.focus()
PODNOSI zIndex ogniskowanego okna na wierzch przy KAŻDYM wywołaniu --
więc "bieżący indeks" ogniskowanego okna jest PO KAŻDYM kroku, w
przeliczonej na nowo liście, zawsze len - 1 (ostatnia pozycja).
Odejmowanie stałej "1" od stałej "len - 1" daje ZAWSZE tę samą docelową
pozycję względną -- program wpadał w nieskończoną pętlę oscylującą
między DWOMA oknami, nigdy nie docierając do pozostałych (potwierdzone
na 3 oknach: focused=3 -> 2 -> 3 -> 2 -> 3..., okno 1 nigdy
nieosiągalne).
Naprawa wykorzystuje własność matematyczną cykli, zamiast łatać
indeksowanie: "w przód" jest -- empirycznie potwierdzonym, i chronionym
osobnym testem regresyjnym -- STABILNYM cyklem o długości dokładnie
visible.len (każde naciśnięcie odwiedza kolejne okno, wraca do
punktu startowego dopiero po tylu krokach, ile jest okien). Wykonanie
kroku "w przód" visible.len - 1 razy z rzędu, na TEJ SAMEJ, raz
posortowanej liście (bez ponownego sortowania między krokami --
wydzielony stepFocusForward), jest matematycznie równoważne jednemu
krokowi w przeciwnym kierunku: N-1 kroków w cyklu o długości N zawsze
ląduje dokładnie tam, gdzie wylądowałby jeden krok wstecz. Zero nowej,
zawodnej logiki indeksowej -- reużycie logiki "w przód", której
poprawność jest już ustalona.
Metoda weryfikacji: test_real_comp_cyclefocus.nim -- 7
scenariuszy na PRAWDZIWYM, niezmodyfikowanym comp/comp.nim: regresja
zwykłego cycleFocus() (zero zmian zachowania), pełny cykl wstecz
przez 3 okna (dokładnie odwrotna kolejność do "w przód", stabilna przez
wiele pełnych okrążeń), "w przód potem wstecz" wraca do punktu
startowego, pojedyncze okno i brak okien jako bezpieczne przypadki
brzegowe, respektowanie granic pulpitów wirtualnych, oraz -- najbardziej
wymowny -- test regresyjny NA SAM ZNALEZIONY BŁĄD z 5 oknami i 20
kolejnymi krokami wstecz, potwierdzający, że wszystkie 5 zostaje
odwiedzonych, żadna pętla dwuokienna się nie powtarza.
Nowy skrót actCycleFocusPrev, domyślnie shift+alt+tab,
konfigurowalny w Ustawieniach na tych samych zasadach co reszta (patrz
runda 21) -- trzy równoległe tablice w shell/shortcuts.nim rozszerzone
o jeden wpis w poprawnej pozycji, przeliczone ręcznie (18 = 18 = 18 =
liczba wartości enuma, shell/shortcuts.nim importuje fidget więc
bezpośrednia kompilacja nie była możliwa).
Domyka naturalne uzupełnienie rundy 21: przyciąganie okien do połowy/ćwiartek ekranu działało dotąd WYŁĄCZNIE ze skrótów klawiszowych (Super+Left/Right/Up/Down) -- brakowało klasycznego gestu MYSZĄ, znanego z Windows/GNOME/KDE: przeciągnięcie paska tytułu okna do krawędzi ekranu i puszczenie przycisku myszy przyciąga je automatycznie.
- Przeciągnięcie do samej góry ekranu (
TopDragSnapZone, 4px -- celowo węższa strefa niż przy bokach, żeby zwykłe przesuwanie okna blisko górnej krawędzi bez intencji maksymalizowania go nie kończyło się przypadkową maksymalizacją) maksymalizuje okno. - Przeciągnięcie do lewej/prawej krawędzi (
SnapMargin, już istniejąca stała, używana też przez magnetyczne wyrównanie pozycji podczas przeciągania) przyciąga do połowy ekranu. - Strefa górna ma pierwszeństwo nad boczną w rogu ekranu -- kursor jednocześnie blisko góry i blisko boku daje PEŁNĄ maksymalizację, nie przyciągnięcie do połowy (potwierdzone testem).
- Sam snap dzieje się DOPIERO na puszczenie przycisku myszy
(
endDrag), nie na bieżąco podczas przeciągania -- w przeciwnym razie okno zmieniałoby rozmiar W TRAKCIE przeciągania, utrudniając dalsze manewrowanie nim, gdyby użytkownik jeszcze zmienił zdanie co do miejsca. Podczas samego przeciągania śledzone jest tylko, CZY kursor jest akurat w strefie aktywacji (DragState.pendingSnapEdge/pendingMaximize, nowe pola wcomp/types.nim). - Cofnięcie przyciągnięcia (Super+Left ponownie, albo przycisk w
tytule) wraca do pozycji okna z CHWILI PUSZCZENIA MYSZY, nie do
pozycji sprzed całego ruchu --
snapWindow/doMaximizezapisują bieżącą (właśnie przeciągniętą) geometrię jako punkt powrotu, dokładnie tak samo jak przy przyciąganiu klawiaturą. toggleMaximizezostało zrefaktoryzowane -- sama logika "przejdź do stanu zmaksymalizowanego" wydzielona dodoMaximize, reużywanej teraz przez oba wyzwalacze (przycisk w tytule ORAZ przeciągnięcie do góry), żeby te dwie ścieżki nie mogły z czasem rozjechać się w drobnych szczegółach geometrii.
Metoda weryfikacji: test_real_comp_dragsnap.nim -- 8 scenariuszy
na PRAWDZIWYM, niezmodyfikowanym comp/comp.nim, symulujących pełną
sekwencję beginMove → updateDrag (jeden lub więcej razy) →
endDrag, dokładnie tak jak wołałby ją shell/shell.nim: maksymalizacja
przez przeciągnięcie do góry, przyciągnięcie do lewej/prawej połowy,
brak jakiegokolwiek snapu przy przeciąganiu z dala od krawędzi,
poprawność savedPos przy cofnięciu (wraca do pozycji z CHWILI
puszczenia myszy, nie sprzed całego ruchu -- najbardziej subtelny z
testowanych przypadków), pierwszeństwo strefy górnej w rogu ekranu,
okno resizable = false ignorujące gest, oraz regresja: nie da się
rozpocząć przeciągania już zmaksymalizowanego okna (zachowanie sprzed
tej rundy, niezmienione).
Nowości w v0.2 (runda 35) -- integracja powiadomień z DBus (realnie zweryfikowana end-to-end), WebP dla tapety przez zewnętrzny dwebp
Kontynuacja rundy 34 (ta sama prośba: rozbudować o WSZYSTKO z listy
ograniczeń) -- tym razem dwa kolejne punkty, oba z MOCNIEJSZĄ
weryfikacją niż większość rundy 34, bo żaden z nich nie zależy od
fidget (jedynej, powtarzającej się przeszkody w tej serii rund).
1. DBus -- org.freedesktop.Notifications
(shell/dbus_notify_shim.c/.h, shell/dbusnotify.nim,
shell/notifications.nim, shell/shell.nim) -- ✅ zweryfikowane
END-TO-END, realnie, w tej sesji. Minimalny serwer powiadomień na
session-bus DBus, w procesie zde-shell (przez FFI do libdbus, ten
sam styl co apps/terminal/pty_shim.c -- zwykłe funkcje C wołane z
głównej pętli, ŻADEN nowy proces/demon). Implementuje Notify (parsuje
app_name/summary/body, odpowiada poprawnym id), GetCapabilities,
GetServerInformation, CloseNotification, Introspect, plus poprawną
odpowiedź błędem UnknownMethod dla wszystkiego innego (żeby nadawca
czekający synchronicznie na odpowiedź nie zawisł). Odebrane
powiadomienia trafiają do TEJ SAMEJ kolejki/historii co powiadomienia
wewnętrzne ZDE (tickDbusNotifications w notifications.nim, wołane
raz na sekundę z tickMain w shell.nim, drenuje całą kolejkę na raz).
Metoda weryfikacji: w tej sandboxie zainstalowano libdbus-1-dev i
libnotify-bin (apt, archive.ubuntu.com na liście dozwolonych domen),
uruchomiono PRAWDZIWY dbus-daemon --session, skompilowano
dbus_notify_shim.c (samodzielnie w C, potem PRZEZ Nim FFI w
dbusnotify.nim -- oba się skompilowały i połączyły bez błędu) i
przepuszczono przez niego PRAWDZIWE wywołania: notify-send "TestApp" "Hello..." odebrane poprawnie po stronie C ORAZ po stronie Nim
(pollDbusNotification(), osobny test), dbus-send dla
GetServerInformation/CloseNotification/nieznanej metody -- wszystkie
z poprawnymi odpowiedziami. To najsilniej zweryfikowany fragment kodu w
CAŁEJ tej rundzie rozbudowy (34+35) -- prawdziwy klient, prawdziwy bus,
prawdziwy protokół, nie tylko izolowany test logiki na std samym.
Świadomie POZA zakresem (nie przemilczane): akcje przycisków, hints
(priorytet/dźwięk/ikona), expire_timeout z wywołania -- wszystkie
ignorowane, każde powiadomienie z zewnątrz wygląda jak zwykłe nkInfo.
CloseNotification wołane przez NADAWCĘ jest przyjmowane, ale nie ma
efektu (brak mapowania id DBus -> id toastu ZDE). Gdy na sesji już
działa inny serwer powiadomień (GNOME/KDE/dunst) i nie oddaje nazwy --
integracja po cichu się nie aktywuje (DBUS_NAME_FLAG_DO_NOT_QUEUE,
świadomie BEZ REPLACE_EXISTING -- odbieranie tej roli aktywnej sesji
w trakcie działania mogłoby zerwać inne aplikacje w trakcie wysyłania
powiadomienia).
2. WebP dla tapety -- przez zewnętrzny dwebp (shell/wallpaper.nim)
-- ✅ zweryfikowane realnie: prawdziwy plik .webp wygenerowany
cwebp (z prawdziwego PNG zrobionego przez Pixie), zdekodowany z
powrotem przez readImageWithWebpFallback z poprawnymi wymiarami; osobno
potwierdzone poprawne, ciche zdegradowanie (bez wyjątku wykraczającego
poza istniejącą obsługę błędów ensureWallpaperCache), gdy dwebp nie
jest w PATH. Pixie samo w sobie WCIĄŻ nie dekoduje WebP (to się nie
zmieniło i nie mogło się zmienić bez podmiany biblioteki) --
readImageWithWebpFallback woła dwebp (pakiet webp/
libwebp-tools) do konwersji na tymczasowy PNG PRZED przekazaniem do
Pixie, ten sam duch integracji zewnętrznym narzędziem co
wl-copy/xclip gdzie indziej w ZDE. Plik tymczasowy trafia do
katalogu cache tapet i jest usuwany zaraz po wczytaniu
(try/finally), niezależnie od powodzenia.
Wciąż bez zmian (te same powody architektoniczne, patrz notatka do
rundy 14/34 wyżej): multi-seat, GUI dla .zpk, wiele
sesji/użytkowników, prawdziwe HiDPI (ZDE dalej nie zna skali monitora).
Nowości w v0.2 (runda 34) -- domknięcie sześciu punktów z listy ograniczeń: Icon Theme Spec, .gitignore (ścieżki + rodzice), schowek plików ↔ zewnętrzne aplikacje, pinch/hold, pełne Ctrl+Z w edytorze
Ta runda powstała w odpowiedzi na wprost sformułowaną prośbę: rozbudować
ZDE o WSZYSTKO, co wcześniej wypisane zostało jako wymagające dalszej
pracy. Zrealizowano sześć z ~piętnastu punktów -- pozostałe (multi-seat,
integracja DBus dla powiadomień, GUI dla .zpk, wiele
sesji/użytkowników, prawdziwe HiDPI, WebP) zostają bez zmian, z tych
samych, architektonicznych powodów opisanych już wcześniej w tym README
(patrz notatka do rundy 14 wyżej w sekcji "Ograniczenia") -- każdy z
nich to osobny, wielodniowy/wielotygodniowy projekt, nie coś do
"dorzucenia przy okazji".
Metoda weryfikacji w TEJ sesji (żeby było jasne, co dokładnie
oznacza "zrobione" dla każdego punktu): sandbox tej rundy MIAŁA dostęp
do apt (archive.ubuntu.com/security.ubuntu.com na liście
dozwolonych domen) -- zainstalowano Nim 1.6.14 i pixie przez nimble.
To wystarczyło do PRAWDZIWEJ kompilacji shell/desktopapps.nim (patrz
punkt 1 niżej) i do dwóch izolowanych, realnie uruchomionych testów
logiki menedżera plików (punkty 2-3) na std/re+std/os samych, bez
fidget. fidget (wymaga Nim >= 2.0, niedostępny w tej sandboxie)
pozostał niedostępny jak w poprzednich rundach -- więc punkty dotyczące
apps/texteditor (5) i wlcomp/ (4, brak libwlroots-dev w TEJ
konkretnej sandboxie) mają SŁABSZĄ weryfikację (kod napisany zgodnie ze
sprawdzonymi wzorcami z wcześniejszych rund, nim check przeszedł poza
samym importem fidget/nagłówków wlroots, ale bez żywego uruchomienia)
-- każdy punkt niżej mówi wprost, do której kategorii należy.
1. Icon Theme Spec -- pełne reguły Fixed/Scalable/Threshold
(shell/desktopapps.nim) -- ✅ skompilowane i przetestowane REALNIE
w tej sesji. dirSizeDistance zastępuje poprzednie, jednolite
abs(size - PreferredIconSize) formalną funkcją odległości ze
specyfikacji: katalog Type=Fixed wymaga DOKŁADNEGO rozmiaru;
Type=Scalable dopasowuje CAŁY zakres MinSize=..MaxSize= bez kary;
Type=Threshold (domyślny, gdy Type= nie występuje w ogóle --
zgodnie ze specyfikacją) dopasowuje Size= ± Threshold= (domyślnie 2)
bez kary. Test: syntetyczny motyw z trzema katalogami (Fixed 16px,
Scalable 16-512px, Threshold 48±3px) -- przy PreferredIconSize=48
katalog Threshold poprawnie wygrywa jako jedyne dokładne dopasowanie
(dystans 0), a dla SVG poprawnie wybierany jest katalog Scalable.
2-3. .gitignore: wzorce ze ścieżką + wędrówka po rodzicach
(apps/filemanager/files.nim) -- ✅ logika przetestowana REALNIE
(4 testy na prawdziwym systemie plików w /tmp, poza repo -- ten sam
styl co reszta projektu, testy nie są commitowane). Dwie zmiany naraz:
loadGitignoreChainwędruje teraz w górę od katalogu startowego aż do korzenia systemu plików (limitMaxGitignoreParentWalk = 64jako siatka bezpieczeństwa), zbierając WSZYSTKIE napotkane.gitignore, nie tylko ten z katalogu bieżącego -- więc.gitignorew korzeniu repo teraz poprawnie wpływa na podkatalogi, nawet gdy przeglądamy je bezpośrednio, bez własnego.gitignore.- Wzorce ZAWIERAJĄCE
/(np.src/generated/,/build) są teraz UŻYWANE zamiast pomijane -- dopasowywane do PEŁNEJ ścieżki względem katalogu, w którym leży dany.gitignore, z poprawną semantyką pojedynczej*(nie przekracza/,[^/]*) i**(przekracza,.*) -- poprzednio*/**były traktowane identycznie, co dla wzorców bez/(jedyne wcześniej obsługiwane) nie miało znaczenia, ale miałoby dla ścieżek.loadGitignorePatterns/isGitignored(jednopoziomowe, bez rodziców) zostały ZACHOWANE dla wstecznej zgodności, ale nowy kod (refresh/searchFilesRecursive) korzysta zloadGitignoreChain/isGitignoredAt.
4. Schowek plików -- jednokierunkowa integracja z aplikacjami spoza
ZDE (apps/filemanager/files.nim) -- ✅ logika fallbacku
przetestowana REALNIE (graceful no-op, gdy brak wl-copy/xclip),
❌ samo wywołanie wl-copy/xclip nie zostało przetestowane wobec
PRAWDZIWEGO kompozytora Wayland (ta sandbox go nie ma). "Kopiuj"/"Wytnij"
teraz DODATKOWO wystawiają skopiowane ścieżki jako text/uri-list na
systemowy schowek (wl-copy --type text/uri-list / xclip -t text/uri-list, dokładnie ten sam wzorzec zewnętrznego narzędzia co
shell/clipboard.nim używa już od dawna dla tekstu) -- pozwala to
wkleić skopiowane pliki w PRAWDZIWEJ, zewnętrznej aplikacji. Kierunek
odwrotny (wklejanie DO ZDE plików skopiowanych gdzie indziej) wciąż
wymaga zmiany w kompozytorze (wl_data_device) i pozostaje
nierozwiązany -- patrz notatka w sekcji "Ograniczenia".
5. Edytor tekstu -- pełne Ctrl+Z/Ctrl+Shift+Z/Ctrl+Y dla zwykłego
pisania (apps/texteditor/texteditor.nim) -- replaceUndoStack), odkładane w onInput z debounce 0,6s
(UndoSnapshotIntervalSec) i limitem głębokości 100
(MaxUndoTypingDepth) -- nowa edycja po cofnięciu kasuje stos "ponów",
standardowe zachowanie. Kluczowy szczegół: po cofnięciu/ponowieniu kod
jawnie ustawia keyboard.focusNode = nil -- to NIE jest nowy pomysł,
tylko ODTWORZENIE techniki, którą ten sam projekt już raz potwierdził
żywym testem pod Xvfb przy zupełnie innym buggu (apps/terminal/term.nim,
runda 12): Fidget trzyma WŁASNY, wewnętrzny bufor edycji dla
skupionego pola, niezależny od tego, co ustawi kod, i bez zerowania
fokusu odtworzyłby swój stary bufor z powrotem na następnej klatce,
kasując efekt cofnięcia. Ryzyko, że mimo tej techniki coś tu nie
zadziała tak jak w terminalu, jest realne i NIE zostało w tej rundzie
wykluczone -- do potwierdzenia w przyszłej sesji z dostępnym Fidget/GLFW.
6. Pinch/hold -- re-broadcast do klientów (wlcomp/gestures.nim,
wlcomp/wlroots.nim, wlcomp/types.nim) -- events.pinch_begin itd., struktury
wlr_pointer_pinch_*_event), NIE skompilowane w tej sesji (brak
libwlroots-dev, w odróżnieniu od punktu 1 wyżej ten pakiet nie był w
tej sesji instalowany). Wzorowane 1:1 na już wcześniej zweryfikowanym
kodzie swipe w tym samym pliku -- ten sam re-broadcast przez
wlr_pointer_gestures_v1, bez żadnej WŁASNEJ reakcji kompozytora
(świadomie -- patrz duży komentarz w pliku o braku dziś w ZDE
jednoznacznego zastosowania dla pinch-to-zoom). Do potwierdzenia
kompilacją w przyszłej sesji z dostępnymi nagłówkami wlroots.
Nowości w v0.2 (runda 21) -- przyciąganie okien do ćwiartek ekranu; przełom w metodzie weryfikacji: comp/ kompiluje się i testuje w PEŁNI, jako jedyny prawdziwie działający fragment zde-shell w tej serii rund
Największe odkrycie metodologiczne od rundy 14. Podczas szukania
kolejnego celu do rozbudowy sprawdzono zależności comp/ (rdzeń
zarządzania oknami -- kafelkowanie, przyciąganie do krawędzi, z-order,
fokus, pulpity wirtualne) i okazało się, że comp/comp.nim/window.nim/
types.nim/drag.nim zależą WYŁĄCZNIE od vmath (biblioteka
matematyczna, już zainstalowana jako zależność pixie w rundzie 14) --
zero zależności od fidget. To pierwszy raz w całej tej serii rund
(13-21), kiedy dało się skompilować i URUCHOMIĆ prawdziwy, produkcyjny
kod ZARZĄDZANIA OKNAMI -- nie kopię logiki w izolowanym skrypcie (jak
rundy 13-20 musiały robić dla files.nim/texteditor.nim), tylko
import ../comp/comp i wywołanie PRAWDZIWYCH, eksportowanych procedur:
openWindow, snapWindow, cycleFocus, closeWindow, switchWorkspace,
hitTestEdge i reszty.
Domyka realny brak -- dotąd SnapEdge (comp/types.nim) miał tylko
seLeft/seRight (Super+Left/Right, od rundy "Aurora" v0.1). Dodano
górną/dolną połowę (seTop/seBottom, nowe skróty Super+Up/Super+Down)
ORAZ cztery ćwiartki, z "doprecyzowaniem" w stylu Windows 11 Snap: gdy
okno jest już przyciągnięte do lewej/prawej połowy, naciśnięcie
Super+Up/Down nie zastępuje tego górną/dolną połową CAŁEGO ekranu, tylko
DOPRECYZOWUJE do odpowiedniej ćwiartki (combinedEdge w
comp/window.nim) -- Super+Left, potem Super+Up, daje ćwiartkę lewą-
górną. Dokładnie to samo, dwukrotne naciśnięcie TEJ SAMEJ krawędzi
(Super+Left, Super+Left) nadal przywraca oryginalny rozmiar okna --
zachowanie sprzed tej rundy, zachowane bez zmian i potwierdzone testem
regresyjnym.
Metoda weryfikacji: test_real_comp_snap.nim importuje PRAWDZIWY,
niezmodyfikowany comp/comp.nim -- 11 scenariuszy na faktycznie
uruchomionym kodzie kompozytora: geometria otwarcia okna, przyciągnięcie
do połowy, toggle przy powtórzeniu tej samej krawędzi (regresja),
doprecyzowanie do ćwiartki (Super+Left potem Super+Up), wszystkie 4
sprawdzone kombinacje doprecyzowania, test NIEZMIENNIKA geometrycznego
(cztery ćwiartki razem wypełniają cały dostępny ekran, bez dziur i bez
nakładania -- nie tylko sprawdzenie pojedynczych wartości), okno
resizable = false ignorujące snap, cykl fokusu (Alt+Tab) po
prawdziwym z-order, oddawanie fokusu po zamknięciu aktywnego okna,
izolacja okien między pulpitami wirtualnymi, wykrywanie krawędzi do
zmiany rozmiaru (hitTestEdge).
Nowe skróty (shell/shortcuts.nim): actSnapUp/actSnapDown, domyślnie
super+up/super+down, konfigurowalne w Ustawieniach na tych samych
zasadach co reszta skrótów (generyczna pętla for a in ShortcutAction,
żadna lista w apps/settings/settings.nim nie wymagała ręcznej
aktualizacji). Trzy równoległe tablice (ActionNames/ActionLabels/
DefaultCombos, indeksowane POZYCYJNIE wg kolejności enuma
ShortcutAction) rozszerzone o dwa wpisy w poprawnej pozycji --
zweryfikowane osobnym testem semantyki Nim (array[EnumType, T]
faktycznie dopasowuje pozycję literału do kolejności enuma) plus ręczne
przeliczenie długości wszystkich trzech tablic (17 = 17 = 17 = liczba
wartości enuma) -- shell/shortcuts.nim importuje fidget, więc nie
dało się tego sprawdzić bezpośrednią kompilacją, ale ryzyko rozjechania
pozycji zostało zaadresowane najlepiej jak się dało bez tego.
Reszta zde-shell (całe UI -- menedżer plików, edytor, launcher, dok)
WCIĄŻ importuje fidget i pozostaje nieskompilowana w tej sesji --
comp/ to jeden, wydzielony fragment silnika, nie cały shell.
Domyka realny brak, znaleziony przy przeglądzie kodu w poszukiwaniu
kolejnego celu: apps/texteditor/highlight.nim obsługiwał tylko 5
języków (Nim/Python/C/JS/Shell), mimo że -- w ODRÓŻNIENIU od reszty
edytora -- zależy WYŁĄCZNIE od std, nie od fidget. To oznacza, że
CAŁY ten moduł dało się w pełni skompilować i przetestować w tej
sandboxie (ten sam status co desktopapps.nim/thumbnails.nim z rund
14/16, ale bez potrzeby instalowania pixie -- highlight.nim nie
potrzebuje nawet tego).
Dodano trzy popularne języki: Go (z obsługą surowych stringów w
backtickach, `...` -- inna, specyficzna dla Go składnia, celowo
jednolinijkowa, bez pełnej obsługi wieloliniowych surowych stringów),
Rust (bez r"..."/r#"..."#, świadomie -- dodanie ich porządnie
wymagałoby osobnej ścieżki parsowania z liczeniem #, nie tylko flagi
bool jak reszta tego prostego tokenizera) i JSON (który formalnie
NIE MA komentarzy w ogóle wg specyfikacji RFC 8259 -- lineCommentFor
zwraca dla niego pusty string, sprawdzone testem, że # w wartości
stringa JSON-a nie zostaje pomylone z komentarzem). Heurystyka
"PascalCase = prawdopodobnie typ" (dotąd tylko dla Nim/C) rozszerzona o
Go i Rust -- w obu tych językach PascalCase ma REALNE znaczenie
językowe (w Go decyduje o eksporcie identyfikatora, w Rust to formalna
konwencja dla structs/enums/traits), więc trafność tej heurystyki jest
tu WYŻSZA niż dla C, nie tylko "tak samo dobra".
Prawdziwy błąd znaleziony i naprawiony podczas testowania tej rundy
(nie przez czytanie kodu): pierwsza wersja dodała obsługę stringów w
backtickach, ale NIE zaktualizowała warunku zatrzymania w bloku
zbierającym "cokolwiek inne" (operatory, białe znaki) -- w efekcie
backtik napotkany W TRAKCIE zbierania takiego fragmentu (bardzo
częsty przypadek: spacja albo operator PRZED stringiem, np. x := \raw`) był po cichu POŁYKANY do tego fragmentu, zamiast zatrzymać go i pozwolić string-owej obsłudze go przechwycić -- string w backtickach nigdy by się nie podświetlił, gdyby poprzedzał go choćby jeden znak "plain". Test wprost odtwarza dokładnie ten przypadek (x := `raw
string`) i potwierdza zarówno że powstaje dokładnie jeden token tkString, jak i że żaden token tkPlain` nie zawiera backticka.
Metoda weryfikacji: test_real_highlight.nim importuje PRAWDZIWY,
niezmodyfikowany highlight.nim (nie kopię logiki) -- 11 scenariuszy,
w tym test regresyjny na opisany wyżej błąd, test na to, że backtick
POZA Go pozostaje zwykłym znakiem (rozbudowa nie wycieka do innych
języków), test na brak komentarzy w JSON, i pełna regresja wszystkich
pięciu języków sprzed tej rundy (Nim/Python/C/JS/Shell) -- bez zmian w
ich zachowaniu.
Domyka brak, którego menedżer plików nie miał w ogóle od pierwszej rundy -- żadnej formy szukania pliku po nazwie, tylko ręczne przeklikiwanie się przez foldery. Przełącznik "🔍" w toolbarze otwiera pasek z polem zapytania i przyciskiem "Szukaj" -- rekurencyjny skan PO NAZWIE (prosty podciąg, case-insensitive, bez glob/regex -- to ma być szybkie "znajdź plik", nie potężne wyszukiwanie) od bieżącego katalogu w dół. Kliknięcie wyniku nawiguje do jego katalogu, zaznacza go i zamyka pasek wyszukiwania.
Świadomie NIE działa na bieżąco przy każdym znaku (w odróżnieniu od Znajdź w edytorze tekstu) -- wymaga kliknięcia "Szukaj" albo Enter. To nie jest ograniczenie techniczne, tylko świadoma decyzja: Znajdź w edytorze przeszukuje treść JUŻ leżącą w pamięci (tanie, można przeliczać co klatkę), to wyszukiwanie robi REKURENCYJNY SKAN DYSKU, więc uruchamianie go na każde naciśnięcie klawisza byłoby marnotrawstwem i potencjalnie zauważalnym zacinaniem przy większych drzewach katalogów.
Dwa niezależne zabezpieczenia przed zawieszeniem na wielkim drzewie:
MaxSearchResults (200 -- więcej i tak nie da się sensownie przejrzeć)
i, ważniejsze, MaxSearchScanned (5000 -- limit liczby ODWIEDZONYCH
wpisów, niezależnie od liczby trafień; bez tego wyszukiwanie frazy z
zerem trafień w ogromnym drzewie, np. przez pomyłkę uruchomione z
katalogu domowego zawierającego całe repozytoria, skanowałoby
WSZYSTKO bez końca, zanim cokolwiek by zwróciło).
Respektuje ukrywanie wg .gitignore (runda 15/18) na tych samych
zasadach co zwykła lista katalogu, gdy hideIgnored jest włączone.
Prawdziwy błąd znaleziony i naprawiony podczas testowania tej rundy
(nie przez czytanie kodu -- przez uruchomienie testu na realnym,
wielopoziomowym drzewie katalogów z .gitignore w korzeniu): pierwsza
wersja wołała wczytywanie wzorców .gitignore OSOBNO na KAŻDYM poziomie
rekursji, tym samym mechanizmem co zwykła lista katalogu (runda 15/18).
To brzmiało konsekwentnie, ale dla wyszukiwania REKURENCYJNEGO było
błędne: reguła node_modules zapisana w .gitignore w katalogu
GŁÓWNYM w ogóle nie obejmowała pliku leżącego dwa poziomy niżej w
podkatalogu, który sam nie miał WŁASNEGO .gitignore -- test
jednoznacznie to wykazał (plik wewnątrz node_modules pojawiał się w
wynikach mimo hideIgnored = true). Naprawione: wzorce są teraz
wczytywane RAZ, z katalogu, w którym wyszukiwanie się ZACZYNA, i
stosowane jednolicie do całego przeszukiwanego poddrzewa -- to wciąż
nie jest pełna semantyka Gita (prawdziwy Git honorowałby też DODATKOWE,
zagnieżdżone .gitignore głębiej w drzewie), ale poprawnie obsługuje
zdecydowanie najczęstszy przypadek: jeden .gitignore w korzeniu repo.
Lista wyników to CELOWO prosty, płaski widok (ikona + ścieżka względna) bez maszynerii przeciągania/zmiany nazwy/zaznaczania zwykłej listy -- wyniki mogą leżeć w wielu różnych katalogach naraz, więc te akcje nie miałyby tu jednoznacznego sensu.
Metoda weryfikacji: test_file_search.nim -- 7 scenariuszy na
prawdziwym, wielopoziomowym drzewie katalogów w /tmp (podstawowe
wyszukiwanie rekurencyjne, trafienia w plik i folder o tej samej
nazwie, pusta fraza, brak trafień, respektowanie .gitignore --
WŁĄCZNIE z testem, który złapał opisany wyżej błąd, limit liczby
wyników z flagą "obcięto", rozbicie ścieżki wyniku na katalog
nadrzędny + nazwę). apps/filemanager/files.nim wciąż importuje
fidget, więc integracja UI (przycisk, pasek, lista wyników) pozostaje
nieprzetestowana wizualnie, jak reszta menedżera plików od rundy 13.
Domyka jawnie udokumentowane, świadome ograniczenie z rundy 15
("negacja ! jest CAŁKOWICIE ignorowana"). loadGitignorePatterns/
isGitignored (apps/filemanager/files.nim) implementują teraz
DOKŁADNIE tę samą semantykę co prawdziwy Git: kolejność wzorców w
pliku ma znaczenie, ostatni pasujący wzorzec wygrywa -- nie
pierwszy. To wymagało realnej zmiany algorytmu, nie tylko przestania
pomijać linie zaczynające się od !: poprzednia wersja isGitignored
zwracała true przy PIERWSZYM trafieniu (return true w pętli), co
dawałoby BŁĘDNĄ, odwrotną semantykę "pierwszy wygrywa", gdyby po prostu
przestać ignorować ! bez zmiany tej pętli. Nowa wersja iteruje przez
WSZYSTKIE wzorce, nadpisując stan ignored przy każdym kolejnym
trafieniu (zwykłym LUB negowanym), więc końcowy wynik zawsze
odzwierciedla OSTATNI pasujący wzorzec w pliku.
.git pozostaje ukrywany zawsze, nawet przy jawnej (bardzo nietypowej)
negacji !.git w .gitignore -- ten wyjątek, ustalony już w rundzie
15, jest teraz jawnie sprawdzony testem, żeby przyszła zmiana kodu nie
mogła go po cichu cofnąć.
Wciąż świadomie poza zakresem, niezmienione względem rundy 15: wzorce
ze ścieżką względną (zawierające / w środku) -- w tym negacje TAKICH
wzorców (!src/generated/keep.txt) -- bo dopasowanie w tym module
działa wyłącznie po nazwie wpisu, nie po pełnej ścieżce; .gitignore
tylko z bieżącego katalogu, bez wędrówki po rodzicach.
Metoda weryfikacji: test_gitignore.nim rozszerzony do 9
scenariuszy (z 6 w rundzie 15) -- w tym Test 4, który wprost
demonstruje ZMIANĘ zachowania na DOKŁADNIE tym samym pliku
.gitignore, którego użyła runda 15 do potwierdzenia poprzedniego,
świadomego ograniczenia (!important.log -- wcześniej plik zostawał
ukryty, teraz jest odkrywany), oraz dwa testy kolejności (ukryj-potem-
odkryj i odkryj-potem-ukryj) potwierdzające, że wygrywa OSTATNI wzorzec,
nie pierwszy.
Nowości w v0.2 (runda 17) -- tapeta: statyczny GIF; realny przegląd dekodera GIF Pixie na 82 plikach
Mała, ale w pełni dopracowana rozbudowa, znaleziona przy okazji
sprawdzania czegoś innego: podczas pisania rundy 16 (miniatury obrazów)
zauważono, że pixie.readImage rozpoznaje format obrazu po SYGNATURZE
BAJTÓW pliku, nie po rozszerzeniu -- co oznacza, że shell/ wallpaper.nim (ensureWallpaperCache) UMIAŁO dekodować statyczne
(pierwsza klatka, bez animacji) pliki GIF od zawsze, mimo że README
od dawna twierdziło "PNG/JPEG bez GIF". Jedyną realną przeszkodą był
PickerImageExts = [".png", ".jpg", ".jpeg"] w apps/settings/ settings.nim -- filtr przeglądarki plików, który sztucznie nie
pozwalał w ogóle WYBRAĆ pliku .gif, mimo że silnik pod spodem by go
przyjął.
Zanim cokolwiek zmieniono, sprawdzono to na 82 PRAWDZIWYCH plikach
.gif zainstalowanych w tej sandboxie (find / -iname "*.gif" --
motywy galerii LibreOffice, dokumentacja różnych pakietów), nie na
pojedynczym, wybranym "na oko" przykładzie:
- 77/82 (94%) dekoduje się bez problemu.
- 5/82 nie -- i każdy z tych przypadków jest tu uczciwie
wymieniony, nie zamieciony pod dywan: jeden to zerwany symlink (bez
związku z Pixie), trzy dają
Invalid GIF buffer, unable to load(prawdziwa, rzadka luka w minimalnym dekoderze GIF Pixie -- niektóre plikiGIF89az lokalną paletą kolorów go wywalają), jeden jest explicite odrzucony przez sam Pixie jakoUnsupported GIF, pixel aspect ratio. - Kluczowe: w KAŻDYM z tych 5 przypadków
ensureWallpaperCache(i, analogicznie,ensureThumbnailz rundy 16) już wcześniej łapało wyjątek i cicho zwracało "" -- więc żaden z nich nie jest w stanie wywalićzde-shell. Potwierdzone konkretnie, nie tylko przez czytanie kodu:test_wallpaper_gif.nimużywa PRAWDZIWEGO, znanego-wadliwego pliku (bullets/rainbow.gif) jako źródła tapety i sprawdza, że wynik to pusty string, nie wyjątek.test_real_thumbnails.nimrobi to samo dla miniatur w menedżerze plików, tym samym plikiem.
Zmiana w kodzie: PickerImageExts rozszerzone o .gif, dwa teksty
pomocnicze w UI zaktualizowane. BMP/QOI/PPM (też realnie dekodowane
przez readImage) świadomie NIE dodane do przeglądarki -- w praktyce
prawie nikt nie trzyma zdjęć/tapet w tych formatach, więc dodanie ich
tylko zaśmieciłoby wybór bez realnej wartości. WebP wciąż całkowicie
poza zasięgiem -- pixie@5.0.7 w ogóle go nie dekoduje (sprawdzone w
źródle pakietu: brak jakiejkolwiek gałęzi WebP w decodeImage), więc
dodanie .webp do filtra dawałoby wybór kończący się zawsze cichym
brakiem podglądu.
Nowości w v0.2 (runda 16) -- miniatury obrazów w menedżerze plików; prawdziwe cofanie dla "Zamień"/"Zamień wszystko"
Domyka oba punkty jawnie odłożone pod koniec rundy 15 -- tym razem oba faktycznie zrealizowane, nie tylko przeanalizowane.
Domyka jawnie wypisany brak. Zamiast dopisywać kod dekodowania
obrazów bezpośrednio do apps/filemanager/files.nim (które importuje
fidget do całego swojego UI i dlatego nie da się go skompilować w tej
sandboxie), cała logika trafiła do NOWEGO, osobnego modułu
apps/filemanager/thumbnails.nim, który importuje WYŁĄCZNIE std/
pixie -- dokładnie ten sam zabieg, który w rundzie 14 pozwolił
naprawdę skompilować i przetestować desktopapps.nim. Architektura i
konwencje (cache kluczowany ścieżką+czasem modyfikacji+rozmiarem w
samej nazwie pliku, "spróbuj raz i zapamiętaj porażkę", sprzątanie
najstarszych plików po przekroczeniu limitu) są w CAŁOŚCI skopiowane z
już wcześniej sprawdzonego shell/wallpaper.nim -- nie wymyślano
nowego podejścia do tego samego rodzaju problemu.
Obsługiwane formaty ustalone przez REALNE sprawdzenie źródła
zainstalowanego w tej sandboxie pixie@5.0.7 (decodeImage rozpoznaje
format po sygnaturze bajtów, nie po rozszerzeniu): PNG, JPEG, BMP, GIF
(tylko pierwsza klatka), QOI, PPM. WebP i TIFF świadomie pominięte --
pixie@5.0.7 w ogóle ich nie podłącza do decodeImage, mimo że
tiff.nim istnieje w pakiecie jako osobny plik.
Integracja z UI (files.nim, wiersz listy plików) używa dokładnie tego
samego, już sprawdzonego wzorca image(...) co shell/wallpaper.nim
dla tapety z pliku (dataDir = "/", ścieżka cache bez wiodącego "/").
Ta integracja, w odróżnieniu od samego thumbnails.nim, pozostaje
nieprzetestowana wizualnie w tej sesji -- jak reszta UI menedżera
plików od rundy 13.
Metoda weryfikacji: test_real_thumbnails.nim importuje PRAWDZIWY,
niezmodyfikowany thumbnails.nim (nie kopię logiki) i woła jego
prawdziwe ensureThumbnail() na prawdziwym pliku PNG zainstalowanym w
tej sandboxie (hicolor/48x48/apps/libreoffice-writer.png) -- 8
scenariuszy: poprawne rozpoznawanie rozszerzeń (wielkość liter bez
znaczenia, .webp świadomie odrzucane), realne wygenerowanie i
zapisanie miniatury o dokładnie kwadratowym rozmiarze, trafienie w
cache przy drugiej próbie (plik cache NIE jest nadpisywany), różne
rozmiary nie kolidują w cache, plik o nieobsługiwanym rozszerzeniu i
nieistniejący plik dają pusty wynik BEZ wyjątku (z zapamiętaną
porażką), uszkodzony plik z poprawnym rozszerzeniem nie wywala
programu, a obraz o proporcjach innych niż kwadrat (sztucznie
wygenerowana panorama 200x50) i tak daje poprawną kwadratową miniaturę
(cover crop).
Runda 15 odkryła, że edytor w ogóle nie ma mechanizmu Ctrl+Z dla treści
dokumentu. Budowa PEŁNEGO, ogólnego cofania (przechwytywanie KAŻDEGO
naciśnięcia klawisza przy zwykłym pisaniu) byłaby ryzykowną ingerencją
w ścieżkę, której zachowania wewnątrz Fidget-owego editableText nie
da się w tej sandboxie zweryfikować -- więc ten mechanizm jest CELOWO
węższy: cofa WYŁĄCZNIE operacje "Zamień"/"Zamień wszystko"
(doReplaceCurrent/doReplaceAll), bo to one i tak manipulują
t.content jako zwykłym stringiem, z pominięciem pola tekstowego --
dodanie stosu TYLKO dla nich w ogóle nie rusza ścieżki zwykłego
pisania.
- Nowe pole
Tab.replaceUndoStack: seq[string]-- migawka CAŁEJ treści dokumentu odkładana TUŻ PRZED każdą faktyczną zmianą (nie przy operacjach, które i tak nic by nie zmieniły -- pusty wynik wyszukiwania nie zaśmieca stosu). Limit głębokości 20 (migawki całego dokumentu, nie pojedyncze znaki -- głębokość rzędu dziesiątek jest z zapasem wystarczająca). - Nowy przycisk "↶" w pasku Zamień (NIE skrót Ctrl+Z -- z tego samego powodu co brak ingerencji w zwykłe pisanie: zwykły przycisk nie koliduje z niczym nieznanym, globalny skrót klawiszowy mógłby). Widoczny/aktywny tylko, gdy jest co cofnąć. Miejsce zrobione przez zwężenie pola "zamień na..." -- pozycje "Zamień"/"Zamień wszystko" po prawej zostają nietknięte, ten sam trik layoutu co przy dodawaniu ".*" w rundzie 14.
- Historia jest zerowana przy wczytaniu INNEGO pliku do tej samej,
ponownie użytej karty (
doOpen) -- bez tego "Cofnij" mogłoby przywrócić treść zupełnie innego, wcześniej otwartego dokumentu. - Świadomie NIE jest to dwukierunkowe "cofnij/przywróć" (brak "Redo") -- tylko cofanie zamian, w kolejności LIFO, aż do wyczerpania stosu.
Metoda weryfikacji: test_replace_undo.nim (logika nie-UI,
texteditor.nim importuje fidget więc całości wciąż nie da się
skompilować w tej sesji) -- 5 scenariuszy: podstawowe cofnięcie
przywraca DOKŁADNIE poprzednią treść, cofnięcie bez historii to
bezpieczny no-op, wiele kolejnych zamian cofa się w prawidłowej
kolejności LIFO aż do wyczerpania stosu, zamiana bez żadnych dopasowań
NIE zaśmieca stosu, limit głębokości faktycznie obcina najstarsze
wpisy zamiast rosnąć bez końca.
Nowości w v0.2 (runda 15) -- menedżer plików: sortowanie, historia wstecz/dalej, .gitignore; edytor: podświetlanie grup regex
Odpowiedź na kolejną prośbę o rozbudowę o "wszystko, co jeszcze bym zrobił" z poprzedniej rozmowy. Cztery z sześciu wtedy wymienionych punktów dało się w tej sesji zrealizować i przetestować; dwa pozostałe (miniatury obrazów, grupowanie historii cofania dla "Zamień wszystko") zostały świadomie odłożone -- ten drugi z zaskakującego powodu, patrz niżej.
Przyciski "↕ Nazwa/Rozmiar/Data" + "▲/▼" w toolbarze. Foldery ZAWSZE
zostają przed plikami (niezmieniona konwencja) -- sortowanie i kierunek
dotyczą tylko klucza WEWNĄTRZ każdej z tych dwóch grup. Remis (np. dwa
pliki o identycznym rozmiarze) rozstrzygany zawsze alfabetycznie, dla
przewidywalnej, stabilnej kolejności. Domyślne wartości (smName,
rosnąco) odtwarzają dokładnie dawne, sztywne zachowanie -- żadne
istniejące okno nie zmienia się, dopóki ktoś świadomie nie kliknie.
Przyciski "◀"/"▶" -- dokładnie ten sam model co w przeglądarce: navBack
to stos katalogów, z których przyszliśmy, navForward to stos, do
którego można wrócić po cofnięciu. Nawigacja w NOWĄ stronę po cofnięciu
się KASUJE starą "przyszłość" (dokładnie jak w przeglądarce). Przyciski
są wizualnie wyszarzone i nieklikalne, gdy odpowiedni stos jest pusty.
Katalog, który zniknął z dysku między odwiedzeniem a próbą powrotu do
niego, jest po cichu pomijany zamiast wywalać błąd.
Przełącznik ".gi" w toolbarze (domyślnie wyłączony). Parsuje
.gitignore z BIEŻĄCEGO katalogu (nie z rodziców wzwyż -- świadomie
mniejszy zakres niż prawdziwy Git, patrz duży komentarz przy
loadGitignorePatterns w kodzie), konwertuje proste wzorce glob (*,
?) na wyrażenia std/re. .git/ jest ukrywany zawsze, niezależnie od
zawartości .gitignore. Świadomie poza zakresem, uczciwie
przetestowane jako takie (patrz test_gitignore.nim, Test 4): negacja
!wzorzec jest CAŁKOWICIE ignorowana (traktowana jak komentarz, NIE
"odkrywa" wcześniej ukrytego pliku), a wzorce ze ścieżką względną
(zawierające / w środku) są pomijane, bo dopasowanie działa wyłącznie
po nazwie wpisu, nie po pełnej ścieżce.
Gdy tryb ".*" jest włączony, każda grupa (...) w dopasowaniu dostaje
własne, dodatkowe podświetlenie (fioletowy pasek pod tekstem) NA
WIERZCHU zwykłego podświetlenia całego dopasowania. Uczciwie
udokumentowane ograniczenie: std/re w tej wersji nie zwraca pozycji
grup, tylko same przechwycone teksty -- pozycje są odtwarzane
HEURYSTYCZNIE (wyszukanie tekstu grupy w obrębie dopasowania, zaczynając
od końca poprzedniej znalezionej grupy). Poprawne dla zwykłego,
sekwencyjnego układu grup (zdecydowana większość realnych wzorców, np.
(\w+)@(\w+)), potencjalnie zawodne przy nietypowych, zagnieżdżonych
wzorcach -- opisane wprost w kodzie i przetestowane wraz z tym
ograniczeniem (test_regex_group_highlight.nim), nie przemilczane.
Szósty punkt z poprzedniej listy ("grupowanie historii cofania dla
'Zamień wszystko'") zakładał, że taki mechanizm istnieje i tylko brakuje
mu grupowania. Przed przystąpieniem do pisania kodu sprawdzono to w
źródle (grep po undo/Ctrl+Z w apps/texteditor/texteditor.nim) --
wynik: PUSTY. Edytor nie ma własnego stosu cofania; jedyne cofanie,
jakie mogłoby działać, to wewnętrzne, nieudokumentowane cofanie pola
tekstowego samego Fidget (jeśli w ogóle istnieje -- nieznane bez
uruchomienia żywego GUI), na które doReplaceAll/doReplaceCurrent (obie
manipulują t.content jako zwykłym stringiem, z pominięciem tego pola)
prawdopodobnie w ogóle nie wpływają. "Zgrupowanie" czegoś, co nie
istnieje, nie miało sensu -- zamiast implementować fasadowe
"rozwiązanie" nieistniejącego problemu, ten punkt jest tu uczciwie
oznaczony jako BŁĘDNE ZAŁOŻENIE z poprzedniej rundy, nie jako zrobiony
ani odłożony. Prawdziwe Ctrl+Z/Ctrl+Y dla całego edytora byłoby osobną,
średniej wielkości funkcją (stos stanów t.content z limitem
głębokości) -- warta zrobienia, ale to NOWA funkcja, nie poprawka do
istniejącej.
Miniatury obrazów w menedżerze plików też świadomie odłożone: mimo
że runda 14 pokazała, że pixie@5.0.7 da się zainstalować i uruchomić w
tej sandboxie, apps/filemanager/files.nim (w odróżnieniu od
shell/desktopapps.nim) importuje fidget do całego swojego UI --
dodanie generowania miniatur wymagałoby albo wciągnięcia pixie do
pliku, który i tak nie idzie skompilować w całości w tej sesji (więc
nowy kod miniatur byłby równie nieprzetestowany jak reszta UI), albo
wydzielenia samej logiki dekodowania/skalowania do osobnego,
testowalnego modułu -- co jest rozsądnym pomysłem na PRZYSZŁĄ rundę,
ale nie zmieściło się uczciwie w tej.
Metoda weryfikacji: jak w rundach 13-14, nimble install fidget
nadal kończy się Error: Unsatisfied dependency: nim (>= 2.0.0), więc
apps/filemanager/files.nim i apps/texteditor/texteditor.nim (obie
importują fidget) wciąż nie dają się skompilować w całości w tej
sesji. Cała logika nie-UI tej rundy (sortowanie -- 5 scenariuszy w
test_sorting.nim; historia nawigacji -- 6 scenariuszy w
test_nav_history.nim, w tym kluczowy "nawigacja w nową stronę kasuje
starą przyszłość"; .gitignore -- 6 scenariuszy w test_gitignore.nim;
lokalizacja grup regex -- 3 scenariusze w
test_regex_group_highlight.nim) jest wydzielona i przetestowana w
izolowanych, realnie skompilowanych i uruchomionych programach Nim.
Nowości w v0.2 (runda 14) -- edytor: wyrażenia regularne w Znajdź/Zamień; menedżer plików: Ctrl=kopiuj i przeciąganie folderów
Ta runda była odpowiedzią na prośbę o rozbudowę ZDE o WSZYSTKO naraz, co
wtedy było wypisane na liście ograniczeń. Po zmierzeniu się z tą listą
uczciwie: część punktów (regex, Ctrl-kopiuj przy przeciąganiu,
przeciąganie folderów) dało się realnie zaimplementować i przetestować w
tej sesji; reszta (multi-seat, gesty pinch/hold, testy na prawdziwym
DRM/GPU i wobec wlroots 0.18/0.20, żywa integracja z wl_data_device dla
schowka/DnD MIĘDZY ZDE a resztą systemu, HiDPI/ScaledDirectories= w
Icon Theme Spec, GUI dla .zpk, wiele sesji/użytkowników, animowane
formaty tapety) to architektonicznie duże, osobne projekty, których nie
dało się w tej sesji ani zaimplementować, ani uczciwie przetestować --
patrz zaktualizowana lista "Ograniczenia" niżej po pełny, aktualny stan
każdego punktu, z tego samego powodu, dla którego reszta tego dokumentu
zawsze mówi wprost, czego NIE zrobiono, zamiast to przemilczeć.
Domyka jawnie wypisany brak ("wciąż bez wyrażeń regularnych").
Nowy przełącznik ".*" w pasku Znajdź (apps/texteditor/texteditor.nim),
obok istniejącego "Aa" -- gdy włączony, t.findQuery jest kompilowane
jako wzorzec std/re (PCRE) zamiast interpretowane jako dosłowny
podłańcuch. Domyślnie wyłączony -- żaden istniejący dokument/karta nie
dostaje niespodziewanej zmiany zachowania.
- Zmienna długość dopasowania:
findMatchesniosło dotąd tylko (linia, kolumna) i zakładało, że długość dopasowania to zawszet.findQuery.len-- prawdziwe dla dosłownego podłańcucha, ale FAŁSZYWE dla regexa (\d+dopasowuje "1" i "12345" o różnej długości). Dodano trzecie pole,length, liczone per dopasowanie -- używane teraz zarówno do podświetlenia (szerokość prostokąta), jak i dodoReplaceCurrent(dokładnie tyle znaków jest wycinane przy zamianie). - Dopasowania o długości 0 (np. wzorzec dopuszczający pusty match, jak "x*" na tekście bez "x") są jawnie obsłużone -- bez tego skan tej samej pozycji w nieskończoność by się zapętlił; pozycja przesuwa się o co najmniej 1 znak niezależnie od długości dopasowania.
- Błędny wzorzec (np. niedomknięty nawias -- naturalny, przejściowy stan PODCZAS pisania wzorca) pokazuje czytelny komunikat "błędny wzorzec" w miejscu licznika wyników, zamiast wywalać całe okno edytora albo cicho pokazywać "brak wyników" (co sugerowałoby poprawny, tylko niepasujący wzorzec -- mylące).
- Rozróżnianie wielkości liter (przełącznik "Aa") działa też w trybie
regex -- mapowane na flagę
reIgnoreCasebibliotekistd/re.
Prawdziwy błąd złapany podczas testowania tej rundy (nie w
dokumentacji -- w izolowanym, realnie uruchomionym programie Nim):
pierwsza wersja "Zamień wszystko" w trybie regex użyła
t.content.replace(pattern, t.replaceQuery) (std/re's replace,
przyjmujący zwykły string jako trzeci argument) w założeniu, że
t.replaceQuery z odwołaniami do grup przechwytujących wzorca ($1,
$2) zostanie poprawnie podstawione -- test z prawdziwym wzorcem
(\w+)@(\w+)\.com i zamianą "$1 [at] $2 [dot] com" ujawnił, że
replace (w odróżnieniu od replacef) traktuje trzeci argument
DOSŁOWNIE -- wynik zawierał litery $1/$2 wprost w tekście, nie
przechwycone grupy. Naprawione użyciem replacef (osobna procedura w
std/re, jedyna, która faktycznie interpretuje $n). Bez tego testu ta
funkcja wyglądałaby na działającą (kompiluje się, nic się nie wywala),
a w praktyce cicho psułaby każdą zamianę korzystającą z grup
przechwytujących.
Domyka DWA punkty, które runda 13 świadomie zostawiła otwarte na liście ograniczeń tej rundy.
- Ctrl = kopiuj (
FileDragState.copyMode,doDropMove) -- sprawdzane w chwili PUSZCZENIA przycisku myszy (updateFileDrag), NIE w chwili złapania pliku (onMouseDown) -- ten pierwszy moment jest już zajęty przez Ctrl+klik do zaznaczania wielu wpisów, więc mieszanie dwóch znaczeń Ctrl w TYM SAMYM momencie przeciągania byłoby mylące. Efekt uboczny: użytkownik może zacząć przeciąganie bez Ctrl, a doszczypnąć/puścić Ctrl dopiero tuż przed upuszczeniem, żeby zmienić zamiar z "przenieś" na "kopiuj" w locie -- naturalne zachowanie, znane z innych menedżerów plików. Podświetlenie celu zmienia kolor (zielony = przenieś, niebieski = kopiuj) NA BIEŻĄCO, klatka po klatce, zanim użytkownik w ogóle puści przycisk. - Przeciąganie folderów -- w rundzie 13 świadomie ograniczone tylko
do plików (foldery nawigują na zwykły klik, co kolidowałoby z
inicjacją przeciągania na tym samym geście). Teraz folder można
przeciągnąć w sytuacjach, w których zwykły klik i tak by NIE
nawigował: Ctrl albo Shift trzymany w chwili złapania (te same dwa
warunki, którymi
onClickrozpoznaje "to zaznacz/zakres, nie wejście do środka"), albo folder jest już częścią wieloelementowego zaznaczenia sprzed tego kliknięcia. Zwykły, pojedynczy klik na niezaznaczonym folderze bez modyfikatorów dalej nawiguje do środka, bez żadnej zmiany względem rundy 13.
Metoda weryfikacji, tak samo uczciwie jak w rundzie 13: nimble install fidget w tej sandboxie nadal kończy się tym samym
Error: Unsatisfied dependency: nim (>= 2.0.0) (dostępny jest tylko Nim
1.6.14 z apt) -- więc znów nie dało się skompilować i zobaczyć żywego
zde-shell. Cała logika nie-UI została wydzielona i przetestowana w
izolowanych, realnie skompilowanych i uruchomionych programach Nim, na
prawdziwym systemie plików: doDropMove z copyMode = true (kopiowanie
pliku/folderu zachowuje oryginał w źródle, kolizja nazw dalej dokłada
"(kopia)", zwykłe przeciąganie bez Ctrl nadal PRZENOSI -- bez regresji
rundy 13), warunek bramkujący przeciąganie folderów (plik zawsze
przeciągalny, folder tylko z Ctrl/Shift/wieloselekcją, zwykły klik na
niezaznaczonym folderze NIE uzbraja przeciągania), oraz cała logika
regex opisana wyżej (zmienna długość, dopasowania o długości 0, błędny
wzorzec, poprawne numery linii, replacef z grupami). Same bloki DSL
Fidget (onMouseDown/onHover/fill, przełącznik ".*") są pisane jako
addytywne rozszerzenie już wcześniej ustalonych wzorców w tym samym
pliku -- integracja z żywym silnikiem renderującym pozostaje do
potwierdzenia w przyszłej rundzie, jeśli środowisko z Nim >= 2.0 będzie
dostępne.
Domyka kolejny jawnie wypisany brak ("brak ScaledDirectories=/@2x w
Icon Theme Spec"). loadThemeMeta/ThemeDirEntry w
shell/desktopapps.nim parsują teraz Scale= z każdej sekcji
index.theme (domyślnie 1, gdy klucz nie występuje -- zgodnie ze
specyfikacją), a pickBestDir stawia katalogi Scale=1 ZAWSZE przed
katalogami o wyższej skali (@2x/@3x) o tym samym nominalnym Size=,
zamiast (jak dotychczas) traktować je identycznie i wybierać między nimi
przez czysty przypadek kolejności w pliku.
To NIE jest pełne wsparcie HiDPI -- ZDE wciąż nigdzie nie zna
rzeczywistej skali monitora (kompozytor jej nie przekazuje do shellu, to
osobne, dużo większe zadanie), więc katalog @2x nigdy nie zostanie
świadomie WYBRANY na potrzeby ostrzejszego wyświetlania. Ta rozbudowa
naprawia węższy, ale realny problem: bez niej dwa katalogi w TYM SAMYM
motywie mogące zawierać RÓŻNE pliki pod tymi samymi nazwami (np.
apps/48 i apps@2/48, oba Size=48) były nierozróżnialne dla
pickBestDir.
Przełom w metodzie weryfikacji tej rundy: shell/desktopapps.nim
zależy WYŁĄCZNIE od std/pixie (NIE importuje fidget) -- co
oznacza, że dało się zainstalować pixie@5.0.7 (DOKŁADNIE tę wersję, o
której od rundy 5 mówi komentarz w kodzie o ograniczeniach rasteryzacji
SVG) samodzielnie, pod Nim 1.6.14 z tej sandboxy, mimo że fidget samo
w sobie nadal wymaga Nim >= 2.0. Dzięki temu, PIERWSZY RAZ w tej serii
rund, dało się skompilować i uruchomić PRAWDZIWY, niezmodyfikowany
shell/desktopapps.nim -- nie kopię jego logiki w izolowanym skrypcie
(jak wszystkie testy w rundach 13-14 do tej pory), tylko realny
import ../shell/desktopapps i wywołanie prawdziwego, eksportowanego
scanDesktopApps().
Test uruchomiony na prawdziwych plikach .desktop i prawdziwych,
zainstalowanych w tej sandboxie motywach ikon (Humanity, Humanity-Dark,
hicolor, Adwaita, ubuntu-mono-{dark,light}):
- Znaleziono 7 realnych aplikacji
.desktopw systemie (LibreOffice x5, ImageMagick, TeXdoctk), 5 z nich dostało realnie rozwiązaną ikonę. - Potwierdzone bezpośrednio na dysku (nie w teorii):
Humanity/apps/48/iHumanity/apps@2/48/NAPRAWDĘ zawierają plik o tej samej nazwie (access.svg) -- to jest realna, a nie tylko teoretyczna, niejednoznaczność, którą ta rozbudowa rozwiązuje. - Kluczowa asercja: PO poprawce ŻADNA z 7 aplikacji nie dostała ikony z
katalogu
@2x/@3x, mimo że taki katalog (apps@2/48w Humanity) istnieje i wcześniej mógł zostać wybrany zamiastapps/48czystym przypadkiem kolejności w plikuindex.theme.
Reszta rundy 13/14 (menedżer plików, edytor tekstu) importuje fidget
(DSL okien/przycisków) i dlatego wciąż NIE dało się jej skompilować w
tej sesji -- ten przełom dotyczy konkretnie desktopapps.nim, nie całego
zde-shell.
Domyka kolejny, jawnie wypisany od dawna punkt z listy ograniczeń ("wciąż bez przeciągania plików myszą (drag & drop)"). Do tej rundy przenoszenie wymagało zawsze przejścia przez schowek plików (Kopiuj/ Wytnij/Wklej -- patrz rozbudowy niżej) -- teraz można też złapać plik (albo zaznaczone Ctrl/Shift-klikiem wiele plików) i upuścić go bezpośrednio na wiersz folderu, tak jak w Nautilusie/Dolphinie/ Eksploratorze.
- Mechanizm (
apps/filemanager/files.nim,FileDragState/updateFileDrag) to dosłownie ten sam, już dwukrotnie sprawdzony w tym projekcie wzorzec "przeciągania przez trzymanie", którego używającomp/drag.nim(przenoszenie/zmiana rozmiaru OKIEN) iSliderDragState/updateSliderDragwshell/taskbar.nim(suwaki quick settings):onMouseDownna wierszu pliku zapamiętuje start przeciągania (BEZ natychmiastowego przenoszenia),onHoverna wierszu FOLDERU ustawia bieżący cel upuszczenia, a noweupdateFileDrag()(wołane raz na klatkę zshell/shell.nim, obok analogicznychcompositor.updateDrag/updateSliderDrag) wykonuje faktyczne przeniesienie dopiero w klatce, w której przycisk myszy zostaje puszczony NAD folderem. onMouseDownjest DODATKOWYM handlerem obok już istniejącegoonClickna tym samym wierszu (Fidget pozwala na wiele bloków zdarzeń na jednym węźle -- ten sam wzorzec coonHover+onClickna przycisku "⬆ Wyżej", obecny w kodzie od dawna) --onClick(zaznaczanie/nawigacja/podwójny klik) pozostaje CAŁKOWICIE nietknięty. Zwykły klik bez ruchu myszy "uzbraja" przeciąganie, ale skoro użytkownik nigdy nie najechał na żaden wiersz folderu, cel upuszczenia nigdy się nie ustawia --updateFileDragprzy puszczeniu przycisku nie ma czego przenieść, więc to czysty no-op: zwykłe klikanie/zaznaczanie działa dokładnie tak jak przed tą rundą.- Działa MIĘDZY dwoma różnymi, otwartymi jednocześnie oknami
menedżera --
gFileDropTargetto zwykła bezwzględna ścieżka na poziomie MODUŁU (ten sam duch cogFileClipboardPaths/gFileClipboardCutz wcześniejszej rozbudowy schowka plików), więc nie ma znaczenia, w KTÓRYM oknie fizycznie znajduje się wiersz-cel. Po przeniesieniu WSZYSTKIE otwarte okna, których katalog (źródłowy LUB docelowy) mógł się zmienić, odświeżają swoją listę automatycznie (gDirsNeedingRefresh, sprawdzane na początkudrawFileManager) -- bez tego drugie, otwarte akurat na katalogu docelowym okno pokazywałoby przestarzałą listę aż do ręcznego "Odśwież". - "⬆ Wyżej" też przyjmuje upuszczenie -- przenosi bezpośrednio do katalogu NADRZĘDNEGO, bez potrzeby wcześniejszej ręcznej nawigacji tam i z powrotem.
- Zabezpieczenia, przez ten sam, już wcześniej sprawdzony mechanizm co
"Wklej" (
uniqueDestName/ochrona przed rekurencją, patrz sekcja "kopiuj/wytnij/wklej" niżej): kolizja nazw w katalogu docelowym dokłada "(kopia)"/"(kopia 2)"/..., próba przeniesienia folderu do samego siebie albo do jego własnego podkatalogu jest wykrywana i odrzucana PRZED dotknięciem dysku, a źródło, które zniknęło z dysku między uzbrojeniem a puszczeniem przycisku, jest pomijane z czytelnym komunikatem zamiast wywalać całą operację. - Świadomie POZA zakresem tej rundy: brak modyfikatora
Ctrl-przeciągnij-żeby-skopiować (przeciąganie zawsze PRZENOSI, nigdy nie
kopiuje) oraz brak przeciągania MIĘDZY ZDE a aplikacjami spoza niego
(wymagałoby integracji z
wl_data_device/DND na poziomie kompozytora, czego architektura ZDE dziś nigdzie nie robi -- ten sam, już wcześniej udokumentowany kompromis co przy schowku plików). Przeciąganie jest możliwe tylko z pojedynczych wierszy PLIKÓW (nie folderów) -- foldery w tym menedżerze nawigują na zwykły klik, więc dodanie im też inicjacji przeciągania naonMouseDownwymagałoby dodatkowego rozróżnienia "klik = wejdź" vs "przeciągnięcie = przenieś" na tym samym geście, czego nie dało się w tej rundzie zweryfikować bez żywego Fidget/GLFW -- bezpieczniej zostawić to jako świadome ograniczenie niż zgadywać.
Uczciwa notatka o metodzie weryfikacji tej rundy: w tej sandboxie
(Ubuntu 24.04 kontenerowe) dało się doinstalować nim 1.6.14 z apt --
ale próba nimble install fidget kończy się tym samym, już wcześniej w
tym README udokumentowanym Error: Unsatisfied dependency: nim (>= 2.0.0) (fidget >= 0.7.10 i cała reszta łańcucha Pixie/typography tego
wymagają) -- więc, tak jak przy wielu wcześniejszych rundach
("wybór dźwięku alarmu", "trwała historia schowka", "ciągłe
przeciąganie suwaków" i inne, patrz wyżej), nie dało się w TEJ
konkretnej sesji realnie skompilować i zobaczyć zde-shell z tą zmianą.
Zamiast tego CAŁA logika nie-UI (bez żadnej zależności od Fidget) --
doDropMove (przenoszenie zwykłego pliku, przenoszenie folderu
rekurencyjnie, kolizja nazw dokładająca "(kopia)", ochrona przed
przeniesieniem folderu do własnego potomka, obsługa zniknięcia źródła,
no-op przy upuszczeniu na ten sam katalog) oraz mechanizm zatrzasku
jednoklatkowego gFileDropTarget/updateFileDrag (podświetlenie celu
żyje dokładnie jedną klatkę i odnawia się przy ciągłym hover, puszczenie
przycisku NAD celem wykonuje przeniesienie dokładnie raz, puszczenie
POZA celem to no-op) -- została wydzielona i przetestowana w
izolowanych, realnie skompilowanych i uruchomionych programach Nim, na
prawdziwym systemie plików w katalogu tymczasowym. Sam kod DSL-a Fidget
(onMouseDown/onHover/fill na wierszach) był pisany z rozmysłem jako
CZYSTO ADDYTYWNY względem już wcześniej, w poprzednich rundach, żywo
zweryfikowanego kodu (onClick selekcji/nawigacji pozostaje bez ŻADNEJ
zmiany) -- ale sama INTEGRACJA z żywym silnikiem renderującym pozostaje
do potwierdzenia w przyszłej rundzie, jeśli środowisko z Nim >= 2.0
będzie dostępne. Zaznaczone tu wprost, zamiast przemilczane -- ten sam
poziom uczciwości co reszta tego dokumentu.
Nowości w v0.2 (runda 12) -- Terminal: PRAWDZIWA przyczyna znaleziona i naprawiona (ciąg dalszy rundy 11)
Runda 11 (niżej) skończyła się uczciwym "nie wiem" -- obaloną hipotezą i przywróceniem niepotrzebnych zmian. Ta runda wróciła do tego samego problemu i, metodyczną bisekcją na żywo (nie zgadywaniem), znalazła PRAWDZIWĄ przyczynę.
Prawdziwa przyczyna: TEKST (nie zwykłe wypełnienia/prostokąty --
te renderowały się poprawnie nawet tuż przy krawędzi) w obrębie
kilkunastu pikseli od DOLNEJ krawędzi frame "term-root" był
całkowicie niewidoczny w tej wersji Fidget/Pixie, niezależnie od tego,
czy to statyczny napis "zde$ ", czy dynamiczne t.input. Rząd
wpisywania poleceń, przyklejony do samego dołu okna z zaledwie 8px
marginesu (pad), siedział dokładnie w tej martwej strefie -- stąd
ZARÓWNO statyczny prompt, JAK I wpisywany tekst były niewidoczne,
mimo że dane po stronie kodu były przez cały czas poprawne (co runda
11 już ustaliła).
Metoda odkrycia -- bisekcja pozycji, nie zgadywanie: dorzucono
jaskrawy, izolowany prostokąt (rectangle "MARKER") dokładnie w miejscu
rzędu wpisywania -- WIDOCZNY, potwierdzając że same kształty renderują
się poprawnie nawet w tym miejscu. Następnie przesunięto CAŁY rząd
wpisywania o 100px w górę (z dala od krawędzi) -- statyczny napis
"zde$ " STAŁ SIĘ WIDOCZNY. Zawężono do 20px przesunięcia w górę --
WCIĄŻ widoczny (dokładny próg w pikselach nie został ustalony co do
jedności, bo nie było powodu szukać go dokładniej, skoro rozsądny
margines bezpieczeństwa rozwiązuje problem raz na zawsze). Po drodze
odrzucono też DWIE inne, kuszące, ale błędne hipotezy -- że problem
dotyczy skalowania okna zmaksymalizowanego (obalone: identyczny brak
tekstu w oknie NIE zmaksymalizowanym) i że problem dotyczy
cornerRadius obcinającego dzieci (obalone: usunięcie cornerRadius
nie zmieniło niczego).
Naprawa (apps/terminal/term.nim): nowa stała BottomTextClipMargin
(24px, celowo hojniejsza niż zmierzony próg ~20px -- margines
bezpieczeństwa, nie wartość "ledwo wystarczająca", żeby nie balansować
tuż przy granicy nieznanego mechanizmu na innych rozdzielczościach/
czcionkach) odejmowana zarówno od wysokości scrollbacku (bodyH), jak
i od pozycji Y rzędu wpisywania (input-row) oraz przycisku "Wklej"
(btn-paste, żeby zostały wyrównane) -- efekt: scrollback jest o 24px
niższy, rząd wpisywania siedzi 24px wyżej niż wcześniej, z dala od
martwej strefy.
Przy okazji naprawiono DRUGI, powiązany bug, znaleziony od razu przy
weryfikacji pierwszej naprawy: samo wyczyszczenie t.input/
keyboard.input po Enter nie wystarczało -- pole dalej POKAZYWAŁO
starą treść, a kolejne znaki DOPISYWAŁY się do niej zamiast zaczynać od
nowa ("echo IT_WORKS_NOW" + x + y dawało widoczne
"echo IT_WORKS_NOWxy"). Fidget najwyraźniej trzyma własny, wewnętrzny
bufor edycji dla aktualnie SKUPIONEGO pola, niezależny od
keyboard.input, i odtwarza go z powrotem po onInput. Naprawa:
keyboard.focusNode = nil po wysłaniu polecenia (ten sam mechanizm co
onHover na "scrollback" już używał do zwalniania fokusu) -- wymusza
pełny reset tego wewnętrznego bufora.
Zweryfikowane pełnym przepływem end-to-end pod Xvfb, krok po kroku:
- Zrzut ekranu PO naprawie pozycji: prompt "zde$" i przycisk "Wklej" widoczne w rzędzie wpisywania -- pierwszy raz w całej tej serii rund.
- Wpisanie
echo IT_WORKS_NOW-- widoczne znak po znaku, z kursorem, podczas pisania. Enter-- scrollback pokazujeecho IT_WORKS_NOWiIT_WORKS_NOW(prawdziwe wyjście PTY).- Wpisanie kolejnych znaków po
Enter-- BEZ naprawy drugiego buga: sklejały się ze starą treścią ("echo IT_WORKS_NOWxy", potwierdzone zrzutem). PO naprawie: pole poprawnie wraca do samego "zde$". - Dwa kolejne, niezależne polecenia (
echo FIRST,echo SECOND) wykonane pod rząd -- każde poprawnie wyczyściło pole po wykonaniu, każde poprawnie wykonane i widoczne na scrollbacku, bez żadnej pozostałości z poprzedniego.
Terminal jest teraz w pełni użyteczny w tej wersji -- pierwszy raz w całej tej serii rund udało się faktycznie wpisać i zobaczyć polecenie podczas pisania w tym oknie.
Nowości w v0.2 (runda 11) -- Terminal: uczciwie udokumentowany, NIE W PEŁNI rozwiązany bug (pisanie "na ślepo")
Ta runda skończyła się BEZ pełnej naprawy -- warto to zapisać uczciwie, zamiast przemilczeć, bo sam proces dochodzenia jest pouczający i zapobiega marnowaniu czasu na tę samą ślepą uliczkę w przyszłości. Kontynuacja i pełne rozwiązanie -- patrz "Nowości w v0.2 (runda 12)" wyżej.
Znaleziony, potwierdzony, realny bug: w terminalu (apps/terminal/term.nim,
pole input-field) wpisywane polecenie jest CAŁKOWICIE niewidoczne na
ekranie podczas pisania. Odkryte przez live-test: wpisanie "ls", potem
(w osobnej próbie) "echo REALTEST123", potem Enter ujawniło na
scrollbacku sklejone "echo REALTEST123lsl" -- dowód, że wszystkie
wcześniejsze, pozornie "nic nie robiące" próby w rzeczywistości cicho
gromadziły się w buforze, tylko nigdy nie były pokazywane. Debugowy
echo potwierdził: onInput faktycznie się odpala, t.input
faktycznie trzyma poprawną wartość co do znaku -- PTY dostaje polecenia
poprawnie (Enter faktycznie je wykonuje). To WYŁĄCZNIE problem
wizualny, nie utraty danych ani przechwytywania wejścia.
Wypróbowana i OBALONA hipoteza: że przyczyną jest brak jawnego
characters t.input w gałęzi "pole ma fokus" (pole pokazywało tekst
TYLKO gdy NIE miało fokusu -- dokładnie odwrotnie niż potrzeba). Zmiana
na wołanie characters t.input bezwarunkowo (ten sam wzorzec co pasek
Znajdź w edytorze tekstu) NIE naprawiła problemu -- potwierdzone
ponownym live-testem po przebudowie. Dla porównania: DOKŁADNIE ten sam
wzorzec (brak gałęzi dla stanu "ma fokus") w polu układu klawiatury w
Ustawieniach działa BEZ ZARZUTU -- osobny live-test pokazał "pl"
wpisywane znak po znaku, w pełni widoczne. Skoro identyczny wzorzec
kodu działa w jednym miejscu i nie działa w drugim, to NIE jest ten
wzorzec winowajcą -- coś SPECYFICZNEGO dla okna terminala psuje
renderowanie, przyczyna wciąż nieznana (podejrzenia na przyszłość: coś
związanego z resizePty przeliczanym co klatkę tuż przed tym blokiem,
albo interakcja z onHover na "scrollback" nad nim).
Dlaczego to jest w README, a nie tylko cicho cofnięte: trzy inne
miejsca w kodzie (apps/settings/settings.nim -- pola X/Y monitora i
układu klawiatury; apps/texteditor/texteditor.nim -- pole ścieżki
"Otwórz") zostały PRZEZ POMYŁKĘ "naprawione" tą samą, obaloną hipotezą,
zanim disproof nadszedł -- i CELOWO PRZYWRÓCONE do oryginalnej postaci
po tym, jak live-test na polu układu klawiatury potwierdził, że
oryginalny kod tam był poprawny od początku. To jest uczciwy zapis
procesu: postawiona hipoteza, przetestowana, obalona, niepotrzebne
zmiany cofnięte -- zamiast zostawić niepotrzebne (choć nieszkodliwe)
"poprawki" tylko dlatego, że już zostały napisane.
Nowości w v0.2 (runda 10) -- Monitor systemu: użycie dysku (i DRUGIE wystąpienie tego samego buga formatowania)
Monitor systemu (apps/sysmonitor/sysmonitor.nim) pokazuje teraz
użycie dysku, obok już istniejących CPU i RAM -- statvfs(2) na /
(to samo POSIX-owe wywołanie, na którym opiera się df), z tym samym
wzorcem obronnym co readCpuSample/readMemInfo (cichy (0, 0) przy
błędzie, żeby jedna nieudana próba nie wywaliła całego okna). Świadome
uproszczenie: pokazuje WYŁĄCZNIE punkt montowania "/" (nie sumę
wszystkich zamontowanych systemów plików) -- to samo podejście co karta
"Przechowywanie" w GNOME Ustawieniach, gdzie montowanie główne jest
domyślnie tym, co się liczy jako "ile miejsca zostało".
Przy pierwszym realnym zrzucie ekranu tej rundy zauważono DRUGIE
wystąpienie dokładnie tego samego buga formatowania, co w rundzie 9
(Kalkulator) -- nie przez osobny, wyspecjalizowany test tym razem,
tylko przez uważne spojrzenie na zwykły zrzut ekranu ze zwykłymi
danymi: humanKb() (funkcja formatująca rozmiar w MB/GB, używana od
dawna do wyświetlania RAM, teraz też dysku) pokazywała "511. MB"
zamiast "511 MB" -- ten sam winowajca co poprzednio, &"{x:.0f}"
zostawiający kropkę na końcu nawet przy zerze miejsc po przecinku,
tylko w INNYM pliku i INNEJ funkcji. Naprawione tym samym .strip(chars = {'.'}). Po tym drugim znalezisku przeszukano CAŁE repozytorium pod
kątem tego samego wzorca (grep -rn ':\.0f') -- te dwa miejsca (teraz
oba naprawione) były JEDYNYMI wystąpieniami, więc to nie epidemia,
tylko dwa niezależne, teraz domknięte przypadki tego samego, łatwego do
przeoczenia zachowania formatowania liczb w tej wersji Nim.
Zweryfikowane realnym uruchomieniem, przed i po naprawie: zrzut
ekranu PRZED naprawą pokazuje "Pamięć RAM 511. MB / 3.9 GB" (błąd
widoczny), zrzut PO naprawie pokazuje czyste "511 MB / 3.9 GB" --
oraz nową sekcję "Dysk (/) 242.2 GB / 252.0 GB" z poprawnie
wypełnionym paskiem, dane zgodne z rzeczywistym stanem dysku tej
maszyny w chwili testu.
Nowości w v0.2 (runda 9) -- Kalkulator: obsługa klawiatury fizycznej (i odkryty przy okazji bug w formatowaniu liczb)
Kalkulator (apps/calculator/calculator.nim) obsługuje teraz klawiaturę
fizyczną -- dotąd JEDYNYM sposobem interakcji było klikanie przycisków
myszą (grep buttonPress przed tą rundą nie dawało żadnego wyniku w
tym pliku). Cyfry (górny rząd I klawiatura numeryczna równolegle),
operatory (+/-/×/÷, w tym Shift+5 jako % i x/X jako alias
mnożenia), Enter/= do wyniku, nowe Backspace (kasuje OSTATNIĄ
cyfrę, w odróżnieniu od "C", które czyści wszystko) i Escape (= "C").
Aktywne TYLKO gdy okno kalkulatora ma fokus (win.focused, ten sam
sprawdzony wzorzec co skrót Ctrl+F w edytorze tekstu), żeby wpisywanie
cyfr nie wpływało na wszystkie otwarte okna kalkulatora naraz.
Przy okazji testowania na żywo wykryto i naprawiono realny,
PRZEDTEM ISTNIEJĄCY bug, zupełnie niezwiązany z samą klawiaturą:
formatNumber (funkcja formatująca wynik do wyświetlenia, używana
identycznie niezależnie od tego, czy wynik wywołało kliknięcie myszą
"=" czy klawisz) zostawiała KROPKĘ na końcu każdej wyświetlonej liczby
całkowitej -- 8 ÷ 2 pokazywało "4.", nie "4". Przyczyna: format
string &"{x:.0f}" w tej wersji Nim/strformat zostawia kropkę nawet
przy zerze miejsc po przecinku -- potwierdzone bezpośrednim, izolowanym
testem poza aplikacją (formatNumber(4.0) → "4."). Naprawa: .strip(chars = {'.'}) na wyniku. To NIE był bug w nowym kodzie klawiatury --
zweryfikowano, że dokładnie ten sam błąd występował przy kliknięciu
myszą w przycisk "=" (istniejący od dawna sposób obsługi), po prostu
nikt wcześniej nie przetestował rzeczywistego wyniku arytmetyki na tyle
dokładnie, żeby to zauważyć -- kolejny przykład tego, dlaczego ta cała
seria rozbudów kładzie nacisk na realne uruchomienie, nie tylko udaną
kompilację.
Uczciwa notatka o ograniczeniach testowania w tej rundzie: klawisz
"=" (bez Shift) okazał się zawodny w syntetyzowaniu przez xdotool w
tej konkretnej piaskownicy Xvfb (najprawdopodobniej mechanizm
tymczasowego przemapowania wolnego kodu klawisza, którego xdotool
używa dla symboli nieobecnych wprost w bardzo minimalnym układzie
klawiatury Xvfb, koliduje z klawiszem PERIOD -- wciśnięcie "=" przez
xdotool konsekwentnie dawało w aplikacji efekt taki, jakby wciśnięto
., nie =). Pełna, wiarygodna weryfikacja arytmetyki użyła więc
Backspace/Escape/cyfr/Minus/Slash/Shift+Equal (wszystkie
potwierdzone działające poprawnie) oraz kliknięcia myszą w przycisk
"=" na ekranie (15 − 3 = 12, bez kropki na końcu po naprawie,
potwierdzone zrzutem ekranu) -- sam klawisz Enter/KP_Enter jako
alternatywa dla = jest w kodzie i powinien działać na prawdziwej
klawiaturze fizycznej (gdzie nie ma potrzeby syntetycznego
przemapowania kodów klawiszy), ale nie dało się tego ostatniego
w pełni niezależnie zweryfikować w tej konkretnej piaskownicy.
Nowości w v0.2 (runda 8) -- edytor tekstu: ten sam wbudowany file picker co przy tapecie, teraz do "Otwórz"
Przycisk 📂 przy polu ścieżki w edytorze tekstu (apps/texteditor/texteditor.nim,
drawFilePicker/openPicker/refreshPickerEntries) -- prosty ponowny
użytek dokładnie tego samego, już zweryfikowanego wzorca z Ustawień
(patrz "Nowości w v0.2 (runda 7)" niżej), zastosowany do DRUGIEGO
realnego miejsca w ZDE, gdzie dotąd trzeba było wpisać całą ścieżkę
ręcznie. W odróżnieniu od pickera tapety (ograniczonego do
.png/.jpg), ten pokazuje WSZYSTKIE pliki bez filtrowania po
rozszerzeniu -- edytor tekstu może sensownie otworzyć dowolny plik, tak
jak zawsze mogło pole ręcznego wpisywania ścieżki, więc zawężanie listy
byłoby tu ograniczeniem, nie usprawnieniem. Stan pickera (katalog,
przewinięcie, lista wpisów) żyje na poziomie KARTY (Tab), nie całego
edytora -- różne karty mogą przeglądać różne katalogi niezależnie, tak
jak każda karta ma już dziś własną, niezależną ścieżkę/treść.
Dzięki temu, że quirk odwróconej kolejności rysowania rodzeństwa w Fidget (patrz "Nowości w v0.2 (runda 7)" niżej po pełny opis, jak został odkryty) był już wtedy w pełni zrozumiany i udokumentowany, ta runda zadziałała poprawnie za pierwszym razem -- bez żadnej z wcześniejszych ślepych uliczek (invisible overlay, offset o sto pikseli, crash na kolorze z kanałem alfa). Realny dowód wartości spisania odkrycia w komentarzu i README zamiast tylko naprawienia i zapomnienia.
Zweryfikowane pełnym przepływem end-to-end pod Xvfb: utworzono
/root/Documents/hello.txt z przykładową treścią, otwarto edytor
tekstu, kliknięto 📂 → nakładka "Otwórz plik" pokazuje /root/ →
nawigacja do Documents → plik hello.txt widoczny na liście (bez
filtrowania, w odróżnieniu od pickera tapety) → kliknięcie →
zakładka zmienia nazwę na hello.txt, pole ścieżki na
/root/Documents/hello.txt, a treść pliku faktycznie wczytuje się i
wyświetla w edytorze -- potwierdzone zrzutem ekranu, nie tylko
sprawdzeniem, że nakładka się otwiera.
Nowości w v0.2 (runda 7) -- Ustawienia: wbudowana przeglądarka plików do wyboru tapety (i istotne odkrycie o kolejności rysowania w Fidget)
Przycisk "Przeglądaj..." przy polu tapety w Ustawieniach (apps/settings/settings.nim,
drawWallpaperPicker/openPicker/refreshPickerEntries) -- lekka,
WBUDOWANA w samą aplikację Ustawienia przeglądarka katalogów jako
pełnoekranowa nakładka, ograniczona do folderów i plików obrazów
(.png/.jpg/.jpeg). To NIE jest systemowy file-picker (żaden inny
toolkit w ZDE go nie ma, więc nie ma z czym się zintegrować) -- to
domyka realny, jawnie wypisany w README brak: dotąd JEDYNYM sposobem
wskazania tapety było ręczne wpisanie całej ścieżki. Wzorzec przewijania
(kółko myszy) skopiowany 1:1 z apps/filemanager/files.nim dla
spójności w całym ZDE.
Najważniejsze odkrycie tej rundy -- nie w kodzie ZDE, tylko w samej bibliotece Fidget, i to takie, które wpływa na KAŻDĄ nakładkę/modal w całym projekcie, nie tylko na ten jeden przycisk: w tej wersji Fidget kolejność malowania RODZEŃSTWA na tym samym poziomie zagnieżdżenia jest ODWRÓCONA względem typowego modelu "malarskiego" CSS/Figmy -- element zadeklarowany WCZEŚNIEJ renderuje się NA WIERZCHU elementów zadeklarowanych PÓŹNIEJ, nie pod spodem.
Odkryte metodycznie, krok po kroku, przez izolowane testy wizualne (nie przez czytanie dokumentacji, bo dokumentacja Fidget tego nie opisuje):
- Pierwsza wersja tej rozbudowy umieszczała nakładkę jako OSTATNI
element
frame "settings-root"(intuicyjny wybór -- "narysowane później" = "na wierzchu" w niemal każdym innym frameworku UI). Efekt: kompletnie niewidoczna, mimo że kod wykonywał się co klatkę z poprawnymi wartościami (potwierdzoneecho). - Test z jaskrawym, izolowanym prostokątem jako OSTATNIM dzieckiem
settings-root: całkowicie niewidoczny, przykryty przez "sidebar"/ "content" zadeklarowane WCZEŚNIEJ. - Test z tym samym prostokątem jako PIERWSZYM dzieckiem: całkowicie przykrył "sidebar"/"content" -- rozstrzygający dowód odwróconej kolejności.
- Naprawa:
drawWallpaperPickerwołane na SAMYM POCZĄTKUframe "settings-root", przed "sidebar"/"content" -- i w środku samej nakładkipicker-panel(ma być na wierzchu) zadeklarowany PRZEDpicker-backdrop(ma być pod spodem), z tego samego powodu.
Po drodze złapano też mniejszy, ale realny bug, który zawaliłby całą
aplikację: fill "#000000cc" (8-cyfrowy hex z kanałem alfa) nie jest
poprawnym formatem koloru dla Fidget/Chroma -- poprawny sposób to
osobny drugi argument, fill "#000000", 0.8. Bez uruchomienia na żywo
pod Xvfb ten konkretny crash (Error: unhandled exception: HTML color invalid: #000000cc) trafiłby prosto do repozytorium.
Ważne zastrzeżenie o zasięgu tego odkrycia: kolejność MIĘDZY
rodzeństwem jest odwrócona, ale WŁASNE wypełnienie (fill) grupy nadal
poprawnie chowa się POD jej własnymi dziećmi -- to znaczy, że każdy
ISTNIEJĄCY już wcześniej przycisk w ZDE (np. wallpaper-apply-btn:
grupa z własnym fill i zagnieżdżonym text) renderuje się poprawnie
i NIE wymaga żadnej poprawki -- problem dotyczy WYŁĄCZNIE przypadków,
gdzie dwa NIEZAGNIEŻDŻONE, nakładające się na siebie elementy są
rodzeństwem tego samego rodzica (jak nakładka na wierzchu reszty UI).
To pierwszy taki przypadek w ZDE (żadna wcześniejsza rozbudowa nie
potrzebowała pełnoekranowej nakładki znad istniejącej zawartości), więc
bug nigdy wcześniej nie miał okazji się ujawnić.
Zweryfikowane pełnym przepływem end-to-end pod Xvfb, nie tylko
pojedynczym zrzutem ekranu: otwarcie Ustawień → kliknięcie
"Przeglądaj..." → nawigacja /root → Pictures → wallpapers (ścieżka
w nagłówku nakładki aktualizuje się poprawnie na każdym kroku) →
kliknięcie test-ocean.png → nakładka się zamyka, pole ścieżki
wypełnione, MINIATURA PODGLĄDU w Ustawieniach od razu pokazuje niebieski
obraz → kliknięcie "Zastosuj" → prawdziwa tapeta pulpitu (widoczna w
tle za oknem Ustawień) zmienia się na niebieską, potwierdzone
zrzutem ekranu całego pulpitu, nie tylko okna Ustawień.
Rezolucja ikon systemowych przeszła z płaskiej, ręcznie wypisanej listy
ścieżek na prawdziwy algorytm ze specyfikacji freedesktop.org "Icon
Theme Specification" (shell/desktopapps.nim, resolveViaThemeSpec/
themeSearchChain/loadThemeMeta). Zamiast sprawdzać kilkanaście
zaszytych na sztywno ścieżek (/usr/share/icons/Humanity/apps/64/$1.png
itd.), kod teraz: (1) parsuje index.theme motywu, (2) wędruje po
łańcuchu Inherits= (np. na tym systemie: ubuntu-mono-dark → Humanity-Dark → Humanity → Adwaita → hicolor), (3) dla każdego motywu w
łańcuchu sprawdza WSZYSTKIE jego katalogi z Context=Applications
zadeklarowane w Directories=, wybierając rozmiar najbliższy
PreferredIconSize (48px). ZDE nie ma dziś ustawienia "motyw ikon"
(IconThemeSeeds to rozsądne punkty startowe łańcucha, faworyzujące
warianty ciemne, bo reszta interfejsu ZDE jest ciemna) -- ale odkąd
dowolny z tych motywów ISTNIEJE i coś dziedziczy, jego CAŁE drzewo
przodków jest teraz przeszukiwane, nie tylko garść ręcznie
przewidzianych ścieżek.
Po drodze znaleziono i naprawiono realny, poważny bug -- nie w kodzie
ZDE, tylko w standardowej bibliotece Nim. Pierwsza wersja tej rundy
użyła std/parsecfg.loadConfig (oczywisty wybór do parsowania formatu
INI w Nim). Test na PRAWDZIWYM pliku /usr/share/icons/Humanity/index.theme
z tego systemu ujawnił: loadConfig cicho porzuca resztę pliku przy
pierwszym napotkanym nagłówku sekcji zawierającym @ (np.
[actions@2/22] -- katalog HiDPI "@2x", część specyfikacji
ScaledDirectories= i CAŁKOWICIE standardowy w prawdziwych motywach
ikon). Efekt zmierzony bezpośrednim testem: z ok. 250 sekcji w tym pliku
loadConfig widziało dosłownie JEDNĄ -- bez wyjątku, bez ostrzeżenia,
żadnego sygnału, że coś poszło nie tak. To nie była awaria do złapania
przez except -- to była cicha utrata danych, która sprawiłaby, że CAŁA
ta rozbudowa wyglądałaby na działającą (kompiluje się, nie wywala), a w
praktyce nie znajdowałaby prawie żadnych ikon. Naprawa: własny,
tolerancyjny parser linii (parseIniLoose) -- dzieli po pierwszym =,
traktuje [cokolwiek] jako nagłówek sekcji bez żadnych ograniczeń na
dozwolone znaki.
Zweryfikowane bezpośrednimi testami na prawdziwych plikach z tego systemu, przed i po naprawie parsera:
- Przed naprawą:
loadThemeMeta("Humanity").appDirs.len == 0. - Po naprawie:
16katalogów z poprawnymiSize/Context/Type. - Regresja:
libreoffice-writer/-chart/-math(działały już wcześniej przez starą listę) nadal się rozwiązują -- teraz przez lepiej dobrany rozmiar (48px zamiast pierwszego trafienia na sztywno, 64px). - Nowa, wcześniej niemożliwa zdolność, potwierdzona wizualnie: ikona
seahorse(klucz z kłódką) istnieje TYLKO wHumanity/apps/24/-- stara płaska lista sprawdzała Humanity wyłącznie w rozmiarach 64/48, nigdy 24, więc ta ikona ZAWSZE dostawała ikonę zastępczą kategorii. Utworzono testowy plik.desktopwskazującyIcon=seahorse, uruchomionozde-shellpod Xvfb -- zrzut ekranu z prawdziwego okna wyszukiwania launchera potwierdza poprawnie wyrenderowaną ikonę Seahorse, nie tylko plik PNG odnaleziony w izolacji.
Świadomie uproszczone względem pełnej specyfikacji (patrz duży komentarz
przy resolveViaThemeSpec w kodzie): brak pełnych reguł dopasowania
Fixed/Scalable/Threshold z MinSize/MaxSize/Threshold -- jedno,
proste kryterium "odległość od PreferredIconSize" zamiast tabeli reguł.
ScaledDirectories=/Scale= (@2x/HiDPI) były tu brakiem AŻ DO rundy
14 -- patrz "Nowości w v0.2 (runda 14)" niżej po to, co dokładnie zostało
domknięte, a co wciąż nie (ZDE nadal nie zna skali monitora). Stara
płaska lista ZOSTAJE jako siatka bezpieczeństwa (patrz resolveIconPath),
na wypadek gdyby żaden motyw z IconThemeSeeds nie istniał na dysku.
Ikony .desktop wskazujące WYŁĄCZNIE plik SVG (bez PNG w żadnym
rozmiarze) są teraz rasteryzowane i pokazywane, zamiast zawsze
dostawać ikonę zastępczą kategorii (shell/desktopapps.nim,
rasterizeSvgIcon/IconSearchTemplatesSvg). Coraz więcej pakietów
(zwłaszcza Flatpak, ale nie tylko) dostarcza ikonę tylko jako SVG w
katalogu scalable/ -- to był realny, jawnie wypisany brak. Pixie ma
własny moduł SVG (pixie/fileformats/svg, NIE eksportowany z głównego
modułu pixie -- stąd osobny import ... as pixieSvg) z
parseSvg(dane, szerokość, wysokość), który rasteryzuje SVG na
żądany rozmiar (tu: 128×128) -- wynik trafia do podręcznego cache'u na
dysku ($XDG_CACHE_HOME/zde/icons/, ponowna rasteryzacja tylko gdy
źródłowy SVG jest nowszy niż cache), więc resolveIconPath (wołane przy
każdym skanie launchera) nie rasteryzuje tej samej ikony od nowa za
każdym razem.
Uczciwe zastrzeżenie, odkryte właśnie w tej rundzie przez realne
testy, nie w dokumentacji Pixie: podzbiór SVG, jaki Pixie 5.0.7
faktycznie potrafi narysować, jest węższy, niż mogłoby się wydawać.
Sprawdzono cztery prawdziwe ikony systemowe z tej maszyny (motywy
Adwaita i Humanity) -- WSZYSTKIE cztery zawiodły, z konkretnymi błędami:
"Unsupported gradient transform" (dwie), "Unsupported SVG tag: clipPath" (jedna), błąd parsowania viewBox (jedna). Dopiero prosty,
syntetyczny SVG bez gradientów/clipPath (płaskie kształty, jednolite
kolory) zrasteryzował się poprawnie -- zweryfikowane wizualnie: zrzut
ekranu z uruchomionego zde-shell pokazuje testową aplikację z
poprawnie narysowaną ikoną (czerwone kółko z białym kwadratem) w
prawdziwym oknie wyszukiwania launchera, nie tylko plik PNG na dysku.
Innymi słowy: ta rozbudowa POSZERZA pokrycie ikon (niektóre SVG teraz
się pokażą, które wcześniej zawsze dostawały ikonę zastępczą), ale to
NIE jest "pełna obsługa SVG" -- dla ikon z nowoczesnych motywów
systemowych współczynnik trafień bywa niski. Awaria jest zawsze CICHA i
BEZPIECZNA (except CatchableError -- błąd Pixie, uszkodzony plik, brak
uprawnień do zapisu w cache'u, cokolwiek), więc aplikacja zawsze
dostaje przynajmniej ikonę zastępczą kategorii, nigdy puste miejsce ani
wywrócony skan launchera -- to zachowanie jest identyczne, zanim ta
rozbudowa powstała.
Protokół zwp_primary_selection_v1 jest teraz obsługiwany przez
zde-comp (wlcomp/wlroots.nim, wlcomp/seatext.nim,
wlcomp/main.nim) -- domyka kolejny konkretny, jawnie wypisany w
README brak: schowek PIERWOTNY (X11-owy "zaznacz, wklej środkowym
klikiem, bez Ctrl+C") w ogóle nie istniał pod ZDE, ponieważ zde-comp
nigdy nie tworzył wlr_primary_selection_v1_device_manager -- klienty
poprawnie wykrywały brak protokołu w rejestrze i po prostu nie
oferowały tej funkcji, więc nie był to "cichy bug", tylko brakująca
funkcja.
Implementacja to dosłownie ta sama para: wlrPrimarySelectionV1DeviceManagerCreate
w main() (analogicznie do wlrDataDeviceManagerCreate dla zwykłego
schowka) plus onRequestSetPrimarySelection w seatext.nim
(analogicznie do onRequestSetSelection) -- oba typy zdarzeń
(wlr_seat_request_set_selection_event/wlr_seat_request_set_primary_selection_event)
mają identyczny kształt w nagłówkach wlroots (source + serial), więc
druga implementacja to dokładne odbicie pierwszej, nie nowy wzorzec.
Zweryfikowane w tej rundzie realnym uruchomieniem i realnym
protokołem, nie tylko kompilacją: zbudowano zde-comp przez
build.janet wobec prawdziwych nagłówków wlroots 0.17.1, uruchomiono
zagnieżdżony pod Xvfb, i odpytano jego rejestr Waylanda narzędziem
wayland-info (WAYLAND_DISPLAY=wayland-0 wayland-info) -- output
potwierdza interface: 'zwp_primary_selection_device_manager_v1', version: 1 w faktycznie działającym rejestrze kompozytora, obok
istniejących globali (wl_data_device_manager, zwlr_layer_shell_v1,
xdg_wm_base itd.). Ta sama korekta (co w poprzednich rundach) --
protokół zweryfikowany na poziomie faktycznie działającego kompozytora,
nie tylko przeczytany w nagłówkach .h.
Świadomie NIE objęte tą rundą: panel historii schowka zde-shell (patrz
sekcja "Ograniczenia" niżej) śledzi wyłącznie zwykły schowek, nie
pierwotny -- to osobna, mniejsza rozbudowa (dodanie drugiego typu wpisu
do istniejącego mechanizmu historii w shell/clipboard.nim), którą
można zrobić następnym razem, jeśli będzie potrzebna.
Launcher wykrywa teraz zmiany W TREŚCI plików .desktop, nie tylko w
strukturze katalogu (shell/desktopapps.nim, ensureInotifyWatches/
appDirsChangedViaInotify; shell/taskbar.nim,
rescanSystemAppsIfChanged). Poprzedni mechanizm (appDirsSignature,
suma mtime katalogów z AppDirs) miał realną, cichą wadę: mtime
KATALOGU zmienia się tylko przy zmianach jego struktury
(dodanie/usunięcie/zmiana nazwy pliku) -- edycja treści ISTNIEJĄCEGO
pliku .desktop w miejscu (np. aktualizacja pakietu nadpisująca
Name=/Icon= bez usuwania i tworzenia pliku na nowo) w ogóle nie
dotykała mtime katalogu nadrzędnego, więc była CAŁKOWICIE niewykrywalna
-- nie tylko wykrywana z opóźnieniem, tylko wcale.
Naprawa otwiera deskryptor inotify RAZ, w trybie nieblokującym
(IN_NONBLOCK), z maską obejmującą zarówno strukturę katalogu
(IN_CREATE/IN_DELETE/IN_MOVED_FROM/IN_MOVED_TO) jak i treść
plików w nim (IN_MODIFY/IN_CLOSE_WRITE). rescanSystemAppsIfChanged
(wołane raz na sekundę z tickMain(), ten sam rytm co dotychczas) robi
jeden nieblokujący read() na ten deskryptor -- koszt per-tick zostaje
dokładnie tak niski jak wcześniej, ale teraz obejmuje też treść, nie
tylko strukturę. Stary mechanizm (mtime katalogu) ZOSTAJE jako
niezależna siatka bezpieczeństwa (na wypadek, gdyby inotify_init1 się
nie powiodło -- np. wyczerpany limit deskryptorów w systemie), nie
został zastąpiony.
Integracja inotify z pętlą zdarzeń Fidget/GLFW (żeby zmiana pojawiała
się klatka-po-klatce, nie z opóźnieniem rzędu sekundy) ŚWIADOMIE zostaje
poza zakresem -- to osobny, większy projekt (patrz duży komentarz w
desktopapps.nim), niepotrzebny do załatania realnej wady korekcyjnej,
która była tu głównym problemem.
Zweryfikowane realnym uruchomieniem, nie tylko przeczytane w kodzie:
utworzono plik .desktop z Name=Original Name Before Edit, uruchomiono
zde-shell pod Xvfb, potwierdzono w launcherze. Następnie plik
NADPISANO W MIEJSCU (cat > tą-samą-ścieżkę, nie usunięcie+utworzenie) z
Name=EDITED IN PLACE via inotify -- stat na katalogu PRZED i PO
edycji potwierdził identyczne mtime katalogu (dowód, że stary mechanizm
faktycznie by tego nie złapał), podczas gdy mtime samego pliku się
zmieniło. Po ~2 sekundach (jeden-dwa ticki) nowa nazwa "EDITED IN PLACE
via inotify" pojawiła się w wynikach wyszukiwania launchera -- zrzut
ekranu w tej sesji to potwierdza.
Ta runda skupiła się na wlcomp/ (kompozytorze), zgodnie z życzeniem --
oraz, po raz pierwszy w historii tego repo, na REALNYM URUCHOMIENIU
CAŁEGO STOSU RAZEM (zde-comp + zde-shell jako jego prawdziwy klient
Wayland, komunikujące się przez faktyczny socket), nie tylko osobnej
kompilacji każdej części.
Autostart zde-shell przez zde-comp (wlcomp/main.nim,
findShellBinary/spawnShell) -- domyka TODO jawnie wypisane w tym
README od dawna: "docelowo zde-comp powinien sam odpalać zde-shell jako
swój 'startup command' zamiast wymagać dwóch TTY". Teraz: uruchomienie
./zde-comp z JEDNEGO TTY wystarczy -- kompozytor sam odpala
zde-shell z odziedziczonym WAYLAND_DISPLAY (i DISPLAY, jeśli
XWayland wystartowało -- to drugie wcześniej było tylko LOGOWANE, nigdy
faktycznie nie trafiało do putEnv, więc dzieci zde-shell odpalane
przez launcher nie dostawałyby go automatycznie). Domyślnie szuka
zde-shell obok własnej binarki (getAppDir() -- oba lądują razem w
dist/); ZDE_NO_AUTOSTART wyłącza to zachowanie (np. do ręcznego
debugowania z dwóch TTY, stary sposób nadal działa), ZDE_SHELL_PATH
wskazuje inną binarkę. Świadomie "best effort", jak reszta integracji
zewnętrznych narzędzi w ZDE -- brak zde-shell obok zde-comp albo
błąd startProcess loguje ostrzeżenie na stderr, ale NIE zatrzymuje
kompozytora (patrz duży komentarz nad spawnShell).
Metodologia weryfikacji tej rundy -- jakościowy skok w stosunku do
poprzednich: dotąd zde-comp i zde-shell były kompilowane i
testowane OSOBNO, w różnych momentach. W tej rundzie po raz pierwszy
zainstalowano libwlroots-dev (0.17.1, Ubuntu 24.04) ORAZ zbudowano
janet ze źródeł (janet-lang/janet na GitHubie -- nie ma go w apt na
Ubuntu 24.04), więc dało się użyć PRAWDZIWEGO build.janet (nie tylko
ręcznych wywołań nim c) do zbudowania obu binarek naraz i uruchomienia
ich RAZEM pod zagnieżdżonym Xvfb:
zde-compwystartował, utworzył gniazdo Wayland, i sam odpaliłzde-shell-- zrzut ekranu potwierdza KOMPLETNY pulpit ZDE (tapeta z "glow", dok z zaokrąglonymi rogami, zegar, przełącznik pulpitów 1-4) wyrenderowany przez WŁASNY kompozytor ZDE, nie przez GLFW/X11 bezpośrednio jak we wszystkich poprzednich rundach testowych zapisanych w tym README.- XWayland + prawdziwy klient X11 (
xclock) ponownie przeszedł pełny cyklnew_surface -> associate-- i tym razem zweryfikowano to wizualnie, nie tylko z logu: zrzut ekranu pokazuje faktycznie wyrenderowaną tarczę zegara (wskazówki, cyfry) skomponowaną w sceniezde-comp. - Czego NIE udało się zweryfikować w tej rundzie: interaktywnego
wejścia (mysz/klawiatura) przez
xdotoolw tym konkretnym, zagnieżdżonym układzie X11-w-Xvfb -- kliknięcia docierały do okna hosta (potwierdzonexwininfo), ale nie wywoływały żadnej reakcji w sceniezde-compani logu. Najbardziej prawdopodobna przyczyna: niezgodność wersjiXInputExtension(XI2) między wlroots a akurat tą kompilacją Xvfb w piaskownicy (przy starcie backendu X11 w logu widać trzy nieszkodliwe, ale sugestywne błędyX11 error: op 18:0, code 5-- kod 5 toBadAtom, typowy objaw żądania nieobsługiwanej wersji/atomu XI2) -- to ograniczenie akurat TEGO testowego zagnieżdżenia (Xvfb, nie prawdziwa sesja Wayland/X11 z nowoczesnym XI2), nie wykryty błąd w kodziewlcomp/. Renderowanie i protokół (co dokładnie zweryfikowano wyżej) działają niezależnie od tego, czy input faktycznie dociera.
Instalacja libwlroots-dev/janet w piaskownicy była wyłącznie do
weryfikacji w TEJ sesji (tak jak poprzednie rundy z nim/fidget) --
żaden plik repo poza wlcomp/main.nim (opisanym wyżej) nie został
trwale zmieniony w wyniku samej instalacji narzędzi.
Nowości w v0.2 -- schowek plików między oknami, rozróżnianie wielkości liter w Znajdź, dźwięk minutnika
Pierwsza rozbudowa oznaczona jako v0.2 (poprzednie wpisy w tej sekcji,
niżej, to wszystkie rozbudowy v0.1 -- historia zostaje nienaruszona,
zde.nimble i tapeta zastępcza dostały bump wersji, patrz niżej).
Domyka TRZY konkretne braki jawnie wypisane w sekcji "Ograniczenia"
(patrz niżej w tym pliku) i zweryfikowane realną kompilacją +
uruchomieniem pod Xvfb (patrz notatka metodologiczna na końcu tej
sekcji).
1. Schowek plików działa teraz między dwoma otwartymi oknami menedżera
plików (apps/filemanager/files.nim). Wcześniej clipboardPaths/
clipboardCut były polami FilesState -- każde okno menedżera plików
ma własną instancję tego stanu (patrz newFileManager), więc "Kopiuj"
w oknie A i "Wklej" w oknie B po prostu nic nie robiło (przycisk "Wklej"
zostawał wyszarzony w oknie B, bo jego WŁASNY clipboardPaths był
pusty). Naprawa: oba pola przeniesione na poziom modułu (var, nie pola
obiektu) -- moduł Nim jest w obrębie procesu singletonem, więc każde
okno menedżera odwołujące się do gFileClipboardPaths/
gFileClipboardCut widzi TĘ SAMĄ pamięć. Nadal NIE jest to prawdziwy
schowek plików w stylu GNOME/KDE (wciąż bez integracji z
wl_data_device, więc nie działa z aplikacjami spoza ZDE, i nie
przeżywa restartu zde-shell) -- ale najczęstszy realny scenariusz
(dwa okna menedżera otwarte naraz, kopiuj w jednym/wklej w drugim) teraz
działa.
2. Znajdź/Zamień w edytorze tekstu ma teraz przełącznik "Aa"
(rozróżnianie wielkości liter) (apps/texteditor/texteditor.nim).
Wcześniej computeFindMatches/doReplaceCurrent/doReplaceAll ZAWSZE
porównywały przez toLowerAscii() po obu stronach, bez wyjątku. Nowe
pole Tab.caseSensitive (domyślnie false -- stare zachowanie, żadna
istniejąca karta nie dostaje niespodziewanej zmiany) + przycisk "Aa" w
pasku Znajdź, między polem wyszukiwania a licznikiem wyników. Włączony
port porównuje line/findQuery WPROST, bez żadnej normalizacji --
zweryfikowane realnie pod Xvfb: dokument "Hello World hello world" +
zapytanie "hello" daje "0 z 2" (oba dopasowania) z wyłączonym
przełącznikiem i "0 z 1" (tylko małe litery) z włączonym.
3. Minutnik ma teraz wybór dźwięku, tak jak alarmy
(apps/clock/clockapp.nim). Wcześniej tickClock zawsze wołało
playAlarmSound() bez argumentu (czyli zawsze "Auto") po zakończeniu
odliczania -- alarmy dostały wybór dźwięku już w poprzedniej rozbudowie
(patrz "Nowości tej rozbudowy -- wybór dźwięku alarmu" niżej), ale
minutnik został pominięty, co README uczciwie wymieniało jako
ograniczenie. Nowe pole ClockState.timerSoundIdx (ten sam wzorzec co
newSoundIdx dla alarmów, -1 = "Auto") + identyczny cykliczny
picker, dorzucony w drawTimerSection między wyświetlaczem odliczania a
przyciskami start/pauza/reset.
Notatka o metodologii weryfikacji tej rundy: wszystkie trzy zmiany
zostały nie tylko przejrzane ręcznie, ale realnie SKOMPILOWANE (nim check per-moduł + pełny nimble c -d:release -d:pixieNoSimd shell/shell.nim budujący dist/zde-shell od zera) i URUCHOMIONE pod
zagnieżdżonym Xvfb (ten sam sposób testowania co w rundzie "pierwsze
prawdziwe testy wizualne zde-shell", patrz niżej) -- z realnymi
zrzutami ekranu i klikami przez xdotool: dwa okna menedżera plików
otwarte jednocześnie z widocznym, żywym przyciskiem "Wklej"
zmieniającym kolor po skopiowaniu w drugim oknie; pasek Znajdź w
edytorze z przyciskiem "Aa" i licznikiem wyników zmieniającym się z "0 z
2" na "0 z 1" po jego kliknięciu; zakładka "Alarmy" zegara z nowym
pickerem dźwięku minutnika widocznym pod wyświetlaczem odliczania.
Jedyne środowiskowe ograniczenie tej rundy: pełny łańcuch zależności
Fidget/Pixie wymaga html5_canvas (pakiet Nimble hostowany na GitLab),
którego nie dało się pobrać z tej konkretnej piaskownicy sieciowej (brak
gitlab.com na białej liście) -- obejście lokalne (usunięcie tej
zależności z lokalnej kopii fidget.nimble, bo jest używana WYŁĄCZNIE
przez nieużywany tu backend HTML/JS Fidgetu, src/fidget/htmlbackend.nim)
posłużyło tylko do weryfikacji w tej sesji -- zde.nimble w repozytorium
NIE zostało zmienione, więc normalne środowisko budowania (z dostępem do
GitLab) nie zauważy żadnej różnicy.
Domknięcie luki, którą poprzednia rozbudowa (patrz sekcja "Nowości tej rozbudowy -- wyszukiwanie w edytorze tekstu" niżej) jawnie zostawiła otwartą: samo wyszukiwanie już działało, ale bez żadnego sposobu na zamianę znalezionego tekstu. Pasek "Znajdź" dostał drugi wiersz: pole "zamień na..." plus przyciski "Zamień" (tylko aktualnie wycelowane dopasowanie) i "Zamień wszystko".
- "Zamień" -- gdy nic nie jest jeszcze "wycelowane" (użytkownik nie kliknął wcześniej ◀/▶/Enter), traktuje to jak "przejdź do pierwszego dopasowania, a POTEM zamień", zamiast po prostu nic nie robić -- bardziej przewidywalne niż wymaganie ręcznego kliknięcia strzałki najpierw.
- "Zamień wszystko" pracuje BEZPOŚREDNIO na całej treści dokumentu jako jednym stringu (bez żadnej konwersji linia/kolumna) -- prostsze i z automatu bezpieczne dla dowolnego stylu zakończeń linii, bo nigdy nie dzieli treści na linie ani jej z powrotem nie składa.
Prawdziwy błąd, którego UNIKNĄŁEM dzięki testowaniu na realnych danych PRZED napisaniem kodu produkcyjnego
Zanim napisałem doReplaceCurrent, sprawdziłem w izolowanym teście, jak
Nim faktycznie zachowuje się przy typowym podejściu "podziel treść na
linie (splitLines), zmień jedną, złóż z powrotem (join("\n")")" -- dokładnie tak, jak dane do wyszukiwania (line, col) są już liczone w computeFindMatches. Okazało się, że dla plików z końcami linii w stylu CRLF (\r\n, typowe dla plików zapisanych w Windows) taki round-trip **cicho zamienia KAŻDE \r\nw całym pliku na samo\n** -- splitLines"zjada"\r\njako jeden separator, ajoin("\n")oddaje z powrotem tylko\n`. Efekt: pojedyncza, drobna zamiana tekstu przez użytkownika
cicho przekonwertowałaby styl końców linii CAŁEGO pliku, co przy
zapisaniu z powrotem na dysk pokazałoby się jako ogromny, niespodziewany
diff w systemie kontroli wersji -- dla zmiany jednego słowa.
Zamiast tego lineColToOffset liczy przesunięcie znakowe WPROST na
oryginalnym stringu (sprawdzając faktyczny znak/parę znaków napotkanych
po drodze), nigdy nie dzieląc i nie składając treści z powrotem --
zweryfikowane wprost testem z prawdziwą zawartością CRLF, potwierdzającym
identyczną liczbę \r\n przed i po zamianie. Dodatkowo doReplaceCurrent
ma wbudowaną "siatkę bezpieczeństwa": PRZED zamianą sprawdza, czy pod
obliczonym przesunięciem faktycznie JEST to, czego szukamy -- gdyby
jednak lineColToOffset się kiedyś pomyliło (np. przy nietypowych,
mieszanych zakończeniach linii), odmawia zamiany zamiast zaryzykować
nadpisanie niewłaściwego fragmentu pliku użytkownika.
Zweryfikowane w izolowanych, realnie skompilowanych i uruchomionych
programach Nim: podstawowa zamiana LF, zamiana z zachowaniem CRLF,
zamiana DRUGIEGO wystąpienia w tej samej linii (nie tylko pierwszego --
sprawdza, czy liczenie kolumny jest poprawne, nie tylko liczenie linii),
odmowa zamiany przy sztucznie wymuszonym błędnym przesunięciu, "Zamień
wszystko" z zachowaniem CRLF, oraz pełna symulacja przepływu UI (znajdź
-> zamień jedno -> przelicz -> zamień resztę), identyczna z tym, co
faktycznie robi kod w onClick.
Zmiana kierunku po kilku rundach skupionych na menedżerze plików i
tapecie: apps/texteditor/texteditor.nim nie miało ŻADNEGO sposobu na
wyszukanie tekstu w dokumencie -- trzeba było przewijać ręcznie, nawet w
dużym pliku. Ctrl+F (albo przycisk 🔍 w toolbarze) otwiera teraz pasek
"Znajdź" z licznikiem wyników, nawigacją ◀/▶ i podświetleniem trafień.
- Wyszukiwanie bez rozróżniania wielkości liter, nieoverlapujące
(podłańcuch "konsumuje" swoją długość przed szukaniem kolejnego, ten
sam standard co wyszukiwanie w przeglądarce), przeliczane na nowo z
całej treści dokumentu przy każdym renderowaniu paska -- ten sam
poziom "wystarczająco dobre" co
drawHighlighted, które retokenizuje widoczne linie na każdej klatce niezależnie od rozmiaru pliku (nie nowy kompromis wydajnościowy, spójny z tym, co już było). - Podświetlenie dopasowań dorysowane POD tokenami podświetlania składni na widocznych liniach (aktualnie "wycelowane" trafienie dostaje wyraźnie wyższą nieprzezroczystość niż pozostałe) -- działa TYLKO w trybie podglądu (gdy pole edycji nie ma fokusu), bo w trybie aktywnej edycji Fidget pokazuje surowe pole tekstowe, nie tokeny.
- Enter = następne trafienie, Shift+Enter = poprzednie (ten sam gest co w przeglądarkach), Escape zamyka pasek.
- Ograniczenie, świadomie zaakceptowane: to NIE jest skok kursora w
dokładne miejsce -- Fidget nie daje programowego dostępu do pozycji
kursora w polu
editableText/multiline(żadna część kodu w tym repo nigdzie tego nie robi), więc "przejdź do trafienia" oznacza "przewiń tak, żeby linia z trafieniem była blisko góry z odrobiną kontekstu nad nią", nie "postaw kursor na literze X w linii Y". Uczciwie nazwane w komentarzach jako uproszczenie, nie ukryte. - Podobnie świadomie POZA zakresem: Ctrl+F nie ustawia fokusu na pole wyszukiwania automatycznie (tylko otwiera pasek) -- programowe ustawienie fokusu klawiatury spoza bloku danego widgetu nie jest gdzie indziej w tym repo robione, więc nie ryzykowałem zgadywania, jak to zrobić poprawnie w tym silniku; użytkownik klika w pole raz, tak jak przy każdym innym polu tekstowym w ZDE.
Ctrl+F musiał wiedzieć, czy TO KONKRETNE okno edytora jest aktywne (bez
tego otwierałby pasek Znajdź we WSZYSTKICH otwartych oknach edytora
naraz). Zwykły sposób na to (win.id == compositor.focusedId, już
używany w shell/chrome.nim i -- inaczej niż tutaj, bez przeszkód -- w
apps/filemanager/files.nim) wymaga zaimportowania shell/state.nim po
compositor. Dla edytora tekstu to NIE JEST możliwe bez cyklu: to
WŁAŚNIE shell/state.nim importuje apps/texteditor/texteditor.nim
(do rejestru texteditors, patrz "wykrywanie zmian pliku na dysku" w
poprzednich rundach), więc texteditor.nim importujący state.nim z
powrotem zamknąłby pętlę.
Rozwiązanie: nowe pole ZdeWindow.focused* (comp/types.nim) -- niższy
poziom w hierarchii importów niż state.nim, więc dostępne wszędzie bez
ryzyka cyklu. Ustawiane RAZ na klatkę przez drawWindowChrome
(shell/chrome.nim, które i tak już liczyło win.id == compositor.focusedId na własny użytek -- podświetlenie ramki aktywnego
okna) TUŻ PRZED narysowaniem treści okna. drawEditor po prostu czyta
win.focused z argumentu, który i tak już dostaje -- zero nowych
zależności, zero cyklu. Ten sam mechanizm będzie mógł posłużyć
przyszłym skrótom klawiszowym w innych aplikacjach importowanych przez
state.nim (dziś: terminal, zegar, monitor systemu, edytor tekstu),
które miałyby ten sam problem.
Pierwsza wersja computeFindMatches BEZWARUNKOWO zerowała
findCurrentIdx na końcu każdego przeliczenia. Ponieważ funkcja ta jest
wołana PRZY KAŻDYM RENDEROWANIU paska Znajdź (nie tylko przy zmianie
zapytania), to zerowanie cofałoby efekt kliknięcia "▶"/"◀"/Enter na
następnej samej klatce -- nawigacja między wynikami wizualnie w ogóle by
nie ruszyła z miejsca, licznik zawsze pokazywałby ten sam stan. Złapane
dopiero przy pisaniu testu SYMULUJĄCEGO wiele kolejnych klatek renderowania
(nie pojedyncze wywołanie) -- test z jednym wywołaniem by tego nie
wykrył, bo błąd ujawnia się dopiero przy PONOWNYM przeliczeniu po
nawigacji. Naprawione przez rozróżnienie "zapytanie faktycznie się
zmieniło" (nowe pole findLastQuery) od "to samo zapytanie, po prostu
kolejna klatka" -- indeks resetuje się tylko w tym pierwszym przypadku
(albo gdy przestał mieścić się w nowej, krótszej liście dopasowań).
Zweryfikowane w izolowanym, realnie skompilowanym i uruchomionym
programie Nim, symulującym dokładnie tę sekwencję: kliknięcie "dalej",
potem dziesięć kolejnych "klatek" przeliczenia bez żadnej innej zmiany
-- indeks poprawnie PRZETRWAŁ wszystkie, zamiast wracać do -1.
Domknięcie luki, którą DWIE poprzednie rozbudowy z rzędu jawnie zostawiły
otwartą (patrz sekcje "tapeta z pliku obrazu" i "podgląd miniatury tapety"
niżej) -- każda zmiana tapety, zmiana rozdzielczości ekranu, a NAWET samo
przeglądanie kandydatów na tapetę w Ustawieniach (każda poprawna ścieżka
wpisana w pole podglądu generowała własny plik miniatury 160x90)
zostawiały nowy plik w $XDG_CACHE_HOME/zde/ bez usuwania starych --
katalog cache mógł rosnąć bez końca.
pruneWallpaperCache (shell/wallpaper.nim), wołane automatycznie zaraz
po każdym udanym zapisie nowego pliku cache (nie na każdej klatce --
tylko wtedy, gdy faktycznie coś nowego przybyło), trzyma co najwyżej 12
najnowszych plików (wg czasu modyfikacji), usuwając resztę. Zamierzenie
jest CELOWO proste i "samoleczące się": usunięcie pliku, który akurat jest
w użyciu (np. bieżącej tapety, gdyby limit trafił akurat na nią przy
bardzo intensywnym przeglądaniu podglądów), niczego nie psuje --
ensureWallpaperCache po prostu wygeneruje go PONOWNIE przy następnym
potrzebnym wywołaniu, dopóki oryginalny plik źródłowy wciąż istnieje na
dysku (co nigdy nie jest zagrożone -- sprzątanie dotyka WYŁĄCZNIE plików
cache w ~/.cache, nigdy oryginalnych zdjęć użytkownika).
Zweryfikowane w izolowanym, realnie skompilowanym i uruchomionym programie Nim na prawdziwym systemie plików: 20 sztucznych plików cache o rozstawionych w czasie znacznikach modyfikacji, przycięte do 12 najnowszych, z potwierdzeniem, że zostały faktycznie NAJNOWSZE (nie przypadkowe), oraz że ponowne wywołanie przy liczbie już poniżej limitu nic nie usuwa (no-op, nie błąd).
Domknięcie luki, którą poprzednia rozbudowa (patrz sekcja "Nowości tej rozbudowy -- tapeta z pliku obrazu" niżej) jawnie zostawiła otwartą na liście ograniczeń: trzeba było kliknąć "Zastosuj" NA ŚLEPO, żeby w ogóle zobaczyć, czy wpisana ścieżka wskazuje na coś sensownego.
Ustawienia -> Wygląd -> Tapeta pokazują teraz miniaturę 160x90 NA BIEŻĄCO,
w trakcie wpisywania ścieżki -- zanim użytkownik w ogóle kliknie
"Zastosuj". Zero duplikacji logiki: ensureWallpaperCache
(shell/wallpaper.nim) zostało po prostu wyeksportowane i wołane z
Ustawień z małym rozmiarem docelowym (160x90) zamiast rozmiaru ekranu --
ten sam algorytm "cover", te same zabezpieczenia przed uszkodzonymi
plikami, ten sam mechanizm cache'a co dla właściwej tapety. Klucz cache'a
zawiera docelowy rozmiar w nazwie pliku (patrz poprzednia sekcja), więc
miniatura 160x90 i pełnowymiarowa tapeta dla TEGO SAMEGO źródła trafiają
naturalnie w dwa różne pliki cache, bez wzajemnej kolizji -- zweryfikowane
wprost w izolowanym teście z prawdziwą Pixie (ten sam obraz źródłowy
przetworzony do obu rozmiarów naraz, oba pliki wyniku odczytane z powrotem
i sprawdzone co do wymiarów).
Trzy stany miniatury: pusta/szara ("Podgląd", gdy pole jeszcze puste), faktyczny podgląd (gdy ścieżka wskazuje na plik, który Pixie potrafi zdekodować), albo czerwony komunikat błędu ("Nie można odczytać obrazu", gdy plik istnieje, ale jest w nieobsługiwanym/uszkodzonym formacie) -- ten trzeci stan to dokładnie ten sam sygnał, jaki i tak dostałoby się po kliknięciu "Zastosuj", tylko wcześniej, bez dodatkowego kliku.
Pierwsza rozbudowa tej serii, która nie domyka punktu z listy ograniczeń,
tylko dokłada zupełnie nową możliwość: pulpit od zawsze pokazywał
wyłącznie wbudowany, syntetyczny gradient (shell/wallpaper.nim) --
zero sposobu na ustawienie własnego zdjęcia/obrazu jako tła, tak jak w
każdym innym środowisku graficznym. Ustawienia -> Wygląd dostały nową
sekcję "Tapeta": pole ze ścieżką do pliku + "Zastosuj"/"Domyślna".
- Wybrany obraz NIGDY nie trafia bezpośrednio do
image(...)Fidget-a. Zamiast tego jest raz przetworzony przez Pixie (ten sam silnik, którego już używashell/desktopapps.nimdo ikon aplikacji) metodą "cover" -- przeskalowany i przycięty do DOKŁADNIE rozmiaru ekranu, bez zniekształcenia proporcji (ten sam algorytm co "Wypełnij ekran" w GNOME/KDE) -- i zapisany jako plik cache w$XDG_CACHE_HOME/zde/. Dwa powody: Fidget prawdopodobnie tylko rozciąga obraz do zadanegoboxbez zachowania proporcji (dowolne zdjęcie o innych proporcjach niż ekran wyglądałoby spłaszczone bez tego kroku), a poza tym to ten sam rodzaj ryzyka co przy ikonach aplikacji -- Pixie w tej wersji potrafi rzucić wyjątkiem przy pewnych plikach, którego NIC w pętli renderowania Fidget-a nie łapie, więc lepiej zdekodować obraz TU, w kodzie, który MY kontrolujemy, niż dowiedzieć się o awarii dopiero w trakcie rysowania klatki. - Cache bez osobnego stanu: nazwa pliku cache koduje ścieżkę
źródłową, jej czas modyfikacji ORAZ docelowy rozmiar ekranu (hash +
wymiary w samej nazwie pliku) -- zmiana tapety, edycja pliku źródłowego
na dysku, albo zmiana rozdzielczości ekranu każde z osobna dają inną
nazwę pliku, więc stary cache po prostu przestaje być trafiany, bez
żadnej logiki "unieważnij, jeśli...". Ten sam styl co
appDirsSignaturewdesktopapps.nim-- policz z tego, co już jest na dysku, zamiast trzymać osobny stan w pamięci procesu. - Nieudana próba (zły/uszkodzony plik, nieobsługiwany format) jest zapamiętywana per (ścieżka, rozmiar) -- kosztowna, skazana z góry na tę samą porażkę próba dekodowania nie powtarza się na każdej klatce (60x/s), tylko raz, po czym cicho spada do wbudowanego gradientu.
- Konfiguracja: nowe pole
wallpaperPathwzdeconfig.nim(domyślnie""-- każda instalacja sprzed tej rozbudowy dostaje dokładnie dawne zachowanie bez żadnej zmiany) i nowa żywa zmiennastate.WallpaperPath, mutowana przez Ustawienia na żywo -- ten sam, już wcześniej sprawdzony wzorzec coAccentColor.
Bez fidget/GLFW w tej sandboxie (jak zawsze -- wymagają Nim ≥2.0, dostępny
jest 1.6.14) nie dało się skompilować samego rysowania. UDAŁO się jednak
zainstalować i realnie wykorzystać samą Pixie -- pixie@4.4.0 (starsza,
kompatybilna z Nim 1.6.14 gałąź, bez zależności od crunchy) skompilowała
się i zadziałała z flagą -d:pixieNoSimd (ten sam, już wcześniej znany
projektowi wymóg -- patrz buildShellDirect w zde.nimble). Dzięki temu
algorytm "cover" i logika cache'a zostały przetestowane na PRAWDZIWEJ
bibliotece obrazów, nie na reimplementacji: syntetyczne zdjęcie zapisane
jako prawdziwy plik PNG na dysku, wczytane, przeskalowane/przycięte do
kilku różnych docelowych rozdzielczości (obraz szerszy niż ekran, węższy/
portretowy, ta sama proporcja, oraz proporcja powodująca zaokrąglenie przy
rzutowaniu na int -- wszystkie dały dokładnie oczekiwane wymiary wyniku),
zapisane i ponownie odczytane jako poprawny PNG, oraz osobno -- że
uszkodzony plik jest bezpiecznie odrzucany zamiast wywalać program. Cała
logika cache'a (generowanie, ponowne użycie bez regeneracji, inwalidacja
przy zmianie źródła/rozmiaru, brak powtarzania nieudanej próby) również
przetestowana w izolowanym programie z tą samą, prawdziwą Pixie.
Do tej pory każda operacja w menedżerze plików wymagała myszy -- nawet proste rzeczy jak usunięcie zaznaczenia czy zmiana nazwy. Ta rozbudowa dokłada cztery skróty, które w każdym innym menedżerze plików są podstawą obsługi z klawiatury:
- Delete -- ten sam mechanizm "uzbrojenia" co przycisk "Usuń" w pasku narzędzi: pierwsze naciśnięcie zbroi (przycisk w toolbarze pokazuje "Na pewno? (N)"), DRUGIE w ciągu 4 sekund faktycznie usuwa zaznaczenie. Wygaśnięcie uzbrojenia między naciśnięciami traktowane jest jak świeże, pierwsze naciśnięcie (ponowne uzbrojenie), nie jak wykonanie -- nieodwracalna operacja nigdy nie wykonuje się przez przypadek po zbyt wolnym drugim naciśnięciu.
- Ctrl+A -- zaznacza wszystko w bieżącym katalogu.
- Escape -- czyści zaznaczenie i anuluje "uzbrojone", ale
niedokończone usuwanie (bez zamykania okna -- za to odpowiada osobno
shell/chrome.nim). - F2 -- wchodzi w tryb zmiany nazwy, ale TYLKO gdy zaznaczony jest dokładnie jeden wpis (przy wielu zaznaczonych nic nie robi -- zmiana nazwy zawsze działa na jednym wpisie naraz, tak jak przy ikonie ✎).
Wszystkie cztery działają WYŁĄCZNIE gdy okno menedżera plików jest
aktywne (win.id == compositor.focusedId, ten sam sprawdzony sposób co
podświetlanie ramki w shell/chrome.nim -- bez tego np. Delete
skasowałoby zaznaczenie we WSZYSTKICH otwartych oknach menedżera naraz,
nie tylko w tym, na które faktycznie patrzy użytkownik) i tylko gdy
użytkownik nie jest akurat w trakcie wpisywania nazwy (zmiana nazwy albo
nowy folder) -- inaczej np. Delete kasowałoby zaznaczenie w trakcie
pisania nazwy nowego pliku, co byłoby mylące. Stałe klawiszy (DELETE,
F2, LETTER_A, ESCAPE) zweryfikowane względem rzeczywistego
źródła Fidget (sklonowanego z GitHuba na potrzeby tej rozbudowy), nie
zgadywane.
Zweryfikowane logicznie w izolowanym, realnie skompilowanym i uruchomionym programie Nim: sekwencja "pierwsze Delete zbroi, drugie wykonuje", F2 działające tylko przy dokładnie jednym zaznaczeniu, oraz wygasłe uzbrojenie traktowane jak świeże naciśnięcie, nie jak wykonanie.
Domknięcie luki, którą poprzednia rozbudowa (patrz sekcja "Nowości tej rozbudowy -- zaznaczanie wielu wpisów..." niżej) jawnie zostawiła otwartą: Ctrl+klik do przełączania POJEDYNCZYCH wpisów już działał, ale zaznaczenie 20 kolejnych plików wymagało 20 Ctrl+kliknięć. Teraz Shift+klik zaznacza cały ciągły zakres jedną operacją -- dokładnie tak jak w Nautilusie/Dolphinie/Eksploratorze Windows.
FilesStatedostał nowe poleanchorName-- "kotwicę" zakresu, ustawianą na KAŻDYM zwykłym kliknięciu (bez modyfikatorów). Shift+klik zaznacza wszystko między kotwicą a klikniętym wierszem WŁĄCZNIE (fs.entries[lo..hi]na BIEŻĄCEJ, posortowanej liście), zastępując dotychczasowe zaznaczenie -- kotwica przy tym NIE przesuwa się na kliknięty wiersz, więc kolejne Shift+kliknięcia poszerzają/zwężają zakres względem TEJ SAMEJ, pierwotnej kotwicy, a nie względem poprzedniego zakresu (dokładnie tak samo działa to w prawdziwych menedżerach plików).- Brakująca/nieaktualna kotwica (np. jeszcze nic nie kliknięto zwykłym klikiem w tej sesji okna, albo wskazywała na coś usuniętego w międzyczasie) sprawia, że Shift+klik po prostu zachowuje się jak zwykły klik -- nowa kotwica ustawia się na kliknięty wiersz, zamiast wywalać się albo zaznaczać coś nieprzewidywalnego.
- Modyfikator odczytywany tym samym sprawdzonym sposobem co Ctrl+klik z
poprzedniej rundy (
buttonDown[LEFT_SHIFT]/RIGHT_SHIFT).
Zweryfikowane w izolowanym, realnie skompilowanym i uruchomionym programie Nim: zakres w obie strony (kotwica przed i po kliknięciu), kolejne Shift+kliknięcia względem tej samej kotwicy (nie względem poprzedniego zakresu), Shift+klik na samej kotwicy, oraz zachowanie przy nieaktualnej/brakującej kotwicy.
Kolejny punkt z listy ograniczeń: dało się już kopiować/wycinać/wklejać,
ale tylko PO JEDNYM wpisie naraz. apps/filemanager/files.nim dostał
Ctrl+klik do zaznaczania wielu wpisów jednocześnie:
- Zwykły klik na wierszu zastępuje całe zaznaczenie tym jednym wpisem
(bez zmian względem wcześniejszego zachowania). Ctrl+klik PRZEŁĄCZA
dany wpis w zaznaczeniu (dodaje, jeśli go tam nie było, usuwa, jeśli
był) i -- w odróżnieniu od zwykłego kliknięcia -- nigdy nie nawiguje do
katalogu ani nie otwiera pliku, nawet dla folderu: intencja
Ctrl+kliknięcia to zawsze "zaznacz to też", nigdy "wejdź do środka".
Modyfikator odczytywany przez
buttonDown[LEFT_CONTROL]/RIGHT_CONTROL, ten sam sprawdzony sposób co wshell/shortcuts.nim(tam opisano, dlaczegokeyboard.ctrlKeybywa zawodny). - Kopiuj/Wytnij/Wklej (z poprzedniej rundy) oraz nowy przycisk
Usuń w pasku narzędzi działają teraz na CAŁYM zaznaczeniu, nie
tylko na jednym wpisie -- "Wklej" pokazuje liczbę w nawiasie, gdy jest
więcej niż jeden element w schowku plików. "Usuń" (zbiorcze) używa
tego samego mechanizmu "uzbrojenia" co ikona 🗑 przy pojedynczym
wierszu (klik zbroi na 4 sekundy, drugi klik faktycznie usuwa) --
osobne pole stanu (
pendingBulkDelete), bo dotyczy WIELU wpisów naraz, nie jednego przy konkretnym wierszu. - Zbiorcze operacje (usuwanie, wklejanie wielu) NIE przerywają się na pierwszym niepowodzeniu -- kontynuują resztę i melduje jedno zbiorcze podsumowanie na końcu ("Usunięto 3 wpisy", "Błąd: 1 z 4 się nie udało"), zamiast zalewać powiadomieniami po jednym na plik albo urwać w połowie przy pierwszym błędzie.
Podczas pisania testu dla zbiorczego usuwania z jednym brakującym plikiem
(symulacja: coś usunięto z zewnątrz między odświeżeniem listy a
kliknięciem) test dał wynik NIEZGODNY z oczekiwaniem -- os.removeFile
w Nim, jak się okazało, cicho nic nie robi dla nieistniejącego pliku,
zamiast rzucić OSError. Bez jawnego sprawdzenia fileExists/
dirExists PRZED próbą usunięcia, doDelete/doBulkDelete zgłosiłyby
fałszywy sukces "Usunięto" dla czegoś, co już nie istniało -- blok
except OSError nigdy by się nie uruchomił, bo nic by go nie wywołało.
Naprawione dodaniem jawnego sprawdzenia istnienia przed każdą próbą
usunięcia, w obu procedurach. Zostawiam to tutaj opisane wprost, bo to
dokładnie ten rodzaj cichej nieścisłości, którą łatwo przeoczyć bez
faktycznego przetestowania na prawdziwym systemie plików -- a ta
rozbudowa, jak wszystkie poprzednie, jest testowana właśnie w ten sposób,
nie tylko czytana wzrokiem.
Reszta zweryfikowana tak samo jak zawsze w tej sandboxie: izolowane, realnie skompilowane i uruchomione programy Nim -- logika przełączania zaznaczenia (dodaj/usuń/zastąp), oraz zbiorcze kopiowanie/usuwanie kilku plików i folderu naraz na prawdziwym systemie plików w katalogu tymczasowym.
Domknięcie luki, którą poprzednia rozbudowa (patrz sekcja "Nowości tej rozbudowy -- prawdziwe operacje na plikach..." niżej) świadomie zostawiła otwartą: dało się już tworzyć foldery, zmieniać nazwy i usuwać, ale nie dało się przenieść ani skopiować pliku/folderu MIĘDZY katalogami -- jedyny sposób to było ręczne przepisywanie w terminalu.
apps/filemanager/files.nim dostał "schowek plików" -- CELOWO osobny
byt od shell/clipboard.nim (schowka TEKSTOWEGO systemu, z poprzednich
rund): to inny rodzaj danych (bezwzględna ścieżka + flaga
kopiuj/przenieś), trzymany wyłącznie w pamięci JEDNEGO okna menedżera,
bez integracji z wl_data_device -- nie działa między dwoma otwartymi
oknami menedżera ani z zewnętrznymi aplikacjami. Prawdziwy schowek plików
w stylu GNOME/KDE wymagałby własnego typu MIME na poziomie kompozytora,
czego architektura ZDE dziś nigdzie nie robi -- świadomy kompromis, nie
przeoczenie (opisany też w liście ograniczeń niżej).
- Kopiuj/Wytnij (przyciski w pasku narzędzi, działają na już istniejącym zaznaczeniu z pojedynczego kliknięcia w wiersz) tylko zapamiętują źródło -- żadna operacja dyskowa nie dzieje się od razu, dokładnie jak Ctrl+C/Ctrl+X w każdym innym menedżerze.
- Wklej wykonuje faktyczną operację:
copyFile/copyDir(rekurencyjnie dla folderów) dla kopiowania,moveFile/moveDirdla wycięcia. "Wytnij" jest jednorazowe -- schowek czyści się po wklejeniu; "Kopiuj" zostaje, więc to samo źródło można wkleić wielokrotnie w różne miejsca. - Kolizja nazw przy wklejaniu (najczęściej: wklejenie z powrotem do TEGO SAMEGO katalogu, czyli zwykłe "zduplikuj") NIE pyta "nadpisać?" (nie mamy jak -- bez natywnego dialogu) ani nie failuje -- dokłada sufiks "(kopia)"/"(kopia 2)"/... aż trafi na wolną nazwę, ten sam mechanizm co "Zdjęcie (kopia).jpg" w Nautilusie/Dolphinie.
- Zabezpieczenie przed rekurencją: wklejenie folderu do samego siebie
albo do jego własnego podkatalogu jest wykrywane i odrzucane z błędem
PRZED próbą operacji na dysku -- bez tego
copyDirwpadłby w nieskończoną rekurencję (kopiowanie tworzyłoby własny cel wewnątrz samo siebie).
Zweryfikowane w izolowanym, realnie skompilowanym i uruchomionym programie Nim z prawdziwymi operacjami na systemie plików w katalogu tymczasowym: kopiowanie pliku między katalogami, rekurencyjne kopiowanie folderu (z zachowaniem oryginału), przenoszenie (cel znika ze źródła), generowanie kolejnych sufiksów "(kopia)"/"(kopia 2)" przy powtórnym wklejaniu tej samej nazwy, oraz osobno -- po poprawieniu błędnego założenia w PIERWSZEJ wersji testu -- zabezpieczenie przed wklejeniem folderu do samego siebie/własnego potomka (odróżnione od w pełni poprawnego przypadku wklejenia folderu do JEGO WŁASNEGO katalogu nadrzędnego, co nie jest rekurencją i powinno działać).
Nowości tej rozbudowy -- prawdziwe operacje na plikach, otwieranie plików, dźwięk zwykłych powiadomień
Największa runda od dawna, bo domyka trzy powiązane braki naraz:
apps/filemanager był do tej pory WYŁĄCZNIE przeglądarką (README już to
uczciwie nazywało "przeglądarką", nie "menedżerem") -- nawigacja po
katalogach działała, ale nie dało się utworzyć folderu, zmienić nazwy,
usunąć, ani nawet OTWORZYĆ pliku. Co więcej, lastClickTime/
lastClickName istniały w FilesState od dawna, najwyraźniej z myślą o
wykrywaniu podwójnego kliknięcia -- ale nigdy nie były faktycznie
odczytywane. Martwy kod, nie żadna ukryta funkcja.
Podwójny klik na pliku (dwa kliknięcia w tę samą nazwę w ciągu 0.4s --
lastClickTime/lastClickName, nareszcie wykorzystane) otwiera go w
edytorze tekstu. Wymagało to przekazania ścieżki startowej AŻ do
launchTextEditor (shell/launcher_apps.nim) -- newEditorState/
newTab (apps/texteditor/texteditor.nim) już wcześniej to wspierały,
brakowało tylko przekazania parametru z góry. apps/filemanager/files.nim
CELOWO nie importuje launcher_apps.nim wprost (uniknięcie cyklu -- to
WŁAŚNIE launcher_apps.nim importuje files.nim) -- zamiast tego
FilesState dostał nowe pole openFile: proc(path: string) {.closure.},
które launchFileManager wypełnia domknięciem wołającym
launchTextEditor. Ten sam wzorzec "callback zapisany na stanie", co
AppEntry.action/win.onClose gdzie indziej w kodzie.
Bez żadnego natywnego dialogu (Fidget go nie ma):
- Nowy folder: przycisk w pasku narzędzi otwiera/zamyka wąski pasek kreatora pod breadcrumbem (pole tekstowe + "Utwórz"/"Anuluj").
- Zmiana nazwy: ikona ✎ przy wpisie zamienia jego wiersz w pole
edytowalne (wypełnione bieżącą nazwą), z "✓"/"×" do zatwierdzenia/
anulowania. Sprawdza kolizję nazw PRZED próbą (
fileExists/dirExistsna celu) -- błąd trafia i doerrorMsg(widoczny w panelu), i donotify(..., nkWarning). - Usuwanie: ikona 🗑 to nieodwracalna operacja, więc dostała osobny
mechanizm "uzbrojenia" zamiast zwykłego dialogu potwierdzenia (którego
Fidget i tak nie ma) -- pierwszy klik zbroi (ikona robi się czerwona,
zamienia się w "✔?"), DRUGI klik w ciągu 4 sekund faktycznie usuwa.
Wygaśnięcie liczone leniwie przy renderze (
epochTime() - pendingDeleteAt), bez osobnego tickera -- ten sam styl coformatAgowshell/notifications.nim.
Wszystkie trzy operacje wołają teraz notify(..., nkInfo) przy sukcesie
i notify(..., nkWarning) przy błędzie -- pierwsze REALNE użycie tych
dwóch wariantów NotifyKind w całym kodzie (przedtem istniały w typie,
ale jedynym wywołującym notify() był zegar, zawsze z nkAlarm).
Kolejny jawnie wymieniony brak: toasty (nkInfo/nkWarning) nie miały
żadnego dźwięku, tylko alarm zegara. shell/sound.nim dostał
playNotifySound/playWarningSound -- osobne, subtelniejsze dźwięki niż
alarm (dialog-information.oga/dialog-warning.oga z tego samego
motywu sound-theme-freedesktop, którego nazwy plików zweryfikowałem
względem RZECZYWISTEJ zawartości pakietu, nie zgadywałem), wołane
automatycznie z notify() w shell/notifications.nim -- ale NIE dla
nkAlarm, bo alarm/minutnik już wołają własny, wybrany przez
użytkownika dźwięk w miejscu, gdzie faktycznie odpalają (patrz sekcja
"Nowości tej rozbudowy -- wybór dźwięku alarmu" niżej) -- dublowanie
dałoby dwa nakładające się dźwięki na jedno zdarzenie. Odtwarzanie
dźwięku respektuje tryb "nie przeszkadzać" tak samo jak sam toast.
Zweryfikowane w izolowanych, realnie skompilowanych i uruchomionych
programach Nim -- tym razem z prawdziwymi operacjami na systemie plików
w katalogu tymczasowym (utworzenie folderu, zmiana nazwy pliku I
katalogu, ochrona przed nadpisaniem istniejącej nazwy, usunięcie), a
także logiki wykrywania podwójnego kliknięcia i wygasania uzbrojenia
usuwania z realistycznym opóźnieniem (sleep), nie sztucznie przesuniętym
czasem. Jak zawsze w tej sandboxie, całego zde-shell z Fidget/Pixie nie
dało się skompilować (wymagają Nim ≥2.0, dostępny jest 1.6.14).
Domknięcie luki, którą poprzednia rozbudowa (patrz sekcja "Nowości tej
rozbudowy -- historia schowka" niżej) świadomie zostawiła otwartą:
historia schowka żyła wyłącznie w pamięci procesu zde-shell -- restart
shellu czyścił ją bezpowrotnie, dokładnie tak jak historia powiadomień
PRZED analogiczną rozbudową opisaną w kolejnej sekcji niżej.
shell/clipboard.nim dostał dokładnie ten sam mechanizm co
shell/notifications.nim: historyFilePath/saveHistoryToDisk/
loadHistoryFromDisk, zapis do $XDG_STATE_HOME/zde/clipboard.json
(fallback ~/.local/state/zde/clipboard.json) -- OSOBNY plik od
notifications.json, bo to koncepcyjnie inne dane (schowek vs
powiadomienia), a nie dlatego, że kod jest inny -- jest niemal identyczny,
tylko przepisany lokalnie (procedury notifications.nim są prywatne, nie
eksportowane, więc nie dało się ich po prostu zaimportować).
Zachowuje się tak samo jak przy powiadomieniach: zapis po każdej zmianie
(nowy wpis w tickClipboard, wyczyszczenie przez "Wyczyść" w panelu) jest
best-effort (nigdy nie wywala shellu przy braku uprawnień/pełnym dysku),
odczyt raz przy starcie modułu z cichym powrotem do pustej historii przy
braku/uszkodzeniu pliku.
Zweryfikowane w izolowanym, realnie skompilowanym i uruchomionym programie Nim (pełny cykl: zapis kilku wpisów, odczyt z zachowaniem kolejności "najnowszy pierwszy", wyczyszczenie i ponowny odczyt dający pustą historię).
Kolejny punkt zdjęty z listy: alarmy zegara (apps/clock/clockapp.nim)
grały dotąd zawsze TEN SAM dźwięk -- pierwszy znaleziony kandydat
(findAlarmSound w shell/sound.nim), bez możliwości wyboru innego,
mimo że system często ma dostępnych kilka. Jawnie wymienione w README
jako brakujące ("wciąż bez wyboru dźwięku alarmu").
shell/sound.nim:availableAlarmSounds()zwraca tylko te dźwięki z ustalonej listy kandydatów, które FAKTYCZNIE istnieją na danym systemie (przefiltrowane przezfileExists) -- picker w UI nigdy nie pozwoli wybrać czegoś, co i tak by nie zagrało.soundLabel()mapuje ścieżkę na czytelną nazwę ("Budzik", "Fanfary", "Dzwonek", "Sygnał (WAV)") zamiast pokazywać surową ścieżkę pliku.playAlarmSound()przyjmuje teraz opcjonalny parametrsoundPath-- pusty string (wartość domyślna, i to, co mają WSZYSTKIE alarmy utworzone przed tą rozbudową, bo Nim zeruje nowe polaobjectdo ich wartości domyślnej) zachowuje dokładnie dawne zachowanie: "Auto", czyli pierwszy dostępny dźwięk.- Nowe pole
AlarmEntry.soundPathpamięta wybór DOKONANY PRZY TWORZENIU danego alarmu -- każdy alarm może grać innym dźwiękiem. W kreatorze nowego alarmu (drawAlarmsList) doszedł drugi wiersz: przycisk cyklujący po kolejnych dostępnych dźwiękach (klik = następny, z "Auto" jako pozycją wyjściową) -- prosty widget zamiast rozwijanej listy, bo Fidget nie ma natywnego combo boxa, a lista i tak jest krótka. - Świadomie POZA zakresem tej rundy: zmiana dźwięku ISTNIEJĄCEGO już alarmu (dziś edytowalne przy tworzeniu, potem tylko włącz/wyłącz/usuń -- ten sam ograniczony poziom edycji, jaki alarmy miały już wcześniej dla godziny/etykiety) oraz osobny wybór dźwięku dla minutnika (zawsze gra "Auto" -- minutnik jest zawsze dokładnie jeden naraz, w przeciwieństwie do wielu alarmów, więc potrzeba rozróżniania dźwięków wydaje się tu mniejsza).
- Zweryfikowane logicznie w izolowanych, realnie skompilowanych i
uruchomionych programach Nim (filtrowanie istniejących plików,
mapowanie na etykiety, arytmetyka cyklowania -1..N-1 bez wpadania w
ujemny wynik operatora
mod) -- z tych samych powodów co poprzednie rundy, w tej sandboxie nie da się skompilować całegozde-shellz Fidget/Pixie (wymagają Nim ≥2.0).
Nowa funkcja, nie tylko domknięcie istniejącego ograniczenia: panel
historii schowka (📋 w doku, obok dzwonka i quick settings) -- lista
ostatnio skopiowanych fragmentów tekstu z możliwością szybkiego powrotu
do dowolnego z nich, tak jak "clipboard manager"/"klipper" w
GNOME/KDE. Do tej pory shell/clipboard.nim dawał tylko jednorazowy
dostęp do BIEŻĄCEJ zawartości schowka -- coś skopiowanego wcześniej
znikało bezpowrotnie przy kolejnym kopiowaniu.
shell/clipboard.nim(tickClipboard, wołane raz na sekundę ztickMain()wshell.nim, tym samym rytmem co zegar/monitor systemu) odpytuje bieżącą zawartość schowka systemowego (tym samymwl-paste/xclip, którego już używałpasteFromClipboard) i dopisuje ją do historii (do 20 wpisów,MaxClipHistory), gdy się zmieniła. Duplikat przenosi istniejący wpis na czoło listy zamiast go powielać (tak samo zachowują się GNOME/KDE).- Nowy panel
drawClipboardHistorywshell/taskbar.nim-- ten sam wizualny język i mechanizm przewijania co centrum powiadomień. Klik na wiersz kopiuje jego treść z powrotem do schowka systemowego i zamyka panel; przycisk "Wyczyść" czyści całą historię. - Świadome kompromisy (ten sam poziom uczciwości co reszta dokumentu):
to ODPYTYWANIE co sekundę, nie subskrypcja zdarzeń
wl_data_device(opóźnienie do 1s, tak jak przy żywym odświeżaniu launchera -- patrz sekcja niżej), więc BEZ "primary selection" (środkowy klik w X11) i BEZ obrazów -- tylko czysty tekst. W TEJ rundzie historia żyła wyłącznie w pamięci procesuzde-shell(jak powiadomienia PRZED analogiczną rozbudową opisaną niżej) -- kolejna runda (patrz sekcja "Nowości tej rozbudowy -- trwała historia schowka" WYŻEJ, chronologicznie późniejsza niż ten akapit) domknęła i to, dokładnie tym samym wzorcem conotifications.nim. - Zweryfikowane logicznie w izolowanym, realnie skompilowanym i
uruchomionym programie Nim (dedup + przesunięcie na czoło, obcinanie
do limitu, ignorowanie natychmiastowych duplikatów) -- z tych samych
powodów co poprzednie rundy, w tej sandboxie nie da się skompilować
całego
zde-shellz Fidget/Pixie (wymagają Nim ≥2.0).
Kolejny punkt zdjęty z listy świadomie odłożonych kompromisów: suwaki
głośności/jasności (shell/taskbar.nim, drawSlider) obsługiwały do tej
pory tylko "kliknij, żeby ustawić" -- samo przytrzymanie i przeciągnięcie
myszą nie aktualizowało wartości w locie, trzeba było klikać wielokrotnie
wzdłuż paska. Powód był czysto techniczny: onMouseDown w DSL-u Fidget to
zdarzenie WYZWALANE RAZ, na przejściu "przycisk wciśnięty" -- nie blok
wołany co klatkę, dopóki przycisk jest trzymany.
Rozwiązanie to dokładnie ten sam wzorzec, którego ZDE już używa do
przenoszenia/zmiany rozmiaru OKIEN (comp/drag.nim + globalny hook
compositor.updateDrag w drawMain(), shell.nim) -- tylko odtworzony
lokalnie dla suwaków, bez dotykania maszyny stanów przeciągania okien
(inny byt, inna skala: pojedynczy globalny stan wystarczy, bo naraz może
być przeciągany co najwyżej jeden suwak):
SliderDragState(shell/taskbar.nim) pamięta, KTÓRY suwak jest przeciągany (przez przechowanie jegosetValuejako domknięcia) oraz geometrię jego paska (trackX/trackW) -- zapisywane wonMouseDownw momencie wciśnięcia, DODATKOWO do dotychczasowego klik-ustaw (które zostaje bez zmian, więc pojedyncze kliknięcie dalej działa identycznie jak wcześniej).updateSliderDrag(), wołane co klatkę zdrawMain()wshell.nim(obok analogicznegocompositor.updateDragdla okien) -- dopóki przycisk myszy jest trzymany i panel quick settings jest otwarty, przelicza wartość z bieżącej pozycji kursora WZGLĘDEM zapamiętanej geometrii paska. Kluczowe: liczy się względem zapamiętanej pozycji paska, NIE względem bieżącego trafienia w jego granice -- dokładnie tak samo jak przeciąganie okna za tytuł działa, nawet gdy kursor "wyjedzie" poza wąski 24px pasek przy szybkim ruchu myszy (naturalne przy przeciąganiu, patrz identyczne uzasadnienie przybeginMovewcomp/drag.nim).- Kończy się samo, gdy przycisk myszy zostanie puszczony ALBO panel quick settings zostanie w międzyczasie zamknięty (np. kliknięciem poza panelem) -- drugi warunek zapobiega sytuacji, w której przeciąganie "po cichu" dalej zmieniałoby głośność/jasność mimo zamkniętego panelu.
- Zweryfikowane logicznie w izolowanym programie Nim (symulacja pozycji
kursora wewnątrz i POZA granicami paska, klamrowanie do 0-100, oraz
zatrzymanie po puszczeniu przycisku) -- w tej sandboxie nie dało się
skompilować całego
zde-shell(patrz notatka metodologiczna na końcu tej sekcji dokumentu), więc integrację z realnym Fidget/GLFW trzeba będzie zweryfikować wizualnie w przyszłej rundzie, tak jak zrobiono to wcześniej dla resztyshell/(patrz sekcja "pierwsze prawdziwe testy wizualne" niżej).
Ta runda domyka dwa punkty, które od dawna wisiały na liście "Ograniczenia"
niżej jako świadomie odłożone kompromisy -- oba bez potrzeby dokładania
nowej infrastruktury (wątków w tle, demonów, zależności) do zde-shell.
Do tej pory historia powiadomień (dzwonek w doku, do 30 wpisów) żyła
WYŁĄCZNIE w pamięci procesu zde-shell -- restart shellu (albo całej
sesji) czyścił ją bezpowrotnie. Teraz każde wywołanie notify() (i
clearHistory()) zapisuje bieżącą historię jako zwykły plik JSON:
- Ścieżka zgodna z XDG Base Directory Specification:
$XDG_STATE_HOME/zde/notifications.json, a w braku tej zmiennej --~/.local/state/zde/notifications.json. To świadomie katalog stanu (state), nie configu (config) ani cache -- historia powiadomień nie jest ustawieniem użytkownika ani czymś bezpiecznym do usunięcia bez utraty informacji, więc żaden z tamtych katalogów nie pasował semantycznie. - Wczytywana RAZ, przy starcie modułu (czyli starcie
zde-shell) --loadHistoryFromDisk(). Brak pliku (pierwsze uruchomienie na danej maszynie) albo uszkodzona zawartość dają po prostu pustą historię, nigdy nie wywalają startu shellu. - Zapis jest best-effort, tak jak reszta integracji ZDE z otoczeniem
systemowym (
quicksettings.nim,sound.nim,desktopapps.nim) -- brak uprawnień do zapisu czy pełny dysk nie mają prawa wywrócićzde-shell, najwyżej historia zostaje nietrwała w danej sesji (dokładnie zachowanie sprzed tej rozbudowy). - Świadomie POZA zakresem: to nie jest integracja z zewnętrznym,
DBusowym
org.freedesktop.Notifications(patrz duży komentarz na górze pliku) -- to wciąż wyłącznie wewnętrzny mechanizm ZDE, tylko już przeżywający restart.
Żywe odświeżanie listy aplikacji systemowych w launcherze (shell/desktopapps.nim, shell/taskbar.nim)
Launcher skanował .desktop pliki RAZ, przy starcie zde-shell -- nowo
zainstalowany program pojawiał się dopiero po restarcie shellu. To był
jawnie wymieniony punkt na liście ograniczeń ("pełne śledzenie katalogów
przez inotify to możliwa przyszła rozbudowa").
Zamiast pełnego inotify (wymagałoby to trzymania osobnego deskryptora i
integracji z pętlą zdarzeń Fidget/GLFW, której dziś zde-shell nigdzie
nie robi -- cała reszta stanu w tym projekcie, patrz state.nim, jest
ODPYTYWANA raz na sekundę, nie subskrybowana) -- rozwiązanie trzyma się
istniejącej architektury:
desktopapps.appDirsSignature()sumuje mtime katalogów zAppDirs, które faktycznie istnieją -- instalacja/usunięcie pliku.desktopzawsze dotyka mtime katalogu, który go zawiera, więc zmiana tej sumy jest niezawodnym (choć niegranularnym co do pojedynczego pliku) sygnałem "coś się tu ruszyło".taskbar.rescanSystemAppsIfChanged(), wołane raz na sekundę ztickMain()wshell.nim(ten sam rytm co zegar/monitor systemu/ wykrywanie zmian pliku w edytorze) -- porównuje bieżącą sygnaturę z zapamiętaną i przebudowujeSystemAppGroups(terazvar, nielet) TYLKO gdy się różnią. W normalnej pracy (nic się nie zmieniło) to więc tylko garść tanichstat(), nie pełne, kosztowne parsowanie wszystkich.desktopplików w systemie co sekundę.- Efekt:
apt install/apt remove(albo dowolny inny menedżer pakietów) w trakcie działania sesji ZDE odzwierciedla się w launcherze z opóźnieniem rzędu jednej sekundy, bez restartuzde-shell. - Wciąż aktualne ograniczenia z poprzednich rund: tylko ikony rastrowe (PNG), bez pełnej specyfikacji Icon Theme (dziedziczenie motywów).
Nowa ikona 🔊 w doku otwiera panel z suwakami głośności i jasności ekranu.
Jak shell/clipboard.nim/shell/desktopapps.nim -- cienka, best-effort
warstwa nad zewnętrznymi narzędziami, NIE własny demon audio:
- Głośność:
wpctl(PipeWire) →pactl(Pulse) →amixer(czyste ALSA), w tej kolejności, pierwszy znaleziony wygrywa. - Jasność:
/sys/class/backlight/*do odczytu,brightnessctldo zapisu (dba o uprawnienia -- bezpośredni zapis do sysfs jako zwykły użytkownik prawie zawsze się nie uda i jest tylko awaryjnym fallbackiem). - Sekcja, dla której nie znaleziono backendu, po prostu się nie
pokazuje -- zweryfikowane zrzutem ekranu: w piaskownicy bez karty
dźwiękowej ani backlightu panel poprawnie pokazuje "Brak sterowania
audio/jasnością na tym systemie" zamiast pustych/martwych suwaków.
Po doinstalowaniu
alsa-utils(amixer) -- bez prawdziwej karty dźwiękowej, więcamixeri tak zwraca błąd -- panel pokazuje realny suwak z wartością "0%" (bezpieczny fallback), reaguje na klik bez crasha. - Własny suwak (
drawSlider) -- Fidget nie ma widgetu suwaka: klik/ kółko myszy nad paskiem, ten sam wzorzecmouse.wheelDeltaco scroll list gdzie indziej w tym pliku. Od kolejnej rozbudowy (patrz sekcja "Nowości tej rozbudowy -- ciągłe przeciąganie suwaków" niżej) obsługuje też PRAWDZIWE przeciąganie, nie tylko klik-ustaw.
Alarm zegara i koniec minutnika (apps/clock/clockapp.nim) odtwarzają
teraz dźwięk, nie tylko toast wizualny -- best-effort przez pierwszy
znaleziony z paplay/pw-play/ffplay/aplay i pierwszy znaleziony
plik dźwiękowy z kilku standardowych lokalizacji
(sound-theme-freedesktop, alsa-utils). ZDE świadomie nie niesie
własnych plików dźwiękowych -- gdy nic nie znaleziono, po cichu nic się
nie odtwarza (alarm i tak budzi wizualnie).
apps/texteditor/texteditor.nim porównuje teraz co sekundę
(checkExternalChanges, rejestr texteditors w shell/state.nim --
ten sam wzorzec co clocks/terminals) czas modyfikacji otwartego
pliku z tym zapamiętanym przy ostatnim wczytaniu/zapisie. Gdy coś INNEGO
zmieniło plik pod nami (inny program, git pull, edycja w terminalu),
pokazuje się baner "Plik zmienił się na dysku" z przyciskami
"Przeładuj"/"Zignoruj".
Cztery pulpity wirtualne, w pełni zaimplementowane w comp/ (pole
workspace w ZdeWindow, currentWorkspace w Compositor,
comp/window.nim: switchWorkspace/moveWindowToWorkspace,
WorkspaceCount = 4):
- Przełącznik w doku (
shell/taskbar.nim) -- cztery kwadraciki "1 2 3 4", aktywny podświetlony akcentem, pozostałe dostają małą kropkę, gdy mają na sobie choć jedno okno (widać "gdzie coś zostało otwarte" bez przełączania). - Skróty klawiszowe:
Ctrl+Alt+Right/Leftprzełącza pulpit (zawija się, 4→1 i 1→4, jak karuzela Alt+Tab okien wwlcomp/),Ctrl+Alt+Shift+Right/Leftprzenosi AKTYWNE okno na sąsiedni pulpit i od razu na niego przełącza. - Pełna izolacja pulpitów: okna z innych pulpitów nie są rysowane
(
windowsInZOrder), nie dostają fokusu (Alt+Tab/cycleFocusfiltruje po pulpicie), nie pokazują się w doku (lista zadań filtruje po pulpicie) -- to nie jest kosmetyczne ukrywanie, okna z innych pulpitów są dla reszty shellu tak samo "niewidoczne" jak zminimalizowane. - Nowe okna otwierają się zawsze na AKTUALNYM pulpicie.
Zweryfikowane zrzutami ekranu: otwarcie Zegara na pulpicie 1, przełączenie na pulpit 2 -- okno i jego przycisk w doku znikają całkowicie, pulpit "1" dostaje kropkę wskaźnika.
Na wyraźną prośbę: launcher dostał pasek "chipów" kategorii pod wyszukiwarką ("Wszystkie" + jedna na każdą kategorię, która faktycznie ma choć jedną aplikację -- puste filtry się nie pokazują). Kliknięcie kategorii (np. "Biuro") zawęża listę TYLKO do niej -- z realnymi ikonami, bez przewijania przez wszystko. Pasek chipów zawija się do kolejnego wiersza samodzielnie, gdy nie mieści się w jednej linii (prosty, własny algorytm zawijania -- Fidget nie ma API do mierzenia szerokości tekstu przed narysowaniem, więc szerokość chipa jest przybliżona z długości etykiety). Kolejność kategorii w pasku jest stała i "sensowna" (Internet, Biuro, Grafika... zamiast alfabetycznej). Zweryfikowane zrzutami ekranu: zawijanie do drugiego wiersza i filtrowanie do samego LibreOffice po kliknięciu "Biuro" -- działa dokładnie tak jak powinno.
Snapowanie okien (Super+Left/Right, od rundy 21 też Super+Up/Down i ćwiartki, od rundy 22 też przeciągnięciem myszą)
comp/window.nim (snapWindow, combinedEdge, typ SnapEdge) + skróty
w shell/shortcuts.nim. Przyciąga aktywne okno do połowy ekranu; drugie
Super+Left/Right na TEJ SAMEJ krawędzi przywraca oryginalny rozmiar
(zapamiętany w savedPos/savedSize, ten sam mechanizm co zwykła
maksymalizacja). Ta część (tylko lewo/prawo) była zweryfikowana
zrzutami ekranu pod Xvfb w oryginalnej rundzie "Aurora", łącznie z
toggle "z powrotem" -- w TEJ sandboxie taka weryfikacja nie jest już
możliwa (fidget wymaga Nim >= 2.0, patrz uczciwe notatki w rundach
13+), więc rozszerzenie o górę/dół i ćwiartki (runda 21, patrz "Nowości
w v0.2 (runda 21)" wyżej) zostało zweryfikowane inaczej: comp/
(rdzeń logiki, bez fidget) dało się w PEŁNI skompilować i uruchomić w
tej sesji, więc test_real_comp_snap.nim testuje prawdziwy,
produkcyjny kod snapowania bezpośrednio -- mocniejsza weryfikacja niż
zrzuty ekranu pojedynczych przypadków, ale wciąż nie to samo, co
zobaczenie tego na żywo w oknie.
Ważna notatka o metodologii testowania, przydatna dla przyszłych
sesji: xdotool key ctrl+alt+t (skrót) najpierw wyglądał, jakby NIE
działał -- w rzeczywistości xdotool key wysyła całą kombinację za
szybko dla jednoklatkowego śledzenia modyfikatorów w Fidget. xdotool keydown/keyup z realnym opóźnieniem (~0.3s) między nimi działa
niezawodnie. To nie był bug w kodzie.
Centrum powiadomień, lista alarmów zegara i teraz też launcher -- każda
z tych list ma teraz GÓRNY LIMIT wysokości i przewija się zamiast rosnąć
w nieskończoność. Wzorzec (clipContent true + onHover/
mouse.wheelDelta, sprawdzony wcześniej w apps/filemanager/files.nim)
teraz powtórzony w trzech miejscach.
To była główna prośba tej rundy: launcher miał pokazywać nie tylko 7 wbudowanych aplikacji ZDE, ale też WSZYSTKO, co faktycznie jest zainstalowane w systemie -- jak w KDE/GNOME, tylko lepiej zorganizowane. Zaimplementowane:
- Skanowanie
.desktopplików z~/.local/share/applications,/usr/local/share/applications,/usr/share/applications(standard XDG, ten sam mechanizm co w każdym "poważnym" DE). - Parsowanie
Name/Exec/Icon/Categories/Terminal/NoDisplay/Hidden/Type-- z pominięciemName[locale]=(jedno poleName),[Desktop Action ...](podmenu akcji) i wpisów innych niżType=Application. - Grupowanie po kategorii XDG, zmapowanej na 9 własnych, przetłumaczonych grup (Internet, Biuro, Grafika, Multimedia, Programowanie, Gry, Edukacja, Narzędzia, System, Inne) -- każda z nagłówkiem w launcherze.
- Uruchamianie przez
osproc.startProcess(odłączony proces,%f/%F/%u/%U/%i/%c/%kusuwane z linii poleceń -- launcher nie przekazuje żadnego konkretnego pliku/URL). - Prawdziwe ikony PNG -- przeszukiwanie kilku najpopularniejszych
lokalizacji motywów ikon (hicolor, Humanity, Adwaita, breeze,
/usr/share/pixmaps), z ikoną zastępczą wg kategorii, gdy nic nie znaleziono.
Realny crash znaleziony i naprawiony w trakcie testów pod Xvfb:
Fidget renderuje image(...) przez Pixie, konwertując PNG do własnego
formatu cache (.flippy) PRZY KAŻDYM PIERWSZYM UŻYCIU -- a Pixie 5.0.1
nie obsługuje np. PNG 16-bit/kanał (realny format, spotkany od razu na
pierwszej ikonie ImageMagicka w tym systemie) i rzuca wyjątkiem, którego
NIC w całej bibliotece Fidget nie łapie (cała pętla renderowania nie ma
ani jednego try/except) -- cały proces zde-shell umierał, zanim
zdążył pokazać cokolwiek. Naprawione przez wstępną walidację: desktopapps.nim
(isDecodablePng) próbuje realnie zdekodować każdego kandydata na ikonę
W CZASIE SKANOWANIA (gdzie kontrolujemy obsługę wyjątków), i odrzuca (na
rzecz ikony zastępczej kategorii) każdy plik, którego się nie da --
zanim, nie w trakcie, rysowania klatki. Bez tego etapu testowania
wizualnego ten bug trafiłby prosto do repo.
Ograniczenie, świadomie poza zakresem: tylko ikony RASTROWE (PNG) --
ikony dostępne wyłącznie jako SVG (częste w nowszych motywach) dostają
ikonę zastępczą kategorii, bo Fidget/Pixie w tej wersji nie rasteryzuje
SVG przez zwykłe image(...). Lista aplikacji jest skanowana przy
starcie zde-shell i od tej rozbudowy odświeżana też w tle -- patrz
sekcja "Nowości tej rozbudowy -- żywe odświeżanie launchera..." niżej --
ale wciąż z opóźnieniem rzędu sekundy, nie natychmiast klatka-po-klatce
(pełny inotify zamiast odpytywania mtime to możliwa przyszła
rozbudowa, patrz uzasadnienie tam).
Do tej pory shell/ był rozwijany wyłącznie przez czytanie kodu i
ręczne śledzenie konwencji Fidget -- nigdy realnie uruchomiony ani
zobaczony. W tej rundzie udało się to zmienić: zainstalowano nim +
fidget/pixie/resztę zależności nimble w piaskownicy (z ręcznym
obejściem tego, że pakiet html5_canvas -- zależność fidget --
jest hostowany na zablokowanym gitlab.com: sklonowano oficjalny mirror
z GitHuba i doinstalowano ręcznie do ~/.nimble/pkgs/), skompilowano
zde-shell, uruchomiono je pod zagnieżdżonym Xvfb i zrobiono realne
zrzuty ekranu (xwd) oraz symulowano kliknięcia (xdotool) -- pierwszy
raz w historii tego projektu ktoś (coś) faktycznie ZOBACZYŁ działające
ZDE, zamiast tylko czytać kod.
To znalazło pięć realnych, poważnych błędów, niewidocznych przy samym czytaniu kodu -- wszystkie naprawione i ponownie zweryfikowane wizualnie (zrzut przed/po dla każdego):
- Dok (pasek zadań) był całkowicie niewidoczny -- renderował się
ok. 750px pod dolną krawędzią ekranu. Przyczyna:
box x, y, ...w Fidget zawsze liczy się względem WŁASNEGO RODZICA (node.screenBox = node.box + parent.screenBox, patrzfidget/common.nim), nie względem ekranu.dockjest dzieckiem ramkitaskbar(którejscreenBox.yto jużwindowSize.y - TaskbarHeight) -- poprzedni kod liczył pozycjędocktak, jakby był dzieckiem ekranu wprost, więc przesunięcie rodzica doliczało się PONOWNIE. Naprawione wshell/taskbar.nim. - Wskazówki i kreski zegara analogowego były całkowicie niewidoczne
-- dwa nakładające się błędy w
apps/clock/clockapp.nim:- błędne założenie (nigdy wcześniej niezweryfikowane), że
rotationw Fidget obraca węzeł wokół zadanego punktu -- naprawdę zawsze obraca wokół ŚRODKA WŁASNEGOboxwęzła.drawRadialBarprzepisany tak, żeby liczyć końcówkę wskazówki trygonometrią i centrować box dokładnie na środku odcinka, zamiast liczyć na nieistniejące zachowanie "obrót wokół lewego-górnego rogu"; - tło tarczy (
dial) było zadeklarowane JAKO PIERWSZE wśród rodzeństwa-węzłów, więc (zgodnie z odwróconą kolejnością rysowania Fidget -- patrzshell/shell.nim) renderowało się NA WIERZCHU kresek/wskazówek, całkowicie je zasłaniając. Przestawione na koniec.
- błędne założenie (nigdy wcześniej niezweryfikowane), że
- Data pokazywała angielską nazwę miesiąca ("09 September" zamiast
"09 września") --
$now.monthzestd/timesnie ma lokalizacji (ten sam problem, który już raz naprawiono dla dnia tygodnia, tu przeoczony). Dodana polska tablicamiesiace. - Pole godziny/minuty w kreatorze alarmu pokazywało "0" i "7" jedna nad drugą zamiast "07" obok siebie -- pole tekstowe było za wąskie (14px) na dwuznakowy tekst monospace przy rozmiarze fontu 15 (~20px potrzebne), więc silnik zawijał tekst na dwie linie. Przyciski -/+ zwężone, pole wartości poszerzone, font zmniejszony.
shell/taskbar.nim(AppCatalog) w ogóle się nie kompilował -- znalezione i naprawione JESZCZE PRZED uruchomieniem, na etapienim c: przypisanie procedur (launchTerminalitd.) do polaaction: proc()nie przechodziło type-checkingu, bo Nim inferuje dla prostych lambd typ BEZ{.closure.}, a dla procedur odwołujących się do zmiennych top-level -- Z{.closure.}, i te dwa "prawie takie same" typy proc nie są dla Nima identyczne. Naprawione jawną pragmą{.closure.}na każdej lambdzie wAppCatalog/SystemCatalog.
Dla przyszłych sesji: jeśli środowisko ma zainstalowany nim +
nimble, zde-shell da się teraz skompilować i uruchomić lokalnie (nie
tylko zde-comp) -- polecenie: nim c -d:release -d:pixieNoSimd -o:dist/zde-shell shell/shell.nim. To rewelacyjnie tania metoda
znajdywania tej klasy błędów (pozycjonowanie, kolejność rysowania,
literały tekstowe) w porównaniu z samym czytaniem kodu DSL-a.
Ta runda skupiła się na wlcomp/ i -- pierwszy raz -- każda zmiana była
od razu kompilowana i uruchamiana w piaskownicy (patrz sekcja o XWayland
niżej po opis, jak dokładnie), więc poniższe punkty mają wyższy stopień
pewności niż wcześniejsze rundy.
- Idle/DPMS (
wlcomp/idle.nim, nowy plik) -- kompozytor sam wygasza wszystkie wyjścia po 5 minutach bez ruchu myszy/klawiatury (DpmsTimeoutSec) i budzi je przy pierwszej aktywności. Dodatkowo wystawia protokółext-idle-notify-v1(wlr_idle_notifier_v1) dla przyszłych klientów (np. demona blokady) -- to i DPMS to dwie NIEZALEŻNE rzeczy, patrz komentarz na górze pliku. - Pełna karuzela Alt+Tab (
wlcomp/toplevel.nim,cycleAltTab/commitAltTab) -- zastępuje poprzedni prosty toggle. Powtarzane Tab przy trzymanym Alt przewija po WSZYSTKICH oknach (Shift = kierunek odwrotny), z podświetleniem kandydata cienkim, półprzezroczystym prostokątem (wlr_scene_rectwoverlayTree, najwyższej warstwie sceny). Wybór zatwierdza się dopiero puszczeniem Alt (wykrywane wonKeyboardModifiers,wlcomp/input.nim). - Gesty touchpada (
wlcomp/gestures.nim, nowy plik) -- 3-palcowy swipe w lewo/prawo przełącza aktywne okno (ten sam mechanizm cocycleFocusBy, używany też przez gesty). Tylko swipe -- pinch/hold świadomie poza zakresem (brak dziś w ZDE naturalnego zastosowania, np. przeglądarki obrazów do zoomowania). Gesty są też przekazywane dalej do klientów przezwlr_pointer_gestures_v1, niezależnie od własnej reakcji kompozytora. - Polityka granic okien X11 (
wlcomp/xwayland.nim,onXwaylandRequestConfigure) -- żądana przez klienta X11 geometria jest teraz klamrowana do granic całego układu monitorów (wlr_output_layout_get_box), więc okno nie może poprosić o umieszczenie się całkowicie poza widocznym ekranem. - Hit-testing subsurface'ów -- sprawdzone ponownie, nie był to realny
problem.
wlr_scene_subsurface_tree_create/wlr_scene_xdg_surface_createjuż budują subsurface'y jako zwykłe węzły-dzieci w tym samym drzewie sceny, atoplevelAtpoprawnie wspina się do właściciela -- poprzedni wpis na liście ograniczeń był nadmiarową ostrożnością.
W tej rundzie udało się zainstalować nim + libwlroots-dev (0.17.1,
Ubuntu 24.04) w piaskownicy i realnie skompilować oraz uruchomić
zde-comp z całym kodem wlcomp/ -- łącznie z pełnym testem end-to-end:
postawiono zagnieżdżony kompozytor pod Xvfb, uruchomiono pod nim
prawdziwe okno X11 (xclock) przez Xwayland i zweryfikowano w logu
poprawną sekwencję new_surface -> associate z odczytanym tytułem okna.
Każda nazwa pola/sygnału w wlr/xwayland/xwayland.h i
wlr/backend/session.h, odtworzona wcześniej "z pamięci", okazała się
zgodna z prawdziwymi nagłówkami. Realne błędy, jakie kompilator faktycznie
złapał przy tej i poprzednich rundach (dla przyszłej orientacji, jakiego
typu usterek szukać przy pracy bez kompilatora):
- brakujący
import std/sequtilsw nowym pliku (wlcomp/xwayland.nim), - pusta linia wewnątrz definicji
objectw Nim 1.6 potrafi rozwalić parser (patrzServerObjwtypes.nim), addrna elemencie tablicyconst(a czasem nawet modułowegolet) nie zawsze ma czego zaadresować przy-d:release-- Nim/gcc potrafią ją w pełni "spłaszczyć" do literałów; trzebavar(patrzAltTabColorwwlcomp/toplevel.nim).
Nie przetestowano jeszcze wobec wlroots 0.18/0.20 (tylko 0.17.1) ani na prawdziwym DRM/TTY (tylko zagnieżdżone pod Xvfb, bez GPU) -- ale sama poprawność API jest teraz potwierdzona kompilacją, nie tylko pamięcią.
- Alt+Tab (
wlcomp/toplevel.nim,cycleToPreviousToplevel) -- pierwszy skrót kompozytora do przełączania OKIEN (wcześniej istniał już tylko Ctrl+Alt+F do przełączania VT, patrz niżej). To celowo prosty przełącznik "wróć do poprzedniego okna" (toplevels[^2]), NIE pełna karuzela z podglądem na przytrzymanym Alt -- ta druga wymagałaby rysowania nakładki UI wprost w scenie kompozytora, czegowlcomp/dziś nigdzie nie robi (cały UI shellu rysuje osobny proceszde-shell). Przechwytywane na poziomie kompozytora wwlcomp/input.nim, więc Tab z wciśniętym Alt nigdy nie dolatuje do aplikacji pod fokusem. - Centrum powiadomień (
shell/notifications.nim+drawNotificationCenterwshell/taskbar.nim) -- dzwonek w doku otwiera panel z historią ostatnich powiadomień (do 30,MaxHistory) i przełącznikiem "Nie przeszkadzać" (setDnd/isDndEnabled). Toasty i historia to dwa osobne byty: DND wycisza WYSKAKUJĄCE toasty, ale historia dalej się zapisuje (poza alarmami zegara -- te świadomie OMIJAJĄ DND, tak jak w telefonach). Kropka na dzwonku sygnalizuje niepustą historię. Wciąż bez przewijania listy (patrz ograniczenia niżej) i bez trwałości między restartamizde-shell-- ten sam kompromis co reszta systemu powiadomień, opisany w komentarzu na górzenotifications.nim.
- System powiadomień ZDE (
shell/notifications.nim) -- wspólne APInotify(title, body, kind)wołane z dowolnej aplikacji ZDE, toasty w prawym górnym rogu ekranu, widoczne nawet na zablokowanym ekranie (żeby alarm faktycznie obudził). Prosty, tylko-w-pamięci-procesu odpowiednik centrum powiadomień -- bez dziennika historii, bez dźwięku i bez integracji z zewnętrznym DBusowymorg.freedesktop.Notifications(patrz komentarz na górze pliku, dlaczego to świadomie poza zakresem). - Alarmy i minutnik w
apps/clock-- druga zakładka zegara ("Alarmy") z listą alarmów o stałej porze dnia (włącz/wyłącz, usuń, dodaj przez steppery godzina/minuta) oraz prostym minutnikiem (presety 1/5/10/15 min, start/pauza/reset). Oba korzystają z powyższego systemu powiadomień -- odliczanie działa w tle przez rejestrclockswshell/state.nim(ten sam wzorzec coterminals/sysmonitors), niezależnie od tego, czy okno zegara jest akurat aktywne.
Cały wygląd powłoki dostał wspólny, nowocześniejszy motyw (shell/state.nim,
sekcja "Aurora" -- paleta, promienie zaokrągleń, poziomy tekstu w jednym
miejscu, żeby zmiana koloru w jednym pliku faktycznie zmieniała cały shell):
- Tapeta (
shell/wallpaper.nim) -- domyślnie pionowy gradient (symulowany pasami, Fidget nie ma tu prawdziwegolinear-gradient) + dwie miękkie plamy koloru akcentu ("glow") zamiast płaskiego jednolitego koloru; od kolejnej rozbudowy można też ustawić PRAWDZIWY obraz z dysku (Ustawienia -> Wygląd), patrz sekcja "Nowości tej rozbudowy -- tapeta z pliku obrazu" niżej. - Ramka okna (
shell/chrome.nim) -- zaokrąglone rogi, kropka wskazująca aktywne okno zamiast samej zmiany koloru obramowania, cienka jaśniejsza linia na górze paska tytułu ("glass highlight"). - Dok (
shell/taskbar.nim) -- pasek zadań przestał być pełną belką od krawędzi do krawędzi; teraz to pływający, półprzezroczysty, zaokrąglony dok wcięty marginesem od krawędzi ekranu, z ikonami per aplikacja i kropką przy aktywnym oknie.TaskbarHeight(comp/types.nim) podniesione z 40 na 56px, żeby dok miał gdzie "oddychać" -- wszystko, co liczy dostępną wysokość pulpitu z tej stałej (maksymalizacja, ograniczenie pozycji okna), dostało tę przestrzeń automatycznie. - Launcher (
shell/taskbar.nim,drawLauncher) -- przeprojektowany na kartę z polem wyszukiwania filtrującym listę aplikacji na żywo (ten sam wzorzec pola tekstowego co ścieżka w edytorze tekstu), ikony w zaokrąglonych plakietkach, sekcje "Aplikacje"/"System" oddzielone cienką linią.
-
XWayland (
wlcomp/xwayland.nim, nowy plik) -- do tej poryzde-compURUCHAMIAŁ proces Xwayland, ale nigdy nie nasłuchiwał na jegoevents.new_surface, więc żadne okno aplikacji X11 nigdy nie trafiało do sceny ani na listę okien -- proces się uruchamiał, ale efektywnie nic nie robił. Teraz okna X11 dostają pełny cykl życia (new_surface → associate → map/unmap → dissociate/destroy) i są wpięte w TEN SAM, uogólniony typToplevel, którego dotąd używał tylko xdg-shell (patrzsurfaceOf/geometryOfwwlcomp/toplevel.nim) -- fokus, przeciąganie, zmiana rozmiaru działają dla okien X11 tą samą ścieżką kodu co dla zwykłych okien Wayland, bez duplikowania logiki. -
Sesja / przełączanie VT (
wlcomp/session.nim, nowy plik) --zde_backend_autocreate(shim.c) do tej pory zawsze przekazywałNULLzamiast wskaźnika wyjściowego nawlr_session, więc kompozytor nie miał jak się dowiedzieć o przełączeniu wirtualnego terminala (Ctrl+Alt+F2 itd.) -- po powrocie na VT kompozytora ekran zostawał zamrożony na ostatniej klatce na zawsze (onOutputFramesamo się nie wznawia). Teraz: (1)Ctrl+Alt+F1..F12przełącza VT (przechwytywane na poziomie kompozytora, po surowym kodzie evdev klawisza -- działa niezależnie od układu klawiatury), (2) po powrocie aktywnej sesji wszystkie wyjścia dostają jawnewlr_output_schedule_frame, więc renderowanie faktycznie się wznawia.Aktualizacja -- zweryfikowane kompilacją: w kolejnej rundzie rozbudowy udało się zainstalować
nim+libwlroots-dev(0.17.1, Ubuntu 24.04) w piaskownicy i realnie skompilować oraz uruchomićzde-compz tym kodem, łącznie z pełnym testem end-to-end pod zagnieżdżonym Xvfb: prawdziwe okno X11 (xclock) uruchomione przez Xwayland poprawnie przeszło sekwencjęnew_surface -> associate(widoczne w logu z odczytanym tytułem okna). Jedyna znaleziona usterka: brakującyimport std/sequtilswwlcomp/xwayland.nim(błąd Nim, nie API wlroots) -- każda nazwa pola/sygnału odtworzona wcześniej "z pamięci" okazała się zgodna z prawdziwymi nagłówkami. Nie przetestowano jeszcze wobec wlroots 0.18/0.20 (tylko 0.17.1) ani pełnego renderowania klatek przez GPU (headless Xvfb bez sprzętowego OpenGL) -- patrz zaktualizowany komentarz nadWlrXwaylandwwlcomp/wlroots.nim.
Ten opis był w poprzednich wydaniach README rozjechany z rzeczywistym stanem kodu (m.in. layer-shell, popupy, schowek/DnD, prawdziwy PTY, podświetlanie składni, panel monitorów i aplikacja "Ustawienia" już istniały, mimo że sekcja niżej twierdziła inaczej) -- poniższa lista jest zweryfikowana względem bieżącej zawartości repo:
Notatka o kompilowalności w TEJ konkretnej sandboxie (istotna dla
zrozumienia, jak mocno zweryfikowany jest który fragment kodu, patrz
poszczególne punkty niżej): fidget wymaga Nim >= 2.0, którego ta
sandbox nie ma (tylko 1.6.14 z apt) -- więc CAŁE UI zde-shell
(launcher, dok, wszystkie okna aplikacji) pozostaje nieskompilowane w
tej sesji. TRZY grupy modułów są od tego WYJĄTKIEM, bo mają węższe
zależności: shell/desktopapps.nim i apps/filemanager/thumbnails.nim
(tylko pixie, dający się zainstalować osobno -- rundy 14/16),
apps/texteditor/highlight.nim (tylko std, żadnych zależności
zewnętrznych -- runda 20), i comp/ (rdzeń zarządzania oknami -- tylko
vmath, zależność pixie -- runda 21, patrz "Nowości w v0.2 (runda
21)"). Rozbudowy w tych czterech miejscach mają dużo mocniejszą
weryfikację (prawdziwie skompilowany i uruchomiony PRODUKCYJNY kod) niż
rozbudowy gdziekolwiek indziej w zde-shell (logika wydzielana i
kopiowana do izolowanych testów, UI nigdy realnie nie uruchomione w tej
sesji) -- każda sekcja "Nowości" mówi wprost, do której grupy należy.
- wlr-layer-shell jest zaimplementowany (
wlcomp/layershell.nim) --zde-shellmoże się zadokować jako pasek, nie tylko jako zwykłe okno xdg-toplevel. - Popupy xdg-shell (
wlcomp/popup.nim) i schowek + drag & drop (wlr_data_device_manager,request_set_selection/request_start_dragwwlcomp/seatext.nim) działają na poziomie kompozytora. - Terminal (
apps/terminal) ma prawdziwe PTY (pty_shim.c), programy pełnoekranowe (vim,top,less) działają poprawnie. Pole wpisywania polecenia od v0.2 (runda 12) poprawnie pokazuje wpisywany tekst na ekranie -- wcześniej (runda 11) było to znane, potwierdzone, nierozwiązane ograniczenie (tekst tuż przy dolnej krawędzi okna był niewidoczny w tej wersji Fidget/Pixie); patrz "Nowości w v0.2 (runda 12)" niżej po pełny opis metodycznego dochodzenia do przyczyny i naprawy, w tym pouczającą historię obalonej hipotezy z rundy 11. - Edytor tekstu (
apps/texteditor) ma podświetlanie składni (highlight.nim, od rundy 20 dla ośmiu języków: Nim/Python/C/JS/ Shell/Go/Rust/JSON -- patrz "Nowości w v0.2 (runda 20)" wyżej), zakładki, wykrywanie zmian pliku na dysku w tle (checkExternalChanges) i wyszukiwanie ORAZ zamianę (Ctrl+F, "Zamień"/"Zamień wszystko" -- patrz "Nowości" wyżej) -- od v0.2 z OPCJONALNYM rozróżnianiem wielkości liter (przycisk "Aa", domyślnie wyłączone) ORAZ od rundy 14 z OPCJONALNYMI wyrażeniami regularnymi (przycisk ".*",std/re/PCRE, domyślnie wyłączone -- od rundy 15 też z podświetlaniem grup przechwytujących(...), pozycje odtwarzane heurystycznie, patrz "Nowości w v0.2 (runda 15)" wyżej) i przyciskiem 📂 do wyboru pliku bez ręcznego wpisywania ścieżki (runda 8) -- wciąż ze skokiem do LINII zamiast dokładnej pozycji kursora (ograniczenie API Fidget). Od rundy 34: PEŁNE cofanie/ponawianie zwykłego pisania (Ctrl+Z / Ctrl+Shift+Z / Ctrl+Y) -- migawki całego dokumentu z debounce, patrz "Nowości w v0.2 (runda 34)" niżej po pełny opis mechanizmu (w tymkeyboard.focusNode = nil, ta sama technika co przy buforze terminala z rundy 12) i uczciwą notatkę o braku weryfikacji na żywym Fidget w tej sesji. Wcześniejsze, węższe cofanie TYLKO dla "Zamień"/"Zamień wszystko" (przycisk "↶") zostaje bez zmian, jako osobny mechanizm. - Układ klawiatury jest konfigurowalny (
zdeconfig.nim, aplikacja "Ustawienia"), nie zaszyty na sztywno naus. - Aplikacja "Ustawienia" (
apps/settings) istnieje, z wizualnym edytorem układu monitorów (przeciąganie, snapowanie krawędzi) i konfiguracją skrótów klawiszowych. - Zegar ma teraz alarmy i minutnik (patrz sekcja "Nowości" wyżej) -- lista alarmów też się teraz przewija (patrz "Nowości" -- scroll list), a każdy alarm może mieć wybrany dźwięk spośród realnie dostępnych w systemie (patrz sekcja "Nowości tej rozbudowy -- wybór dźwięku alarmu" niżej) -- od v0.2 minutnik dostał TEN SAM picker dźwięku, nie tylko "Auto" (patrz "Nowości w v0.2" wyżej).
Wciąż aktualne, rzeczywiste ograniczenia:
Notatka do rundy 14: ta runda powstała w odpowiedzi na prośbę o
rozbudowę ZDE o WSZYSTKIE punkty z tej listy naraz. Udało się w niej
dodatkowo domknąć częściowo ScaledDirectories=/Scale= w ikonach
launchera (patrz "Nowości w v0.2 (runda 14)" wyżej) -- ale poniższe
pozycje, mimo tej prośby, POZOSTAJĄ bez zmian -- każda z przyczyn
architektonicznych opisanych przy niej, nie dlatego, że zostały
pominięte przez przeoczenie: wlcomp/ (kompozytor) wymaga prawdziwych
nagłówków wlroots i najlepiej prawdziwego sprzętu/GPU do uczciwej
weryfikacji, których ta sesja nie ma; integracja z wl_data_device dla
DnD/schowka MIĘDZY ZDE a resztą systemu to nowy protokół na poziomie
kompozytora, nie zmiana w zde-shell; GUI dla .zpk, wiele
sesji/użytkowników i PRAWDZIWE, pełne wsparcie HiDPI (ZDE wciąż nigdzie
nie zna skali monitora, patrz "Nowości w v0.2 (runda 14)" po dokładną
granicę tego, co zrobiono, a co nie) to osobne, wielotygodniowe projekty
każdy z osobna. Patrz też duży komentarz na początku sekcji "Nowości w
v0.2 (runda 14)" wyżej po pełne uzasadnienie tej decyzji o zakresie.
- pojedynczy seat (jeden zestaw klawiatura+mysz na sesję kompozytora)
-- świadomie poza zakresem tej rundy: prawdziwy multi-seat wymaga
przypisywania urządzeń wejścia/wyjścia do osobnych seatów przez udev i
osobnego zarządzania sesją per seat (
seatd/logind), co jest dużym, odrębnym projektem, potrzebnym praktycznie tylko w kioskach wieloosobowych -- nie regresja, tylko rozsądna granica zakresu - gesty touchpada -- swipe (działanie kompozytora) + pinch/hold (od
rundy 34: re-broadcast do klientów) (
wlcomp/gestures.nim, patrz "Nowości" wyżej i "Nowości w v0.2 (runda 34)"); kompozytor sam wciąż nie wiąże pinch/hold z ŻADNĄ WŁASNĄ akcją (brak dziś w ZDE naturalnego zastosowania, np. przeglądarki obrazów do zoomowania) -- ale aplikacje nasłuchującewlr_pointer_gestures_v1bezpośrednio już je dostają. Nieskompilowane w tej sesji (braklibwlroots-dev) -- do potwierdzenia. hit-testing kursora bez specjalnej obsługi zagnieżdżonych subsurface'ów-- sprawdzone ponownie przy tej rundzie rozbudowy: to jednak NIE jest realne ograniczenie.wlr_scene_subsurface_tree_create/wlr_scene_xdg_surface_create(wlcomp/toplevel.nim,wlcomp/xwayland.nim) budują subsurface'y jako zwykłe węzły-dzieci w TYM SAMYM drzewie sceny, atoplevelAt(wlcomp/toplevel.nim) wspina się ponode.parentaż do korzenia z ustawionym.data-- więc trafienie w subsurface poprawnie rozwiązuje się do właściwegoToplevelbez żadnego dodatkowego kodu. Poprzedni wpis na tej liście był nadmiarową ostrożnością, nie opisem realnej luki.- XWayland i sesja/VT-switch -- zweryfikowane kompilacją i podstawowym
testem runtime wobec wlroots 0.17.1 (patrz sekcja "Nowości" wyżej), ale
jeszcze NIE wobec 0.18/0.20 (docelowe wersje reszty
wlcomp/) ani na prawdziwym DRM/TTY (tylko zagnieżdżone pod Xvfb, bez GPU) -- rozsądnie wysokie zaufanie, ale nie to samo co pełny test na docelowym sprzęcie brak reguł polityki okien dla X11-- już NIEAKTUALNE: geometria żądana przez klienta X11 jest klamrowana do granic układu monitorów (wlr_output_layout_get_box,wlcomp/xwayland.nim,onXwaylandRequestConfigure) od poprzedniej rundy rozbudowy- system powiadomień (patrz "Nowości" wyżej): historia (do 30 wpisów,
MaxHistorywnotifications.nim) od pewnej rozbudowy PRZEŻYWA restartzde-shell, a zwykłe toasty (nkInfo/nkWarning) od tej rozbudowy mają własny, subtelny dźwięk (patrz sekcja "Nowości tej rozbudowy -- dźwięk zwykłych powiadomień" wyżej) -- od rundy 35: integracja z zewnętrznymi aplikacjami spoza ZDE PRZEZ DBUS ISTNIEJE -- minimalny serwerorg.freedesktop.Notificationsna session-bus (libdbus,shell/dbus_notify_shim.c+shell/dbusnotify.nim, patrz "Nowości w v0.2 (runda 35)" niżej po pełny opis, w tym zakres tego, co świadomie ignorowane -- akcje przycisków, hints,expire_timeout) -- realnie zweryfikowane END-TO-END: prawdziwydbus-daemon --sessionw piaskownicy tej sesji, prawdziwynotify-send(libnotify), poprawnie odebrane i poprawnie odpowiedzianeGetCapabilities/Notify/GetServerInformation/CloseNotification, plus poprawny błądUnknownMethodzamiast zawieszenia nadawcy przy nieznanej metodzie. Gdy na sesji już działa INNY serwer powiadomień (typowe np. przy GNOME/KDE/dunst) -- integracja po cichu się nie aktywuje, bez wpływu na resztę ZDE. panel historii (dzwonek w doku) przewija się tak samo jak launcher i lista alarmów (patrz "Nowości" wyżej) - aplikacje systemowe w launcherze -- lista jest odświeżana w tle
(patrz "Nowości" niżej) -- od v0.2 przez realny
inotifyNA ZAWARTOŚĆ plików.desktop(patrz "Nowości w v0.2" wyżej), nie tylko przez porównanie mtime katalogów jak wcześniej -- to drugie zostaje jako niezależna siatka bezpieczeństwa, ale samo w sobie miało realną, cichą wadę (edycja TREŚCI istniejącego pliku w miejscu nie dotyka mtime katalogu nadrzędnego, więc była niewykrywalna), którąinotifyteraz łata -- zweryfikowane realnym uruchomieniem, nie tylko przeczytane w kodzie. Odświeżenie nadal ma kadencję rzędu sekundy (ten sam tick co zegar), nie "klatka po klatce" -- integracjainotifyz pętlą zdarzeń Fidget/GLFW pozostaje świadomie poza zakresem (patrz duży komentarz wdesktopapps.nim). Od v0.2 (runda 5) obsługiwane są też ikony SVG -- rasteryzowane do PNG i podręcznie zbuforowane -- ale TYLKO te mieszczące się w podzbiorze SVG obsługiwanym przez Pixie 5.0.7 (patrz "Nowości w v0.2" niżej po uczciwy opis, ile realnych ikon systemowych to w praktyce obejmuje -- niewiele: większość nowoczesnych motywów używa gradientów z transformacją alboclipPath, czego Pixie nie obsługuje). Od v0.2 (runda 6) obsługiwana jest też prawdziwa rezolucja Icon Theme Spec -- parsowanieindex.theme, wędrówka po łańcuchuInherits=, dobór najbliższego rozmiaru (patrz "Nowości w v0.2" niżej) -- zamiast płaskiej, ręcznie wypisanej listy ścieżek. Ta sama runda znalazła i naprawiła realny bug wstd/parsecfg(cicho gubi resztę pliku przy sekcjach[nazwa@2/...], standardowych w prawdziwych motywach HiDPI) -- zastąpiony własnym, tolerancyjnym parserem linii. Od rundy 14Scale=/ScaledDirectories=SĄ parsowane i katalogiScale=1są świadomie preferowane nad@2x/@3xo tym samymSize=(patrz "Nowości w v0.2 (runda 14)" wyżej po pełny opis i test na prawdziwym, zainstalowanym motywie Humanity) -- ale to WCIĄŻ nie jest prawdziwe wsparcie HiDPI (ZDE nie zna skali monitora) ani pełne regułyFixed/ThresholdzMinSize/MaxSize(patrz duży komentarz przyresolveViaThemeSpec) -- od rundy 34 DOMKNIĘTE:dirSizeDistanceimplementuje teraz pełną funkcję odległości ze specyfikacji, osobną dlaFixed/Scalable/Threshold(MinSize=/MaxSize=/Threshold=parsowane zindex.theme) -- zweryfikowane REALNĄ KOMPILACJĄ (pixiezainstalowane w tej sesji) i izolowanym testem na syntetycznym motywie. Prawdziwe HiDPI (ZDE dalej nie zna skali monitora) pozostaje bez zmian. - menedżer plików (patrz "Nowości" wyżej -- tworzenie folderu, zmiana
nazwy, usuwanie, otwieranie w edytorze, kopiuj/wytnij/wklej,
zaznaczanie wielu wpisów przez Ctrl+klik i Shift+klik, skróty
klawiszowe Delete/Ctrl+A/Escape/F2) -- od v0.2 (runda 13) obsługuje też
przeciąganie plików/folderów myszą (drag & drop), łącznie z
upuszczeniem w INNYM, otwartym jednocześnie oknie menedżera, a od
rundy 14 dodatkowo Ctrl+przeciągnij KOPIUJE zamiast przenosić i
foldery też można przeciągać (gdy zwykły klik i tak by nie nawigował
do ich środka -- patrz "Nowości w v0.2 (runda 14)" wyżej). Integracja
z żywym Fidget/GLFW wciąż nie została w żadnej z tych dwóch rund
zweryfikowana wizualnie (patrz uczciwe notatki o metodzie weryfikacji
tam), tylko logika nie-UI w izolowanych testach. Schowek plików
(kopiuj/wytnij/wklej) od v0.2 DZIAŁA MIĘDZY DWOMA OTWARTYMI OKNAMI
menedżera (patrz "Nowości w v0.2" wyżej -- schowek przeniesiony na
poziom modułu, nie pola instancji) -- wciąż jednak nie działa z
aplikacjami spoza ZDE (prawdziwy schowek plików w stylu GNOME/KDE, jak
i prawdziwe przeciąganie MIĘDZY ZDE a aplikacjami spoza niego,
wymagałyby własnego typu MIME na
wl_data_device, czego architektura ZDE dziś nigdzie nie robi -- to jedyny punkt z tej rodziny, którego runda 14 ŚWIADOMIE nie ruszyła, bo wymaga nowego protokołu na poziomie KOMPOZYTORA, nie samegozde-shell) ani nie przeżywa restartuzde-shell. Od rundy 34: JEDNOKIERUNKOWY obejście z poziomu samegozde-shell-- "Kopiuj"/"Wytnij" wystawiają teraz listę ścieżek jakotext/uri-listna SYSTEMOWY schowek przezwl-copy/xclip(ten sam mechanizm coshell/clipboard.nimdla tekstu), więc wklejenie w PRAWDZIWEJ, zewnętrznej aplikacji (Nautilus, Dolphin, okno "Zapisz jako") już działa -- ale WKLEJANIE plików skopiowanych GDZIE INDZIEJ DO WNĘTRZA ZDE (kierunek odwrotny) wciąż wymaga tej samej zmiany w kompozytorze i pozostaje nierozwiązane. Od rundy 15: sortowanie listy (nazwa/rozmiar/data, rosnąco/malejąco), historia nawigacji wstecz/dalej i opcjonalne ukrywanie wg.gitignore(od rundy 18 z prawdziwą obsługą negacji!wzorzec, semantyka "ostatni pasujący wzorzec wygrywa" jak w prawdziwym Gicie -- patrz "Nowości w v0.2 (runda 18)" wyżej; od rundy 34: wzorce ze ścieżką względną (src/generated/) ORAZ wędrówka po katalogach nadrzędnych są już obsługiwane -- patrz "Nowości w v0.2 (runda 34)", zweryfikowane 4 izolowanymi testami na prawdziwym systemie plików). Od rundy 16: miniatury obrazów (PNG/JPEG/BMP/GIF pierwsza klatka/QOI/ PPM, patrzapps/filemanager/thumbnails.nimi "Nowości w v0.2 (runda 16)" wyżej) zamiast generycznej ikony pliku dla obsługiwanych formatów. Od rundy 19: wyszukiwanie plików po nazwie w poddrzewie katalogów (przełącznik "🔍", uruchamiane jawnie -- nie na bieżąco przy pisaniu, patrz "Nowości w v0.2 (runda 19)" wyżej po limity i pełny zakres). Wciąż bez integracji z systemowym schowkiem/DnD spoza ZDE (patrz akapit wyżej). Alt+Tab to toggle do poprzedniego okna, nie pełna karuzela-- już NIEAKTUALNE, patrz sekcja "Nowości" wyżej: teraz to pełna karuzela z podświetleniem w scenie kompozytora- brak menedżera pakietów z GUI dla formatu
.zpk(patrzpackaging/) -- budowanie/instalacja paczek to na razie tylko CLI (zpk.build,recipe.janet) - historia schowka (patrz "Nowości" wyżej) -- od tej rozbudowy
PRZEŻYWA restart
zde-shell(patrz sekcja "Nowości tej rozbudowy -- trwała historia schowka" niżej, ten sam wzorzec co historia powiadomień), ale wciąż tylko tekst (bez obrazów) i odpytywana raz na sekundę (nie na żywo przez zdarzeniawl_data_device) -- od v0.2 kompozytor OBSŁUGUJE protokół "primary selection" (zwp_primary_selection_v1, patrz "Nowości w v0.2" wyżej), więc środkowy klik działa teraz MIĘDZY APLIKACJAMI (np. zaznacz w terminalu, wklej środkowym klikiem w innym oknie) -- ale panel historii schowkazde-shellśledzi wyłącznie zwykły schowek "clipboard" (Ctrl+C/Ctrl+V), nie pierwotny -- to dwie osobne rzeczy: protokół działa niezależnie od tego, czy jego treść trafia do panelu historii ZDE - brak przełączania wielu użytkowników / wielu jednoczesnych sesji
- tapeta z pliku (patrz "Nowości" wyżej -- podgląd miniatury na żywo,
automatyczne sprzątanie cache'a do 12 najnowszych plików) -- od v0.2
(runda 7) ścieżkę można wybrać przyciskiem "Przeglądaj..." (wbudowana
w
apps/settingsprzeglądarka katalogów, patrz "Nowości w v0.2" niżej), nie tylko wpisać ręcznie -- to wciąż NIE jest systemowy file-picker (żaden inny toolkit w ZDE go nie ma), tylko lekki, ograniczony do plików obrazów odpowiednik wbudowany w samą aplikację Ustawienia; formaty od rundy 17 to PNG/JPEG/GIF (statyczny, pierwsza klatka -- 94% realnych plików.gifw praktyce, patrz "Nowości w v0.2 (runda 17)" wyżej po dokładny przegląd i znane wyjątki) -- od rundy 35: WebP DZIAŁA, ale nie przez samo Pixie (pixie@5.0.7w dalszym ciągu w ogóle go nie dekoduje -- to nie się zmieniło i nie mogło się zmienić bez podmiany biblioteki) --readImageWithWebpFallbackwshell/wallpaper.nimwywołuje zewnętrzne narzędziedwebp(pakietwebp/libwebp-tools) do konwersji na tymczasowy PNG PRZED wczytaniem przez Pixie, ten sam duch integracji cowl-copy/xclipgdzie indziej w ZDE. Best-effort: gdydwebpnie jest zainstalowane, WebP zostaje nieobsługiwane jak wcześniej (cichy powrót do wbudowanego gradientu). Zweryfikowane REALNIE -- prawdziwy plik.webpwygenerowanycwebp, zdekodowany z powrotem przez ten dokładny kod z poprawnymi wymiarami, plus potwierdzone poprawne, ciche wycofanie się, gdydwebpnie jest wPATH.