Proof Portal

Project overview

Omarchy

ProbeLabs65 findings · 95 requirements

A proof layer — requirements, tests and verified fixes — for two of Omarchy's subsystems: the application menu (launcher scripts, QML model, JSONC config, search and selection) and the lock screen (lock scripts, QML, PAM authentication). Scope is deliberately limited to those components of omacom/omarchy; the rest of the distribution is not covered.

Inbox
ReportR-4995GitHub issuePublicReceived

Battery service forks powerprofilesctl get every 2 s for the whole session (~5% of a core, on battery too)

Reported by vstoyanov · filed Sep 9, 2026Closed upstream on 2026-09-13 by the merge of PR #11637 (closed by dhh). GitHub close reason: completed.

Received and recorded verbatim. Nothing has been checked yet.

At a glance

1 · What is claimed?
Only the report text below — no validator has restated it as checkable claims yet.
2 · What does it affect?
Not yet mapped to any requirement or component.
3 · Is it reproducible?
No validation has been run yet.
4 · What remains unclear?
  • Not validated yet: no claims, no reproducer.
5 · What happens next?
Our staff decide whether the report is valid. Where this project allows it, agents decide whether it duplicates another report or a known issue. No decision has been recorded yet.

Original report

Battery service forks powerprofilesctl get every 2 s for the whole session (~5% of a core, on battery too)revision 1 · captured 2026-10-04 12:10 UTC via poll

System details

ThinkPad P16 Gen 2 (i5-13600HX, 20 threads, hybrid NVIDIA), Omarchy 4.0.0 (omarchy-dev 4.0.0.r2092.g5ead870, i.e. current quattro), Hyprland 0.56.2, Quickshell 0.3.1, power-profiles-daemon 0.30.

What's wrong?

The battery service (shell/plugins/services/battery/Service.qml) runs powerprofilesctl get on a repeating 2 s Timer for the whole session, to keep activePowerProfile fresh for the wallpaper and lock services (powerSaverOnBattery). powerprofilesctl is a Python program that imports GObject introspection and talks to power-profiles-daemon over D-Bus on every run, so the shell pays a fork, a Python interpreter start and a D-Bus round trip 30 times a minute, on AC and on battery alike, whether or not anything changed.

Measured on the machine above (details and scripts on request; a 10 ms /proc sampler attributed the forks to the quickshell -n -p /usr/share/omarchy/shell process, and command time-style rusage timed the calls):

MetricValue
powerprofilesctl get, CPU per call (median of 12)111 ms (user 96 + sys 14)
… wall time per call114 ms
… resident set32 MB
Equivalent D-Bus property read (busctl get-property … ActiveProfile)5.8 ms CPU
Forks by the shell in 3 min90 powerprofilesctl (one every 2.0 s)
CPU spent in those forks (sampled inside the children)9.5 CPU-seconds per 3 min ≈ 3.2 CPU-seconds per minute ≈ 190 CPU-seconds per hour
Battery service's share of one core, steady state≈ 5.3% (the shell's children total 6.5% of a core in a passive 150 s window; powerprofilesctl is most of it)

On a laptop this is the worst kind of load: it is not heavy, but it never stops. Every two seconds a core has to leave its deep idle state, page in and start CPython, load gi/Gio, connect to the system bus, and tear it all down again. That keeps the package out of its deeper idle states and shows up as a permanent few-percent floor in top for quickshell children, on battery where it matters most.

The polling is also unnecessary. power-profiles-daemon exposes ActiveProfile on org.freedesktop.UPower.PowerProfiles (and the legacy net.hadess.PowerProfiles) with emits-change, so a subscriber gets a PropertiesChanged signal the moment the profile changes. Quickshell already ships that subscriber: the PowerProfiles singleton in Quickshell.Services.UPower, the same module the service already imports for UPower.onBattery. On Quickshell 0.3.1 here, PowerProfiles.profile updated within ~150 ms of powerprofilesctl set … and within ~530 ms of the menu's omarchy-powerprofiles-set autodetect … path, with zero processes spawned. The code comment on the timer ("powerprofilesctl has no portable monitor subcommand") is true of the CLI, but the shell does not need the CLI to watch the daemon.

Proposed fix

Bind powerSaverOnBattery to UPower.onBattery && PowerProfiles.profile === PowerProfile.PowerSaver and delete the reader Process, the 2 s Timer, refreshPowerProfile() and the cached string. Setting the profile on AC/battery transitions keeps going through omarchy-powerprofiles-set, so per-source preferences and their persistence are untouched. #10951 already implements exactly this; with that branch running as the session shell the forks drop to zero and the shell's children CPU from 15.5 s to 1.6 s per 3 minutes (measurements in the PR).

