GuideVirtual screenaction_overlay.rs

Can I use a TV for a computer monitor? Yes, and plugging it in quietly resizes the overlay window every app on your PC is drawing.

The cable advice is fine. HDMI 2.1, 4:4:4 chroma, game mode, refresh rate, input lag. All written. The part that lives one layer below, that nobody on the first page of Google touches, is what happens to the virtual-screen bounding box the moment the TV becomes display 2. Every overlay on your PC, including the one Terminator paints for each automation step, sizes itself against that box. So the TV does not just show more pixels. It quietly joins the status UI of every tool already running.

T
Terminator
8 min read
4.9from Open-source, MIT
Overlay HWND sized by SM_CXVIRTUALSCREEN / SM_CYVIRTUALSCREEN (action_overlay.rs:287-291)
WS_EX_TRANSPARENT means the banner cannot eat clicks on either display
TERMINATOR_ACTION_OVERLAY=0 opts out when the TV is for watching
Cooldowns: CHANGE_COOLDOWN_MS=100, MINIMUM_DISPLAY_MS=300, no strobe

Virtual screen, before and after the TV

Windows exposes four metrics that together describe the bounding rectangle of every monitor the OS can see. SM_XVIRTUALSCREEN, SM_YVIRTUALSCREEN, SM_CXVIRTUALSCREEN, SM_CYVIRTUALSCREEN. Plug a 4K TV to the right of a 2560x1600 laptop, and the bounding box changes like this.

0CX before (laptop alone)
0CX after (laptop + 4K TV)
0msCHANGE_COOLDOWN_MS
0msMINIMUM_DISPLAY_MS

The anchor fact: four GetSystemMetrics calls, one overlay, every display

Inside create_overlay_window() in crates/terminator/src/platforms/windows/action_overlay.rs, lines 287 through 291 read the virtual screen directly from Windows and pass those values to CreateWindowExW. No branching on monitor count, no special TV handling. The OS tells Terminator how big the desktop is; Terminator makes an overlay that size.

crates/terminator/src/platforms/windows/action_overlay.rs

That is the entire reason the banner stretches onto the TV. It is not a multi-monitor mode. It is four Windows system metrics feeding a single WS_EX_TRANSPARENT window. Everything else in the overlay system, the cooldowns, the RAII guard, the env var, is built on top of that one geometry call.

Watch the overlay follow the TV in

Four-frame walkthrough of what happens between “you plug in the TV” and “Terminator's status banner is painting across two displays.”

From HDMI-in to ambient dashboard

01 / 04

Step 1. One display, everything normal

SM_CXVIRTUALSCREEN returns the laptop's width (2560). The action overlay paints a 2560x1600 rectangle across the laptop panel. Every click shows a status banner. Everything works the same as always.

Every automation step flows through the same overlay HWND

UIElement.click()
UIElement.type_text()
UIElement.press_key()
UIElement.invoke()
show_action_overlay
Laptop panel
LG TV

Six concrete things the overlay gives you on a TV

All of this is in one file: crates/terminator/src/platforms/ windows/action_overlay.rs. Grep for any of these names in a fresh clone of the repo and the implementation is right there.

One overlay window, N displays

The action overlay is a single HWND sized by (SM_XVIRTUALSCREEN, SM_YVIRTUALSCREEN, SM_CXVIRTUALSCREEN, SM_CYVIRTUALSCREEN). Add a TV, and that rectangle grows to include it. Remove the TV, it shrinks back. No per-monitor bookkeeping.

Click-through by design

WS_EX_TRANSPARENT + WS_EX_LAYERED + WS_EX_NOACTIVATE. The overlay is visible on the TV but cannot intercept a click anywhere, so your hands on the laptop stay in control even when the overlay is painting across both screens.

Cooldowns tuned for TV refresh

OVERLAY_CHANGE_COOLDOWN_MS = 100, MINIMUM_DISPLAY_MS = 300. Those two constants stop the banner from strobing when the automation fires many actions per second, which matters on a 60 Hz TV further away from your eyes.

One env var to opt out

