Commit Graph
188 Commits
Author SHA1 Message Date
nevaforget d476c7875a fix(camera): stop hardcoding /usr/bin in the camera scripts
Update PKGBUILD version / update-pkgver (push) Successful in 5s
2026-09-23 17:28:34 +02:00
nevaforget a172f90c1c feat(lock): open the lock screen on the focused monitor
Update PKGBUILD version / update-pkgver (push) Successful in 5s
gtklock shows its input form on one monitor, chosen from its own -M
priority list, while niri assigns the lock screen's keyboard focus by
cursor position. Locking from any other monitor left form and focus
apart, so typing went nowhere until the mouse was moved.

moonarch-lock resolves the focused output and passes it as -M. All five
call sites go through it; "$@" is forwarded unchanged, so -d stays the
caller's decision.
2026-09-14 18:41:40 +02:00
nevaforget 2b0132aa94 docs: describe the session that is actually installed
CLAUDE.md and README.md still named Waybar as the bar, Walker as the
launcher and swaync as the notification daemon. quickshell took all three
over in July, and the packages left the lists in 117e927, so the docs were
describing a session nobody runs.

Corrected: component table, startup sequence, keybinds, config-override
table and the user-service list in README; the quickshell and Waybar
sections, the battery and nightlight toggles and the fontconfig note in
CLAUDE.md. The Waybar merger section is kept and marked retired -- the
configs and scripts are still shipped.

Two things checked against the source rather than assumed: quickshell is
started by quickshell.service, not spawn-at-startup, and the Alt+W toggle
is already gone from config.kdl. moonarch-waybar-updates stays live --
UpdatesIndicator.qml calls it.
2026-09-14 09:13:35 +02:00
nevaforget 117e927b2e chore(packages): drop walker, elephant, waybar and swaync
Update PKGBUILD version / update-pkgver (push) Successful in 5s
walker-bin and the nine elephant providers were uninstalled on 2026-09-11;
the lists still carried them, so moonup reported them as missing on every
run and offered to reinstall them. quickshell's launcher and
ClipboardPopout had already taken over the two modes that were in use.

waybar, swaync and the two waybar-niri modules go with them. They were
kept as a reserve bar when quickshell became the default in July; the
reserve is no longer intact anyway, because two of its modules dispatch
through walker as a dmenu.

Not touched: defaults/xdg/{waybar,swaync,walker}/, walker.service, the
waybar helper scripts and the Alt+W toggle. moonarch-waybar-updates has to
stay regardless of its name -- UpdatesIndicator.qml calls it.
2026-09-14 09:05:19 +02:00
nevaforget 908aebb5e0 Merge branch 'main' of gitea.moonarch.de:nevaforget/moonarch
Update PKGBUILD version / update-pkgver (push) Successful in 5s
2026-09-14 07:27:53 +02:00
nevaforget b289d17e25 fix(packages): add xdg-terminal-exec for terminal launcher entries
LauncherPopout dispatches desktop entries with Terminal=true through
app2unit-term rather than app2unit. app2unit hardcodes
A2U__TERMINAL_HANDLER=xdg-terminal-exec and runs check_terminal_handler()
before launching: a missing binary is an error and exit 1, with no fallback
to $TERMINAL.

The package was never in aur.txt, so terminal entries from the launcher
start nothing on a fresh install. The failure is partial, which is why it
went unnoticed: GUI entries keep working.

Belongs with 20ee46f, which added app2unit itself.
2026-09-14 07:27:49 +02:00
nevaforgetandClaude Opus 5 98f51dddda fix(theme): install the GTK theme at all, and make it Mocha
Update PKGBUILD version / update-pkgver (push) Successful in 3s
Two defects, found while porting moonix's switch back.

Nothing installed a GTK theme. settings.ini named
Colloid-Grey-Dark-Catppuccin and the PKGBUILD symlinked
/usr/share/themes/Colloid-Grey-Dark-Catppuccin/gtk-4.0/ into /etc/xdg, but
no package in either list ships it: colloid-catppuccin-theme-git is a split
package of colloid-icon-theme-git running `install.sh -s catppuccin`, which
installs icons only, and kvantum-theme-catppuccin-git is Qt. A fresh
install got Adwaita and three dangling symlinks. post-install.sh already
had the error branch for this — "GTK4 theme not found … libadwaita apps
will use fallback theme" — firing on every fresh machine.

The package name is what hid it: colloid-catppuccin-theme-git reads like a
GTK theme and is an icon set. aur.txt now says so in a comment, and the
icon entry stays — there is no Mocha icon counterpart and none is needed.

