Skip to main content

uwmirror

CI PyPI Python License: MIT

Mirror the center 16:9 of an ultrawide monitor to a TV — without running OBS.

You have a 32:9 ultrawide (5120×1440 or 3840×1080) and a TV on the same PC in Extend mode. You want the middle of the ultrawide — where the game or content actually is — fullscreen on the TV, with nothing else running. That's the whole tool: a borderless window on the TV showing a live center crop of the ultrawide, captured with the same zero-copy DXGI Desktop Duplication API OBS uses, in a single lightweight Python process (~150 MB RAM, a few percent of one core).

(demo GIF coming soon)

Windows only. Desktop Duplication is a Windows API; this tool is Windows 10/11 by design.

Install

No Python? Download uwmirror.exe from the latest release — a single self-contained file with the system-tray icon built in. Double-click it (no console window), or point Task Scheduler at it for autostart.

With Python (3.10+):

pipx install "uwmirror[tray]"      # or: uvx "uwmirror[tray]", or: pip install "uwmirror[tray]"

The [tray] extra adds the system-tray icon (pystray + Pillow). Plain uwmirror works too — you just control it with the global hotkeys instead of a tray. All dependencies are pure wheels (dxcam, pygame-ce, numpy).

Quickstart

uwmirror diagnose   # see both display enumerations and what auto-detection picks
uwmirror            # start mirroring

With two displays in Extend mode that's usually all you need: the widest display becomes the capture source and the other display the mirror target. If detection guesses wrong, pin the indices:

uwmirror --source 0 --target 1

Note: --source is a dxcam output index and --target is a pygame display index — two independent numbering schemes that don't necessarily match. uwmirror diagnose shows both.

Press Esc or Q in the mirror window to quit (or Ctrl+C in the console).

Options

Every flag can also be set in %APPDATA%\uwmirror\config.toml (same names, _ or - both accepted). Precedence: CLI flag > config file > auto-detection.

Flag Default Description
--source N auto dxcam output index of the monitor to capture
--target N auto pygame display index of the TV
--fps N 15 capture/present rate; also switchable at runtime from the tray
--scale smooth|fast smooth downscale filter when the crop is larger than the TV
--cursor / --no-cursor on overlay the mouse cursor (capture omits it)
--topmost / --no-topmost on keep the mirror above other windows, without stealing focus
--windowed off small framed window instead of fullscreen (debugging)
--hotkeys / --no-hotkeys on global pause/blank/quit hotkeys
--tray / --no-tray on system-tray icon (needs the [tray] extra)
--pause-hotkey SPEC ctrl+alt+p freeze the mirror on the last frame
--blank-hotkey SPEC ctrl+alt+b black out the TV (privacy)
--quit-hotkey SPEC ctrl+alt+q quit the mirror
--backend dxcam|dxcam-cpp dxcam capture library (pip install uwmirror[cpp] for the C++ fork)
--config PATH %APPDATA%\uwmirror\config.toml config file location
--log-level LEVEL info debug, info, warning, error

Example config.toml (run uwmirror diagnose to get one pre-filled):

source = 0
target = 1
fps = 30
pause-hotkey = "ctrl+alt+f9"

Controls

The mirror window on the TV deliberately never takes focus, so you drive it from the tray icon (right-click → Pause / Blank / Frame rate / Quit, with live checkmarks) and from global hotkeys:

Keys Action
Ctrl+Alt+P (global) pause/resume — freezes the last frame
Ctrl+Alt+B (global) blank/unblank — black screen
Ctrl+Alt+Q (global) quit
Space / B / Esc / Q (mirror window focused) same, as a local fallback

Global hotkeys use RegisterHotKey — no admin rights, no keyboard hooks. If a chord is taken by another app, uwmirror logs a warning and keeps running; pick a different chord with --pause-hotkey/--blank-hotkey/--quit-hotkey.

Frame rate offers 15 / 30 / 60 / 120 fps. The default is 15 — a desktop mirror rarely needs more, and raising it for a game or a video is two clicks. Switching restarts the capture, so the mirror freezes for a moment. The choice lasts for the session only and is never written back to config.toml; set fps there (or pass --fps) to change the rate uwmirror starts at. A non-preset value such as --fps 90 simply leaves all four entries unchecked until you pick one. Frame rate is tray-only — with no tray there is no runtime control, so use --fps.

Start automatically at logon

Point Task Scheduler at uwmirror.exe, or at uwmirrorw (the console-less entry point of a pip install) — see docs/autostart.md for the one-line schtasks command and the reason for the startup delay.

Limitations

Inherent to Desktop Duplication (OBS has the same ones):

  • No real cursor in the capture — uwmirror draws its own arrow overlay; it's always an arrow (no I-beam/hand shapes).
  • DRM-protected content (Netflix and friends) shows as a black region.
  • HDR desktops look washed out on an SDR TV — run the desktop in SDR.
  • UAC prompts briefly freeze the mirror (secure desktop); it recovers by itself.
  • Exclusive-fullscreen games reset the capture device on entry/exit; uwmirror reinitializes within a couple of seconds. Borderless-windowed games avoid the hiccup entirely.

Why not just OBS?

An OBS fullscreen projector does the same job well. uwmirror is for when you don't want an OBS instance running for it: one process, no compositor, no UI, roughly a third of the RAM (~150 MB vs ~400 MB), and it starts from Task Scheduler in about a second. If you already run OBS for streaming, keep using OBS.

Development

See CONTRIBUTING.md. Short version:

py -3.12 -m venv .venv && .venv\Scripts\activate
pip install -e . --group dev
ruff check . && ruff format --check . && mypy && pytest

Real-capture integration tests (need a real desktop + GPU): pytest -m local_display --no-cov.

Building the standalone exe is documented in docs/build-exe.md.

License

MIT © Jonathan Allard

Metadata

Release files for uwmirror 1.1.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for uwmirror 1.1.0
File Size Uploaded
uwmirror-1.1.0.tar.gz 58.6 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for uwmirror 1.1.0
File Interpreter ABI Platform
uwmirror-1.1.0-py3-none-any.whl Python 3 none any Details

Total release size: 91.0 kB

Release files / uwmirror-1.1.0.tar.gz

Download URL uwmirror-1.1.0.tar.gz
Size 58.6 kB
Tags Source
SHA-256 checksum
How to use checksums
ccc824e759afcdd9aba8854e1e4e47f84542170843dca04b6ef6f3214eeb933b
BLAKE2b-256 checksum
How to use checksums
69210220635f819783d02c012e3c74cdad61fc21c8629be30f067dd341628c9d
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/6.1.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Jul 24, 2026.

Transparency log

Release files / uwmirror-1.1.0-py3-none-any.whl

Download URL uwmirror-1.1.0-py3-none-any.whl
Size 32.4 kB
Tags Python 3
SHA-256 checksum
How to use checksums
34d71a612668812237791880b1c8ef390634ec8a53d9cb932f6fbb78256a5484
BLAKE2b-256 checksum
How to use checksums
9384ff5fb80148767cbdbf502e9e91672facf0fa76d539775fe84d456ef35318
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/6.1.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Jul 24, 2026.

Transparency log

Release history Release notifications | RSS feed

This release

1.1.0 This release

2 release files

1.0.0

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page