TERMINATOR_ACTION_OVERLAY=0 disables the overlay globally. Use it when the TV is showing a movie, a Figma mirror, or a browser window you are screen sharing. The automation still runs; the status banner just stops drawing.

Recording-aware highlighter

When you record a workflow across laptop + TV, set_recording_mode(true) in highlighting.rs tells the per-element highlighter to stop calling scroll_into_view, so the recording does not capture spurious scroll events the user did not actually do.

RAII guard around every action

ActionOverlayGuard wraps each click, type, and press_key. Overlay shows on .new(), hides on drop. Even if the automation panics mid-step, Drop runs and the overlay goes away. The TV never gets stuck with a stale banner.

MIT

Terminator is an open-source desktop automation framework. Every file and line number on this page is grep-able in a fresh clone of mediar-ai/terminator.

github.com/mediar-ai/terminator

Two commands: overlay on, overlay off

Same automation script. First invocation paints the status banner across laptop + TV. Second invocation runs silently because TERMINATOR_ACTION_OVERLAY=0 flips 0 atomic bool in process memory.

run.sh
action_overlay.rs (env var check)

What it prints when you actually run it

Two monitors listed, cx=6400 from GetSystemMetrics, one CreateWindowExW call sized to that. This is the entire round-trip on a laptop plus 4K TV setup.

laptop + LG TV, overlay turning on

How to set this up in practice

Five steps from a cold laptop to a TV showing automation feedback in real time. None of them require recompiling Terminator.

1

1. Plug the TV into HDMI, extend the desktop

Windows renumbers monitors immediately; no reboot needed. In Display Settings, pick Extend (not Duplicate). The TV becomes display 2 with its own resolution and scale factor.

2

2. Verify the virtual screen grew

Run GetSystemMetrics(SM_CXVIRTUALSCREEN) (system metric index 78) and SM_CYVIRTUALSCREEN (79). On a laptop alone you will see your laptop width; after plugging in a 4K TV to the right, expect 2560 + 3840 = 6400 as the new width.

3

3. Run any Terminator flow

Every click, type, and press_key call goes through show_action_overlay(), which spawns a single full-virtual-screen overlay window. You will now see the status banner span both displays without any configuration.

4

4. Decide: feature or nuisance

If the TV is your ambient dashboard, leave it on. If the TV is for video, streaming, or a mirrored presentation, set TERMINATOR_ACTION_OVERLAY=0 in the environment before launching the automation.

5

5. Recording a workflow? Flip the recording mode flag

set_recording_mode(true) in highlighting.rs stops the per-element highlighter from triggering scroll_into_view. Without that flag, a TV-as-display-2 workflow picks up scroll events the user did not make, and replay drifts.

Naive full-screen overlay vs virtual-screen-aware

Most tools that draw a status overlay bind to the primary monitor and ignore additional displays. Terminator sizes itself to the bounding box of every display at once, then uses WS_EX_TRANSPARENT so the overlay stays out of the way.

FeatureNaive overlayTerminator
Overlay placement when TV is plugged inBound to primary monitor only, TV shows nothingWindow uses SM_CXVIRTUALSCREEN, auto-spans both displays
User clicks on the TV while overlay is upOverlay eats the click, user gets a weird dead zoneWS_EX_TRANSPARENT, input passes through on every display
Turning the overlay off while watching TVRecompile or edit a config fileTERMINATOR_ACTION_OVERLAY=0 env var (action_overlay.rs:38)
Overlay flashes every frame during a long automationVisible flicker on the TV as it redrawsOVERLAY_CHANGE_COOLDOWN_MS=100 + MINIMUM_DISPLAY_MS=300
Recording a workflow that uses the TV as display 2Highlight triggers scroll_into_view, pollutes the recordingset_recording_mode(true) disables scroll during highlights
Highlight rectangle on a TV-side elementHardcoded to primary monitor rectPer-element bounds resolved via element.monitor().bounds

When to set TERMINATOR_ACTION_OVERLAY=0

Green checks are cases to turn the overlay off. Empty circles are cases to leave it on. Pick by the intent for the TV, not by reflex.

Disable the overlay in these situations

  • TV is for watching video while automation runs in the background
  • TV is mirroring a presentation where a status banner would be distracting
  • Screen sharing the whole desktop and you do not want 'Clicking X' on the call
  • TV is an ambient dashboard and you WANT the readout (leave the env var unset)
  • Recording a workflow and every visual artifact matters (leave it on, use recording mode instead)

The unintended good thing: TV as an ambient automation readout

The cable-advice articles treat a TV-as-monitor as a compromise: bigger pixels, more distance, probably OK for casual use. That framing misses the shape of how people actually use a TV at a desk. A TV is usually off to the side, a little further away, easier to glance at than to focus on. That is exactly the ergonomics of a status display.

Terminator's action overlay hits that ergonomics on accident. Because the banner is already as big as the virtual screen, the TV portion of it becomes a giant readout of what your automation is currently doing. “Clicking Chrome->Extensions.” “Typing on email field.” Legible from the couch, invisible to the automation itself (the cursor never leaves the laptop).

Terminator is a developer framework for desktop automation, not a consumer app. It gives existing AI coding assistants the ability to control your whole OS (not just write code), which is exactly the shape of workload where you want a second-screen status readout anyway.

Related flags and constants in the overlay path

One env var, two cooldown constants, one recording-mode flag, one RAII guard. Every name here resolves to a real line in the open-source repo.

TERMINATOR_ACTION_OVERLAY=0OVERLAY_CHANGE_COOLDOWN_MS = 100MINIMUM_DISPLAY_MS = 300WINDOW_CREATION_DELAY_MS = 50WS_EX_TRANSPARENTWS_EX_LAYEREDWS_EX_NOACTIVATEWS_EX_TOPMOSTSM_CXVIRTUALSCREEN (78)SM_CYVIRTUALSCREEN (79)ActionOverlayGuard (RAII)set_recording_mode(true)

See it on your own TV

Clone Terminator, plug in a TV, run any automation script. The status banner will show up on both displays without any multi-monitor code. To opt out, set TERMINATOR_ACTION_OVERLAY=0 before launch. The file lives at crates/terminator/src/platforms/windows/action_overlay.rs, 620 lines total.

Open mediar-ai/terminator on GitHub

Frequently asked questions

Can I use a TV for a computer monitor at all?

Yes. Any modern TV with an HDMI input works as a display. Windows and macOS treat it as monitor 2 the moment you extend the desktop onto it. The SERP answer stops there, which is fine for most people. The part that matters for anyone running automation software on the PC is that plugging the TV in also changes what Windows reports for the virtual screen bounding box (SM_XVIRTUALSCREEN, SM_YVIRTUALSCREEN, SM_CXVIRTUALSCREEN, SM_CYVIRTUALSCREEN), and any overlay or full-screen tool that sizes itself by those metrics will now extend onto the TV.

What is the anchor fact nobody else writes about?

Terminator's action overlay window is created at crates/terminator/src/platforms/windows/action_overlay.rs lines 287 through 291 by calling GetSystemMetrics(SM_XVIRTUALSCREEN), GetSystemMetrics(SM_YVIRTUALSCREEN), GetSystemMetrics(SM_CXVIRTUALSCREEN), GetSystemMetrics(SM_CYVIRTUALSCREEN), then passing those four values to CreateWindowExW with the WS_EX_TRANSPARENT flag. The overlay is one HWND that covers every display the OS can see. Plug in a 4K TV to the right of a 2560-wide laptop and SM_CXVIRTUALSCREEN returns 6400. The overlay HWND is now 6400 pixels wide. Running Grep on the open-source repo reproduces this verbatim.

Why does Terminator even draw an overlay in the first place?

So a human glancing at the screen can tell what the AI agent is currently doing. The overlay says things like 'Clicking Chrome -> Subscribe' or 'Typing on email field'. Each action method (UIElement.click, UIElement.type_text, UIElement.press_key in element.rs) wraps itself in an ActionOverlayGuard RAII type defined at action_overlay.rs line 242. The guard calls show_action_overlay in its constructor and hide_action_overlay in its Drop impl, so the banner is always in sync with whatever step is running. On a single display this is a small status bar. With a TV as display 2, the same status bar extends onto the TV, which makes it a perfectly readable dashboard from further away.