The theme was also never Mocha. Colloid's catppuccin tweak produces Frappé
with a grey accent (base #292c3c, selection #ccd0da); #1e1e2e and #b4befe
appear nowhere in its gtk.css. Kvantum, Zed and the lock screen were all
Mocha, so GTK was the one layer out of step, unnoticed because the two dark
bases sit close together.

catppuccin-gtk-theme-mocha is maintained by Catppuccin itself and ships the
upstream 1.0.3 release zips. No change in shape: their GTK theme is
Colloid, carried as a git submodule and recolored, so widget shapes and
spacing are exactly what they were.

The name will not match moonix, ever. Arch installs the release zips, whose
directories carry the full build name including the +default tweak marker;
nixpkgs builds the same 1.0.3 from source without tweaks and drops the
suffix. moonix therefore keeps rewriting this one value when it deploys
settings.ini — a permanent difference now, not a migration step.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-12 02:25:00 +02:00
nevaforgetandClaude Opus 5 d9a142cbb4 feat(lock): gtklock replaces swaylock
moonix switched first; this follows so the two distributions agree again
and moonix can drop the two substitute derivations that were rewriting
these configs at deploy time.

Two things made it worth doing. gtklock's modules show an avatar, a clock
and power buttons, none of which swaylock can do at all. And the old
objection is gone: 4.0.0 links libgtk-session-lock.so.0, so it speaks
ext-session-lock-v1 exactly like swaylock and the compositor still holds
the lock if the process dies. Only the <=2.x versions used layer-shell,
where a crash unlocked the screen.

The config location is the real prize. gtklock searches $XDG_CONFIG_DIRS,
so defaults/xdg/gtklock/config.ini rides the generic sweep and takes effect
through the package alone. Everything built for swaylock in 2026-08-18 goes
with it: the explicit install line in the PKGBUILD, the pre_upgrade
retirement of seeded home copies, check_swaylock_config in moonarch-doctor,
the comment in post-install.sh. swaylock needed all of that because its
sysconfdir was not an XDG path.

The blur stays baked into the wallpaper. gtklock cannot blur either — GTK3
has no filter CSS property and it takes no screenshot of the session, so
there is nothing to blur at lock time.

playerctl-module is deliberately not installed; it needs playerctld, which
nothing here starts. The other two are named bare in modules= because
gtklock's compiled-in module directory really is /usr/lib/gtklock on Arch.
All three packages are in extra, so official.txt covers them.

Both callers keep spawning the locker directly. There is no Wayland
protocol for "lock the session" — ext-session-lock-v1 is how a client
becomes the lock — and no portal either; logind's Session.Lock only emits a
signal nothing here subscribes to. Routing the popout through `stasis
trigger lock-screen` would name the locker once instead of twice, at the
price of a Lock button that fails silently whenever stasis is down. The
reason to want it does not exist: the plan continues after a manual lock
anyway, because stasis takes the state from logind's LockedHint rather than
from having started the locker.

The PKGBUILD side of this is a separate commit in moonarch-pkgbuilds, and
it carries the part that fails silently if forgotten: pre_upgrade rewrites
already-seeded ~/.config/stasis/stasis.rune, which is copied once and never
overwritten. Without it an existing install keeps calling a locker that is
no longer there.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-12 02:25:00 +02:00
nevaforget 20ee46fe12 fix(packages): add app2unit to the AUR package list
Update PKGBUILD version / update-pkgver (push) Successful in 4s
The quickshell launcher and NetworkPopout start apps through app2unit so
each one gets its own transient systemd scope instead of inheriting
quickshell's cgroup. app2unit is an AUR package and was never listed, so
on Arch the binary is absent and Quickshell.execDetached fails silently:
the launcher opens, filters entries and starts nothing, leaving no trace
in the quickshell log. NixOS carries app2unit in the system environment,
which is why the switch went unnoticed there.

Not a PKGBUILD depends — pacman cannot resolve AUR packages
(moonarch-pkgbuilds/DECISIONS.md, 2026-04-21). moonarch-update reports it
as missing and offers to install it from aur.txt.
2026-09-11 08:47:47 +02:00
nevaforgetandClaude Opus 5 ba3745603c fix(quickshell): start launched apps in their own systemd scopes
Update PKGBUILD version / update-pkgver (push) Successful in 4s
app.execute() spawns the app as a child of quickshell, and a child
inherits its parent's cgroup, so everything started from the launcher
ended up inside quickshell.service. systemd kills a unit's whole cgroup
on stop, so a crash or restart of the bar took the open apps with it —
Librewolf counted each one as a crash and eventually stopped restoring
the session at all.

app2unit wraps systemd-run and gives each app its own transient
app-*.scope, the same shape niri gives the windows it spawns itself.
Quickshell 0.3.0 exports no desktop-entry id, only the command, so the
prefix takes that; Terminal=true entries need the -term variant, and a
Path= from the entry travels as a unit property because app2unit
otherwise starts the app in the bar's directory.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-09 15:53:03 +02:00
nevaforgetandClaude Opus 5 7a24cdb6ae fix(stasis): give the desktop plan its suspend step back
Update PKGBUILD version / update-pkgver (push) Successful in 5s
The desktop plan was built from the ac: blocks before it got its own, so a
machine without a battery did suspend after idle. Adding dpms and lock_screen
under default: dropped that -- the desktop stopped sleeping.

Timeout matches the ac: plan: 1800s after the blank, 36 min idle.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-09 14:09:47 +02:00
nevaforget eb2810c4ec fix(foot): drop stale [colors2] section
Update PKGBUILD version / update-pkgver (push) Successful in 5s
foot 1.28 rejects the config with "invalid section name: colors2".
The alternate color theme section was renamed to [colors-light]
alongside [colors-dark]; our block only held comments, so it is
removed rather than renamed.

Verified with foot --check-config (exit 0).
2026-09-07 08:56:58 +02:00
nevaforgetandClaude Opus 5 b94cc567c5 fix(stasis): timeouts are cumulative, lock before dpms
Update PKGBUILD version / update-pkgver (push) Successful in 5s
Every `timeout` counts from the previous step in the same block, not from
the last input event; only the first step measures raw idle time. This file
carried the upstream template's numbers while reading them as absolute, so
the screen went dark after 2 minutes where 6 were intended, and the
`brightness 300` step sat behind `dpms 120` where it could never run --
the display was already off when its turn came.

Verified on a running daemon: with `dpms 120` followed by `lock_screen 180`,
dpms fired at 18:43:30 and the lock at 18:46:31, 181 seconds later.

Reorder every block to lock before dpms, so the session is secured before
the display shuts off and the display then stays off instead of being lit
again to show the locker. Recompute all timeouts as offsets.

Note in the header that each command's binary has to be on the PATH of the
unit starting stasis -- there is no login shell, so a missing binary fails
silently and stops the plan.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-01 19:33:25 +02:00
nevaforgetandClaude Opus 5 5b5eb3dcb2 fix(stasis): do not daemonize the lock command
Update PKGBUILD version / update-pkgver (push) Successful in 5s
stasis runs the lock and waits for it to exit, which is how it knows the
session is locked. With -f swaylock forks and the parent returns at once, so
stasis logged the lock and resumed in the same millisecond, turning the
monitors back on. lid_close_action keeps -f: that one is not awaited.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-31 16:58:32 +02:00
nevaforgetandClaude Opus 5 82ec36beb1 fix(quickshell): quit niri on logout instead of terminating the session
Update PKGBUILD version / update-pkgver (push) Successful in 5s
loginctl terminate-session kills the login session scope, which holds the
niri-session script, but niri.service runs in the user manager and survives it
-- along with the cleanup line the script would have run next. The compositor
stayed on seat0, greetd returned to the greeter, and every login after that
died immediately. Quitting niri stops niri.service, lets the script finish and
takes graphical-session.target down with it.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-31 14:04:08 +02:00
nevaforgetandClaude Opus 5 56eae1d81d feat(quickshell): run it as a systemd user service
Update PKGBUILD version / update-pkgver (push) Successful in 4s
spawn-at-startup gave it no restart, so a crash left the session without a bar
until the next login — which is what the Alt+W toggle used to paper over.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-31 12:55:07 +02:00
nevaforgetandClaude Opus 5 e5eb946f66 feat(quickshell): close the window on a middle click of its title
Update PKGBUILD version / update-pkgver (push) Successful in 2s
Closing goes through the window id rather than niri's focused-window default:
the click and the moment niri acts on it are not the same instant, and the
focus can move in between.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-31 12:49:16 +02:00
nevaforgetandClaude Opus 5 02d0126fd9 fix(niri): drop the bar toggle keybind
Update PKGBUILD version / update-pkgver (push) Successful in 2s
Alt+W switched between quickshell and Waybar. Quickshell owns the bar now, so
the branch back was already dead: it calls moonarch-waybar, which no longer
exists. Pressing it killed the bar with nothing to restore it.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-31 12:41:00 +02:00
nevaforgetandClaude Opus 5 49cf07919f fix(stasis): declare the suspend inhibit knobs
Update PKGBUILD version / update-pkgver (push) Successful in 5s
stasis 1.5.1 discards any config missing a knob its own template carries,
backs it up and starts over from that template, so the timeouts here never
took effect. Both lists are empty on purpose.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-31 11:55:05 +02:00
nevaforget a1e06bf380 feat(niri): bind Mod+M to the media popout
Update PKGBUILD version / update-pkgver (push) Successful in 4s
The media popout was registered in the keybind router but reachable
only by clicking the bar widget.
2026-08-27 12:50:34 +02:00
nevaforget 1a524fcfad fix(stasis): give the desktop plan its own dpms and lock steps
Update PKGBUILD version / update-pkgver (push) Successful in 5s
A machine without a battery never blanked and never locked. stasis builds
the desktop plan from the blocks directly under `default:` and excludes
the ac:/battery: containers (src/config/mod.rs:180-183, :230-233), so a
config that keeps every step inside those containers leaves the desktop
plan empty. select_plan_source only falls back onto the desktop plan,
never from it onto ac: (src/core/config.rs:288-306), and the last-resort
rebuild_plan_default_order() builds from blocks the parser has already
disabled — a plan of disabled steps.

dpms (120s) and lock_screen (180s) now sit directly under `default:`,
timeouts matching the ac: plan. Deliberately only those two: brightnessctl
cannot drive external monitors, and suspending a desktop is a separate
decision from idle screen handling.
2026-08-25 09:45:10 +02:00
nevaforget 0e110894b1 refactor(power): switch the CPU widget backend to power-profiles-daemon
Update PKGBUILD version / update-pkgver (push) Successful in 5s
auto-cpufreq switches the scaling governor, but under amd-pstate in
active mode the platform decides from the min/max and EPP registers and
ignores the governor. power-profiles-daemon writes exactly those knobs,
and its polkit policy allows switch-profile for the active session, so
the popout no longer loses focus to a pkexec prompt.

- CpuGovService.qml: powerprofilesctl get/set, no pkexec
- indicator + popout: powersave -> power-saver, Auto (reset) -> balanced
- moonarch-cpugov, moonarch-waybar-cpugov: same switch, the Waybar module
  now reads the profile instead of scaling_governor
- power-profiles-daemon moves from aur.txt to official.txt (it is in extra)
- post-install.sh and moonarch-doctor name it in their service lists
2026-08-25 08:30:18 +02:00
nevaforget e21c046e32 feat(packages): add thunar with volume and thumbnail support
Update PKGBUILD version / update-pkgver (push) Successful in 5s
thunar plus thunar-volman, thunar-archive-plugin, tumbler and
ffmpegthumbnailer. nautilus stays in the list and keeps its preload
service for now, so both file managers are installed side by side.
2026-08-18 17:35:55 +02:00
nevaforget e9e12cc280 fix(swaylock): ship config to /etc/swaylock, blur the background
swaylock reads ~/.swaylock/config, $XDG_CONFIG_HOME/swaylock/config and
SYSCONFDIR/swaylock/config — never /etc/xdg/, where the generic XDG sweep
put it. The config was made effective by seeding it into user homes, but
both seeds are conditional, so a machine that pulled no moonarch-git
update since swaylock arrived ran with no config at all: no wallpaper,
no Catppuccin ring.

The config now lives in defaults/etc/swaylock/config and is owned at
/etc/swaylock/config, which the package alone makes effective. Home
copies still win by search order, so the .install hook retires the
pristine ones; an edited copy stays.

Upstream swaylock has no blur (effect-blur is swaylock-effects only), so
the blur is baked into a second image: wallpaper-blur.jpg, generated with
downsample-blur-upsample. Regenerate it when the wallpaper changes.

moonarch-doctor gains check_swaylock_config, which names the config file
that actually wins and fails when none exists.
2026-08-18 17:33:28 +02:00
nevaforget 5526cdb6ad feat(quickshell): popouts close when the focus leaves them
Update PKGBUILD version / update-pkgver (push) Successful in 5s
An open popout only closed on a backdrop click, Escape, a toggle, or
another screen claiming the slot. Focus moving away without a click —
Alt+Tab, a focus keybind, a workspace switch, a newly mapped window —
left it standing over the desktop.

Three signals, all needed:

- the host surface losing the keyboard, read from Qt's Window.active
  (PanelWindow exports no focus state); the only one that catches a
  focus grab by another layer surface
- niri focusing an actual window, filtered on id >= 0: niri drops window
  focus to none while the popout holds the keyboard, so reacting to every
  change would make each open close itself
- the focused workspace changing, via the new Niri.focusedWorkspaceId;
  workspace ids are unique across outputs, so a monitor switch counts too

No exception for pkexec/keyring prompts raised by popout actions: they
take the focus, so the popout closes and the new state shows on the next
open. Rationale and measurements in DECISIONS.md.
2026-08-18 15:30:06 +02:00
nevaforget 32372b486a fix(quickshell): logout uses the loginctl "auto" session id
Update PKGBUILD version / update-pkgver (push) Successful in 4s
"self" fails with "Caller does not belong to any known session" — the
shell process is not always counted as a session member. "auto" resolves
to the current session and otherwise to the user's graphical session;
verified on systemd 261 to hit session 3, wayland, class user.
2026-08-14 23:31:45 +02:00
nevaforget fe552fc7a5 feat(quickshell): power popout replaces moonset
Update PKGBUILD version / update-pkgver (push) Successful in 4s
Five rows over a model, registered from Launcher.qml under "power" with
the borrowed anchor the clipboard popout uses, so shell.qml and every
user copy of it stay unchanged. Mod+Escape calls the popout by IPC.

Logout goes through `loginctl terminate-session self`, which needs no
XDG_SESSION_ID in the shell environment. Glyph codepoints were read out
of UbuntuSansNerdFont-Regular.ttf.

The reserve Waybar's power button keeps the IPC call and says in its
tooltip that it needs the Quickshell bar; there is no way to reach a
Quickshell popout from a session where Quickshell was killed.
2026-08-14 17:47:06 +02:00
nevaforget a17474f52d feat: replace moongreet with regreet, moonlock with swaylock
Update PKGBUILD version / update-pkgver (push) Successful in 4s
Greeter and lock screen move to the official repos. The greeter keeps
running inside its own niri instance, so niri-greeter.kdl only changes
which binary it spawns; regreet.toml and regreet.css return from f79128b^
with current theme and font names.

swaylock replaces moonlock in the stasis lid and lock_screen actions. Its
config is seeded to the user home, because swaylock never reads /etc/xdg
and the system-wide path does not work on NixOS.

moonlock stays installed: moonset invokes it by name and has no
lock_command option yet.
2026-08-14 14:21:13 +02:00
nevaforget 070a9065be feat(quickshell): replace walker's clipboard picker
Update PKGBUILD version / update-pkgver (push) Successful in 6s
Super+C opened walker's clipboard provider — the last keybind that still
reached walker at all. ClipboardPopout takes over and shells out to
cliphist: list on every open (cliphist has no change signal, so the open
is the refresh point), decode | wl-copy on Enter or click, and delete
with the entry's raw list line on stdin. Every call carries -db-path,
because the history lives in the session db that cliphist-text.service
creates, not in cliphist's ~/.cache default; every shell argument is
positional, so no clipboard content becomes part of a script.

Delete is Ctrl+D, not Del: focus sits in the search field, where Del is
a character delete. It is also what walker's provider used. A footer
hint spells the keys out, since a destructive shortcut is not guessable.

Image entries have no text preview, only a "[[ binary data 6 KiB png
128x128 ]]" placeholder. That placeholder now yields both the thumbnail
extension and a readable "png · 128x128 · 6 KiB" label, and the entry is
decoded to $XDG_RUNTIME_DIR/moonarch-clip for an Image to load — one
decode process per row, so rows do not serialize. The directory is
tmpfs, so thumbnails expire with the session like the history itself.

