Skip to content

Show the second screen on hybrid laptops, carried onto current main - #98

Merged
whoisaldo merged 44 commits into
fix/second-screen-fallbackfrom
fix/hybrid-second-screen
Oct 27, 2026
Merged

whoisaldo merged 44 commits into
fix/second-screen-fallbackfrom
fix/hybrid-second-screen

Conversation

@whoisaldo

@whoisaldo whoisaldo commented Oct 26, 2026 •

Copy link
Copy Markdown
Member

What changed

Stacked on #97 (fix/second-screen-fallback): this branch merges #97 and targets it. It carries the never-merged #72 forward ("Show the second screen on hybrid laptops and say why it didn't appear"). 0.3.2 shipped without it.

From #72, unchanged in intent (see #72):

  • vdd_settings.xml names the graphics card that drives the main screen. The bundled driver reads that name on every device add and takes the first adapter whose name matches, ignoring case. Without a name it picks the card with the most memory, which on Intel + NVIDIA laptops drives no screen.
  • The Stream page says why the second screen didn't appear, Setup repairs a preserved driver whose device is missing or broken, and a failed attempt logs a display snapshot (device state, cards with LUIDs and output counts, display paths).

Added on this branch before #97:

  • A card Windows lists twice under one name is still named when only its first listing drives a screen.
  • The Enable task's check makes two PnP calls instead of four.

Where #72's attach work lives after #97

#97 removed the attach block from reconcile_virtual_display; one worker thread in capture/second_screen.rs now makes every change. Each piece of #72's attach work moved into that worker:

#72 attach work Now
Name the main screen's card and write the modes (prepare_vdd_settings) Driver::prepare, just before the Enable, after the toggle gap
Enable failure report (vdd::enable) Driver::enable returns the task's report; the worker reports the device, logs the snapshot, and turns off a device it enabled that cannot show a display (after the 10 s gap)
Device report and snapshot when nothing attached Driver::report(AttachTimeout), before the Disable
Snapshot of an attach that ended early The iPad no longer ends a start; it times out and logs its snapshot. A mode change that ends a start after 2 s or more still logs one, once per launch
Second screen mode (settle_vdd_mode) and the card the driver renders on Driver::settle, before the switch onto the display
30 s wait after "kept your desktop off it" The next start waits 30 s for Extend. With the mirror on the iPad meanwhile, the iPad stays connected, so the notice's wait can now come true
Debug ETERNAL_SIMULATE_ATTACH_TIMEOUT In the worker's attach

New with #97: a second screen that shows no picture (no_frames, duplication_failed) logs the snapshot, with the card it rendered on, while the display is still on, and then goes back to the mirror. It does not set a device problem: the device worked.

Review fixes

  • A late monitor is the host's to wait for. The Enable task no longer fails when the device has not started or has no monitor yet. It still fails for a missing device, no driver bound, or a Windows problem code. The host's attach wait decides, and its timeout logs vdd_device=no_monitor.
  • The card name changes only when this host may. The name in vdd_settings.xml is replaced only on a driver Setup installed, or when it is empty, default, or the last name this host wrote (recorded in vdd-pin.txt in the app data folder). This holds whether or not Match the iPad's resolution rewrites the whole file. When this host has no card to name, it takes back its own earlier name, so the file never keeps a name the log says was not made.
    • "Setup installed" means what the removal and repair scripts check: Setup's marker names the uninstaller and version the driver is registered with now. A driver another app replaced or upgraded since is not this host's.
    • A settings file this host cannot read (for example a name in CDATA) may name anyone's card. Match the iPad's resolution leaves it as it is, and the snapshot says so, unless Setup installed the driver and there is a card to name.
    • If writing the file fails, the record goes back to the name the file still holds, so a later attempt still knows that name as its own.
  • A mode change while the second screen's mode is being set turns the display back off, after the 10 s gap, instead of leaving it on as the active second screen. The worker records the display under the mode lock, so a mode change either comes first and is seen there, or comes after and its capture generation turns the display off.
  • A second screen that attached but showed no picture (no_frames, duplication_failed) has its own Stream page notice instead of "Windows didn't add the second screen in time".
  • The log records the card the driver rendered on: the adapter holding the virtual display's path, or the one adapter new since the Enable.

