193 Commits
Author SHA1 Message Date
nevaforget 4398ecb4db feat(quickshell): show every window of the workspace as an icon
Update PKGBUILD version / update-pkgver (push) Successful in 5s
WorkspaceWindows replaces WindowTitle in the default composition: one
icon per window on the screen's active workspace, in niri layout order,
the marked window's title after the icons. Niri.qml handles
WindowLayoutsChanged and gains focusWindow(id).
2026-10-15 18:16:10 +02:00
nevaforget 3ecb55357f feat(niri): start thunar as a daemon at login
Update PKGBUILD version / update-pkgver (push) Successful in 4s
2026-10-09 08:53:16 +02:00
nevaforget f90dd92352 fix(waypaper): switch the backend from swww to awww
Update PKGBUILD version / update-pkgver (push) Successful in 5s
swww is no longer installed; packages/official.txt ships awww. Drops
swww_transition_step along with it.
2026-10-03 01:39:12 +02:00
nevaforget 56552962b5 docs(cpugov): drop stale walker and pkexec references
walker is gone, so it no longer switches profiles out of band; the
remaining sources are powerprofilesctl and D-Bus clients. cpugov never
raised an auth prompt: ppd's polkit policy allows switch-profile for the
active session.
2026-10-03 01:38:24 +02:00
nevaforget bad576881b fix(quickshell): keep the bar clock correct after suspend
Update PKGBUILD version / update-pkgver (push) Successful in 4s
SystemClock waits on a QTimer that does not advance during suspend, so
the bar kept showing the suspend time for up to a minute after resume.
Tick every second while still showing minutes; Text skips unchanged
strings, so the bar repaints only on the minute.
2026-09-29 13:37:17 +02:00
nevaforget 2774d0947d fix(camera): stop hardcoding /usr/bin in the camera scripts 2026-09-23 17:28:34 +02:00
nevaforget 906109d666 feat(lock): open the lock screen on the focused monitor
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 8f1309c596 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 22c2b92, 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 22c2b92aee chore(packages): drop walker, elephant, waybar and swaync
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 11a26e9548 Merge branch 'main' of gitea.moonarch.de:nevaforget/moonarch 2026-09-14 07:27:53 +02:00
nevaforget 0af94bed26 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 5768f21, which added app2unit itself.
2026-09-14 07:27:49 +02:00
nevaforget 33d1cf017f fix(theme): install the GTK theme at all, and make it Mocha
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.
2026-09-12 02:25:00 +02:00
nevaforget 8d31ff26e0 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.
2026-09-12 02:25:00 +02:00
nevaforget 5768f21645 fix(packages): add app2unit to the AUR package list
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
nevaforget 924d5c07f4 fix(quickshell): start launched apps in their own systemd scopes
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.
2026-09-09 15:53:03 +02:00
nevaforget 435364d2e8 fix(stasis): give the desktop plan its suspend step back
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.
2026-09-09 14:09:47 +02:00
nevaforget eae6b09441 fix(foot): drop stale [colors2] section
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
nevaforget a5c915bcd2 fix(stasis): timeouts are cumulative, lock before dpms
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.
2026-09-01 19:33:25 +02:00
nevaforget b6d6696180 fix(stasis): do not daemonize the lock command
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.
2026-08-31 16:58:32 +02:00
nevaforget bcb82b131b fix(quickshell): quit niri on logout instead of terminating the session
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.
2026-08-31 14:04:08 +02:00
nevaforget 447b27af63 feat(quickshell): run it as a systemd user service
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.
2026-08-31 12:55:07 +02:00
nevaforget fac6d2bd7f feat(quickshell): close the window on a middle click of its title
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.
2026-08-31 12:49:16 +02:00
nevaforget f5809b160f fix(niri): drop the bar toggle keybind
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.
2026-08-31 12:41:00 +02:00
nevaforget 633318d5cb fix(stasis): declare the suspend inhibit knobs
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.
2026-08-31 11:55:05 +02:00
nevaforget 675d094e5b feat(niri): bind Mod+M to the media popout
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 9689845936 fix(stasis): give the desktop plan its own dpms and lock steps
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 16bf1f349d refactor(power): switch the CPU widget backend to power-profiles-daemon
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 9f4db41ed8 feat(packages): add thunar with volume and thumbnail support
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 2044ecc5b9 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 cf1db166e9 feat(quickshell): popouts close when the focus leaves them
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 6fd603e0eb fix(quickshell): logout uses the loginctl "auto" session id
"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 a3bd6a04a1 feat(quickshell): power popout replaces moonset
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 77eb787b61 feat: replace moongreet with regreet, moonlock with swaylock
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 7e29246^
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 de142ef73f feat(quickshell): replace walker's clipboard picker
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 b5fabbc0be feat(quickshell): open a calendar on clock click
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 2919d096d2 fix(quickshell): always list audio devices in the popout
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 b710b48c0e feat(quickshell): add camera switch module
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 f489495240 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 dd81b39635 feat(quickshell): bar height 36, invert geometry coupling, unify centering
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 ff8c9c393b fix(quickshell): prefer wired over wifi in network widget
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 279c246c79 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 9b92c69424 fix(quickshell): clear launcher search field on reopen
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 78762d7f40 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 7bd15e36f3 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 e307028b1c fix(quickshell): let popout-spawned prompts take keyboard focus
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 8e91c94aa3 docs: record why nm-applet stays in autostart
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 ce879c64f0 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 aac717867b feat(quickshell): add application launcher + hover affordance
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 17ee43c7b4 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 54b22a9141 feat(quickshell): operate popouts fully by keyboard
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