The popout is keybind-only, but register() needs an on-screen item for
the anchor rect, so it registers from Launcher.qml and borrows that one.
That also leaves shell.qml — and therefore every user copy of it —
unchanged. walker stays installed: moonarch-sink-switcher,
moonarch-cpugov and moonarch-vpn use it as a dmenu, and those are live
in the Waybar reserve config.
2026-08-10 17:04:39 +02:00
nevaforget a95b524d79 feat(quickshell): open a calendar on clock click
Update PKGBUILD version / update-pkgver (push) Successful in 6s
The clock's click toggled the date between short and long form, which the
tooltip already showed in full. It now opens a calendar popout instead.

CalendarPopout uses MonthGrid + WeekNumberColumn + DayOfWeekRow from
QtQuick.Controls (already imported by TrayMenu) so the locale rules —
week start, ISO week numbers, weekday and month names — come from Qt
rather than being re-implemented; only the delegates are ours. Leading
and trailing days stay visible but dimmed, since the grid always renders
six weeks and hiding them would leave the week-number column labelling
blank rows.

Clock takes the full bar height on `height`, not `implicitHeight`: on a
Text root that property is read-only, and the assignment fails the config
load outright.
2026-08-03 11:06:50 +02:00
nevaforget 798f73c5d3 fix(quickshell): always list audio devices in the popout
Update PKGBUILD version / update-pkgver (push) Successful in 6s
The output/input switchers were hidden below two devices, so a machine
with a single sink showed no device list at all — indistinguishable from
a broken popout. The list doubles as the readout of which device is
active, so show it whenever there is at least one node.
2026-07-31 10:59:41 +02:00
nevaforget 7568fe223d feat(quickshell): add camera switch module
Update PKGBUILD version / update-pkgver (push) Successful in 6s
Cameras are off after every boot and get switched on per camera from the
bar. This inverts the privacy problem instead of chasing it: the previous
entry point, detecting camera *use*, cannot see raw V4L2 access at all,
and inotify on /dev/video* cannot tell a real capture from a device query.
An unauthorized camera cannot be opened in the first place.