Related, not addressed here: the power panel spawns three helpers every 5 s while open (#7582), and Menu.qml runs powerprofilesctl get once when the power menu opens (on demand, fine).

View raw text
### System details

ThinkPad P16 Gen 2 (i5-13600HX, 20 threads, hybrid NVIDIA), Omarchy 4.0.0 (omarchy-dev 4.0.0.r2092.g5ead870, i.e. current `quattro`), Hyprland 0.56.2, Quickshell 0.3.1, power-profiles-daemon 0.30.

### What's wrong?

The battery service (`shell/plugins/services/battery/Service.qml`) runs `powerprofilesctl get` on a repeating 2 s `Timer` for the whole session, to keep `activePowerProfile` fresh for the wallpaper and lock services (`powerSaverOnBattery`). `powerprofilesctl` is a Python program that imports GObject introspection and talks to power-profiles-daemon over D-Bus on every run, so the shell pays a fork, a Python interpreter start and a D-Bus round trip 30 times a minute, on AC and on battery alike, whether or not anything changed.

Measured on the machine above (details and scripts on request; a 10 ms `/proc` sampler attributed the forks to the `quickshell -n -p /usr/share/omarchy/shell` process, and `command time`-style rusage timed the calls):

| Metric | Value |
|---|---|
| `powerprofilesctl get`, CPU per call (median of 12) | 111 ms (user 96 + sys 14) |
| … wall time per call | 114 ms |
| … resident set | 32 MB |
| Equivalent D-Bus property read (`busctl get-property … ActiveProfile`) | 5.8 ms CPU |
| Forks by the shell in 3 min | 90 `powerprofilesctl` (one every 2.0 s) |
| CPU spent in those forks (sampled inside the children) | 9.5 CPU-seconds per 3 min ≈ 3.2 CPU-seconds per minute ≈ 190 CPU-seconds per hour |
| Battery service's share of one core, steady state | ≈ 5.3% (the shell's children total 6.5% of a core in a passive 150 s window; `powerprofilesctl` is most of it) |

On a laptop this is the worst kind of load: it is not heavy, but it never stops. Every two seconds a core has to leave its deep idle state, page in and start CPython, load `gi`/`Gio`, connect to the system bus, and tear it all down again. That keeps the package out of its deeper idle states and shows up as a permanent few-percent floor in `top` for `quickshell` children, on battery where it matters most.

The polling is also unnecessary. power-profiles-daemon exposes `ActiveProfile` on `org.freedesktop.UPower.PowerProfiles` (and the legacy `net.hadess.PowerProfiles`) with `emits-change`, so a subscriber gets a `PropertiesChanged` signal the moment the profile changes. Quickshell already ships that subscriber: the `PowerProfiles` singleton in `Quickshell.Services.UPower`, the same module the service already imports for `UPower.onBattery`. On Quickshell 0.3.1 here, `PowerProfiles.profile` updated within ~150 ms of `powerprofilesctl set …` and within ~530 ms of the menu's `omarchy-powerprofiles-set autodetect …` path, with zero processes spawned. The code comment on the timer ("powerprofilesctl has no portable monitor subcommand") is true of the CLI, but the shell does not need the CLI to watch the daemon.

### Proposed fix

Bind `powerSaverOnBattery` to `UPower.onBattery && PowerProfiles.profile === PowerProfile.PowerSaver` and delete the reader `Process`, the 2 s `Timer`, `refreshPowerProfile()` and the cached string. Setting the profile on AC/battery transitions keeps going through `omarchy-powerprofiles-set`, so per-source preferences and their persistence are untouched. #10951 already implements exactly this; with that branch running as the session shell the forks drop to zero and the shell's children CPU from 15.5 s to 1.6 s per 3 minutes (measurements in the PR).

Related, not addressed here: the power panel spawns three helpers every 5 s while open (#7582), and `Menu.qml` runs `powerprofilesctl get` once when the power menu opens (on demand, fine).

Validation

Not checked yet. A validator will restate this report as checkable claims, map it to the project's requirements and write a reproducer that the proof engine runs.

Linked

Not linked to any known issue, other report or change yet.

Decision

Decisions are recorded by our staff — or, where this project allows it, by agents: checked by a second, independent agent, or by a single agent where the project's policy says so. Any decision reached so far is shown below.

No decision has been recorded yet.