Watch
1
0
Fork
You've already forked souveraine
0
souveraine/.handoff-from-phone/quickshell-idle-power/TASK.md
Fimeg 19fe6b1d0e shell: dock reorder, fullscreen detection fix, idle-power, sessiond, misc shell work
- Dock drag-to-reorder for pinned apps (insertion gap, quick-slide vs dwell)
- Fullscreen detection: scan all windows via HyprlandData.windowList
- IdleCoordinator, GlobalStates, Session.qml updates
- Deploy script, qmldir, settings, wallpaper, visualizer fixes
- sessiond server, memory module updates

Co-Authored-By: Claude <noreply@anthropic.com>
2026-07-22 22:18:20 -04:00

135 lines
7 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

---
# quickshell-idle-power — lazy-load panels + consolidate polling processes
**Status:** 2026-07-21 — scoped, not started. Two quick wins already landed
separately (see "Already done" below); this doc covers the two larger sweeps
(#3 LazyLoader coverage, #4 process consolidation) to be worked tomorrow.
**Config:** `qs -c souveraine` → symlinks into `~/souveraine-surfaces/quickshell/`
(the `~/.config/quickshell/souveraine/` tree is symlinks; edit the real tree).
Quickshell 0.3.0. Composed of three layers:
- `modules/ services/ panelFamilies/`**souveraine** (owned, edit freely)
- `ii-base/`**borrowed ii/Caelestia tree** (treat
as replaceable; keep coupling to souveraine singletons OUT of here)
- `ii-phone/`, `ii-previous/` → phone snapshot / previous deploy
## Why (measured)
At a 49-min uptime, `qs` had burned **149 CPU-sec** and held **560 MB RSS
(15% of RAM)** on the Pixel 3 — second only to the active claude session. The
display idle layer (`services/IdleCoordinator.qml`) is already excellent
(state machine Active→Dimmed→Lock→Suspend→Asleep→Waking, native IdleMonitor
with respectInhibitors, brightness dim, hypridle screen-off). The gap is that
**the idle machine dims the screen but does not quiet the widgets** — timers
tick and processes poll regardless of visibility. These two sweeps attack the
steady-state CPU (over-eager construction) and RSS (per-widget processes).
Quickshell FAQ guidance backing this:
- "The main thing you can do to reduce memory usage is to use Loaders."
- Loader for Item-derived components; LazyLoader otherwise.
- "Using a process per widget will use significantly more memory than using
a single process."
Ref: https://quickshell.org/docs/v0.3.0/guide/faq/
## Already done (2026-07-21, do NOT redo)
1. `ii-base/services/DateTime.qml` — uptime Timer was `interval: 10` (reloading
/proc/uptime off disk 100×/sec). Changed to `interval: 60000` +
`triggeredOnStart: true`. The clock itself is `SystemClock` (event-driven),
was never the problem.
2. `ii-base/modules/ii/background/widgets/visualizer/VisualizerWidget.qml`
the wave visualizer's 33ms (30fps) repaint Timer was `running: root.style
=== "wave"`; now `running: parent.visible` so it stops repainting when the
canvas isn't shown. (Considered a `Window.visibility` gate too, but that
needs `import QtQuick.Window` in the borrowed tree — deferred; `parent.visible`
is dependency-free and sufficient.)
Both verified: qs hot-reloaded without QML errors, pid stayed alive.
---
## Task 3 — Lazy-load the heavy panels (RSS lever)
**Current:** 9 LazyLoader vs 123 Loader across 683 QML files → a lot is
eagerly constructed at startup. Top-level panel modules under
`ii-base/modules/ii/` that are candidates (not visible at boot):
cheatsheet overlay overview sidebarLeft sidebarRight wallpaperSelector
sessionScreen screenTranslator regionSelector mediaControls polkit
notificationPopup onScreenKeyboard dock
**Do:**
- For each panel not shown at boot, wrap its instantiation in a `LazyLoader`
(non-Item / window-like → LazyLoader; Item-derived → Loader). Load on the
signal/state that reveals it (e.g. sidebar open toggle, GlobalStates flag),
unload after.
- Especially: the anime/booru sidebar (`ii-base/modules/ii/sidebarLeft/anime/`)
and cheatsheet — heavy, rarely open.
- Verify each panel still opens with no first-open UI-thread stall (LazyLoader
loads sync if shown before background load finishes — see LazyLoader docs).
**Watch:** keep LazyLoader wrapping in the layer that OWNS the panel. Don't add
souveraine-singleton dependencies into ii-base while doing this.
**Measure before/after:** `ps -eo pid,pmem,rss,comm | grep qs` (RSS) at a fresh
launch, before opening any panel.
## Progress 2026-07-22 (task 4 partially landed)
Landed live + promoted to the laptop repo (souveraine tree):
- GlobalStates.qml (souveraine override): new bool — the
shared cross-layer idle surface the doc prescribed.
- services/IdleCoordinator.qml: publishes displayActive on every state
transition (Active/Waking = true).
- ii-base/services/ResourceUsage.qml + NetworkTraffic.qml: repeat pollers
gated (+ ).
- VisualizerWidget.qml: cava watchdog gated on , cava torn down when either goes false.
- SystemInfo.qml inspected: its timers are one-shot startup probes, left
alone. Notifications.qml: no repeat poller found, left alone.
One hot-reload during editing tripped the known
"lockscreen surfaces without active lock" FATAL (reload race with sessiond,
not a QML error); the service restart loaded clean. CPU-delta measurement
while locked still pending (baseline at /tmp/qs-idle-baseline-*).
Task 3 note from code reading: most panels ALREADY gate content internally on
GlobalStates.*Open via Loaders; their IpcHandlers live inside the
panel files, so adding at the
PanelLoader level would unload the very handler that opens them — do NOT do
that. The real task-3 work is converting the -gated panels
(Overview line 23, SidebarLeft lines 89/181) to internal Loaders,
one panel at a time. Overview deliberately deferred until the 2026-07-21
search-fix has device acceptance.
## Task 4 — Consolidate per-widget processes (CPU + RSS lever)
**Current:** ~48 `running: true` Process instances (souveraine 14, ii-base 32,
ii-phone 2). FAQ: one process feeding many widgets ≫ cheaper than N processes.
**Do:**
- Audit `ii-base/services/*.qml` for multiple widgets independently spawning
processes for the SAME data. Known pollers to review:
ResourceUsage.qml, NetworkTraffic.qml, SystemInfo.qml, Notifications.qml,
plus /proc readers. Route each data source through ONE Singleton service
that widgets bind to (the `services/` layer already does this well — the
duplication lives in the borrowed ii-base tree).
- Gate long-poll services on idle state where a stale value is fine while
dimmed/locked: e.g. `running: IdleCoordinator.state === IdleCoordinator.Active`
for background stats/resources/network widgets. NOTE: IdleCoordinator is a
souveraine singleton — if gating an ii-base widget on it, do the gating in a
souveraine wrapper/override, not by importing it into ii-base. Alternatively
expose an idle bool via the existing `GlobalStates`/config surface that both
layers already share.
- The cava process behind the visualizer (VisualizerWidget.qml watchdog, 2s)
is a good candidate to also stop when the background isn't visible.
**Measure before/after:** cumulative CPU: `ps -eo pid,times,comm | grep qs`
after a fixed idle interval (e.g. leave locked 10 min, compare CPU-sec delta).
## Acceptance
- Fresh-launch RSS meaningfully below ~560 MB.
- `qs` CPU-sec growth while the screen is off/locked approaches zero (today
it keeps climbing from always-on timers/pollers).
- No panel regressions: every lazy-loaded panel opens without a stall; every
consolidated service still updates its widgets live when Active.