Mechanism is `authorized` on the USB device. Measured: writing 0 drops the
/dev/videoN nodes and the PipeWire node, writing 1 brings them back and
uvcvideo rebinds itself.

A camera is identified by USB interface class 0x0e in the descriptor blob,
which stays complete while the device is deauthorized (the interface
directories do not) — so no state of our own is needed to keep a
switched-off camera listable and switchable.

The privileged helper re-derives every precondition from kernel data: id
shape, resolved path under /sys/devices, DEVTYPE=usb_device, and a video
interface class. It cannot be used to deauthorize a hub, keyboard or disk.

Switching a camera on authorizes the whole USB device, so the popout names
every other function it carries and flags in red what does not belong on a
camera. Shown rather than blocked: the internal camera carries a DFU
interface and multimedia devices commonly expose HID for their buttons, so
a class filter would lock out legitimate hardware.

Device-supplied labels are rendered as plain text and stripped of the
invisible and directional Unicode range, so a name cannot masquerade as
another device's.

A hot-plugged camera stays on — plugging one in is a deliberate act.
2026-07-29 16:46:56 +02:00
nevaforget 6b345258a4 fix(quickshell): repair privacy indicator detection
The indicator never became visible. Three causes, each measured with
throwaway probe configs against quickshell 0.3.0:

- Link groups report state Unlinked (-1) unless bound by a
  PwObjectTracker, so the "is the link active" guard rejected
  everything. Node properties need the same binding.
- PwNodeType flags share bits between in- and out-streams
  (AudioInStream 13, AudioOutStream 21), so the bitwise test also
  matched playback — browser audio alone set micActive.
- Video streams are left as Untracked with isStream false, so no
  screencast or camera could ever match.

Match media.class of active link endpoints instead, the signal waybar's
privacy module uses. Desktop-audio recording is excluded via the peer
class Audio/Sink, since quickshell does not expose stream.monitor.

Raw V4L2 camera use stays undetectable (no pipewire node exists);
rationale for not observing /dev/video* is in DECISIONS.md.
2026-07-29 14:28:12 +02:00
nevaforget fe1fa43a4d feat(quickshell): bar height 36, invert geometry coupling, unify centering
Update PKGBUILD version / update-pkgver (push) Successful in 6s
moduleHeight (24) becomes the primary vertical token and barHeight is
derived as moduleHeight + moduleMarginV * 2. Previously ModuleBox sized
its pill as barHeight - moduleMarginV * 2, so lowering the bar shrank
every pill by the same amount — the margin is absolute and does not
scale. Bar height is now set via moduleMarginV while the pills hold.

