aboutsummaryrefslogtreecommitdiffstats
path: root/desktop/modules
AgeCommit message (Collapse)AuthorFilesLines
8 daysfeat(updates): the drawer tile and pageDanilo M.2-0/+228
The tile counts what can be installed now (Slackware and the binary repo) and what only needs a build, and goes accent when there is something to install. It runs updatesctl while the drawer is open and not otherwise; a result over 30 minutes old is followed by a refresh that runs nvchecker, never after a refresh, so a failing nvchecker cannot loop. The page has one section per next action: Slackware upgrades, new and removed first, then upgrade here, awaiting build, upstream newer, app updates, the order the work is done in. Each row names the package, the repo it comes from and the one version jump its action covers. A source that could not be read says so under the section it empties instead of reading as nothing pending. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
8 daysfix(updates): review findings in updatesctlDanilo M.2-12/+27
A git hash starting with a letter (3.1-a59215d) ordered behind its base, because any trailing alphabetic chunk counted as a pre-release; only alpha, beta, rc, pre and dev do now. Trailing zeros (1.14 against 1.14.0) compare equal instead of reading as an upstream bump. A bad line in the slackpkg blacklist is skipped rather than crashing the script and leaving the drawer with nothing. An AppImage keeps its key over a flatpak of the same name, as the spec orders them, and flatpak list has a timeout. A Slackware upgrade compares version and build, so an arch change alone is not a row. The spec's prose now matches the code: the upstream comparison includes the installed version. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
8 daysfix(updates): drop a redundant clause, test the mirror blacklistDanilo M.2-2/+3
The awaiting-build branch also required the build to be older than upstream, which cannot fail there: a build newer than installed was already taken as an upgrade, so the build is at most the installed version, which is behind upstream whenever a row is emitted. Removing it left every test passing, which is how it was found. The blacklisted testing/ entry sat after the main one in the fixture, so setdefault kept the main one and the mirror-side blacklist was never exercised; it now comes first, and dropping the check fails the test. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
8 daystest(updates): a hung NFS read reports unreachableDanilo M.1-0/+12
A fifo with no writer blocks cat at open() the way a hard NFS mount blocks during a NAS outage. The repo section reports unreachable within the timeout and the mirror sections still fill. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
8 daysfeat(updates): Slackware upgrades, new and removed packagesDanilo M.2-1/+92
Compared mirror against installed, as upgrade-all does, not against slackpkg's local ChangeLog: `slackpkg update` replaces that without installing anything, so a diff against it reads clean after an update that was never applied. New and removed come from the mirror's ChangeLog, the newest Added or Removed line deciding, so a package added and later upgraded still counts as new. Blacklisted build tags and directories are skipped, as slackpkg skips them. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
8 daysfeat(updates): sort tracked packages into the next actionDanilo M.2-1/+351
Each nvchecker stanza lands in one section: upgrade here when the binary repo's PACKAGES.TXT holds a newer build than installed, awaiting build when the recipe (the user's or SBo's) has caught up but the build has not, upstream newer when the recipe lags, app updates for AppImages and flatpaks. Built comes from PACKAGES.TXT rather than the .info, which is why gamescope and kitty no longer read as installable: the repo still held the old build. The repo is on a hard NFS mount, so it is read by a child the timeout can kill. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
8 daysfeat(updates): version order for updatesctlDanilo M.2-0/+117
nvtable compares versions for equality, which cannot tell an installed 2.1.278 from an SBo recipe of 2.1.267 that is older than it. vercmp orders numeric chunks as numbers and treats a trailing alphabetic chunk as a pre-release, so a kernel suffix or git hash orders as newer than its base and never reads as behind. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
8 daysfeat(desktop): performance and clipboard pages, a scrolling tile gridDanilo M.6-0/+413
The tile grid sits in a Flickable capped at three rows, so the fourth row the new modules bring scrolls inside that box instead of eating the notification space; a thin accent bar in the margin shows only when a row is hidden. Tiles are reordered with the everyday ones first. Performance shows the island's PerfPanel in a Page, with a tile line of CPU and RAM and the warning dot. Perf.detailed is now a count of live PerfPanel instances instead of a binding in the status pill: the island and the drawer both host the panel, and a bool with two writers would let one clear it under the other. Clipboard reads cliphist when its page opens: search, copy on click or Enter, delete per row, clear all behind a 3 second confirm, image entries as thumbnails decoded once onto tmpfs. Entries reach sh as $1, never inside a command string. A copy closes the drawer through a new Page.dismiss signal the drawer connects like back. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
8 daysfeat(status): quiet mode mutes the Claude and opencode soundsDanilo M.6-9/+156
A new registry mode, status.quiet, with a row on the status page. Both tools will play their sounds through notify-sound, which asks statusctl and plays nothing while quiet is on, so their popups stay and only the sound goes. Presentation sets quiet and restores it afterwards, like dnd and nolock: a sound during a call or a game is what quiet is for. notify-sound ignores any argument it has no sound for, because the opencode notifier runs its command on every event, session_started and user_message included. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-07docs: the island, and waybar goneDanilo M.2-53/+37
AGENTS.md describes the hybrid model (desktop/ hosts drawer and island) and gains the gotchas found while building the island: UseQApplication for tray menus, WheelHandler vs MouseArea, accent colliding with green, xkb layout switching, and the tray popup un-hovering the island. The Blur waybar measurement becomes history. READMEs drop their present-tense waybar references. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-07chore: remove waybarDanilo M.2-71/+3
The island replaces both bars: clock and VM dots in the clock pill, volume, tray, layout flag and privacy in the status pill and its sliver, mail count in the drawer. The icon switch no longer regenerates or reloads waybar, and the mail pill's watcher script goes with it. Workspaces and the taskbar have no persistent view by decision: the flick and the expose cover them. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-07fix(mail): recount unread when the notmuch database commitsDanilo M.2-0/+28
Accounts counted only at shell start and when the mail page opened, so the drawer tile drifted behind the inbox for the rest of the session: it read 14 while notmuch and the waybar pill both said 16. The waybar pill never drifted because waybar-mail.sh watches the database; the drawer had no watcher. Accounts now runs its own inotifywait -m on the xapian directory (the directory, because Xapian replaces files on commit) and refreshes 300ms after a commit burst settles. The watcher respawns after 5s if it exits, since a dead one reproduces the drift with no visible error. This is the prerequisite for removing waybar: the drawer becomes the only place the unread count is shown. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-05docs(ai): module README, drawer module listDanilo M.1-0/+51
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-05feat(ai): drawer page for the local AI stackDanilo M.4-0/+334
Engines are read-only, since stopping llama-server or sd-server takes its web UI down too. Apps start and stop through their own controls, detached so a start outlives a quickshell reload. Fanfictioner has no start (a run needs a story) and stops by pid, children first, so its sd-cli runs go with it and nothing else does. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-05fix(ai): count a running sd-cli as the sd engineDanilo M.2-7/+32
fanfictioner runs sd-cli one-shot, not through sd-server, so the sd row read down while a CLI run held VRAM and generated. A running sd-cli now reads up and busy, its model marked (sd-cli), and outranks the web UI. The model is the first --diffusion-model/-m: sd-cli's prompt follows and is free text that may contain "-m". Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-05feat(ai): state sweep for the AI stackDanilo M.2-0/+242
Six TSV lines per sweep: llama and sd engines, four apps. llama's slot probe passes autoload=false, since a plain /slots?model= loads the model into VRAM. sd-server has no progress endpoint, so busy is its log having been written in the last 5s. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-03fix(status): watch for create, so a mode's first write is seenDanilo M.2-2/+25
Qt's atomic write builds the file as an unnamed O_TMPFILE. When the target does not exist yet it links that straight into place, and the only event naming the file is IN_CREATE: no close_write, no moved_to. statusctl watch did not listen for create, so the first write of each mode in a session went unseen. On a fresh session that is the write that matters: a Teams call started an hour in left the waybar pill reading off until some later rename happened to redraw it. The test reproduces the event with a hard link, which is what the linkat from Qt's temp file looks like to inotify. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-03docs(status): ~/bin/statusctl is a symlink to the repo copyDanilo M.1-1/+1
It was a hand-installed copy, which can drift from the tested source silently. Replaced with a symlink after confirming the two were identical. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-03feat(status): present during a Teams callDanilo M.1-1/+1
The auto-detector now sets presentation while Teams holds a mic stream, alongside the game checks. Teams runs for the whole session, so its process alone is no signal and would have kept lock, DND and the breaktimer off all day. A call is Teams holding a source-output, which pactl lists tagged with application.process.binary = "teams-for-linux"; verified matching during a live call. It reuses status.gaming rather than status.presentation, so a call ending clears only the automatic half and never a manual toggle. The statusctl tooltip for that state said "game running", which is now wrong, so it reads "auto: game or call". The file and waybar class keep the gaming name: renaming them touches four places for no behaviour change. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-29fix(sound): swap background cover when the track changesDanilo M.1-4/+9
The crossfade only brought a loaded cover to the front when the image's source compared equal to the wanted URL. It never did for a title with spaces or brackets: the URL carries the title as a percent-encoded fragment cache buster, and QUrl reads it back decoded, so "#Foo%20Bar" came back as "#Foo Bar". The load finished, loaded() bailed, and the old cover stayed. Confirmed with a standalone qml test. The slot being loaded is now tracked by index instead. Stale status changes cannot match: only the back image is ever reloaded, and a newer want resets it before assigning. Seen working on screen by the user. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-29feat(sound): crossfade album art, fade to plain without itDanilo M.2-59/+113
The OSD background held one Image, so a track change swapped covers in a single frame. Two images now take turns: the new cover loads into the hidden one and is brought to the front only once it reports Ready, then both fade over 300ms. Waiting for Ready is what keeps a slow or remote art server from fading through black. The blur, scrim, sheen and grain are grouped under one item whose opacity follows artOn. It goes false when the player has no art or the cover for the current track errors, so the panel fades back to its plain translucent look instead of snapping, or keeping a stale cover that reads as a fault. It goes true again only when a new cover has loaded, so returning from plain never flashes the previous one. The back image's source is cleared before being set, so a URL it already holds still reloads and reports a status. onWantChanged misses the value at load, hence the Component.onCompleted call. Not yet seen on screen: the shell reloaded it cleanly but the fade was not watched through a track change. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-29feat(sound): fake frost on the album-art scrimDanilo M.1-0/+40
The art background is opaque, so Hyprland's blur behind the OSD cannot show through it; making the art translucent to let it through would also thin the scrim that keeps light covers legible. Fake the frosted glass instead: a faint top-down sheen in Theme.text and a fixed grain, painted once by a Canvas and masked to the panel's rounded corners. The grain is a Canvas rather than a ShaderEffect because a shader needs a compiled .qsb beside it; the Canvas repaints only on resize. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-29feat(sound): album art as the OSD backgroundDanilo M.2-0/+56
When the current track has art, the volume OSD now draws it across the whole panel, blurred (MultiEffect blur 0.5, default blurMax) and masked to the panel's rounded corners, since clip only cuts to the bounding box. The thumbnail in the track row stays sharp on top. A dark scrim (Theme.base at 0.82) sits over the art. The palette is dark and the text light, so a light cover made the panel unreadable, and more blur did not fix that; only darkening did. The scrim is a sibling of the MultiEffect, not a child: nested inside it, it did not paint at all, which is why raising its alpha first appeared to change nothing. Without art the panel keeps its translucent, compositor-frosted look. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-25fix(sound): collapse the OSD track row when the player goes awayDanilo M.1-0/+5
A Loader keeps the height of its last item after active goes false. When a browser tab playing media was closed, its MPRIS player vanished, Player.active dropped and the window shrank to 100px, but the Loader still reserved 46px plus the Column spacing on top. The volume row was pushed down and clipped at the bottom edge, which is the "stuck" OSD a hot reload used to cure: a fresh Loader starts at zero height. Hiding the Loader while inactive makes the Column skip it. Both the retained height and the collapse were confirmed with headless qml6 runs. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-25feat(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>
2026-09-21feat(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>
2026-09-21feat(status): detect PCSX2 for game modeDanilo M.1-3/+3
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>
2026-09-20feat(status): auto-present while DuckStation runsDanilo M.1-4/+6
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.
2026-09-20feat(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.
2026-09-20feat(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.1-1/+27
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.1-2/+4
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-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(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.6-6/+151
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-16refactor(theme): read the palette from ~/.cache/udtDanilo M.3-3/+3
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>
2026-09-15feat(status): add the snooze rowDanilo M.2-0/+124
A switch and a free-text minutes field driving notify-snooze.sh. Snooze is a file the balloon shell reads, so the row only writes it; the switch follows the file, including a snooze that ends while the page is open.
2026-09-15fix(status): address final review findingsDanilo M.4-6/+7
F1: after a click the shared Switch writes checked directly, dropping StatusRow's declarative binding, so an external mode change from waybar or statusctl no longer moved the switch. Resync via onValueChanged. F2: the spec claimed hyprctl clients counts idle inhibitors, which is false for a layer-surface inhibitor; replace with the behavioral hypridle marker check. F3: README said the check covers both watch states; it exercises the activated report and the absent-file down report. F4: give breakProc an onExited check so a failed breaktimer.sh verb is visible instead of silent. F5: drop StatusTile's unused required st property and its injection. F6: comment the startup-order limit on dndBeforePresentation.
2026-09-15docs(status): document the module and record its trapsDanilo M.1-0/+59
A FileView fires its own fileChanged on setText, so a handler that writes in response to a change loops unless it compares first. IdleInhibitor has no window of its own and does nothing without one, so the singleton is handed the keepalive window by shell.qml. Both were read from the documentation while designing and confirmed while implementing.
2026-09-15fix(desktop): keep statusctl quiet when the mode file is absentDanilo M.1-6/+6
read_mode() failed on a missing mode file with a shell redirection error, 'statusctl: line 56: .../status.dnd: No such file or directory', printed to stderr on every get/toggle before the file existed and polluting waybar's exec stderr. The '2>/dev/null' sat on the 'tr' command, but the error is the parent shell's input redirection failure, which that redirect cannot suppress. Guard on '[[ -e "$file" ]]' before reading; a missing file still reads as 0, which was always the contract. Mirror the fix into the plan so plan and script agree.
2026-09-15feat(desktop): add the status page and register the moduleDanilo M.1-0/+43
One row per mode. Adding a mode is one file in the singleton and one row here, which is what a registry buys over two separate toggles.
2026-09-15feat(desktop): add the status module, tile and rowDanilo M.3-0/+122
The module is thin because the singleton owns the modes: it is a tile and a page over Status, always active so the effects hold whether or not the drawer has been opened. Injected as st rather than mod, since a component property named the same as the enclosing object's id binds to itself and arrives undefined.
2026-09-15feat(desktop): add the statusctl CLI and its checkDanilo M.2-0/+219
statusctl reads and writes the mode files directly rather than going through the shell, so it works while quickshell is down. Setting a mode that way records the state without firing its effects; the shell sees the change through its own watch and reasserts them. The watch listens on the directory, not the file: an atomic write replaces the file, so a watch held on the old inode dies with it. Same trap the mail watcher hit with Xapian, and the reason moved_to is in the event list. An unknown mode exits non-zero rather than reading as off, so a typo cannot masquerade as a mode that happens to be disabled. The watch loop runs inotifywait through process substitution, held open on fd 3, with the PID captured and trapped on EXIT/TERM/INT. Piped straight into the while loop, inotifywait would be a pipeline sibling rather than a child, so a plain kill on statusctl leaves it running, watching a directory nobody reads anymore. Task 7 wires this CLI into waybar's exec, and waybar kills and respawns exec children on every reload the same plain way, so every reload would otherwise leak another watcher for the rest of the session. Process substitution makes inotifywait a real child whose PID the trap can hold and kill. A SIGKILL still orphans it, since KILL cannot be trapped, but nothing in the described path sends one. A failed rename exits non-zero instead of being swallowed. Reporting success on a write that did not land would leave the caller and the shell disagreeing about the mode, with an orphan temp file as the only trace, and this script is the interface the whole registry is read and written through. Verified against an unwritable directory: exit 1, no orphan left, prior value intact. Reading uses a redirect rather than cat piped into tr, so that an unreadable file falling back to off is explicit rather than a side effect of a pipeline discarding cat's exit status. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01A7ThHHh5iTYbVfp3rNAkw2
2026-09-15fix(desktop): stop bluetooth and wifi scans after 60sDanilo M.2-0/+20
Neither scan property self-terminates. BlueZ discovery runs until StartDiscovery is stopped, and the NetworkManager scanner repeats until disabled; the docs for both say as much and give no timeout or interval property. The scan button was the only writer of either property, and closing the drawer destroys the page without touching them, so a scan started and forgotten kept the radio busy for the rest of the session. Continuous wifi scanning also costs throughput on the connected link. Each page gets a Timer bound to the scan property rather than started by the button. Binding it that way means a manual stop cancels the clock, and reopening the page on a scan still running from an earlier visit re-arms a fresh 60s instead of leaving it running forever. Verified both files parse: the desktop shell hot-reloaded without dropping its process. The timeout firing at 60s is not verified here, since it needs a real scan on the radio. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01A7ThHHh5iTYbVfp3rNAkw2
2026-09-14fix(desktop): queue KDE Connect actions instead of interruptingDanilo M.3-8/+26
A second action while one was in flight killed the first process, whose onExited then fired under the new label and raised a false failure notification. Actions are now queued and launched on exit, so each exit reports under its own label. Also correct two comments that contradicted the code: the pairing banner is only noticed on the on-open poll, and the laptop glyph keys off the daemon's desktop type.