How do I turn the overlay off when the TV is showing a movie?

Set the environment variable TERMINATOR_ACTION_OVERLAY=0 (or 'false' or 'off', all case-insensitive) before launching your automation. The check lives at action_overlay.rs line 38. When the var is set, ACTION_OVERLAY_ENABLED flips to false, show_action_overlay becomes a no-op, and nothing paints on either display. Your automation still runs identically, you just lose the status banner. On Windows PowerShell: $env:TERMINATOR_ACTION_OVERLAY='0'; python run.py. On Linux/macOS: TERMINATOR_ACTION_OVERLAY=0 python run.py.

Does the overlay block me from clicking things on the TV?

No. The overlay window is created with WS_EX_TRANSPARENT in its extended style flags, which means every mouse event passes through to whatever is behind it. It is also created with WS_EX_NOACTIVATE, so it never steals focus. You can keep working on the laptop or interact with the TV as a touch display (if it is one) while the banner is painting across both. The overlay is strictly cosmetic output, not input capture.

What about flicker if the automation runs actions very fast?

Two constants in action_overlay.rs handle that. OVERLAY_CHANGE_COOLDOWN_MS is 100 milliseconds, so if two actions fire within 100ms of each other, the second one does not trigger a fresh show/hide cycle. MINIMUM_DISPLAY_MS is 300 milliseconds, so once the overlay is visible it stays visible for at least that long even if the action completes sooner. Together those mean a burst of fast clicks shows up as a continuous status banner on the TV, not a strobe.

I want to record a workflow that uses the TV as display 2. Anything to watch out for?

Yes. The per-element highlighter in crates/terminator/src/platforms/windows/highlighting.rs will by default call scroll_into_view on whatever it is about to highlight, to make the highlight visible. During a recording that is exactly what you do not want, because it creates a synthetic scroll event that was not part of the user's real workflow. Call set_recording_mode(true) (highlighting.rs line 60) before you start capturing. It flips a global AtomicBool that the highlight path reads, and scroll_into_view is skipped for the duration. Call set_recording_mode(false) when you are done.

Does this work the same on macOS with a TV as a second display?

The overall story is the same, but the concrete metric names differ. macOS reports the equivalent bounding box through NSScreen.screens.reduce over the .frame property, not GetSystemMetrics. Terminator's macOS engine does not ship the same SM_CXVIRTUALSCREEN-style action overlay today (action_overlay.rs is Windows-specific; it sits in src/platforms/windows/). The element highlighter has a cross-platform path, but the full-screen banner is Windows-only at the moment. On macOS you still get per-monitor screenshots via the capture_monitor_by_id path described in the companion guide.

Is this overlay related to the Monitor struct in lib.rs?

They are complementary. The Monitor struct at crates/terminator/src/lib.rs line 274 is the read side: it tells your automation code what displays exist, where they are, and at what scale factor. The action overlay in action_overlay.rs is the write side: it uses the OS-level virtual screen metrics to draw visible feedback across every display, no Monitor struct required. You can run one without the other, or combine them (for example: enumerate monitors, decide the TV is display 2, then set TERMINATOR_ACTION_OVERLAY=0 if the TV is is_primary=false and you know it is being used for video).

How is this different from just resizing my taskbar or wallpaper across monitors?

Taskbar and wallpaper extension are OS-level affordances: Windows takes care of them, and every app sees them passively. The action overlay is app-level: it is drawn by Terminator specifically because Terminator wants to give the user a live status readout. The reason it stretches onto a TV plugged into the PC is that Terminator sizes its overlay window by the exact same virtual-screen metrics the OS uses, so when the OS says 'the desktop is now 6400 by 2160', the overlay says yes to that without any special-case code. This is why it just works when you plug in a TV and just stops working (correctly) when you unplug it.

Terminator gives existing AI coding assistants the ability to control your whole desktop, not just write code. Like Playwright, but for every app on your OS.

terminatorDesktop automation SDK
© 2026 terminator. All rights reserved.