Compare commits

..

2 Commits

Author SHA1 Message Date
nevaforget d101b23351 docs: translate CLAUDE.md to English
Update PKGBUILD version / update-pkgver (push) Successful in 5s
Per the committed=English rule. Also documents the windowed-video idle-inhibit added in the same series.
2026-06-16 10:46:05 +02:00
nevaforget e6b7f53794 feat: keep windowed browser video awake via wayland-pipewire-idle-inhibit
stasis ignores browser audio (pactl, browser-excluded), so windowed/muted browser video let the screen sleep. Add wayland-pipewire-idle-inhibit (AUR + user service) holding a Wayland idle-inhibitor while audio plays. Enabled on fresh installs and checked by moonarch-doctor.
2026-06-16 10:46:05 +02:00
5 changed files with 72 additions and 61 deletions
+67 -59
View File
@@ -1,86 +1,94 @@
# Moonarch
Reproduzierbares Arch-Linux-Setup basierend auf archinstall + Post-Install-Automatisierung.
Reproducible Arch Linux setup based on archinstall + post-install automation.
## Projektstruktur
## Project Structure
- `config/` — archinstall-Konfiguration (inkl. custom-commands die das Repo nach /opt/moonarch klonen, root-owned)
- `scripts/`Post-Install- und Helper-Scripts
- `packages/`Paketlisten (offiziell + AUR), getrennt gepflegt
- `defaults/` — XDG-Configs, Shell-Config, Helper-Binaries, systemd Services, udev-Regeln, greetd/moongreet-Config, Wallpaper
- `config/` — archinstall configuration (incl. custom-commands that clone the repo to /opt/moonarch, root-owned)
- `scripts/`post-install and helper scripts
- `packages/`package lists (official + AUR), maintained separately
- `defaults/` — XDG configs, shell config, helper binaries, systemd services, udev rules, greetd/moongreet config, wallpaper
## Battery Conservation Mode
Laptops mit `charge_control_end_threshold`-Support (ThinkPad, Framework, etc.) erhalten einen Waybar-Toggle:
- Klick auf das Battery-Modul schaltet zwischen 80% und 100% Ladegrenze um
- Bei aktiver Conservation erscheint ein ♥-Icon neben der Battery-Anzeige
- Zustand wird in `/var/lib/moonarch/batsaver-threshold` persistiert und beim Boot via systemd-Service (`moonarch-batsaver-restore`) wiederhergestellt
- Toggle-Flow: `moonarch-batsaver-toggle` (User-Script) liest sysfs, entscheidet 80↔100, ruft `pkexec /usr/bin/moonarch-batsaver-apply $NEW` für den privilegierten sysfs+state-Schreibschritt. Standard-pkexec-Prompt (Passwort einmal pro Session-Cache)
- Auf Desktops ohne Battery-Support versteckt sich das Feature komplett
Laptops with `charge_control_end_threshold` support (ThinkPad, Framework, etc.) get a Waybar toggle:
- Clicking the battery module toggles the charge limit between 80% and 100%
- When conservation is active, a ♥ icon appears next to the battery indicator
- State is persisted in `/var/lib/moonarch/batsaver-threshold` and restored on boot via a systemd service (`moonarch-batsaver-restore`)
- Toggle flow: `moonarch-batsaver-toggle` (user script) reads sysfs, decides 80↔100, calls `pkexec /usr/bin/moonarch-batsaver-apply $NEW` for the privileged sysfs+state write step. Standard pkexec prompt (password once per session cache)
- On desktops without battery support the feature is hidden entirely
## Nightlight (Blaufilter)
## Nightlight (Blue Light Filter)
Waybar-Toggle für wlsunset (Wayland-nativer Blaufilter), persistenter Zustand via systemd:
- `wlsunset.service` (systemd User-Service) mit `After=kanshi.service` — startet erst wenn alle Outputs konfiguriert sind
- **Default OFF** — frische Installs starten ohne Filter. PKGBUILD legt für `wlsunset` bewusst KEINEN Symlink in `/etc/systemd/user/graphical-session.target.wants/` an, post-install.sh enabled den Service nicht.
- Klick auf das Nightlight-Modul in `group/brightness` toggled wlsunset an/aus (`enable --now` / `disable --now`)
- Zustand überlebt Reboots (User-Scope-Symlink in `~/.config/systemd/user/...wants/`)
- Aktiver Zustand zeigt 󰌵 in Catppuccin Yellow, inaktiv 󰌶 in Standard-Textfarbe
- Signal SIGRTMIN+11 für sofortiges Waybar-Refresh
- Scripts: `moonarch-nightlight` (Toggle), `moonarch-waybar-nightlight` (Status-JSON)
- **Wichtig**: Auf gar keinen Fall einen Global-Scope-Symlink in `/etc/systemd/user/...wants/wlsunset.service` anlegen — der überstimmt jedes User-`disable` und macht den Filter de-facto unausschaltbar.
Waybar toggle for wlsunset (Wayland-native blue light filter), persistent state via systemd:
- `wlsunset.service` (systemd user service) with `After=kanshi.service` — starts only once all outputs are configured
- **Default OFF** — fresh installs start without the filter. The PKGBUILD deliberately creates NO symlink for `wlsunset` in `/etc/systemd/user/graphical-session.target.wants/`, and post-install.sh does not enable the service.
- Clicking the nightlight module in `group/brightness` toggles wlsunset on/off (`enable --now` / `disable --now`)
- State survives reboots (user-scope symlink in `~/.config/systemd/user/...wants/`)
- Active state shows 󰌵 in Catppuccin Yellow, inactive 󰌶 in the default text color
- Signal SIGRTMIN+11 for immediate Waybar refresh
- Scripts: `moonarch-nightlight` (toggle), `moonarch-waybar-nightlight` (status JSON)
- **Important**: Never create a global-scope symlink at `/etc/systemd/user/...wants/wlsunset.service` — it overrides any user `disable` and makes the filter effectively impossible to turn off.
## Waybar Config Merger (moonarch-waybar)
Waybar wird über `moonarch-waybar` gestartet (nicht direkt). Der Wrapper merged eine optionale User-Config (`~/.config/waybar/userconfig`) mit der System-Config (`/etc/xdg/waybar/config`):
- `prepend`/`append`-Keys in der userconfig erweitern `modules-left`/`modules-center`/`modules-right` Arrays
- Alle anderen Top-Level-Keys werden als Modul-Definitionen per Object-Merge eingefügt
- Merge wird nur bei Änderungen ausgeführt (Timestamp-Vergleich)
- Bei Fehler: `notify-send` + `logger`, Waybar startet mit System-Config
- Generiert `~/.config/waybar/style.css` mit `@import` der System-Styles falls nicht vorhanden
- Benötigt `jq` (in PKGBUILD als Dependency)
- System-Config muss valides JSON sein (kein JSONC)
Waybar is started via `moonarch-waybar` (not directly). The wrapper merges an optional user config (`~/.config/waybar/userconfig`) with the system config (`/etc/xdg/waybar/config`):
- `prepend`/`append` keys in the userconfig extend the `modules-left`/`modules-center`/`modules-right` arrays
- All other top-level keys are inserted as module definitions via object merge
- The merge runs only on changes (timestamp comparison)
- On error: `notify-send` + `logger`, Waybar starts with the system config
- Generates `~/.config/waybar/style.css` with an `@import` of the system styles if not present
- Requires `jq` (declared as a dependency in the PKGBUILD)
- The system config must be valid JSON (no JSONC)
## mpv + ModernZ OSC
Videoplayer ist `mpv` mit [ModernZ](https://github.com/Samillion/ModernZ) als OSC, Thumbnails via thumbfast:
- `mpv-modernz-git` liefert `modernz.lua` + Font + Default-Config nach `/etc/mpv/`
- `mpv-thumbfast-git` liefert `thumbfast.lua` nach `/etc/mpv/scripts/` (wird von ModernZ automatisch erkannt)
- `defaults/etc/mpv/mpv.conf` wird von moonarch-git direkt nach `/etc/mpv/mpv.conf` installiert (owned)
- Stock-OSC + Titelleiste deaktiviert, `autofit-larger=80%x80%` cappt übergroße Fenster
- ModernZ-Overrides per `script-opts-append` in mpv.conf: Orange-Akzent → Catppuccin Lavender (`#b4befe`), OSC-Scale 0.75, `window_title_font_size=18`, `ontop_button=no`
- **Wichtig**: mpv behandelt `#` als Mid-Line-Kommentar; Hex-Farben müssen gequotet werden: `script-opts-append="modernz-seekbarfg_color=#b4befe"` (nicht `\#`, das escaped nur und verschluckt den Rest)
- Niri öffnet mpv floating (`window-rule` in `defaults/xdg/niri/config.kdl`)
The video player is `mpv` with [ModernZ](https://github.com/Samillion/ModernZ) as the OSC, thumbnails via thumbfast:
- `mpv-modernz-git` provides `modernz.lua` + font + default config to `/etc/mpv/`
- `mpv-thumbfast-git` provides `thumbfast.lua` to `/etc/mpv/scripts/` (auto-detected by ModernZ)
- `defaults/etc/mpv/mpv.conf` is installed directly to `/etc/mpv/mpv.conf` by moonarch-git (owned)
- Stock OSC + title bar disabled, `autofit-larger=80%x80%` caps oversized windows
- ModernZ overrides via `script-opts-append` in mpv.conf: orange accent → Catppuccin Lavender (`#b4befe`), OSC scale 0.75, `window_title_font_size=18`, `ontop_button=no`
- **Important**: mpv treats `#` as a mid-line comment; hex colors must be quoted: `script-opts-append="modernz-seekbarfg_color=#b4befe"` (not `\#`, which only escapes and swallows the rest)
- Niri opens mpv floating (`window-rule` in `defaults/xdg/niri/config.kdl`)
## System Health Check (moonarch-doctor / moondoc)
Diagnose-Script das den Systemzustand gegen moonarch-Defaults prüft:
- Pakete (official.txt + aur.txt installiert? Orphans?)
- System-Services (NetworkManager, bluetooth, greetd, ufw, auto-cpufreq, etc.)
- User-Services (kanshi, wlsunset, stasis, walker, nautilus, cliphist-text, cliphist-image)
- Config-Dateien (SHA256-Vergleich deployed vs. moonarch-Default)
- Helper-Scripts + Symlinks (moonup, moondoc)
- System-Config (UFW, Pacman/Paru Repos, Default Shell)
- Verzeichnisse + Permissions
Diagnostic script that checks the system state against moonarch defaults:
- Packages (official.txt + aur.txt installed? Orphans?)
- System services (NetworkManager, bluetooth, greetd, ufw, auto-cpufreq, etc.)
- User services (kanshi, wlsunset, stasis, walker, nautilus, cliphist-text, cliphist-image)
- Config files (SHA256 comparison deployed vs. moonarch default)
- Helper scripts + symlinks (moonup, moondoc)
- System config (UFW, pacman/paru repos, default shell)
- Directories + permissions
## Fontconfig Defaults
Systemweite generic-family Defaults via `defaults/etc/fonts/conf.d/65-moonarch-fonts.conf` (owned by moonarch-git):
System-wide generic-family defaults via `defaults/etc/fonts/conf.d/65-moonarch-fonts.conf` (owned by moonarch-git):
- `sans-serif` → UbuntuSans Nerd Font, `monospace` → UbuntuSansMono Nerd Font
- Nummer **65**: lädt nach `60-latin.conf`, sodass die moonarch-Prefs den Stock-Default (Noto/DejaVu) schlagen. `local.conf` (bei 51 via `51-local.conf`) lädt zu früh und wird von 60-latin überstimmt — daher **nicht** verwenden (außerdem für lokale User-Overrides reserviert).
- Aliases brauchen `binding="strong"`ein weak `<prefer>` (fontconfig-Default) rankt hinter dem effektiven generischen Fallback und greift nicht.
- Greift nur für Apps die generische Familien nutzen (z.B. Firefox-Web-Fallback). moonarch-Apps (Waybar, foot, GTK, walker, swaync) setzen den Font explizit.
- Number **65**: loads after `60-latin.conf`, so the moonarch prefs beat the stock default (Noto/DejaVu). `local.conf` (at 51 via `51-local.conf`) loads too early and is overridden by 60-latin — therefore **do not** use it (it is also reserved for local user overrides).
- Aliases need `binding="strong"`a weak `<prefer>` (fontconfig default) ranks behind the effective generic fallback and does not take effect.
- Only applies to apps that use generic families (e.g. Firefox web fallback). moonarch apps (Waybar, foot, GTK, walker, swaync) set the font explicitly.
## Browser Idle-Inhibit (xdg-desktop-portal)
Damit Fenster-Browservideo (Firefox/Waterfox) den Screen wachhält, via `defaults/etc/xdg-desktop-portal/niri-portals.conf` (owned by moonarch-git, höhere Priorität als niris `/usr/share/xdg-desktop-portal/niri-portals.conf`):
- `xdg-desktop-portal-gtk` meldet das `Inhibit`-Interface als success, obwohl es unter Niri niemand implementiert → Firefox glaubt, der Idle-Inhibit lief über den Portal, und nutzt den nativen Wayland-`idle-inhibit` nicht. Ergebnis: kein Inhibitor, Screen schläft.
- Fix: `org.freedesktop.impl.portal.Inhibit=none` → Firefox fällt auf `zwp_idle_inhibit` zurück, den Niri honoriert. Die übrigen `[preferred]`-Zeilen sind 1:1 von niris Default übernommen (portals.conf wird nicht gemerged — die höchstpriore Datei gilt komplett).
- stasis ist hier unbeteiligt: `monitor_media` (pactl) erfasst Browser-Audio per Design nicht (nur Non-Browser-Player); Browser laufen über den Inhibit-Pfad.
- **Aktivierung:** xdg-desktop-portal + Browser neu starten — Firefox fragt den Portal-Support beim Start ab.
So that windowed browser video (Firefox/Waterfox) keeps the screen awake, via `defaults/etc/xdg-desktop-portal/niri-portals.conf` (owned by moonarch-git, higher priority than niri's `/usr/share/xdg-desktop-portal/niri-portals.conf`):
- `xdg-desktop-portal-gtk` reports the `Inhibit` interface as success even though nobody implements it under Niri → Firefox believes the idle-inhibit went through the portal and does not use the native Wayland `idle-inhibit`. Result: no inhibitor, screen sleeps.
- Fix: `org.freedesktop.impl.portal.Inhibit=none` → Firefox falls back to `zwp_idle_inhibit`, which Niri honors. The remaining `[preferred]` lines are taken 1:1 from niri's default (portals.conf is not merged — the highest-priority file applies in full).
- stasis is uninvolved here: `monitor_media` (pactl) does not capture browser audio by design (non-browser players only); browsers go through the inhibit path.
- **Activation:** restart xdg-desktop-portal + the browser — Firefox queries portal support at startup.
## Konventionen
### Windowed / Muted Video: wayland-pipewire-idle-inhibit
- Paketlisten sind einfache Textdateien, ein Paket pro Zeile, Kommentare mit `#`
- Shell-Scripts müssen POSIX-kompatibel oder explizit bash/zsh sein
- Alle Pfade im archinstall-Config relativ zum Installationsziel
The portal fix only applies to **fullscreen** video — Firefox/Waterfox send the idle-inhibit only in fullscreen. Windowed video remains unprotected: stasis ignores browser audio (pactl, browser-excluded, no MPRIS backend), Niri gets no inhibitor, the screen sleeps mid-video.
Solution: `wayland-pipewire-idle-inhibit` (AUR, systemd user service; in `packages/aur.txt` + `post-install.sh` USER_SERVICES + moonarch-doctor). Holds a Wayland `zwp_idle_inhibit` inhibitor while PipeWire outputs audio (default threshold 5s). Niri then suppresses idle → stasis stays awake, daemon-independent. Releases automatically when audio stops.
- Catches windowed video **with sound**. Misses muted video (no audio signal) — edge case, accepted.
- **Never** put `waterfox`/a browser in stasis `inhibit_apps`: that inhibits unconditionally as long as the browser process runs → the system never idles. This exact wrong fix caused the "no idle" bug.
## Conventions
- Package lists are plain text files, one package per line, comments with `#`
- Shell scripts must be POSIX-compatible or explicitly bash/zsh
- All paths in the archinstall config are relative to the install target
+2 -1
View File
@@ -16,7 +16,7 @@ desktop that can be rebuilt from scratch in minutes.
| **Greeter** | [greetd](https://sr.ht/~kennylevinsen/greetd/) + [moongreet](https://gitea.moonarch.de/nevaforget/moongreet) | Minimal, Wayland-native login. moongreet provides GTK4 UI with fingerprint support, running inside its own Niri instance. |
| **Lock Screen** | [moonlock](https://gitea.moonarch.de/nevaforget/moonlock) | ext-session-lock-v1 protocol — compositor guarantees lock on crash. PAM + fprintd, GPU blur, multi-monitor. |
| **Power Menu** | [moonset](https://gitea.moonarch.de/nevaforget/moonset) | GTK4 Layer Shell overlay above Waybar. Lock, logout, hibernate, reboot, shutdown with confirmation. |
| **Idle Manager** | [stasis](https://aur.archlinux.org/packages/stasis) | Separate AC/battery power plans. Brightness dimming, DPMS, lock (via moonlock), and suspend on configurable timeouts. |
| **Idle Manager** | [stasis](https://aur.archlinux.org/packages/stasis) + [wayland-pipewire-idle-inhibit](https://github.com/rafaelrc7/wayland-pipewire-idle-inhibit) | Separate AC/battery power plans. Brightness dimming, DPMS, lock (via moonlock), and suspend on configurable timeouts. The companion inhibitor holds a Wayland idle-inhibitor while audio plays — keeps the screen awake during windowed browser video, which stasis' pactl detection skips by design. |
| **Bar** | [Waybar](https://github.com/Alexays/Waybar) | Wayland-native, highly customizable. Niri workspace/window modules via community plugins. |
| **Launcher** | [Walker](https://github.com/abenz1267/walker) + [Elephant](https://github.com/abenz1267/elephant) | Wayland-native GTK4 launcher with built-in providers for apps, clipboard, bluetooth, audio, files, and calculator. Dmenu mode for custom scripts (VPN, CPU governor). |
| **Terminal** | [Foot](https://codeberg.org/dnkl/foot) | Fast, minimal Wayland-native terminal. Server mode for instant window spawning. |
@@ -206,6 +206,7 @@ are part of the system and updated via `paru -Syu`.
| kanshi | Dynamic display configuration (auto-switch output profiles on hotplug) |
| nautilus | File manager preload (faster first launch) |
| stasis | Idle manager (dimming, DPMS, lock, suspend on AC/battery plans) |
| wayland-pipewire-idle-inhibit | Holds a Wayland idle-inhibitor while audio plays (keeps screen awake for windowed browser video that stasis skips) |
| walker | Walker application launcher (GTK4 service mode for instant startup) |
## Moonarch Ecosystem
+1
View File
@@ -37,3 +37,4 @@ mpv-thumbfast-git
# System & Tools
auto-cpufreq
stasis
wayland-pipewire-idle-inhibit
+1 -1
View File
@@ -186,7 +186,7 @@ elif pacman -Qq moonarch-git &>/dev/null; then
EXPECTED_SVCS+=("$(basename "$svc_file" .service)")
done < <(pacman -Qql moonarch-git | grep -E '^/etc/systemd/user/[^/]+\.service$')
# Services enabled by post-install.sh from other packages
EXPECTED_SVCS+=(stasis)
EXPECTED_SVCS+=(stasis wayland-pipewire-idle-inhibit)
for svc in "${EXPECTED_SVCS[@]}"; do
check_user_service "$svc"
+1
View File
@@ -134,6 +134,7 @@ log "Enabling systemd user services..."
USER_SERVICES=(
"kanshi"
"stasis"
"wayland-pipewire-idle-inhibit"
"cliphist-text"
"cliphist-image"
)