Widget implicitHeight stays on barHeight: ModuleBox has no clip, so the
items overflow the pill and keep the whole bar height clickable, which
matters because the display edge stops the pointer.

Also fixes pre-existing vertical centering. anchors.verticalCenter
centers the Text layout box (ascent + descent) and rounds down on an odd
layout height, which put the network and bluetooth glyphs 1 px above the
bar mid and several others off by 0.5 px. Replaced across 13 widgets with
height: parent.height + verticalAlignment: Text.AlignVCenter, which
centers by font metrics. Workspace dots move from a hardcoded 22 to
moduleHeight.

Verified by pixel measurement: all 7 pills on rows 6-29 (mid 17.50 = bar
mid) and all 14 glyphs with even ink height exactly on 17.50. The
remaining 7 have odd ink height, where 17.50 is not representable on the
pixel grid; two of those are Image, where verticalAlignment does not
apply.
2026-07-28 12:03:32 +02:00
nevaforget 30c9b55be4 fix(quickshell): prefer wired over wifi in network widget
Update PKGBUILD version / update-pkgver (push) Successful in 6s
When both a wired and a wifi connection are up, NetworkManager routes
over ethernet (lower metric) and nm-applet shows the wired icon. The
network indicator and popout checked wifi first, so an ethernet-connected
machine with wifi still associated wrongly showed the wifi glyph and
SSID. Check wiredDev first in the icon, tooltip, popout activeDev, and
compact connection text.
2026-07-16 15:13:56 +02:00
nevaforget 9ce20a096f docs: confirm Kvantum reads /etc/xdg config (open item resolved)
Fresh-path test after the r152 rebuild: with ~/.config/Kvantum moved
aside, a niri-spawned VLC still rendered Catppuccin — Kvantum reads
/etc/xdg/Kvantum/kvantum.kvconfig, so the glob deploy suffices and no
user-home seed is needed.
2026-07-15 15:35:20 +02:00
nevaforget 152253642a fix(quickshell): clear launcher search field on reopen
Update PKGBUILD version / update-pkgver (push) Successful in 6s
The host's Loader keeps the LauncherPopout instance alive across
close/reopen, so a stale query from the previous open lingered in the
search field. Reset the input in focusFirst() (called by PopoutHost on
every open); the text reset cascades through onTextChanged -> query ->
currentIndex.
2026-07-15 15:25:38 +02:00
nevaforget 5fe7d2aaeb feat(qt): theme Qt apps with Catppuccin via Kvantum
Qt apps (e.g. VLC) rendered in the default Fusion style — GTK theming
does not cover Qt. Add the Kvantum style engine + Catppuccin Mocha
Lavender theme, wired via QT_STYLE_OVERRIDE, which covers both Qt5 and
Qt6 (QT_QPA_PLATFORMTHEME can select only one plugin at a time).

Also move all GUI-toolkit env vars (QT_*/GDK/SDL/CLUTTER) from zshrc to
niri's environment{} block: niri is systemd-started, so zsh exports
never reached niri-spawned GUI apps (verified via /proc/environ), only
terminal-launched ones — the app launcher included.

