Skip to content

ZoomIt: Add DemoMirror to mirror a monitor, region, or window to a second display - #49472

Open
markrussinovich wants to merge 14 commits into
microsoft:mainfrom
markrussinovich:main
Open

ZoomIt: Add DemoMirror to mirror a monitor, region, or window to a second display#49472
markrussinovich wants to merge 14 commits into
microsoft:mainfrom
markrussinovich:main

Conversation

@markrussinovich

Copy link
Copy Markdown
Contributor

Summary of the Pull Request

Adds DemoMirror to ZoomIt: a new hotkey family (default Ctrl+9) that live-mirrors content onto a second monitor — designed for presenters who want to demo an app on their laptop screen while the audience sees it on the presentation display (e.g., on top of a PowerPoint slideshow), without leaving Presenter View.

  • Ctrl+9 — mirror the entire monitor under the cursor
  • Ctrl+Shift+9 — mirror a selected region
  • Ctrl+Alt+9 — mirror the window under the cursor (with optional window tracking)

The mirror includes the mouse cursor, targets the monitor running a PowerPoint slideshow when one is found (otherwise the first non-source monitor), letterboxes on a black backdrop, shows a bright green border around the mirrored source, and works with ZoomIt's existing zoom and draw/annotation modes. The mirrored content is visible in Teams/meeting screen shares.

PR Checklist

  • Closes: #xxx
  • Communication: I've discussed this with core contributors already. If the work hasn't been agreed, this work might be rejected
  • Tests: Added/updated and all pass
  • Localization: All end-user-facing strings can be localized
  • Dev docs: Added/updated
  • New binaries: None added
  • Documentation updated: If checked, please file a pull request on our docs repo and link it here: #xxx

Detailed Description of the Pull Request / Additional comments

Capture & rendering: Uses Windows.Graphics.Capture through the existing CaptureFrameWait helper (cursor capture enabled, system capture border suppressed). A new MirrorWindow class hosts a topmost, no-activate, click-through window on the target monitor with an HWND-bound flip-model swapchain; a render thread caches frames so static content keeps rendering. A black backdrop window letterboxes the mirrored content.

Window mode: Windows mirror at native size (scaled down only if larger than the target monitor), crop to the DWM extended frame bounds (DWMWA_EXTENDED_FRAME_BOUNDS) to exclude invisible resize borders, adapt to source-window resizes by recreating the frame pool and swapchain, and auto-stop when the mirrored window closes. A new Track window setting (default on) captures the monitor cropped to the tracked window each frame so annotations show in place and the crop follows moves/resizes; when off, window-surface capture is used instead.

Annotations: Window capture can't see ZoomIt's own overlay windows, so while zoom/draw/LiveZoom is active the render thread switches capture to the monitor under the cursor and back on exit, letting annotations appear in the mirror.

Screen-share compatibility: The mirror and backdrop windows are visible to display capture so remote meeting attendees see the mirrored content; self-capture feedback loops are avoided by substituting the source monitor when the annotation-override capture would resolve to the target monitor. The green border stays above ZoomIt's own fullscreen-topmost windows via the mirror's topmost-reassert timer.

Win11 capture border: IsBorderRequired(false) is silently ignored until the process calls GraphicsCaptureAccess::RequestAccessAsync(Borderless); this is now requested (auto-granted for desktop apps) before session creation.

Settings: New DemoMirror tab in ZoomIt's options dialog (hotkey + track-window checkbox), persisted in registry settings, and exposed in the PowerToys Settings UI (ZoomItProperties, ZoomItViewModel, ZoomItPage.xaml) with localizable strings in Resources.resw.

Validation Steps Performed

Manually tested on a two-monitor setup (including a Surface ARM device with an external monitor):

  • Monitor, region, and window mirroring via all three hotkeys, with the mouse cursor visible in the mirror
  • Native-size and scaled-down window mirroring; letterboxing on the backdrop
  • Window move/resize while mirrored (both tracking and non-tracking modes); auto-stop on window close
  • Zoom and draw annotations appearing in the mirror in all modes
  • Mirrored content visible to remote attendees in a real Microsoft Teams display share
  • Green border correctly colored and staying above static zoom; no lingering Windows 11 capture border
  • Settings round-trip through both the ZoomIt options dialog and the PowerToys Settings UI

