| Age | Commit message (Collapse) | Author | Files | Lines |
|
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>
|
|
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>
|
|
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.
|
|
|
|
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 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.
|
|
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>
|
|
A notification is untrusted input. Inline <img> now renders only for
local sources; an http(s) source is removed before the RichText body is
shown, so a remote sender cannot make the shell fetch a URL. The row and
the balloon share the one sanitizer in the Notify singleton.
|
|
Each row shows when the notification appeared ("Sep 15 14:32", from created), sits on its own rounded card with space between rows instead of reading as one list, and uses the balloon's 16/18/16 sizing rather than 13/11. The close target grows to a 32px hit area in the row and the balloon, since a 20px box was hard to hit.
|
|
Mirror the balloon's right-click close-all on the drawer row: the spec says
the gestures are identical in both forms, and a right-click on a row
previously did nothing. The right-button branch precedes the live guard so it
behaves the same on history rows.
Stop importing the Status singleton into the notification shell. Referencing
it instantiated it, and its onPresentationChanged writes status.dnd and runs
breaktimer.sh, so a read-only consumer was writing state and shelling out on
every presentation toggle, and doubled the dndBeforePresentation race. Notify
now reads "/run/user/<uid>/status.dnd" directly through a FileView, the same
convention as the notifyd files; a missing file means off.
AGENTS.md named Drawer.qml as the drawer's reserved space; it is
desktop/NotificationList.qml.
|
|
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.
|
|
show() is the IPC deep-link path and set only page/open, so if the history
view was open, historyLoader stayed active alongside pageLoader and painted
over the requested module page. Clear history in show(), as close() already
does.
|
|
Reachable only from the reserved space's History button, so it is a drawer
view, not a module and not a tile. Rows are inert; clear-all empties the
ring, since the daemon has no per-id history deletion.
|
|
onOpenChanged does not fire for the initial value, so a restart while the
drawer was open left notifyd/drawer at "1" and every balloon stayed
suppressed until a drawer open/close cycle. Write the starting state from
Component.onCompleted so the file always reflects the drawer.
|
|
The reserved Item becomes the live queue with a History button, scrollable
because the queue can hold 20. The drawer writes notifyd/drawer so the
balloon shell stands down while this space is showing, which is what stops a
notification appearing twice.
|
|
The daemon publishes its queue, history, drawer flag and snooze as files;
this reads them for both renderers, the same files-are-the-interface
convention the status registry set. Mutations and the rofi action picker run
notifyctl through a Process, which is the one path back to the daemon.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
Presentation mode sets DND, asserts a Wayland idle inhibitor and pauses
breaktimer. The effects hang off the mode property rather than the setter,
so a mode set with statusctl while the drawer is closed asserts them too.
DND has two writers once presentation mode exists, so turning presentation
off restores the value DND had before rather than clearing it, or an
afternoon of hand-set DND would vanish when a talk ends. That prior value
lives in the singleton, not in a file: it means nothing once presentation
mode is off, and presentation mode does not survive a reboot.
breaktimer owns its own state file and is driven only through its verbs.
Two writers on that file would race with its daemon loop.
|
|
IdleInhibitor needs a non-null window and this is the only one that lives
for the whole session, so presentation mode attaches to it. Naming it is a
prerequisite for that and changes nothing else.
|
|
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
|
|
Modes live as files under XDG_RUNTIME_DIR, one per mode, holding 0 or 1,
with a missing file meaning off. That directory is tmpfs, so a reboot
resets every mode and no cleanup code is needed.
FileView covers both directions: atomicWrites for the write, watchChanges
for the watch, so an external writer repaints the drawer with no polling.
Both are already the documented defaults in 0.3.1 and are set explicitly
anyway, because statusctl watches close_write,moved_to precisely because an
atomic write lands as a rename. A future release flipping either default
would break the watcher with no error, and a declaration is a stronger
guarantee than a default.
The documented behaviour is that a FileView fires its own fileChanged on
setText, so the reparse compares before assigning and a self-write is a
no-op rather than a loop. That has not been observed running yet: nothing
consumes the singleton until the status module exists, so the guard stays
unproven until then.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01A7ThHHh5iTYbVfp3rNAkw2
|
|
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
|
|
Page.qml gains an icon rendered before the title, and the drawer binds
it to the loaded module's icon so a module whose glyph changes (network
follows the active link) keeps the header in step.
|
|
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.
|
|
The tile, page and row each took a required property named mod while the
module object's id is also mod. In a Component scope the id resolves to
the property's own unset self, so every page.mod read was undefined and
the device list never rendered. Only a runtime TypeError showed it; the
smoke check was clean because the drawer was closed.
kc matches the net and bt convention and cannot collide with the id.
|
|
No D-Bus module means shell-outs and no push; the daemon and the
indicator are separate; the battery plugin is not on every device; and
qdbus reads properties and methods through one call shape.
|
|
Records that the phone-to-PC clipboard is the daemon's plugin and not the
indicator's, so removing the indicator does not break it, and that polling
stops when the drawer closes.
|
|
A QtQuick.Dialogs FileDialog held on the module, not the page, so a
closed drawer does not destroy it mid-pick. The path is decoded from the
file:// URL before it reaches kdeconnect-cli.
|
|
Outgoing pair and unpair go through kdeconnect-cli; accepting and
rejecting an incoming request go straight to the daemon, since the CLI
has no verb for them. The verification key is shown on the row while a
pairing is in flight so the two ends can be compared.
An incoming request is not noticed while the drawer is closed, because
the poll only runs when it is open; the daemon keeps the request, so it
is there on the next open.
|
|
Ring, clipboard, mount and unpair, all shell-outs since there is no D-Bus
module. Actions share one serialized Process and a failure notifies, for
the case where the drawer has closed by the time the command returns.
Mount runs --mount and then --get-mount-point in one shell, because
--mount prints nothing and the path is a second call.
|
|
State is polled from kdeconnect-state.sh because there is no D-Bus module
to subscribe with. The poll runs only while the drawer is open: the tile
is created when the panel loads and destroyed when it unloads, and it
flips the module's polling flag.
The laptop reports no battery plugin, so an absent charge is an empty
field and renders as nothing, never as a zero.
|
|
Quickshell 0.3.1 has no generic D-Bus module, so device state comes from
qdbus6 shell-outs. The script emits a tab line protocol for the QML side
and is checked by a stub-daemon test, since the live daemon is not an
oracle an agent can rely on.
A failed daemon query exits non-zero with no output so the module keeps
its last list, which is not the same as a valid empty device list.
|
|
|
|
|