Skip to content

ConnectorScanner does not re-emit Connected event when modes change on already-connected connector #1922

@coleleavitt

Description

@coleleavitt

Problem

ConnectorScanner::scan() in smithay-drm-extras only tracks connector::State transitions (Connected ↔ Disconnected ↔ Unknown). When a connector remains Connected across two consecutive scans, the (Connected, Connected) => {} arm is a no-op — no event is emitted regardless of whether the connector's properties (specifically its mode list) have changed.

File: smithay-drm-extras/src/drm_scanner/connector_scanner.rs, line 55

(State::Connected, State::Connected) => {}

Impact

This causes a permanent dead state for connectors on USB-C docks with DP MST/alt-mode (and potentially other hotplug scenarios):

  1. Kernel DRM reports a connector as Connected before EDID data is available (firmware negotiation in progress)
  2. get_connector(handle, true) returns the connector with state() == Connected but modes() returns an empty list
  3. The compositor calls pick_mode() → gets None → skips activation
  4. A subsequent udev Changed event triggers another scan
  5. get_connector() now returns populated modes, but ConnectorScanner sees (Connected, Connected) and emits nothing
  6. The connector is stuck in "connected but never activated" state permanently

Affected Compositors

This affects any compositor using ConnectorScanner or DrmScanner from smithay-drm-extras:

  • niriYaLTeR/niri#869 (crash/freeze on USB-C dock), YaLTeR/niri#2691 (inconsistent display detection)
  • Potentially any smithay-based compositor with USB-C dock or DP MST users

Cross-Compositor Analysis

For context, I analyzed how other compositors handle this:

Compositor Handling
Mutter (GNOME) 3-second CONNECTION_CHANGE_TIMEOUT debounce timer per connector; detects mode list changes as META_KMS_RESOURCE_CHANGE_FULL triggering full reload
wlroots No retry — accepts connected+empty-modes as-is, relies on subsequent udev events
KWin isConnected() requires !m_driverModes.empty() — connected+no-modes treated as disconnected; no retry for this case

Mutter is the only compositor that handles mode-list changes on already-connected connectors at the library level.

Proposed Fix

Compare the old and new connector::Info mode lists in the (Connected, Connected) arm and re-emit a Connected event when they differ:

(State::Connected, State::Connected) => {
    if old.modes() != conn.modes() {
        added.push(conn);
    }
}

This is safe because:

  • connector::Info derives PartialEq, and modes() returns &[control::Mode] which also implements PartialEq
  • Compositors already handle duplicate Connected events gracefully (they check if a surface already exists for the CRTC)
  • The comparison is cheap for the common case (both mode lists are the same)

Reproduction

  • Hardware: ThinkPad P16 Gen 3, NVIDIA RTX PRO 4000 (nvidia-drm 590.48.01), Intel iGPU (i915), Lenovo ThinkPad USB-C Dock Gen 2
  • Monitors: Two LEN S27q-10 (2560x1440@60Hz) connected via HDMI + DP MST through dock
  • Symptoms: Intermittent — sometimes both monitors activate on boot, sometimes one stays dark permanently until dock is re-plugged

Related

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions