Skip to content

Add force_switch option to always write the input source - #206

Open
henrikosorensen wants to merge 2 commits into
haimgel:mainfrom
henrikosorensen:force-switch-option
Open

henrikosorensen wants to merge 2 commits into
haimgel:mainfrom
henrikosorensen:force-switch-option

Conversation

@henrikosorensen

Copy link
Copy Markdown

Add force_switch option to always write the input source

Fixes #151.

Problem

try_switch_display reads a monitor's current input before writing, and returns early if the
monitor already reports the requested input. That optimization assumes the read is trustworthy.

On some monitors it isn't. Queried over a connection that is not the one currently being
displayed
, they return a stale value: asked over DisplayPort 1 while actually displaying
DisplayPort 2, they answer DisplayPort 1. display-switch concludes nothing needs to change and
issues no write, so the monitor never switches.

The failure is asymmetric, which makes it confusing to diagnose. Switching away works — the
monitors really are on the current input then, so the read is accurate and the write proceeds.
Switching back silently does nothing. That is the direction that matters most.

Reproduced on 2× Acer XV273K (2019), Windows, direct GPU connection, with the second input driven
by a different machine. Log of a full round trip on stock v1.4.1:

15:58:00 [INFO] Monitored device is ("feed:0666") is disconnected
15:58:00 [INFO] Display 'Generic PnP Monitor #1' set to DisplayPort2(0x10)
15:58:00 [INFO] Display 'Generic PnP Monitor #2' set to DisplayPort2(0x10)
15:59:31 [INFO] Monitored device ("feed:0666") is connected
15:59:31 [INFO] Display 'Generic PnP Monitor #1' is already set to DisplayPort1(0xf)
15:59:31 [INFO] Display 'Generic PnP Monitor #2' is already set to DisplayPort1(0xf)

At 15:59:31 both monitors were physically displaying DisplayPort 2. The read returned 0x0f.

Worth being precise about the failure mode: the read does not error, time out, or return something
malformed. It returns a confident, well-formed, stale value. Retrying it or sleeping before it does
not help — there is nothing to wait for.

Why skipping the read is a valid fix

Reads and writes do not fail together on these monitors. With both monitors parked on DisplayPort 2
and actively displaying the other machine, writing the input source from the inactive connection
works reliably. Verified independently of display-switch, using ControlMyMonitor, which writes
unconditionally without reading first:

ControlMyMonitor.exe /SetValue "\\.\DISPLAY1\Monitor0" 60 15 /SetValue "\\.\DISPLAY2\Monitor0" 60 15

Both monitors switched immediately. So the DDC channel is functional on an inactive input for
writes; only reads are untrustworthy. Always issuing the write is therefore sufficient, and it is
the read — not the write — that has to be given up.

Changes

force_switch (default false) skips the read-before-write check and always issues the write.

force_switch = "true"

It is opt-in because the check is genuinely useful on monitors that report their input correctly:
it avoids redundant writes. Existing configurations are unaffected — there is a test asserting the
default.

A second commit adds switch_delay_ms (default 0), for setups where USB re-enumeration and the
DDC write race. The sleep happens before enumerating displays rather than immediately before the
write, which is deliberate: it means display handles are acquired after the topology has settled
rather than before, so the delay also covers the OS still re-enumerating monitors. These two
options are independent
— the force flag fixes a wrong assumption, the delay works around a
race. Happy to drop the second commit into its own PR, or drop it entirely, if you'd prefer.

Testing

  • cargo test — 14 pass, including new coverage for defaults, quoted ("true"), unquoted (true),
    and explicitly-disabled values.
  • cargo fmt --check and cargo clippy clean for the changed files.
  • Both commits build and test independently.
  • Verified on the hardware above: full round trip in both directions, from a cold boot, with the
    log showing set to rather than is already set to on every handle. The
    invalid DDC/CI length warnings that this setup used to emit also disappear, since they came
    from the read path.
17:48:27 [INFO] Monitored device is ("feed:0666") is disconnected
17:48:28 [INFO] Display 'Generic PnP Monitor #1' set to DisplayPort2(0x10)
17:48:28 [INFO] Display 'Generic PnP Monitor #2' set to DisplayPort2(0x10)
17:48:50 [INFO] Monitored device ("feed:0666") is connected
17:48:51 [INFO] Display 'Generic PnP Monitor #1' set to DisplayPort1(0xf)
17:48:51 [INFO] Display 'Generic PnP Monitor #2' set to DisplayPort1(0xf)

Notes

  • I kept both options global rather than per-monitor. Per-monitor force_switch would be
    defensible — in a mixed setup only some panels misbehave — but it complicates InputSources
    merging, and the global flag is enough to fix the reported issue. Glad to extend it if you want.
  • Implemented as typed Option<bool> / Option<u64> fields. These deserialize correctly through
    #[serde(flatten)] with the INI source, for quoted and unquoted values alike, which the tests
    cover explicitly.

🤖 Generated with Claude Code

henrikosorensen and others added 2 commits August 16, 2026 19:59
display-switch reads a monitor's current input before writing, and skips the
write if the monitor already reports the desired input. Some monitors report a
stale input source when queried over a connection that is not the one currently
being displayed: asked over DisplayPort 1 while actually displaying
DisplayPort 2, they answer "DisplayPort 1". The write is then skipped and the
monitor never switches.

The failure is asymmetric, and shows up in the log as "is already set to" where
"set to" is expected: switching away works, because the read happens to be
accurate then, while switching back silently does nothing.

Writes and reads do not fail together. On affected monitors DDC writes over an
inactive input are delivered correctly, and only reads are unreliable, so always
issuing the write is sufficient.

"force_switch" defaults to false, leaving the read-before-write check in place
for monitors that report their input correctly.

Fixes haimgel#151

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
USB re-enumeration and the DDC write can race with each other, and the OS may
still be re-enumerating displays when the USB event arrives, which can make
switching intermittent.

The sleep happens before enumerating displays rather than immediately before
the write, so that display handles are acquired after the topology has settled
rather than before.

Defaults to 0, so behavior is unchanged unless the option is set.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Add option to force switch monitor.

1 participant