orbit-menu and horizon-dock (and their eww-bar launcher buttons, autostart
hooks, and O-bind wrapping) were built inside hyprlua before hyprdrive
existed as a separate DE. hyprdrive is now the home for the full Cosmonaut
Shell suite, so hyprlua drops back to eww bars + vicinae launcher + the
astal-menu pop-open menu, unchanged, matching last Thursday's state.
hyprdrive itself is untouched.
New leaner Hyprland DE (setup/modules/Desktop-Environments/hyprdrive.sh),
a sibling of hyprlua that drops the older EWW/nwg-shell/wofi/walker/
ulauncher stack in favor of a purpose-built, GTK4 layer-shell component
suite sharing one CyberQueer hologram visual language:
- orbit-menu radial power/utility menu (focal-point zoom nav, holographic
overlay, cosmetic satellites)
- horizon-dock hover-scrollable orbital app dock (open windows/favorites/
all apps orbits, SNI tray satellite)
- astro-menu astal-menu re-themed as a hologram info display (Location/
Weather/Bluetooth/Network quads, taskbar, app drawer)
- station-bar the EWW top bar's replacement ("Voidstation Status Bar"):
a thin curved layer-shell bar tracking Hyprland workspaces/
active window natively over .socket2.sock, no polling
Every component now shares one consistent hologram treatment: translucent
violet panel/node fills (replacing solid gray), a config-toggleable
scanline+sweep+noise overlay (lib/hologram.py, ported to horizon-dock and
station-bar to match orbit-menu/astro-menu), and glow-on-hover throughout.
Also adds horizon-dock to hyprlua (built there first, before hyprdrive
existed) and wires dependency checks, config-updater entries, and
apply-theme.sh color sync for every new component across both DEs.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Nu8JkQ95hPAT8vADeGDAmL
New GTK4 layer-shell radial menu (desktopenvs/hyprlua/orbit-menu/), following
astal-menu's single-instance D-Bus daemon conventions. Super+Shift+O opens
straight into a Power ring (sleep/reboot/poweroff/soft-reboot via hyprshutdown);
Super+Ctrl+O opens the full 5-category root (Power, Tools, Scripts sourced live
from ~/Documents/Scripts, Management, and a live open-Windows list with
go-to/bring-here/close/force-kill/get-pid/send-to-workspace actions). Adds a
round launcher button to all 3 eww bar variants with matching glow/hover CSS.
Also retunes hypridle (lock 30s, sleep 1min) and adds a HibernateDelaySec
systemd-sleep.conf.d fragment (deployed by sysupdate.sh) so the machine
hibernates ~80 minutes after going idle instead of drifting to sleep indefinitely.
hypr-nav fell back to the raw "lua:columns" backend string on any
workspace that had never been explicitly switched via the menu (i.e.
every fresh workspace, since columns is the global default), which
didn't match the "columns" layout name it checks against — so focus/
move/resize silently took the generic dwindle-style path instead of
being forwarded to the custom layout. Strip the "lua:" prefix in the
fallback.
Also make focus/scroll u/d within a column wrap around instead of
stopping at the ends.
presence-detect.sh no longer mirrors the camera through a v4l2loopback device,
so the module setup is dead weight. Remove it:
- delete etc-v4l2loopback/ (modprobe.d + modules-load.d configs)
- drop the deploy blocks and linux-headers/v4l2loopback-dkms/v4l2loopback-utils
package installs from the niri and hyprlua DE installers
- remove _ensure_v4l2loopback() and its call from sysupdate.sh
- reword enroll-biometrics.sh's loopback filter comment (kept as a harmless
defensive exclusion) so it no longer points at the deleted config
Live system cleanup (module unload + /etc configs) is separate and needs root.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Before each tick, check whether another process already has the real camera
open (fuser, which sees same-user holders without root — every camera app runs
as us). If so, skip the read entirely for that tick: attempting would be
pointless (we'd get EBUSY) and a simultaneous open could make the other app
fail. A camera in use is proof the user is present, so mark presence and wait
it out, touching the camera zero times for the whole duration another app holds
it — instead of poking it every tick and racing the app for access.
V4L2 has no way to be notified that another app wants the device, so this
can't preempt an in-progress read; it just guarantees we never contend while
another app is already streaming. Falls through to a normal read (and the
Python helper's exit-3 busy path) if fuser isn't installed.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
When idle is inhibited manually (a caffeine toggle, not this daemon), the lock
is held regardless of presence, so polling the camera is pointless. Guard the
loop: if the shared idle lock is alive but we don't own it (same test as
caffeine-manual-status.sh), skip the read and leave the camera free for other
apps for the whole manual session. PRESENCE_FLAG is cleared while skipping,
since we're no longer watching — so the widget's presence input reads false
during a manual session instead of showing a stale "camera sees you".
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The loopback feeder (defa69f) held /dev/video0 open 24/7, so real apps —
video calls, howdy — could never open the camera, and the single loopback
device only ever served one reader at a time (a second consumer got
VIDIOC_REQBUFS -EBUSY). Net effect: the camera was permanently blocked and
nothing could access it.
Drop the ffmpeg mirror and read the physical camera directly, only for the
~0.5s per tick it takes to grab frames, then release it — so the camera is
free for other apps the rest of the time. When another app is already holding
the camera, the Python detector now distinguishes "busy/in use" (device node
present but unreadable → exit 3) from "unavailable" (node gone → exit 2), and
the daemon treats busy as presence: a camera in use is itself proof the user
is here, so a video call keeps the session awake.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Rework the Eww caffeine widget into two independent inputs — manual inhibit
and presence detected — yielding four states across all three bars:
neither → sleep, purple
manual only → ☕ coffee, purple
presence only → eye, pink
manual + presence → ☕ coffee, pink
Colour tracks presence; the coffee cup shows only while manually inhibiting.
To make the two inputs independent, the daemon now writes a standalone
/tmp/presence-detected flag each tick (decoupled from lock ownership, so it's
true even during a manual session). presence-status.sh reports that flag
(presence input); new caffeine-manual-status.sh reports lock-held-and-not-
presence-owned (manual input).
Make detection less finnicky: space the detector's frame grabs (0.06s) so slow
movement registers and loopback reads never diff a stale duplicate frame, and
lower the thresholds (delta 25→18, area 2%→0.8%). Add a 90s grace window so
brief stillness never flickers presence off; any motion resets the clock.
Add a third poll tier — INTERVAL_FAST=2s when CPU-or-RAM usage is below 30%
(LOAD_LIGHT) — alongside the existing 20s normal and 120s busy tiers.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Add presence-status.sh reporting true only when the daemon (not a manual
caffeine toggle) owns the shared idle lock, via the existing OWNED_FLAG.
Wire it into all three eww bars: the caffeine button gains a
.caffeine-presence class → the #E40046 Active accent used elsewhere, so
camera-driven auto-awake is visually distinct from a manual session.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
presence-detect.sh now feeds the real webcam into a v4l2loopback virtual
device (/dev/video42) via a self-healing ffmpeg background process, and reads
motion off that mirror instead of opening the physical camera directly. This
means the daemon's 20s-interval checks never contend with other apps (video
calls, howdy) for exclusive camera access. Falls back to direct camera access
if the loopback isn't set up yet, so the feature degrades gracefully rather
than breaking.
New etc-v4l2loopback/ ships the modules-load.d/modprobe.d config (fixed
video_nr=42, exclusive_caps=1) following the repo's existing etc-greetd/
etc-lightdm/ convention. Deliberately no runtime sudo/modprobe calls inside
the daemon itself — the module loads declaratively at boot instead, matching
this repo's existing caution around unattended sudo.
hyprlua.sh and niri.sh installers now pull in linux-headers,
v4l2loopback-dkms, and v4l2loopback-utils and deploy the module config
immediately. sysupdate.sh gained _ensure_v4l2loopback(), wired into
sync_configs(), so existing machines pick up the new packages and /etc
config on their next --config or --both run without needing a fresh install.
enroll-biometrics.sh's camera picker now excludes the loopback device itself
(by card_label) so configuring the real camera can't accidentally target the
mirror it feeds.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Replace the Haar-cascade face detector with lightweight frame-differencing
motion detection so the daemon can poll every 20s (backing off to 120s under
heavy CPU/RAM load) without adding noticeable overhead.
Presence detection now shares caffeine's systemd-inhibit lock instead of
holding its own, so the caffeine status widget reflects either source. A new
ownership flag ensures the daemon only ever releases a lock it acquired
itself, so a manually-started caffeine session is never interrupted by the
user stepping away. Also re-enables the daemon in hyprlua's autostart (was
previously only wired up for niri).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Root cause of "can jump one way across monitors but not back" on a
desk with a rotated monitor: both columns.lua's own edge-crossing and
the fallback this adds go through Hyprland's focusmonitor/movewindow
mon: -1/+1, which cycle through Hyprland's internal monitor *list
order* — not actual spatial position. A monitor's transform swaps its
logical width/height (same math as monitor-manager's logical_width/
logical_height) but doesn't reorder that list, so list order and
physical left-to-right order can silently diverge once one monitor is
rotated — confirmed with the reported layout (main 3840x2160 normal +
rotated 1920x1080 to its right): jumping right resolved correctly,
jumping back left did not.
scripts/monitor-adjacent replaces the index cycling with real spatial
adjacency: it sorts monitors by logical bounding-box center each time
and dispatches focusmonitor/movewindow to the actual next/prev monitor
in the requested screen direction. Verified against the exact reported
monitor layout (simulated, transform=1 on the right monitor) — both
directions now resolve to the correct monitor.
Wired in two places (Super+,/. is untouched, still index-cycling, by
request):
- columns.lua's edge-crossing (off the left/right edge of the column
strip already flowed onto the next monitor; now via spatial lookup)
- hypr-nav's scrolling-layout perpendicular "no neighbour" case, as a
new fallback: regular directional movement (Super+hjkl) that
previously just stopped at the edge of a monitor now spills onto
whichever monitor is spatially in that direction, same as columns.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TLz6HWsXCwzQ97LrLt2em6
menu-toggle.sh always cold-started a second python3+PyGObject+GTK4
process to forward one verb to the already-running daemon, per
GApplication's command-line-forwarding mechanism. That's ~0.5s
baseline and spikes past 5s under load, easily read as an
unresponsive/broken menu button on repeat taps.
main.py's own do_command_line comment already describes the intended
fix: call the registered org.gtk.Actions directly over D-Bus for an
already-running instance. Wire that up (gdbus call, ~20-100ms) and
keep the python3 spawn only as the cold-bootstrap path when no
instance is registered yet.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TLz6HWsXCwzQ97LrLt2em6
Overhaul the custom "columns" Lua layout and its navigation wiring:
- Flexible sizing: per-column width weights + per-window height weights
(Super+Alt resize), scale-to-fill when a column is underfull, scroll/pan
when overfull. Drops the old fixed viewport/rows window heights.
- Window parking: move u/d within a column parks two windows side by side in
one slot (persistent, split across the column axis with a divider parallel
to the scroll direction; the column closes up so there is no gap); moving a
parked window again pops it out past its partner.
- New windows stack into the working column (with a balance guard) instead of
scattering, so two windows share a column/height by default.
- Cross-monitor focus/move at the column edges (relative +1/-1 selectors).
- hypr-nav: adds resize + scroll kinds; Super+wheel travels along the focused
strip (column / tape) and falls back to workspace switch elsewhere.
- Super+Alt arrows/hjkl route resize through the layout; Super+rightclick
auto-floats then resizes ONLY in columns (no pointer-resize hook for custom
layouts) and is a plain resize everywhere else.
- Reload-safety: hyprctl reload dropped per-ws layout rules + Lua state,
silently reverting columns workspaces to scrolling. init.lua now replays
layouts-state.json (M.restore) and columns.lua re-seeds cols/rows/orient.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- Expand/collapse controls now use consistent fullscreen ()/unfullscreen ()
glyphs: the quad "Back" and the taskbar-panel "Back" became buttons (matching
the appdrawer's - toggle).
- The taskbar panel takeover now plays the same bouncy quad-pop scale as the quads
when it expands, so every expansion in the menu is animated.
- monitor-manager (curses) and timer-pick (bash TUI) now use the CyberQueer accent
palette (red #E40046 + violet #5018dd, true-colour with ANSI fallback) instead of
cyan/green, matching the rest of the config.
- Added a .stepper-value style for the Columns count stepper.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SUN7gg6GGfnToghMijLm5Y
Replaces the scrolling-preset "columns" with a genuine custom tiling layout
(hl.layout.register "lua:columns"): a row of columns where each column is an
INDEPENDENTLY panning vertical strip. Moving focus up/down inside a column pans
only that column to keep the focus in view; the other columns stay frozen — the
combination-lock behaviour that the built-in scrolling engine (single shared tape
offset) structurally can't do.
- columns.lua: recalculate places every window box; per-workspace state holds each
window's column assignment and each column's pan offset. layout_msg handles the
focus/move/count/orient commands. New windows fill the least-populated column;
changing the column count reflows evenly.
- init.lua: for Lua layouts (lua = true), orientation is sent via layout_msg
("orient h|v") instead of the global scrolling:direction option.
- hypr-nav: forwards focus/move/count for the columns layout to hl.dsp.layout(...);
the built-in scrolling path is unchanged.
- binds.lua: Super+Ctrl+Alt+H/J/K/L manage the column / windows-per-view count
(axes follow the workspace's horizontal/vertical orientation).
Menu dropdown for "Columns" now selects orientation (Horizontal / Vertical).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SUN7gg6GGfnToghMijLm5Y
astal-menu: the taskbar ⤢ toggle now collapses the compact strip and mounts
its workspace/window panel as a takeover overlay covering the 2x2 quads (with
a Back button), instead of a popover/inline reveal. QuadGrid gains a generic
takeover overlay; the menu window coordinates show/hide and resets on close.
hypr: new "Columns" layout (scrolling backend presented as independent columns)
with a menu dropdown to pick orientation — "Left / Right" or "Up / Down" — via a
new dir_labels manifest field. The layout menu now tracks direction dropdowns and
fit switches per-layout, so a second directional layout no longer clobbers the
first.
binds/scripts: focus & window-move keys (hjkl + arrows) route through a new
hypr-nav helper so they stay screen-absolute in the scrolling/columns layouts
(the layout otherwise resolves a perpendicular direction onto "scroll the tape",
rotating the keys with scrolling:direction). Adds explicit cross-monitor binds:
Super+,/. focus prev/next monitor, Super+Shift+,/. move the window there.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SUN7gg6GGfnToghMijLm5Y
- Add a minimalist system-stats line at the top of the panel: CPU / MEM / GPU / disk
utilisation + network down/up rate, read straight from /proc and /sys on a 2s timer
(no subprocess). ui/statsbar.py.
- Opening side is selectable: `main.py --side top|bottom|left|right` (also via
`menu-toggle.sh <verb> <side>` or just `menu-toggle.sh <side>`). The panel anchors
flush to that edge, and the compositor's layer slide brings it in from there.
Unified all layer anchoring into MenuWindow._apply_anchor.
- App drawer now does a bouncy pop-expand (CSS scale, play-once guard like the quads).
- Tighten the action-pill buttons vertically (padding 6->1px, min-height 32->22) so
they stop wasting vertical space.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XUWCXM4KhjRkwheaA3X7bP
astal-menu-start.sh LD_PRELOADs libgtk4-layer-shell so it loads before
libwayland-client. That preload is only needed at the daemon's own exec — the
library is resident afterwards and the variable is never re-read — but every app
started via AstalApps.launch() inherited it, and Firefox aborts at startup with
libgtk4-layer-shell preloaded (gdk_display_manager_get() before gtk_init()).
main.py now drops LD_PRELOAD from its environment right after startup, so
launched GUI apps (and backend subprocesses) run with a clean env. Verified the
layer-shell library stays mapped and the menu surface still maps afterwards.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XUWCXM4KhjRkwheaA3X7bP
A touch-friendly GTK4 popup control centre (Python + PyGObject, wlr-layer-shell)
launched from the EWW bar or Super+D. Replaces nwg-dock and nwg-drawer.
Layout: a floating, drawn-bordered panel (anchored top-centre so it never blocks
the rest of the screen) with a 2x2 quad grid over a full-width app drawer, plus a
top taskbar. Any quad expands over the others; the drawer expands to the bottom.
Modules:
- Location — static OSM map (backend/staticmap.py) on IP geolocation
(libshumate won't paint tiles in this env; shumate-demo is blank too)
- Weather — wttr.in ANSI art rendered via an SGR parser into a TextView
- Bluetooth — AstalBluetooth: discovery/connect/disconnect + local history
- Network — AstalNetwork wifi + nmcli/ip/ss for IP, DHCP/manual, routes, ports,
public IP; per-feature toggles
- Taskbar — open windows from hyprctl, grouped by app with pop-out per instance
- Favorites — pinned apps (right-click/long-press to pin) atop the app drawer
Notes:
- Frontend is Python/GTK4, not Lua: lgi/Astal-Lua are GTK3-only.
- Module borders are drawn with a Cairo overlay (lib/border.py); this GTK build's
renderer skips CSS border/background on plain container widgets.
- Consumes Astal GObject libs (io/apps/network/bluetooth) via introspection.
Wiring: autostart + Super+D/Super+Shift+A binds, EWW launcher button in all three
bar variants, apply-theme.sh (_colors.css), installer packages, config-updater.
drawer.sh moved to scripts/deprecated/.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The dialog was landing off-cursor and at ~half-screen. Two Hyprland
gotchas were fighting each other: `move` is evaluated against the
window's natural size and `size` is applied afterwards from the top-left,
so a cursor_x-(window_w/2) move centred the pre-shrink window. zenity's
file chooser also ignores --width/--height and has a ~738x363 GTK
minimum.
Fix the size at 760x460 and hard-code the move offsets to half that
(380x230) so the shrunk window lands exactly on the pointer. Drop the
dead zenity --width/--height sizing from the script.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Add filepicker-clipboard.sh: a zenity file dialog that copies the
selected file's path (Super+F) or contents (Super+Shift+F) to the
clipboard via wl-copy. Path mode collapses $HOME to ~.
Repoints Super+F / Super+Shift+F from wofi-file-search/foldersearch
(kept as comments) in usr/binds.lua.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Diagnosed live in a VM: the bar rendered twice (two stacked rows). `hyprctl
layers` showed two `gtk-layer-shell` bar surfaces owned by two different eww
pids — an `eww daemon` and an `eww open bar`. The monitor count was correctly 1,
so the per-monitor loop was not at fault.
Root cause: ewwstart.sh started `eww daemon` and then immediately ran `eww open`
in a loop. The first `eww open` frequently ran before the freshly-started daemon
was ready, so it spawned its OWN second daemon and drew a second bar.
Fix (all three DE variants — hyprlua, hyprland, niri):
- Drop the separate `eww daemon`; rely on `eww open` to auto-start the daemon and
block until the window is mapped, so the first call establishes a single daemon
that the rest reuse (no race, no second daemon).
- killall eww + short settle before opening, so a reload starts clean.
- hyprlua/hyprland: count monitors via `hyprctl monitors -j | jq length` instead
of the fragile `hyprctl monitors | grep ID | wc -l`.
- export GTK_THEME (the bare assignment never took effect).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01R5kHioUMK3mtf2eiLEozCM
Target ~/.config/hypr/usr/monitors.lua (the config Hyprland actually
loads) so `hyprctl reload` applies layouts immediately; the Dotfiles
repo copy stays the deploy source and is no longer written.
Add overlap geometry helpers and integrate them into the apply flow:
- block moves that would drive a monitor into a neighbor (TUI coords)
- snap positions to the MOVE_STEP_FINE grid to avoid frozen digits
- auto-resolve snap-induced collisions by re-reading the live layout
and nudging the moved monitor clear, up to MAX_RESOLVE_ITERS
- warn on residual overlap after apply and after save/reload
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
logical_width/height now divide physical pixels by scale so the canvas
correctly represents the compositor's coordinate space (e.g. 3840px @1.5x
is 2560 logical px wide, not 3840).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Replaces fixed 0.25 increments with mathematically valid scales p/q
(lowest terms, q≤6) where both width/s and height/s are integers.
For 1920x1200 this gives 25 steps including 2.4, matching what
Hyprland actually applies — no more mismatch between TUI display
and live value.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
hyprctl returns mirrorOf:"none" (string) for non-mirrored monitors.
Python treated it as truthy, causing apply_monitor to always emit a
mirror command, resetting resolution and scale on every keypress.
Also restores monitors.lua with correct mode/scale/transform fields.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Python curses TUI for managing Hyprland monitors interactively:
- Canvas shows monitors as boxes at their real relative positions
- Tab/Shift+Tab to cycle selection; hjkl/HJKL to move (50/10 px)
- u/i to rotate CCW/CW; n/N to cycle display modes live
- m to mirror (pick target with Tab, confirm with Enter)
- s saves to hypr/usr/monitors.lua atomically
- Scale cached and only recomputed on resize or viewport overflow
- Bound to Super+Shift+M as a centered-L floating kitty popup
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- caffeine: inhibit only `idle`, not `sleep`, so lid close still locks
- binds: lid-close unconditionally calls hyprlock; lid-open does dpms on
- hypridle: reduce lock timeout from 3 min to 2.5 min (150 s)
- eww (all 3 variants): add caffeine toggle button (☕/) with tooltip
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The Brio (and most UVC webcams) expose a second /dev/video* node that is
metadata-capture only. The chooser listed it, the existence-only guard
accepted it, and howdy aborted with a bare exit 1 because that node can
never deliver a frame.
- list_cameras: skip nodes with no capture pixel formats (is_capture_device)
- howdy_camera_ok: require an actual capture node, not just an existing path,
so a stale metadata-node device_path triggers reconfiguration
- howdy_add: tee howdy's output and surface the real reason on failure
instead of only the exit code
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Enrollment failed because howdy's own config.ini kept device_path=none,
so VideoCapture aborted before ever opening the lens (generic exit 1).
The presence-detect camera setting is separate and never reached howdy.
- add a "Howdy face auth — configure camera" menu entry that writes
device_path into /usr/lib/security/howdy/config.ini
- guard howdy_add: detect an unset/stale device_path and offer to fix it
before enrolling, instead of surfacing howdy's opaque exit 1
- shared camera chooser shows a live still from the selected node via
kitty's icat (gated on KITTY_WINDOW_ID), used for presence + howdy
- support non-IR colour webcams: detect IR vs colour from the preview's
saturation and tune recording_plugin (ffmpeg) + dark_threshold per type
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
howdy 2.x installs its CLI under root-only /usr/lib/security/howdy/ with
/usr/bin/howdy symlinked into it, so `command -v howdy` reports "not found"
for a normal user — the script wrongly thought howdy wasn't installed and
tried to reinstall it. Detect via the symlink and pacman -Qq instead; all
ops already run through `sudo howdy`.
This version also has no pam_howdy.so — it authenticates via pam_python.so
loading /usr/lib/security/howdy/pam.py. Switch the emitted PAM block to that
module and add pam_python_require() to install the AUR `pam-python`
dependency, replacing the dead pam_howdy.so existence check.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
enroll-biometrics.sh used non-existent howdy flags and a broken install
path. Fixes and additions:
- howdy add: label has no flag; feed it via stdin, use -U "$USER"
- howdy remove: id is positional, not -I
- list/test: pass -U "$USER" so all ops target the same account
- install: howdy is AUR-only, so the pacman fallback could never work;
require an AUR helper (yay/paru) and message clearly if absent
- new PAM 2FA menu: enroll FIDO key + wire howdy + pam_u2f into
sudo/hyprlock/login (both factors required, password fallback kept)
- hyprlock gets its own clean fallback (include system-auth) so the
block is not re-run via include login
- idempotent, timestamped backups, and a symmetric teardown option
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Start chamel as a daemon in autostart so keybinds (toggle/clear/
clear-and-deactivate) work on first use. Fix stale hyprland script
paths in frequentcommands.list and add all utility scripts.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Add presence-detect.sh daemon: checks webcam every 2 min via OpenCV haar
cascade (presence_detect.py); holds systemd-inhibit --what=idle lock while a
face is detected so hypridle never fires during an active session
- Add enroll-biometrics.sh: Cyberqueer dialog TUI for camera configuration/test
and howdy face auth enrollment (add/list/remove/test models)
- Rewrite caffeine.sh (hyprlua + niri): replace kill/restart of idle daemon with
systemd-inhibit --what=idle:sleep + PID file; idle daemon stays running
- Fix hypridle.conf: correct ;; → ; in after_sleep_cmd, add
ignore_dbus_inhibit=false, bump lock timeout 120s→180s to account for the
2-min presence detection cycle
- Wire Super+Shift+B keybind for enrollment TUI in both hyprlua and niri
- Fix niri/scripts/python: was real dir with inner symlink causing cp -rL to
create a nested python/python/ hierarchy; replaced with direct symlink
- Add python-opencv and v4l-utils to hyprlua + niri installers
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
wallpaper-picker now calls write_hyprlock_state() on apply, writing
per-monitor background blocks to ~/.config/hypr/hyprlock-backgrounds.conf.
hyprlock.conf sources that file instead of a hardcoded background block,
with a committed default for first-boot.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Tablet devices require output= in hl.device() for transform to take
effect, unlike touchscreens which auto-bind to their display.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Replace hyprctl dispatch exec with direct daemon invocation in caffeine.sh,
and replace defunct \$LAPTOP_KB_ENABLED .conf variable with the actual
device key path in hyprland-toggle-touchpad.sh.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Duplicates desktopenvs/hyprland/ as desktopenvs/hyprlua/ and converts all
Hyprland-specific configs (.conf) to Lua (.lua) using the 0.55+ hl.* API:
hyprland.lua, envvars.lua, monitors.lua, input.lua, autostart.lua,
windowrules.lua, binds.lua. Non-Hyprland tool configs (hyprpaper, hyprlock,
hypridle, hyprtoolkit) remain as .conf. Adds hyprlua.sh installer (user-side
.lua files install to ~/.config/hypr/ for require() resolution) and registers
HyprLua as the recommended DE option in tui-install.sh, marking the old
hyprlang-based Hyprland install as legacy.
Also consolidates hyprland (legacy) env vars into hypr-usr/envvars.conf,
removing duplicates from hyprland.conf and monitors.conf.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>