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

            Bug ID: 526366
           Summary: KDE display-configuration deadlock
    Classification: Plasma
           Product: KScreen
      Version First 6.6.0
       Reported In:
          Platform: NixOS
                OS: Linux
            Status: REPORTED
          Severity: normal
          Priority: NOR
         Component: common
          Assignee: [email protected]
          Reporter: [email protected]
  Target Milestone: ---

# (A) NEW BUG

**Product:** KScreen · **Component:** libkscreen · **Version:** 6.6.6 (Bug also
present in newer versions)

**Summary:**

Exceeding the number of connected displays that a GPU display engine can drive
results in libkscreen reporting output but the compositor can not enable. This
also breaks the KDE display configuration GUI tool, which reports "All displays
are disabled. Enable at least one." while some of the screens are actually on
and working properly.
Empty replication-source sentinel matches an output with an empty UUID, so
every output reports replicationSource = that output's id

## DESCRIPTION

On a system with more connected displays than the GPU's display engine can
drive
simultaneously, libkscreen reports every output as a replica of the one output
the compositor could not enable. Nothing is configured as a mirror, and KWin's
saved configuration contains no replication for any remembered setup.

The cause appears to be in `WaylandOutputDevice::updateOutput()`. The
replication
source arrives over the `kde-output-device-v2` protocol as a UUID string, and
"nothing is mirrored" is signalled by an empty string. libkscreen resolves that
string to an output id by walking the output map and comparing each output's
UUID
against it, with no check that the string is non-empty. An output that KWin
failed
to configure has an empty UUID, so the empty sentinel matches it — and because
the
loop breaks on the first match, every output is assigned that output as its
replication source, including the empty-UUID output itself, which ends up
replicating itself.

The practical effect is that the Display Configuration KCM, which filters for
outputs that are both enabled and have no replication source. With every output
carrying a replication source, the filter matches nothing, so the KCM reports
that
all displays are disabled while three are lit, and clears its needs-save flag,
which disables Apply. The check re-runs after every edit, so the panel returns
to
the same state after each change and the configuration cannot be corrected from
the GUI at all. `kscreen-doctor` is the only remaining route.

Present in 6.6.6 and unchanged on `Plasma/6.7` and `master`.

## STEPS TO REPRODUCE

1. Connect more displays than the display engine can drive simultaneously. On
this
   machine that is four connected outputs on an Intel Lunar Lake iGPU that
drives
   three; a DisplayPort MST hub is a convenient way to exceed the limit. KWin
   enables three, leaves the fourth connected but disabled, and logs
   `Applying output configuration failed!`.
2. Run `kscreen-doctor -o` and `kscreen-doctor -j`, and look at the UUID on
each
   output header line and at `replicationSource` on each output.
3. Open System Settings → Display & Monitor and try to enable the disabled
display
   or disable one of the active ones.

## OBSERVED RESULT

The output that could not be enabled has no UUID. From `kscreen-doctor -o`,
header
lines only:

```
Output: 1 eDP-1 30cd5d98-6e1a-4087-8a4a-610af87ddea6
Output: 2 DP-2  41a6211b-c757-4bf0-85fe-66a6c873c580
Output: 3 DP-3  1b27f2db-88db-40c4-983b-515fb913fd35
Output: 4 DP-4
```

Every output, DP-4 included, reports itself as a replica of output 4, while
`clones` is empty on all four — the two fields contradict each other. From
`kscreen-doctor -j`, one line per output:

```
id=1 eDP-1 enabled=True  prio=1 replicationSource=4 clones=[] 2880x1800
id=2 DP-2  enabled=True  prio=2 replicationSource=4 clones=[] 2560x1440
id=3 DP-3  enabled=True  prio=3 replicationSource=4 clones=[] 1920x1080
id=4 DP-4  enabled=False prio=0 replicationSource=4 clones=[] 3840x2160
```

Nothing was ever configured as a mirror. All ten remembered setups in
`~/.config/kwinoutputconfig.json` carry `"replicationSource": ""`.

DRM confirms three outputs are lit and the fourth is connected but not enabled,
so
the hardware state is as expected and only the reported replication is wrong:

```
card0-DP-1     status=disconnected enabled=disabled
card0-DP-2     status=connected    enabled=enabled
card0-DP-3     status=connected    enabled=enabled
card0-DP-4     status=connected    enabled=disabled
card0-eDP-1    status=connected    enabled=enabled
card0-HDMI-A-1 status=disconnected enabled=disabled
```

System Settings → Display & Monitor shows "All displays are disabled. Enable at
least one." with three displays lit, and will not apply any change — including
the
change that would resolve it, whether that is enabling DP-4 or disabling one of
the active outputs.