- packages: kvantum, kvantum-qt5, qt6ct + kvantum-theme-catppuccin-git (AUR)
- niri config.kdl: environment{} block; zshrc: GUI-toolkit vars removed
- qt6ct.conf (style=kvantum, icon_theme=Colloid), Kvantum/kvantum.kvconfig
  (theme=catppuccin-mocha-lavender, the Lavender accent)
- CLAUDE.md + DECISIONS.md
2026-07-15 15:20:21 +02:00
nevaforget de44949c0c feat(niri): bind Mod+Space to the quickshell launcher
Replace walker on Mod+Space with the quickshell launcher popout
(qs ipc call popout toggle launcher). walker stays installed and keeps
Super+C clipboard plus its run/calc modes (no longer keybound).
2026-07-15 13:13:46 +02:00
nevaforget 9760400c18 fix(quickshell): let popout-spawned prompts take keyboard focus
Update PKGBUILD version / update-pkgver (push) Successful in 5s
The popout used Exclusive layer-shell keyboard focus while open. On the overlay layer that sits above all normal windows, so a keyring/PSK/polkit prompt spawned by a popout action (e.g. selecting a VPN) mapped unfocused and stayed untypeable — focus was stuck on quickshell.

Switch to OnDemand. Verified on niri 26.04: a keybind-opened popout still grabs the keyboard on open (Escape/Tab/arrow navigation preserved) while an externally-mapped toplevel can steal focus. General fix — covers every popout and every prompt type, no per-action handler.
2026-07-09 09:17:09 +02:00
nevaforget 9566f0d45c docs: record why nm-applet stays in autostart
Update PKGBUILD version / update-pkgver (push) Successful in 5s
Its tray icon is redundant next to the quickshell network widget, but
nm-applet is the session's NM secret agent: agent-owned VPN passwords
(keyring) are delivered — and saved on GUI import — only through it.
Documents the rejected alternatives (indicator flag, tray filter,
system-owned secrets, standalone agents) so this isn't re-litigated.
2026-07-08 16:47:33 +02:00
nevaforget 376ea66e36 feat(quickshell): show connecting, VPN, and wired state on the network indicator
The bar icon only knew off/no-connection/signal strength: an NM (re)connect
read as "no connection", an active VPN was invisible, and a wired-only
machine showed a dead wifi glyph.

- Connecting: cycle the strength ramp (nm-applet style) while the wifi
  device is in ConnectionState.Connecting (covers reconnects too)
- VPN: swap to MDI wifi-strength-N-lock glyphs; wired+VPN uses
  security-network (MDI has no ethernet-lock); tooltip lists active VPNs
- Wired: ethernet glyph, ranked above wifi-off so ethernet-only machines
  show their connection

VPN state moves from NetworkPopout (2s poll while shown) into a shared
NetworkService singleton — the indicator needs it permanently, and an
always-on poll is wasteful. Event-driven via a long-running `nmcli monitor`
(300ms debounce, events burst) with a 60s fallback poll. Rationale:
DECISIONS.md (2026-07-08).
2026-07-08 15:42:26 +02:00
nevaforget 456f2ade99 feat(quickshell): add application launcher + hover affordance
Update PKGBUILD version / update-pkgver (push) Successful in 6s
New Launcher bar widget (first in the left cluster) opening a LauncherPopout
over DesktopEntries — search field + keyboard-navigable app list, launches via
entry.execute(). Icon-click only for now; Mod+Space stays on walker (drun-only
scope, walker keeps dmenu/run/calc).

Placing a popout in the left cluster forced the shared host anchor to become
side-aware: left-cluster triggers left-align (body grows rightward), right
cluster keeps the original right-align — otherwise a left-anchored popout
renders off-screen. The anchor rect now carries the trigger's left edge + width
(PopoutState.openByName, Tray).

Clickable bar widgets gain a hover affordance via opacity (0.8), not color:
the icon color already encodes status (muted/off/active/critical), so a
color-based hover would clobber that signal. Launcher keeps an accent hover as
it carries no status color.
2026-07-08 11:09:28 +02:00
nevaforget c1c2af1abd feat(quickshell): expand network popout with VPN, info, and collapsible sections
Rework the minimal wifi-list popout into a full network panel:
- compact current-connection line (SSID + signal, or Wired/off state)
- VPN section toggling connections via nmcli (Networking API has no VPN state)
- collapsible available-networks list; scanner now gated on shown AND expanded
- collapsible IP details (address/gateway/DNS) via nmcli device show
- nm-connection-editor launcher

Add a shared Divider component for popout section rules and register it in qmldir.
Rebind Super+N from moonarch-vpn to the quickshell network popout toggle.
2026-07-08 10:21:34 +02:00
nevaforget a229ee9c6a feat(quickshell): operate popouts fully by keyboard
Update PKGBUILD version / update-pkgver (push) Successful in 6s
Popouts can now be opened by niri keybind and driven entirely without a
mouse — the whole point of the keybinds.

