Files
moonarch/CLAUDE.md
T
nevaforget d101b23351
Update PKGBUILD version / update-pkgver (push) Successful in 5s
docs: translate CLAUDE.md to English
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

7.5 KiB

Moonarch

Reproducible Arch Linux setup based on archinstall + post-install automation.

Project Structure

  • 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 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 (Blue Light Filter)

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 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

The video player is mpv with 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)

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

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
  • 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)

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.

Windowed / Muted Video: wayland-pipewire-idle-inhibit

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