How it was tested

  • macOS: cargo fmt --check, cargo clippy --workspace --all-targets -- -D warnings, cargo test --workspace: 557 passed.

  • Windows 11 (RTX 5080 desktop with AMD integrated graphics), in a separate worktree: cargo fmt --check, cargo build --workspace, cargo clippy --workspace --all-targets -- -D warnings, cargo test --workspace --no-fail-fast (548 passed; two timing tests, startup::reporter and telemetry idle uploader, failed once under load and passed three times each alone), and the six VDD and installer PowerShell tests.

  • Unit tests for the worker (paused clock, fake driver): the card is named before the Enable, and an attached display is settled before the switch. A timeout is reported before its Disable. A failed Enable is reported, and a device it enabled that cannot show a display is turned off after the gap, but a missing device is not. The start after "kept your desktop off it" waits 30 s, while other failures wait the usual 10 s; Extend chosen 20 s in brings the second screen up. A mode change after 2 s or more is reported once. A second screen without a picture is reported before its Disable, from both the transport tick and the next capture generation. Also tested: the ownership rule for both the edit-only and the whole-file rewrite, a marker for another driver install, an unreadable settings file, a failed write, a mode change while the mode is set, and the no-picture notice.

  • Enable task on the same PC (three runs each, display attached every time):

    Script Task finished Display on the desktop
    main 3.8–3.9 s (first, cold run 10.8 s) 3.4 s (first 10.3 s)
    this branch 4.2–4.4 s 3.2–3.4 s

    With the driver named to the AMD iGPU: main 3.8–4.0 s, this branch 4.4–4.7 s, display on the desktop at 3.4–3.7 s for both. In an earlier run on a busy PC, three Enable runs (both scripts) did not finish within the harness's 25 s window. They exited 0 later, and a rerun was clean.

  • Native run, debug host from this branch, Second screen selected, a test client connecting:

    • The settings file names the AMD iGPU, which this host did not write, with Match on and with Match off: the name stays, the log says so, and the driver renders on the AMD iGPU.
    • The file leaves the card to the driver: the host names the RTX 5080 and records it, and the driver renders there.
    • With Match on, the second screen's first image arrived 71 ms after the switch.
    • A simulated attach timeout logs the device state and the snapshot, with the pin, the render card and the adapters, before the Disable.
    • Afterwards the settings file was restored byte for byte, the device was back at code 22, and the display layout was unchanged.
  • Not reproduced here: the hybrid-laptop failure itself. The fix still needs a check on an Intel + NVIDIA laptop.

The attach block this branch extended is gone from reconcile_virtual_display:
the second screen worker turns the display on now. The conflict keeps that
side; the attach work moves into the worker in the next commit.
…d screen worker

The worker now does what the removed attach block did: just before each
Enable it names the main screen's graphics card and writes the modes
(prepare_vdd_settings), it waits 30 s for Extend after "kept your desktop
off it", it brings an attached display to the iPad's mode and logs the card
the driver renders on, and it reports the device and logs a display snapshot
while the display is still on: when the Enable task fails (turning off a
device it enabled that cannot show a display), when no display attaches,
when a mode change ends a long start, and when the second screen shows no
picture (no_frames, duplication_failed).
@whoisaldo
whoisaldo changed the base branch from main to fix/second-screen-fallback October 27, 2026 02:26
@whoisaldo
whoisaldo marked this pull request as ready for review October 27, 2026 08:45
@whoisaldo
whoisaldo merged commit 33d14a7 into fix/second-screen-fallback Oct 27, 2026
6 checks passed
whoisaldo added a commit that referenced this pull request Oct 27, 2026
…laptops (#97, #98)

When an iPad connects in Second screen mode the PC now streams its main display at once, turns the virtual display on in the background, and switches to it once it shows a real picture. If it doesn't within 5 seconds the PC goes back to mirroring and says why. One worker turns the virtual display on and off, at most once every 10 seconds, so reconnects no longer make the screen blink. NVIDIA and Intel encoders answer a keyframe request with a real keyframe, so a reconnecting iPad gets a picture at once. Includes #98: the virtual display driver is pinned to the GPU that drives the screens on hybrid laptops, only when this PC may change the driver's settings.
@whoisaldo whoisaldo mentioned this pull request Oct 27, 2026
3 of 4 tasks
@whoisaldo
whoisaldo deleted the fix/hybrid-second-screen branch October 27, 2026 17:13
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.

2 participants