## EXPECTED RESULT

With nothing configured as a mirror, every output should report
`replication source:0`.

The Display Configuration panel should list the three enabled displays plus the
fourth as connected but disabled, and should allow enabling the fourth while
disabling one of the others. "All displays are disabled. Enable at least one."
should not appear while three displays are enabled.

## SOFTWARE/OS VERSIONS

```
Operating System: NixOS 26.05
KDE Plasma Version: 6.6.6
KDE Frameworks Version: 6.26.0
Qt Version: 6.11.1
Kernel Version: 7.2.3 (64-bit)
Graphics Platform: Wayland
Processors: 8 × Intel® Core™ Ultra 7 258V
Memory: 32 GiB of RAM
Graphics Processor: Intel® Arc Graphics 140V (8086:64a0, xe driver, display
version 20.00 B0)
```

libkscreen 6.6.6, kscreen 6.6.6, kwin 6.6.6, Mesa 26.1.8.

## ADDITIONAL INFORMATION

### The code path

`backends/kwayland/waylandoutputdevice.cpp:212-219` — unchanged on `master` and
`Plasma/6.7`:

```cpp
int replicationSourceId = 0;
for (auto it = outputMap.begin(); it != outputMap.end(); it++) {
    if (it.value()->uuid() == m_replicationSource) {
        replicationSourceId = it.key();
        break;
    }
}
output->setReplicationSource(replicationSourceId);
```

`m_replicationSource` is assigned verbatim from the protocol in the same file:

```cpp
void WaylandOutputDevice::kde_output_device_v2_replication_source(const QString
&source)
{
    m_replicationSource = source;
}
```

It is an empty QString when nothing is mirrored. Nothing guards the comparison
against that, so an output whose UUID is also empty satisfies it.

### Proposed fix

An empty source means "no replication" by definition, so the lookup should not
run
at all; and an output with no UUID should never be a match target:

```cpp
int replicationSourceId = 0;
if (!m_replicationSource.isEmpty()) {
    for (auto it = outputMap.begin(); it != outputMap.end(); it++) {
        const QString uuid = it.value()->uuid();
        if (!uuid.isEmpty() && uuid == m_replicationSource) {
            replicationSourceId = it.key();
            break;
        }
    }
}
output->setReplicationSource(replicationSourceId);
```

Either guard alone fixes this case. Both together also cover an empty UUID
arriving for any other reason.

### Why the KCM becomes unusable

`kscreen` `kcm/kcm.cpp:382-388`:

```cpp
std::copy_if(outputs.cbegin(), outputs.cend(),
std::back_inserter(enabledOutputs), [](const OutputPtr &output) {
    return output->isEnabled() && output->replicationSource() == 0;
});

if (enabledOutputs.empty()) {
    Q_EMIT invalidConfig(NoEnabledOutputs);
    m_configNeedsSave = false;
}
```

No output can satisfy `replicationSource() == 0`, so the list is empty,
`NoEnabledOutputs` is emitted, and `m_configNeedsSave` is cleared — which
disables
Apply. `checkConfig()` is called from `configReady()` when the panel loads and
again from `continueNeedsSaveCheck()` after every edit, so the flag is cleared
again after each change and no configuration that would resolve the state can
be
applied.

### Corroboration from an existing report

The reported value equals the id of the output that could not be configured,
which
is what this code path predicts:

- Bug 520098: the internal panel disabled by a lid close is output 1, and the
  reported value is `replication source:1`.
- This report: the undriveable output is 4, and the reported value is
  `replication source:4`.

### Where the empty UUID comes from (kwin side — offered as a question)

`OutputConfigurationStore::registerOutputs()`
(`src/outputconfigurationstore.cpp:886`) creates a UUID, but only in its own
`OutputState`:

```cpp
if (!state.uuid.has_value()) {
    state.uuid =
QUuid::createUuid().toString(QUuid::StringFormat::WithoutBraces);
}
```

It reaches the output through `DrmOutput::queueChanges()`
(`src/backends/drm/drm_output.cpp:528`):

```cpp
m_nextState->uuid = props->uuid.value_or(m_state.uuid);
```

— that is, via a configuration that succeeds. When the apply fails, the UUID
never
lands, yet the output is still advertised to clients with
`send_uuid(resource->handle, m_uuid)` (`src/wayland/outputdevice_v2.cpp:500`).

I have not instrumented KWin to confirm that path directly; it is inferred from
the code above together with `Applying output configuration failed!` in the
journal at the time the state appeared. Whether output identity should depend
on a
successful modeset is a separate question from the guard proposed above, which
is
needed either way.

### Related

Bug 520098, Bug 525693.

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

Reply via email to