- keybind: PopoutRouter singleton + IpcHandler (target "popout") route
  toggle/open/close to the focused monitor's PopoutState via
  Niri.activeOutput; each widget registers its popout by name and its
  click handler toggles by name. Mod+A now opens the audio popout
  (replacing walker's audio menu).
- navigation: shared controls (Toggle/ListRow/VolumeSlider) and the new
  IconButton are focusable (activeFocusOnTab, focus ring, Space/Enter,
  slider Left/Right); PopoutPanel focuses the first control on open and
  maps Up/Down onto Qt's Tab chain; an open popout grabs the keyboard.
- ad-hoc glyph buttons (audio mute, media transport, notif dismiss, BT
  forget) folded into IconButton; destructive actions turn red.

Tray stays mouse-only (per-icon menu, not keybind-addressable).
2026-07-08 09:46:01 +02:00
nevaforget 8680a6e5dd docs(quickshell): mark cross-screen single-popout as verified
Live multi-monitor test confirmed the cross-screen fold; the running
instance's log stayed error/warning-free through popout interaction.
2026-07-06 23:43:07 +02:00
nevaforget c34d1d29fe feat(quickshell): keep only one popout open across all screens
Update PKGBUILD version / update-pkgver (push) Successful in 6s
Each bar owns a per-screen PopoutState, so on multi-monitor a popout
could stay open on one screen while another opened on the next — and
per-screen `shown` drives global services (bluetooth discovery, wifi
scan), so closing one screen's popout could kill the service under the
other's still-open popout.

A small PopoutBus singleton holds an `owner` token: open() claims it,
and any state that loses ownership folds up. Per-screen state (and thus
per-screen anchoring/rendering) stays; only cross-screen exclusivity is
added.
2026-07-06 23:34:55 +02:00
nevaforget a7bf8fe37b fix(quickshell): tie up loose ends from the popout composition refactor
The composition + composable-popouts refactor (450580a, 89988c1) left
three rough edges:

- 6 popouts still read the removed PopoutState.currentName in their
  `shown` gate (undefined === "x" → permanently false), so wifi scan,
  bluetooth discovery, threshold reread, governor refresh, toast-silence
  and tray drill-down reset never fired again. Only the current component
  is ever instantiated, so `hasCurrent` alone is the correct gate.
- ModuleBox stopped self-hiding when empty: the Battery box on a desktop
  (no battery) and an empty tray rendered a bare rounded pill. Gate the
  shared container on `inner.implicitWidth > 0` so the fix covers both.
- CLAUDE.md customization example missed `import Quickshell` (unloadable
  as written) and called a Row's rejected centerIn anchor "harmless"
  (Row warns at runtime and ignores it).
2026-07-06 23:34:35 +02:00
nevaforget 89988c1405 feat(quickshell): composable popouts — each widget owns its popout
The composition refactor left Popouts.qml a hardcoded switch, so a
user-composed widget could join the bar but couldn't open a popout in
the shared animated overlay — second-class, and a widget without a
popout is useless for most cases. quickshell's native PopupWindow would
be composable but drops the unfold/morph animation; keep the animation,
make the registry composable instead (caelestia's central Panels.qml
registry was considered and rejected — it fits a few author-owned edge
drawers, not many user-composable per-widget popouts).

PopoutState.open() now takes a Component instead of a name; each widget
carries its popout inline and hands it to the shared host, which renders
whatever is current. Popouts.qml is gone — no central registry, a user
widget brings its own popout and it renders in the same morphing frame.

Verified: qmllint clean, and an end-to-end config where a user widget
opens its own popout on startup instantiates with no errors or binding
loops. Visual/animation not machine-verifiable.
2026-07-06 17:17:59 +02:00
nevaforget 450580aec1 refactor(quickshell)!: replace toggle-config with QML composition
The user-extension layer (moonarch.json + UserConfig singleton +
per-widget enabled() gates + UserWidgets slot) was a bespoke graft.
quickshell has no native settings/toggle/override concept — a config
is composable QML, and $XDG_CONFIG_HOME wins over $XDG_CONFIG_DIRS
(verified in the docs and src/launch/command.cpp).

Ship the bar as a named config (/etc/xdg/quickshell/moonarch/, launched
`-c moonarch`) of importable components; the composition (which widgets,
in what order) lives in shell.qml. Users customize by copying shell.qml
to ~/.config/quickshell/moonarch/ (wins by XDG precedence) and editing
it — reorder, add, and replace all fall out of plain composition, which
also retires the deferred reorder/user-popout/override items.

Also fixes the ModuleBox childrenRect binding loop (Item -> Row).

BREAKING CHANGE: ~/.config/quickshell/moonarch.json and UserWidgets.qml
are no longer read; per-widget toggles are gone in favor of composition.

Verified: qmllint clean (only quickshell C++ type-resolution artifacts),
quickshell loads the default config and a cross-dir composed config with
no binding loops or errors.
2026-07-06 13:17:36 +02:00