| Age | Commit message (Collapse) | Author | Files | Lines |
|
A label is the only place the reminder says what a bind does, so
cutting it short loses the part that matters, as with the column moves'
scrolling-layout note. A long one now takes a second line.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
|
Each key showed its plain action as text. It now carries the real
cap's legends, Nerd Font glyphs checked by name in Inconsolata, with
the shifted or Alt legend below and the cap in the palette colour
nearest the real one. Hovering still lists every action.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
|
For the keybind reminder's macropad keycaps. blue reads termblue, not
blue: udt-palette's blue is the active-border role, magenta under
candy-night, and termblue is the terminal's actual blue. The defaults
are Macchiato's, as for the rest.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
|
The pad glyph on each row was too small to read. The left fifth of the
overlay now draws the pad at size, each key labelled with its plain
action; hovering a key or an encoder lists everything it does, held
keys and turn direction spelled out. padActions() in Binds.js inverts
the grouped rows into that per-key list, and PAD's turn values became
words for it. The panel is pinned at a fixed height: centred, it moved
whenever the hovered key's list changed length.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
|
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>
|
|
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>
|
|
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>
|
|
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>
|
|
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>
|
|
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>
|
|
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
|
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>
|
|
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>
|
|
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>
|
|
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>
|
|
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>
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
|
|
The one rule worth writing down is which side writes: the daemon owns its
files and the shell only calls verbs.
|
|
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.
|
|
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.
|
|
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.
|
|
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>
|
|
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>
|
|
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>
|
|
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.
|
|
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.
|
|
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.
|
|
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>
|
|
The chat/internet-chat aliases in the icon theme all resolve to an empty
outlined monitor; dialog-messages is the filled speech bubble.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
Rows for each listener with an enable toggle and a timeout field, the
command shown read-only. Save rewrites the file and restarts hypridle.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|