Poll the active power profile over D-Bus instead of powerprofilesctl
The battery service ran `powerprofilesctl get` every two seconds to keep the active profile visible to the wallpaper and lock services. That command is a PyGObject script, so the shell spawned a Python interpreter for it tens of thousands of times a day. Roughly once a day one of those exits into a CPython 3.14 finalization race (python/cpython#124619): the GLib D-Bus worker thread calls PyGILState_Ensure after the interpreter is torn down and the process dies with SIGSEGV, leaving a core dump and a crash notification behind. Read the ActiveProfile property straight from power-profiles-daemon with busctl, the same way omarchy-powerprofiles-set already reads UPower. The output is JSON, so an empty or malformed reply when the daemon is not running still reads as no active profile, matching the previous behaviour. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011gbfh4Mi9dK6SAd1P2xMTi
This commit is contained in:
1 parent
31bd80daa4
commit
55799c4ab0
2 files changed
+23
-6
No files matched your search
@@ -59,6 +59,16 @@ Item {
|
||||
if (!powerProfileReadProcess.running) powerProfileReadProcess.running = true
|
||||
}
|
||||
|
||||
function parseActiveProfile(text) {
|
||||
// busctl --json=short prints {"type":"s","data":"balanced"}; an empty or
|
||||
// malformed reply (daemon not running) reads as no active profile.
|
||||
try {
|
||||
return String(JSON.parse(text).data || "").trim()
|
||||
} catch (e) {
|
||||
return ""
|
||||
}
|
||||
}
|
||||
|
||||
Process { id: warningProcess }
|
||||
|
||||
Process {
|
||||
@@ -71,17 +81,23 @@ Item {
|
||||
|
||||
Process {
|
||||
id: powerProfileReadProcess
|
||||
command: ["powerprofilesctl", "get"]
|
||||
// Read the property straight from the daemon rather than via
|
||||
// `powerprofilesctl get`. That is a PyGObject script, and spawning a Python
|
||||
// interpreter for it every two seconds trips a CPython 3.14 shutdown race
|
||||
// (python/cpython#124619): the GLib D-Bus worker thread re-enters the
|
||||
// interpreter after finalization and the process dies with SIGSEGV,
|
||||
// leaving a core dump and a crash notification behind roughly daily.
|
||||
command: ["busctl", "--json=short", "get-property", "net.hadess.PowerProfiles", "/net/hadess/PowerProfiles", "net.hadess.PowerProfiles", "ActiveProfile"]
|
||||
stdout: StdioCollector {
|
||||
waitForEnd: true
|
||||
onStreamFinished: root.activePowerProfile = String(text || "").trim()
|
||||
onStreamFinished: root.activePowerProfile = root.parseActiveProfile(text)
|
||||
}
|
||||
}
|
||||
|
||||
Timer {
|
||||
// powerprofilesctl has no portable monitor subcommand; keep profile changes
|
||||
// visible to consumers such as the wallpaper service without requiring the
|
||||
// power panel to be open.
|
||||
// There is no portable way to subscribe to profile changes from QML; keep
|
||||
// them visible to consumers such as the wallpaper service without requiring
|
||||
// the power panel to be open.
|
||||
interval: 2000
|
||||
running: true
|
||||
repeat: true
|
||||
|
||||
@@ -174,7 +174,8 @@ assert(
|
||||
assert(
|
||||
batteryService.includes('property string activePowerProfile') &&
|
||||
batteryService.includes('UPower.onBattery && activePowerProfile === "power-saver"') &&
|
||||
batteryService.includes('["powerprofilesctl", "get"]') &&
|
||||
batteryService.includes('"get-property", "net.hadess.PowerProfiles", "/net/hadess/PowerProfiles", "net.hadess.PowerProfiles", "ActiveProfile"') &&
|
||||
!batteryService.includes('["powerprofilesctl"') &&
|
||||
batteryService.includes('interval: 2000'),
|
||||
'the battery service tracks the active power-saver profile'
|
||||
)
|
||||
|
||||
Reference in new issue
Block a user