Add top-processes drawer to the CPU widget
Clicking the CPU pill opens a popup listing the highest-CPU processes, refreshed live while open. services/topproc.sh computes top-style per-process CPU% and mem% from two /proc snapshots and prints one ranked frame per run; SysStats streams it only while procPollEnabled (drawer open), so the bar pays nothing at rest. CLAUDE.md documents the architecture and the runtime constraints discovered here (helper scripts must be git-tracked for `nix run`; single-shot + exit to flush stdout; bash+coreutils only; StdioCollector/Timer don't fire in this Quickshell build; fixed-size popup to avoid stale-buffer on resize under Sway). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
45
CLAUDE.md
45
CLAUDE.md
@@ -49,10 +49,36 @@ modules (right). Changing module order/presence is purely editing that RowLayout
|
||||
reassigning its `path`, as `_parseNet` does for the active interface), writing
|
||||
a `_parseX()`, and calling `reload()` + `_parseX()` inside `_tickOnce()`.
|
||||
- Deltas (CPU%, net rates) keep a `_prev*` field and diff against the last tick.
|
||||
- **Two metrics escape the pure-virtual-file rule:** disk usage runs `df` via a
|
||||
`Process` only every 30 ticks (`_tick % 30`), and a one-shot `Process` runs
|
||||
`services/discover.sh` at startup. The bar avoids per-tick subprocess spawns
|
||||
by design — keep new metrics reading virtual files, not shelling out.
|
||||
- **Three things escape the pure-virtual-file rule:** disk usage runs `df` via a
|
||||
`Process` only every 30 ticks (`_tick % 30`); a one-shot `Process` runs
|
||||
`services/discover.sh` at startup; and the top-processes list (`topProcs`) is
|
||||
fed by `services/topproc.sh`, re-run for each frame only while `procPollEnabled`
|
||||
(true while the CPU drawer is open) — at rest the bar still spawns nothing. The
|
||||
bar avoids per-tick subprocess spawns by design — keep new always-on metrics
|
||||
reading virtual files, not shelling out, and gate anything heavier behind an
|
||||
opt-in flag like `procPollEnabled`.
|
||||
- **`topproc.sh` does the work, QML just displays.** Each run takes two `/proc`
|
||||
snapshots `procInterval` apart, computes top-style per-process CPU% and mem% in
|
||||
the shell, prints one ranked frame of `P <cpu> <mem> <name>` lines, and
|
||||
**exits**. `SysStats` accumulates stdout via a `SplitParser`, parses it in
|
||||
`_parseProcFrame` on `onExited`, then re-arms (`Qt.callLater(running=true)`)
|
||||
while the drawer stays open. Hard-won constraints — violate any one and you get
|
||||
silent empty frames / a stuck "sampling…":
|
||||
- **The helper script must be tracked by git.** `nix run` builds from the flake
|
||||
snapshot (`${self}`), which excludes untracked files (`git status` will warn
|
||||
"tree is dirty") — an untracked script simply isn't found at runtime. For
|
||||
iterative work prefer `quickshell --path .`, which reads the working dir
|
||||
(untracked files included) and hot-reloads.
|
||||
- **Single-shot + exit, not a long-running loop.** A looping script's output
|
||||
stuck in bash's block buffer (pipe stdout is block-buffered), so the QML side
|
||||
never saw a frame; exiting per run forces a flush.
|
||||
- **bash builtins + coreutils only** (`nproc`/`sort`/`head`/`sleep`/`cat`). The
|
||||
flake wrapper's `runtimeInputs` is just `bash` + `coreutils`, so `awk` and
|
||||
`getconf` are absent at runtime.
|
||||
- **`StdioCollector.streamFinished` never fired** in this Quickshell build, and
|
||||
a `Timer`-driven re-arm didn't repeat — but `Process.onStarted`/`onExited` and
|
||||
`SplitParser.onRead` all work (`discover.sh` is the reference). Invoke helpers
|
||||
as `["bash", Quickshell.shellPath("services/x.sh")]`.
|
||||
|
||||
**`discover.sh` handshake.** At startup it prints `TEMP <path>` and `BAT <path>`
|
||||
(hwmon temp sensor + main battery, skipping `scope=Device` peripherals) on stdout;
|
||||
@@ -78,3 +104,14 @@ the QML side parses those lines into `tempPath`/`batPath`, which then feed
|
||||
to pick up layer-surface fixes ahead of release.
|
||||
- Native Quickshell services back some modules: `Quickshell.I3` (workspaces),
|
||||
`Quickshell.Services.SystemTray` (tray), PipeWire (volume).
|
||||
- **Making a module interactive (popups/drawers):** wrap the module's `Pill` in
|
||||
an `Item` (forward `implicitWidth`/`implicitHeight` from the pill so the bar's
|
||||
RowLayout still sizes it), add a `MouseArea`, and open a `Quickshell.PopupWindow`;
|
||||
`CpuGraph.qml` is the reference for a full drawer. Anchor it with
|
||||
`anchor.item: <the wrapper Item>` (**not** `anchor.window` — the module is
|
||||
nested in `Bar.qml`'s RowLayout, so its coordinates aren't window coordinates,
|
||||
and a window-relative rect lands in the screen corner). `anchor.item` makes the
|
||||
rect item-relative and derives the window automatically; position the dropdown
|
||||
with `anchor.edges`/`anchor.gravity` (`Edges.*`) and let Quickshell slide it
|
||||
on-screen. Adding a `MouseArea` directly to a `Pill`'s default content would
|
||||
place it in the layout — wrapping in an `Item` avoids that.
|
||||
|
||||
Reference in New Issue
Block a user