aboutsummaryrefslogtreecommitdiffstats
AgeCommit message (Collapse)AuthorFilesLines
8 daysfeat(keybinds): show where a bind sits on the macropadDanilo M.4-5/+120
The macropad's firmware sends ordinary combos, so the overlay listed them as keyboard shortcuts nobody presses. A PAD table in Binds.js maps each combo to its key (1-12) or encoder (top or bottom, turned or pressed) and what is held with it, and the row draws the pad small with that key lit. The workspace row is built in a loop, as it is in keybindings.lua. Every entry was checked against hyprctl binds -j. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
8 daysfeat(keybinds): show mnemonic names for obscure keysDanilo M.3-10/+38
Hyprland's key names are its own (mouse:272, XF86Calculator), not what is printed on the keyboard or how the key is pressed, which defeats a reminder. A fixed map in Binds.js renames them for display only; an unlisted key shows as before. SUPER reads as Win for the same reason. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
8 daysfix(keybinds): fill columns top to bottomDanilo M.1-2/+9
Groups flowed left to right in rows, so each row was as tall as its longest group: short groups sat above empty space and the block ran past the top and bottom of the screen. Columns filled to the screen's height pack the groups and keep everything visible. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
8 daysfeat(keybinds): live keybind reminder overlayDanilo M.6-3/+234
Modelled on window-switcher: a Scope with a 1x1 keepalive PanelWindow plus a LazyLoader'd overlay, opened via IpcHandler (SUPER+k is bound in a different repo). hyprctl binds -j is asked fresh on every opening so the list matches whatever Hyprland has loaded, with Binds.group() from task 1 turning it into the grid's {name, rows} groups. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
8 daysfix(keybinds): survive group names shadowing Object.prototypeDanilo M.2-1/+9
A description like "toString: X" made byName["toString"] resolve to the inherited Object.prototype.toString instead of undefined, so the group was never created and .rows.push threw. Object.create(null) gives byName no prototype to shadow. Also note in a comment that CAPS/MOD2/MOD3/MOD5 modmask bits are not decoded and silently drop from the combo string. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
8 daysfeat(keybinds): group hyprctl binds by descriptionDanilo M.2-0/+95
Binds.js parses hyprctl binds -j output into display groups for the upcoming keybind reminder overlay. Group name and row label come from the "Group: Label" convention in each bind's description; a bind without one still surfaces under "Undescribed" rather than vanishing, so a gap in the Hyprland config stays visible. Pure functions with no QML dependency, so test_binds.js checks them directly with node, stripping the .pragma library line the QML runtime requires but node does not understand. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
8 daysdocs(keybinds): implementation planDanilo M.1-0/+587
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
8 daysdocs(keybinds): design for a live keybind reminderDanilo M.1-0/+64
The list comes from hyprctl binds -j on every opening, so it is always what Hyprland has loaded, loop-generated binds included. Labels come from each bind's description option, since hyprctl reports Lua actions as an opaque __lua handle. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
8 daysfeat: reload.sh restarts quickshell componentsDanilo M.2-0/+44
The volume OSD drew only its "Output 72%" label, with the icon, bar and device line clipped below the panel edge. The code and the theme were not at fault: a hot reload fixed it, and a debug line logged just after showed the volume row laid out correctly at the top of the panel. The stuck state lived in the running process, so the fix is a restart. quickshell has no reload subcommand and no IPC target for it, and a hot reload needs a real content change to a file. reload.sh kills and restarts each named component, or every directory holding a shell.qml, and prints the qs count afterwards, since the process is qs and not quickshell. It must run from the user's terminal. A detached qs started from an agent's tool call dies with the call, which would leave the desktop without its shells; AGENTS.md now says so under Reloading. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
8 daysfeat(vm): notify when a started VM accepts sshDanilo M.1-0/+28
libvirt reports "running" as soon as QEMU is up, long before the guest has booted, so the panel had no way to say when a VM is actually usable. After a successful start, a detached script polls the VM's sshd and sends a notification once it accepts connections, or a warning after five minutes. Host and port come from `ssh -G <vm>`: a Host alias named after the VM may put sshd on a non-default port (buildsystem listens on 2290, and a probe of port 22 never succeeded). Without an alias `ssh -G` echoes the name back, and the guest agent's IPv4 address on port 22 is used. The probe is a plain `nc -z`, so no authentication or key is involved. Detached rather than a Process so several VMs can wait at once and the wait survives a shell reload. Only starts issued from the panel are watched; a VM started from virt-manager or the CLI is not. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
12 daysfeat(status): report a running game as its own presentation stateDanilo M.3-3/+92
status.gaming is written by the shell's game detector and presentation is the OR of it and the manual file, but statusctl read only the manual half. A game therefore asserted the presentation effects while the waybar pill still read deactivated. Report gaming as a third state, for presentation only. The watch follows status.gaming as well as its own file, and emits only on a real change so an unchanged rewrite draws nothing. Toggle is refused while a game holds the mode: the detector reasserts the file within its poll, so the write would not stick. set stays allowed, the manual half is worth setting for when the game exits. gaming stays out of MODES. The detector owns that file and nothing here may write it. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
12 daysfeat(status): detect PCSX2 for game modeDanilo M.2-7/+7
PCSX2 joins gamescope, Steam and DuckStation in the game detector, so presentation is set automatically while a PS2 game runs and the idle lock, DND and breaktimer stay out of the way. The binary is /usr/bin/pcsx2-qt, the same -qt shape as duckstation-qt, so it takes the same pgrep -x exact name match and cannot self-match the way the bracketed steam pattern has to guard against. Verified against the real emulator rather than a stand-in: with pcsx2-qt running, status.gaming reads 1. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
13 daysfeat(status): auto-present while DuckStation runsDanilo M.2-8/+11
Game detection probed only gamescope and Steam, so a standalone DuckStation session never turned presentation on. Add a third probe for the duckstation-qt process. pgrep -x rather than the bracketed pgrep -f the Steam path needs: this is an exact match on the process name, so it cannot match the sh running the check, and the binary name is under the 15 character comm limit so it is not truncated.
13 daysfeat(desktop): dismissable bluetooth pairing error on the pageDanilo M.2-5/+52
A failed pairing showed the raw bluetoothctl output as a plain red line that only cleared on the next attempt, and the desktop notification is suppressed while the drawer is open, so the failure was easy to miss and impossible to dismiss. Show it in a red card with a close control that clears pairing.error. It has no timeout, and clearing the source means the next identical failure shows again rather than staying hidden.
13 daysfeat(desktop): read gamepad battery from power_supplyDanilo M.3-2/+81
BluetoothRow only read BluetoothDevice.battery, which is BlueZ's org.bluez.Battery1. A gamepad never publishes that interface: a Sony pad exposes its charge as a kernel power_supply instead, so the percentage never appeared. Read every power_supply whose name ends in a MAC on a 30s timer while anything is connected, keyed by address. BlueZ still wins when it has a value.
2026-09-19feat(status): auto-present while a game runsDanilo M.2-2/+77
Presentation is worth having automatically while a game runs: it is a fullscreen app that already holds the screen, and the idle lock, DND and breaktimer are all unwanted during it. Detection is a 5s poll for a gamescope process or a Steam game binary, either one enough. The Steam pattern is bracketed so pgrep -f cannot match the sh running the check and report a game forever. The result goes to its own status.gaming file, and presentation becomes the OR of it and status.presentation. That separation is the point: a game ending clears only the gaming half, so a presentation set by hand survives a game session instead of being clobbered on exit.
2026-09-19feat(kdeconnect): open the KDE Connect window from the pageDanilo M.3-2/+12
The drawer absorbed the indicator's device actions but not the settings window, so there was no path to the full KDE Connect app. Add an Open app button beside Refresh, launching kdeconnect-app through execDetached so the window outlives a quickshell reload.
2026-09-19feat(desktop): press feedback on the shared buttonsDanilo M.3-5/+13
The hand-rolled buttons only reacted to hover, so a press gave no visual confirmation until the action's outcome appeared. kdeconnect made this obvious: its row buttons shell out to kdeconnect-cli, and nothing on screen acknowledged the click. Button.qml now fills solid accent while the mouse is held (red for danger), with the label flipped to base for contrast. TransportButton gets the same treatment, and the window-switcher close button lightens on press. Toggles, tiles and tabs are left alone: their own state change is already the feedback.
2026-09-18fix(status): only pause/resume a running breaktimer daemonDanilo M.1-2/+4
breaktimer.sh's pause and resume arms write the state file whether or not a daemon is running, so statusctl presentation set while the daemon was stopped recorded a phantom paused/running state and the tile then showed Paused or a frozen countdown for a daemon that does not exist. Gate both verbs on btRunning, the singleton's running-or-paused derived state, so a verb is only sent when a daemon actually exists.
2026-09-18docs(status): list stopped in the phase domainDanilo M.1-1/+1
2026-09-18docs(status): document the breaktimer read directionDanilo M.1-3/+41
The one rule worth writing down is which side writes: the daemon owns its files and the shell only calls verbs.
2026-09-18feat(status): show the breaktimer countdown on the tileDanilo M.1-1/+8
Below every mode in the priority chain, so an active mode still owns the line and breaktimer replaces only the idle 'All clear'. It does not count toward activeCount. A running breaktimer is not a mode the user switched on, and accenting the tile for an ordinary working day would read as a mode stuck on.
2026-09-18feat(status): a breaktimer row in the status pageDanilo M.2-0/+104
Into the status module rather than a module of its own: break state is one more thing the desktop is doing, which is what that page already is, and Status.qml was already where breaktimer.sh is called from. The paused state shows no number. The countdown is frozen while paused and a frozen number reads as a bug, so the row says what it is instead.
2026-09-18feat(status): read breaktimer phase, state and countdownDanilo M.1-0/+74
The daemon already publishes these three files under XDG_RUNTIME_DIR and waybar already consumes them, so a second consumer costs three FileViews and invents no interface. Read only, deliberately. The daemon loop rewrites state and phase on every transition, so a second writer would race it, which is why presentation mode has always called a verb rather than writing the file. The countdown does not interpolate between the daemon's five second writes: a local clock would drift against it and keep ticking while the daemon is frozen outside the work window.
2026-09-18docs(status): implementation plan for breaktimer controlDanilo M.1-0/+1259
Nine tasks over two repos. The daemon half gains a sourced config file and a config verb that prints the effective settings, which is what makes the config testable without running a thirty minute work block. The shell half reads the daemon's three runtime files, adds a row and a tile line. Three assumptions were probed rather than trusted while writing it, each with a precedent for biting here: padStart exists in this QML engine (matchAll does not), a Connections resync fires correctly on a singleton property, and the config source line leaves unnamed variables alone. The qs verification steps redirect rather than pipe. Piped output is lost when timeout kills the process, which reads as a silent success, and that failure happened twice while probing. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-18docs(status): spec breaktimer control and a config fileDanilo M.1-0/+221
The daemon already publishes its phase, state and countdown as files under XDG_RUNTIME_DIR, and the status module already shells out to it for presentation mode. Reading those files makes the traffic two-way for the cost of three FileViews, with no new interface to invent. Into status/ rather than a module of its own: breaktimer state is one more thing the desktop is doing, which is what that page already is, and Status.qml is already where breaktimer.sh is called from. The config file is the other half, and stands alone. Every tunable is a shell variable in a tracked script, so the installed copy and the committed one have already drifted over three sound paths pointing into a home directory. One sourced file fixes that with no parser and no format. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-18fix(appearance): drop the live cursor hover previewDanilo M.1-33/+5
Hovering a cursor card called `hyprctl setcursor` to switch the real pointer, and reverted it on unhover. That looked read-only and was not: Hyprland's setcursor handler also writes the theme to org.gnome.desktop.interface cursor-theme, so every hover persisted a setting the user had not confirmed. Closing the panel with the pointer over a card left that theme set, surviving both the shell and the session. A `dconf watch /org/gnome/desktop/interface/` during a hover across three cards showed the key alternating between each hovered theme and the current one, one write per hover and one per revert. `strings` on the Hyprland binary carries both `org.gnome.desktop.interface` and `cursor-theme`; `hyprctl` carries neither, so the write is the compositor's, not the caller's. The revert could not be made reliable either. It ran through `execDetached`, which gives no ordering, so a fast card-to-card move could land the revert after the new set. Guarding it by lifecycle did not help: `Component.onDestruction` fires too late to be certain, and the same spawn race applied to Apply, which cleared the hover before `applyCursor` and so raced the old theme against the new one. None of that is fixable while the preview exists, because the side effect belongs to the compositor. The four rendered shapes on each card were always the real preview; the live switch was a bonus that could not be had without writing the setting. Remove it. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-18feat(sound): show the active device name in the volume OSDDanilo M.1-2/+12
The OSD showed only "Output"/"Input", not which device was in use, so switching between, say, the analogue output and a USB DAC gave no visible confirmation beyond the volume itself. The name falls back description -> nickname -> name, the same order SoundPage.devLabel uses, and elides. The panel height grows to fit the new line.
2026-09-18feat(status): add a screen lock modeDanilo M.7-14/+201
dnd and presentation were the only modes, and presentation was the sole holder of the Wayland idle inhibitor. That inhibitor is the only thing that stops hypridle's loginctl lock-session at the 600s timeout, so screen locking is on by default and there was no way to turn it off on its own. nolock is stored as "auto-lock disabled" so the file keeps the registry's "missing means off" rule, and the row inverts it so the switch reads as the default: lock on. Presentation now drives nolock the same way it drives dnd, holding the prior value in memory and restoring it on exit, so it needs no second inhibitor and the inhibitor has a single holder. The page sets the disable permanently or for a number of minutes. The deadline and its timer live in the singleton rather than the row because the drawer page is destroyed on close while the effect has to outlive it. The timer is shell-lifetime, the same class of limit as dndBeforePresentation: a restart during a timed disable leaves the lock off until it is toggled, which is safe because the user opted into the disable. The mode governs idle-triggered locking only. The SUPER+l bind and the lock on suspend are separate paths and stay live.
2026-09-17fix(status): read a missing state file as off, not downDanilo M.3-5/+9
statusctl watch reported class "down" for a missing state file and waybar's presentation rule drew a warning for it. That fires on every fresh session, before anything has written a file, which is the normal off state the registry's own invariant describes. The state was meant to flag a dead registry, but a crashed shell leaves a stale present file, not a missing one, so it never caught that case. get already read a missing file as 0; watch now matches it and the down state is gone.
2026-09-16fix(appearance): stage a cursor theme, apply on confirmDanilo M.1-5/+41
Clicking a cursor card called applyCursor straight away, so the pointer, gsettings, environment.lua and both GTK settings files all changed on a single click with nothing to confirm. The wallpaper tab already stages a pick and commits it from an Apply button; the cursor grid now does the same, with a Reset beside it. The hover preview is unchanged: it still switches the real cursor and reverts on unhover, which is what makes staging readable, you see the theme before committing to it. That preview is also why Apply clears liveCursorCursor before calling applyCursor. Moving the mouse off the card to reach the button fires the unhover revert, which would otherwise set the old theme back over the one just applied. A staged card takes the solid accent border and the current one drops to a half-alpha border, so both stay visible at once. Icon themes and cursor size still apply on click. Neither has a hover preview to confirm against. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-16fix(window-switcher): use a messaging icon for desktop 7Danilo M.1-1/+1
The chat/internet-chat aliases in the icon theme all resolve to an empty outlined monitor; dialog-messages is the filled speech bubble.
2026-09-16fix(window-switcher): swap the desktop 2 and 3 iconsDanilo M.1-2/+2
Desktop 2 is the editor and desktop 3 the terminal; the map had them the other way round, matching waybar's stylesheet before it was corrected.
2026-09-16feat(window-switcher): mark desktops with waybar's iconsDanilo M.3-8/+62
The card's desktop line showed the workspace number. waybar draws the same eight desktops as CSS background images keyed by number, because its format-icons takes text rather than paths; those names now live in the Windows singleton and are resolved through the active icon theme, so a theme switch follows the drawer and the bar and the switcher agree. A named workspace, or one past eight, falls back to its number, and so does an icon the active theme cannot resolve.
2026-09-16feat(appearance): polish the Icons tab previews and applyDanilo M.4-129/+270
Cursor previews were extracted only on hover, so the row stayed blank until each card was visited. They are now batched on open: one python pass extracts four shapes per theme (arrow, hand, text, horizontal resize) through the theme's aliases and every storage form it may use (hyprcursor .hlc, a shape directory SVG, or a legacy Xcursor binary via xcur2png). The card rows are Flows so they wrap instead of running off the right edge, and the tab scrolls. Cards widened and names wrap to two lines. A size selector (16-64) applies the pointer size live, to gsettings, the GTK settings.ini files and environment.lua. gsettings alone does not reliably switch icon/cursor here: both gtk-3.0 and gtk-4.0 settings.ini carry the names, so apply now rewrites gtk-icon-theme-name / gtk-cursor-theme-name / gtk-cursor-theme-size there too. The symlinked `default` cursor alias is skipped in the scan and the current value resolved through it, so the real theme carries the current marker. The tab row is centred against the panel so it reads as drawer chrome rather than the first line of a tab's content.
2026-09-16fix(appearance): wait for hyprsunset exit before restartDanilo M.1-1/+7
pkill only sends SIGTERM, and hyprsunset binds Hyprland's CTM manager exclusively, so the replacement started immediately after pkill raced the old process and exited with 'A CTM manager is already running'. Saving from the Sunset tab therefore killed the night-light daemon instead of restarting it. Bound the wait to 5s (50 x 0.1s) so a wedged process cannot hang the restart.
2026-09-16fix(appearance): final review fixes for the Sunset, Idle and Icons tabsDanilo M.4-7/+31
xcur2png wrote its config file into the qs CWD, which is the repo root, so hovering a legacy Xcursor theme left a stray left_ptr.conf in the tree. Point -c at the per-theme cache dir instead. Hyprsunset.save() could run before refresh()'s two cat processes landed and write a header-only hyprsunset.conf, blanking the location. Guard on the same confLoaded/appLoaded flags Hypridle already uses. The idle save had no acknowledgement, which the spec requires. Add a notice after the restart and show it in the tab, and switch Hypridle's save guard to a loaded flag so a config whose first block is a listener is not mistaken for an unloaded one. classify() used else if, so a directory carrying both index.theme and cursors/ (Adwaita) was listed as a cursor theme only and never reached the icon picker. Make the two tests independent; selftest gains the dual-purpose case.
2026-09-16docs(appearance): fix stale Theme tab phrase and align the tab diagramDanilo M.1-9/+9
The five-tab shell's paragraph still said theme opened "the other tab", and the widened top border left the body and bottom border 6 columns short. Widen lines 7-14 to match and name the Theme tab.
2026-09-16docs(appearance): document the Sunset, Idle and Icons tabsDanilo M.1-1/+27
Records the IPC verbs, the shared hyprsunset.conf format, the preserved hypridle comments, and the preview mechanisms. The hardcoded icon theme in udt, waybar and rofi is tracked as a TODO in those repos.
2026-09-16feat(appearance): add the Icons tabDanilo M.2-0/+185
Icon themes preview four glyphs each from the GTK lookup; cursor themes preview an extracted left_ptr and change the real cursor on hover, reverted on leave and on close. Selection applies both.
2026-09-16fix(appearance): show idle timeouts as m:ssDanilo M.1-1/+1
The spec calls for a seconds field shown as m:ss, but the first pass rendered the brief's '9m 30s' form. Zero-pad the seconds so 570 reads 9:30 and 600 reads 10:00.
2026-09-16feat(appearance): add the Idle tabDanilo M.2-0/+92
Rows for each listener with an enable toggle and a timeout field, the command shown read-only. Save rewrites the file and restarts hypridle.
2026-09-16feat(appearance): add the Sunset tabDanilo M.5-11/+302
Tab bar and Tab-key cycle become five entries, the Loader picks inline or file tabs, and show() refreshes the model. The tab edits the profiles in place, fetches sun times and previews through the daemon.
2026-09-16feat(appearance): add the icon and cursor theme modelDanilo M.2-1/+237
Lists themes from ~/.icons, ~/.local/share/icons and /usr/share/icons, distinguishing cursor themes by cursors/ or manifest.hl. Icon previews come from one GTK lookup process for every theme; cursor previews extract left_ptr from a hyprcursor .hlc via unzip or from an Xcursor theme via xcur2png. Application sets gsettings and the Qt configs, and rewrites the cursor env in environment.lua.
2026-09-16fix(appearance): refuse to save hypridle before the config has loadedDanilo M.1-0/+1
save() could run before refresh()'s async cat completed, when prefix and tail are empty and listeners is empty, so it would write an empty file and restart hypridle with no listeners. The real config always has a general block, so an empty prefix means the load has not happened or failed.
2026-09-16feat(appearance): add the hypridle listener modelDanilo M.2-1/+157
Parses the general block and comments before the first listener and keeps them verbatim; only timeouts and the enabled flag are editable, commands are copied. Disabled listeners are written commented out, and the parser matches the closing brace at line start so the dpms command's inline brace does not truncate the block.
2026-09-16fix(appearance): preserve the raw cache path on settings saveDanilo M.1-2/+4
appConfText() hardcoded path = ~/.config/hyprsunset-qt/sun.json, so any saveSettings() -- from save() or detect() -- silently reset a user-customized cache path while fetchSun() kept writing to the parsed location. Mirror sunset-qt's settings.py: hold the raw unexpanded string in cachePathRaw for serialization and derive the expanded cachePath for file access, so a no-op save is byte-identical and the two stay consistent.
2026-09-16feat(appearance): add the hyprsunset profile modelDanilo M.2-0/+310
Parses and serializes ~/.config/hypr/hyprsunset.conf in the exact format hyprsunset-qt writes, so both editors share the file. Location lives in hyprsunset-qt's own INI; fetching mirrors its curl calls and browser User-Agent. Selftest round-trips a fixture byte-for-byte.
2026-09-16docs: the old waybar notification module is archived, not just deadDanilo M.1-3/+3
The note pointed at waybar/scripts/notifications.py and its module by path. Both moved to ~/bin/archive/ with the rest of the stock waybar theme, whose scripts had no caller left once this shell took over notifications. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-16refactor(theme): read the palette from ~/.cache/udtDanilo M.7-8/+8
unified-desktop-theme moved its generated files out of ~/.cache/wal, which pywal used to own and nothing writes any more. The palette udt-accent generates is now at ~/.cache/udt/udt-palette.qml. shared/Theme.qml is the only code change; every component reaches it through a symlink. The module READMEs cite the path, so they move with it. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>