🤖 Generated with Claude Code

markrussinovich and others added 14 commits July 20, 2026 13:56
…cond monitor

Mirrors a selected screen region (Ctrl+9) or the window under the cursor
(Ctrl+Alt+9), including the mouse pointer, onto a second monitor on top of a
slide show, so a presenter can demo an app on their laptop while the audience
watches it on the presentation monitor without leaving PowerPoint presenter
view. Ctrl+Up/Ctrl+Down zoom the mirrored image around the mouse pointer.

Implementation: Windows.Graphics.Capture frames (cursor capture enabled) are
cached to a texture and the visible sub-rectangle is copied into a flip-model
swapchain on a topmost, no-activate, click-through, capture-excluded window;
the swapchain source size drives the zoom via DXGI linear-filtered stretch.
The target monitor prefers the one showing a PowerPoint slide show, falling
back to the first non-source monitor. Mirroring stops automatically when the
mirrored window closes.

Co-Authored-By: Claude Mythos 5 <noreply@anthropic.com>
When the mirrored window resizes, recreate the capture frame pool and
swapchain at the new content size and re-fit the mirror window to the new
aspect ratio on the target monitor. Previously both were fixed at the
window's initial size, clipping content when the window grew and distorting
it when the aspect ratio changed.

Co-Authored-By: Claude Mythos 5 <noreply@anthropic.com>
Scale-to-fit already handled sources larger than the target display, but the
mirror window was sized exactly to the image, so the presentation showed
around it. Add a full-monitor black backdrop window behind the mirror so the
letterbox areas above/below (or beside) the image are black, kept directly
below the mirror by the topmost timer and excluded from capture.

Co-Authored-By: Claude Mythos 5 <noreply@anthropic.com>
…oring

Window capture pulls frames from the mirrored window's own surface, so
ZoomIt's zoom, draw, and live-zoom overlays - separate top-level windows -
never appeared in a window mirror. When one of those modes activates, the
mirror now temporarily switches to capturing the monitor under the cursor
(where the annotation UI appears) and switches back to occlusion-proof
window capture when the mode exits. Cursor capture is disabled during live
zoom, which already renders a magnified cursor, to avoid doubling it. The
mirror re-fits to the monitor's aspect ratio during the override and back to
the window's afterward.

Co-Authored-By: Claude Mythos 5 <noreply@anthropic.com>
Add a track-window mode (on by default, toggle on the DemoMirror options
tab): window mirroring captures the monitor and crops to the tracked window
rectangle every frame instead of capturing the window surface. Zoom, draw,
and live-zoom annotations then show in place without the mirror jumping to
the full monitor, and the crop follows the window as it moves and resizes.
The trade-off is that overlapping windows become visible in the mirror;
unchecking the option restores occlusion-proof window-surface capture with
the monitor-switch annotation behavior.

Co-Authored-By: Claude Mythos 5 <noreply@anthropic.com>
… fit

Window mirrors previously scaled to fill the target monitor, so every source
window resize rescaled the whole image. Show mirrored windows at 1:1 pixel
size, centered on the black backdrop, and only scale down when the window is
larger than the target monitor. Regions still fill the monitor since
enlarging a selected area is the point of region mirroring.

Co-Authored-By: Claude Mythos 5 <noreply@anthropic.com>
Window mirroring had no border showing what was mirrored; add a green frame
that follows the source window as it moves and resizes, drawn just outside
the window rect with the same width and translucency as the record border
and excluded from capture. The region-mirror selection border is now the
same bright green, distinguishing mirror borders from the yellow
record/panorama borders (SelectRectangle gains a BorderColor setter).

Co-Authored-By: Claude Mythos 5 <noreply@anthropic.com>
…border

The yellow border seen while mirroring was the Windows 11 system capture
border: IsBorderRequired(false) is ignored until the process requests
borderless capture access via GraphicsCaptureAccess::RequestAccessAsync,
which is granted without a prompt for desktop apps. Request it on the render
thread (the call blocks, so it needs an MTA thread) and re-apply the border
setting to the live session. Also make both mirror borders fully opaque so
the green reads bright instead of blending with what is underneath.

Co-Authored-By: Claude Mythos 5 <noreply@anthropic.com>
…ture border

Two fixes from testing:

- Window mirrors showed a margin of desktop around the window because
  GetWindowRect includes the invisible resize/shadow frame. Use the DWM
  extended frame bounds (visible edges) for the mirror crop, the green
  border, and cursor mapping.

- The system capture border could still appear because IsBorderRequired was
  set after StartCapture, before the borderless-capture grant existed.
  Acquire the grant in Start on a short-lived MTA thread before creating the
  session, and let CaptureFrameWait apply IsBorderRequired(false) before
  StartCapture via a new constructor parameter.

Co-Authored-By: Claude Mythos 5 <noreply@anthropic.com>
SelectRectangle::Start resets the border color to yellow on every call (to
recover from the recording-active orange), silently discarding the green set
for mirror borders beforehand. Track the configured color and have Start
reset to it instead of hardcoded yellow; the record and panorama instances
never configure a color and keep the yellow default.

Verified with an automated region-mirror run capturing OutputDebugString:
the border now paints 0x00FF00 and borderless capture access is granted.

Co-Authored-By: Claude Mythos 5 <noreply@anthropic.com>
…om zoom

Rework the mirror hotkeys to match recording: the base hotkey (Ctrl+9)
mirrors the entire monitor under the cursor with a full-monitor green
border, Shift selects a region, and Alt mirrors the window under the
cursor.

Remove the mirror-specific Ctrl+Up/Ctrl+Down zoom and mouse-follow pan;
standard ZoomIt zoom and draw modes show in the mirror on all
monitor-capture paths, so the custom controls were redundant and conflicted
with LiveZoom hotkeys. Monitor captures (screen, region, window tracking)
now also disable cursor capture while live zoom renders its magnified
cursor.

Co-Authored-By: Claude Mythos 5 <noreply@anthropic.com>
Add MirrorToggleKey to the settings interop shortcut map so it round-trips
between the registry HOTKEYF encoding and PowerToys hotkey JSON, declare
MirrorToggleKey/MirrorTrackWindow in ZoomItProperties, and add a DemoMirror
group to the ZoomIt settings page with the activation shortcut (showing the
derived Shift=region and Alt=window variants like Record does) and the
track-window checkbox. MirrorTrackWindow flows through the existing generic
boolean handling in the interop.

Co-Authored-By: Claude Mythos 5 <noreply@anthropic.com>
Put the explanation in static text above the checkbox and use a short
right-side checkbox label matching the style of the other option tabs.

Co-Authored-By: Claude Mythos 5 <noreply@anthropic.com>
…static zoom

Remove WDA_EXCLUDEFROMCAPTURE from the mirror and backdrop windows so
Teams and other WGC/DDA screen shares of the target monitor show the
mirrored content. The annotation monitor-override now pins to the source
monitor so a capture-visible mirror can never capture itself.

Static zoom and draw raise a full-screen topmost window that buried the
green source border; the mirror's topmost timer now reclaims the border
(the mirror-owned window border, or the SelectRectangle border passed
via the new sourceBorderWindow parameter), skipped during live zoom
whose own reclaim timer would fight it.

Co-Authored-By: Claude Mythos 5 <noreply@anthropic.com>
@azure-pipelines

Copy link
Copy Markdown
Azure Pipelines:
There may be pipelines that require an authorized user to comment /azp run to run.

@github-actions github-actions Bot added the Product-ZoomIt Refers to the ZoomIt tool label Jul 23, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Product-ZoomIt Refers to the ZoomIt tool

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant