https://bugs.kde.org/show_bug.cgi?id=523605

            Bug ID: 523605
           Summary: beginSuppressingScreenPowerManagement silently no-ops
                    after the first release because
                    stopSuppressingScreenPowerManagement never clears its
                    cookie
    Classification: Plasma
           Product: plasmashell
      Version First 6.7.3
       Reported In:
          Platform: Compiled Sources
                OS: Linux
            Status: REPORTED
          Severity: normal
          Priority: NOR
         Component: DataEngines
          Assignee: [email protected]
          Reporter: [email protected]
  Target Milestone: 1.0

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.

-- 
You are receiving this mail because:
You are watching all bug changes.

Reply via email to