SUMMARY In PowerManagementJob, stopSuppressingScreenPowerManagement declares the wrong QDBusReply type, so its process-wide cookie is never cleared. Every subsequent beginSuppressingScreenPowerManagement then silently no-ops while reporting success. Screen power management can be inhibited exactly once per plasmashell process; after the first release it can never be re-acquired. stopSuppressingSleep is unaffected, because it uses the correct specialisation for the identical situation 30 lines earlier in the same function. MECHANISM src/dataengines/powermanagement/powermanagementjob.cpp:90-99 } else if (operation == QLatin1String("stopSuppressingScreenPowerManagement")) { QDBusMessage msg = QDBusMessage::createMethodCall(QStringLiteral("org.freedesktop.ScreenSaver"), QStringLiteral("/ScreenSaver"), QStringLiteral("org.freedesktop.ScreenSaver"), QStringLiteral("UnInhibit")); msg << m_lockInhibitionCookie; QDBusReply<uint> reply = QDBusConnection::sessionBus().call(msg); // <-- wrong type m_lockInhibitionCookie = reply.isValid() ? -1 : m_lockInhibitionCookie; setResult(reply.isValid()); return; org.freedesktop.ScreenSaver.UnInhibit returns void. Introspection confirms: method void org.freedesktop.ScreenSaver.UnInhibit(uint cookie) A void reply carries zero arguments, so Qt's qDBusReplyFill falls through its type check (qtbase src/dbus/qdbusreply.cpp:172): if (reply.arguments().size() >= 1 && reply.arguments().at(0).metaType() == data.metaType()) { and sets QDBusError::InvalidSignature. reply.isValid() is therefore ALWAYS false, and m_lockInhibitionCookie is never reset to -1. The UnInhibit call itself succeeds, so the inhibition really is released. But m_lockInhibitionCookie is inline static (powermanagementjob.h:30), i.e. process-wide, so every later begin hits the guard added in bea735c5: } else if (operation == QLatin1String("beginSuppressingScreenPowerManagement")) { if (m_lockInhibitionCookie != -1) { // an inhibition request is already active setResult(true); return; } and returns success without contacting PowerDevil at all. For comparison, the sleep path at line 64 is correct: QDBusReply<void> reply = QDBusConnection::sessionBus().call(msg); HOW THIS AROSE Two commits, neither wrong on its own: 6379d1ec (2021-09-06) "Call UnInhibit with correct signature in powermanagement dataengine" fixed the ARGUMENT type on both UnInhibit calls (toInt() -> toUInt()) but left the screen path's reply type as uint. Harmless at the time: isValid() only fed setResult(), which nothing consumed. bea735c5 (2024-01-23) "don't trigger another inhibition request while one is already in place" added the double-begin guard and made the cookie reset conditional on reply.isValid(). Correct for sleep. This is what made the pre-existing wrong reply type on the screen path load-bearing. The file then moved to plasma5support (d91b8300, 2024-09-27) and has not been modified since. WHY THIS HAS GONE UNREPORTED No in-tree component calls this operation. On an Arch system running Plasma 6 with plasma5support 6.7.3, the only files mentioning beginSuppressingScreenPowerManagement are the dataengine that implements it and /usr/share/plasma5support/services/powermanagementservice.operations, which declares it. The Power and Battery applet's "Manually block sleep and screen locking" checkbox does not use this dataengine; it holds its own C++ cookie against PowerDevil directly. The code path is therefore dead in-tree and reachable only by third-party plasmoids using the plasma5support powermanagement dataengine. That is why this has been latent since 2021 and functionally broken since January 2024 without a report. STEPS TO REPRODUCE There is no stock-Plasma reproducer, for the reason above. Any plasmoid using the dataengine will do; this suffices: Plasma5Support.DataSource { id: pm engine: "powermanagement" connectedSources: ["PowerDevil"] } function screen(on) { const s = pm.serviceForSource("PowerDevil"); const op = s.operationDescription(on ? "beginSuppressingScreenPowerManagement" : "stopSuppressingScreenPowerManagement"); if (on) op.reason = "test"; s.startOperationCall(op); } PowerDevil debounces enforcement by about 5 seconds, so wait ~6s before each check. 1. screen(true), wait, then: qdbus6 org.kde.Solid.PowerManagement \ /org/kde/Solid/PowerManagement/PolicyAgent HasInhibition 4 -> true 2. screen(false), wait, check again -> false (correct) 3. screen(true), wait, check again -> false <-- THE BUG OBSERVED RESULT Step 3 leaves HasInhibition(4) false. The PolicyAgent's RequestedInhibitions property lists only the unrelated sleep entry, confirming the screen request was never sent. The operation reported success. Nothing is logged. Recovery requires restarting plasmashell. EXPECTED RESULT Step 3 returns true, as the equivalent sleep sequence does for HasInhibition(1). PATCH --- a/src/dataengines/powermanagement/powermanagementjob.cpp +++ b/src/dataengines/powermanagement/powermanagementjob.cpp @@ -93,7 +93,7 @@ void PowerManagementJob::start() QStringLiteral("org.freedesktop.ScreenSaver"), QStringLiteral("UnInhibit")); msg << m_lockInhibitionCookie; - QDBusReply<uint> reply = QDBusConnection::sessionBus().call(msg); + QDBusReply<void> reply = QDBusConnection::sessionBus().call(msg); m_lockInhibitionCookie = reply.isValid() ? -1 : m_lockInhibitionCookie; // reset cookie if the stop request was successful setResult(reply.isValid()); return; Note: the QDBusReply<uint> used for ScreenSaver.Inhibit in the begin branch is correct and must not be changed. Inhibit genuinely returns a uint cookie. SOFTWARE/OS VERSIONS Arch Linux, Plasma 6, plasma5support 6.7.3. Present in plasma5support master as of 2026-07-28. POSSIBLY RELATED, IN THE SAME DATAENGINE powermanagementengine.cpp:304 demarshals PolicyAgent.ListInhibitions into QList<InhibitionInfo>, where InhibitionInfo is QPair<QString, QString>, i.e. wire signature a(ss). The method advertises QMap<QString,QString> in introspection and renders as aas on the wire. Neither matches, so the conversion fails, the async callback never runs, and the engine's "Inhibitions" source is permanently empty. Verified by connecting a plasmoid to that source while two inhibitions were held: no data was ever delivered. Note that ListInhibitions is marked org.freedesktop.DBus.Deprecated=true in PowerDevil's introspection, so porting the engine to the ActiveInhibitions/RequestedInhibitions properties may be the better fix. Happy to file this separately if preferred.
Thanks for the patch! The next step is to create a merge request. In the MR, please add BUG: [the number of this Bugzilla ticket] so it gets linked to this report. https://community.kde.org/Infrastructure/GitLab#Submitting_a_merge_request If you want to discuss the development work, please reach out in our Matrix channel for new contributors https://community.kde.org/Get_Involved#New_Contributor?_Say_Hello_In_Matrix Thanks again!
Merge request opened: https://invent.kde.org/plasma/plasma5support/-/merge_requests/57
Git commit 5d9f76c0acad446537b849693864e976eaf09a18 by Vincent de Robert. Committed on 29/07/2026 at 07:43. Pushed by ngraham into branch 'master'. Fix screen power management inhibition never being re-acquirable stopSuppressingScreenPowerManagement calls org.freedesktop.ScreenSaver.UnInhibit, which returns void, but declared the reply as QDBusReply<uint>. A void reply carries no arguments, so Qt's qDBusReplyFill falls through its type check and sets QDBusError::InvalidSignature, making isValid() always false. The cookie reset introduced in bea735c5 is conditional on isValid(), so m_lockInhibitionCookie is never cleared. Since that cookie is inline static and therefore process-wide, every subsequent beginSuppressingScreenPowerManagement hits its guard, returns success, and never contacts PowerDevil. Screen power management can only be inhibited once per process. Use QDBusReply<void>, matching stopSuppressingSleep in the same function. M +1 -1 src/dataengines/powermanagement/powermanagementjob.cpp https://invent.kde.org/plasma/plasma5support/-/commit/5d9f76c0acad446537b849693864e976eaf09a18
Git commit 1812089139a10ceb9e35552d9fce4c20142130c6 by Nate Graham. Committed on 29/07/2026 at 17:34. Pushed by ngraham into branch 'Plasma/6.6'. Fix screen power management inhibition never being re-acquirable stopSuppressingScreenPowerManagement calls org.freedesktop.ScreenSaver.UnInhibit, which returns void, but declared the reply as QDBusReply<uint>. A void reply carries no arguments, so Qt's qDBusReplyFill falls through its type check and sets QDBusError::InvalidSignature, making isValid() always false. The cookie reset introduced in bea735c5 is conditional on isValid(), so m_lockInhibitionCookie is never cleared. Since that cookie is inline static and therefore process-wide, every subsequent beginSuppressingScreenPowerManagement hits its guard, returns success, and never contacts PowerDevil. Screen power management can only be inhibited once per process. Use QDBusReply<void>, matching stopSuppressingSleep in the same function. (cherry picked from commit 5d9f76c0acad446537b849693864e976eaf09a18) Co-authored-by: Vincent de Robert <vincent.de.robert@gmail.com> M +1 -1 src/dataengines/powermanagement/powermanagementjob.cpp https://invent.kde.org/plasma/plasma5support/-/commit/1812089139a10ceb9e35552d9fce4c20142130c6
Git commit 4039eed5cd578cc8da696961f25bbf361bea0eab by Nate Graham. Committed on 29/07/2026 at 17:27. Pushed by ngraham into branch 'Plasma/6.7'. Fix screen power management inhibition never being re-acquirable stopSuppressingScreenPowerManagement calls org.freedesktop.ScreenSaver.UnInhibit, which returns void, but declared the reply as QDBusReply<uint>. A void reply carries no arguments, so Qt's qDBusReplyFill falls through its type check and sets QDBusError::InvalidSignature, making isValid() always false. The cookie reset introduced in bea735c5 is conditional on isValid(), so m_lockInhibitionCookie is never cleared. Since that cookie is inline static and therefore process-wide, every subsequent beginSuppressingScreenPowerManagement hits its guard, returns success, and never contacts PowerDevil. Screen power management can only be inhibited once per process. Use QDBusReply<void>, matching stopSuppressingSleep in the same function. (cherry picked from commit 5d9f76c0acad446537b849693864e976eaf09a18) Co-authored-by: Vincent de Robert <vincent.de.robert@gmail.com> M +1 -1 src/dataengines/powermanagement/powermanagementjob.cpp https://invent.kde.org/plasma/plasma5support/-/commit/4039eed5cd578cc8da696961f25bbf361bea0eab