The Windows automation program you can watch work from the desktop you are already using
Most Windows automation programs either run invisibly or take over the foreground. Terminator paints a WS_EX_LAYERED | WS_EX_TRANSPARENT full-screen window at 77 out of 255 alpha over your whole virtual desktop, writes the current action in 32-pixel Segoe UI, and lets your mouse and keyboard fall straight through to whatever app it is acting on. One file, 565 lines, MIT-licensed, and on by default.
The problem most Windows automation software does not try to solve
Two people are sharing the same Windows machine. One is an agent. The other is a person. The agent is running a multi-step automation that clicks buttons, types into forms, scrolls panels, and moves windows. The person is trying to read email and answer Slack.
The existing programs in this space give you two choices. Run the automation silently, in which case the person has no idea what is happening and panics when a window they were not looking at suddenly has a new dropdown selected. Or run the automation with a status window, which steals focus, appears in Alt+Tab, and has to be dismissed before the person can get back to whatever they were doing. Neither is good. Both are what every commercial Windows automation program has shipped for a decade.
Terminator took a different route. There is a third option: draw a click-through, translucent, full-screen layer over the whole desktop that narrates what the agent is doing, and let the person keep using the computer without a single missed keystroke. The implementation is one file, one RAII guard, and five Win32 extended window styles.
The anchor fact, in one line
“Terminator's on-screen action HUD is a WS_EX_LAYERED full-screen window at alpha 77 out of 255, which is exactly 30 percent opaque. The WS_EX_TRANSPARENT flag on the same window makes it click-through, so a user can keep typing into the app underneath while the agent narrates its actions on top in 32-pixel Segoe UI.”
crates/terminator/src/platforms/windows/action_overlay.rs, lines 287 through 347
the uncopyable line
Alpha 0 out of 255 = 0% opaque. A 0ms minimum display. A 0ms anti-flash cooldown. All in one file, 0 lines long, running every time the automation touches a Windows UI element.
Grep the repo for SetLayeredWindowAttributes, MINIMUM_DISPLAY_MS, and ActionOverlayGuard. Each one lands on exactly one line in crates/terminator/src/platforms/windows/action_overlay.rs.
How the overlay actually gets on screen
Five extended window styles, one alpha call, one ShowWindow. The five styles are what make the overlay behave like a heads-up display instead of a regular app window. Each one carries a specific promise about how Windows treats the window, and dropping any of them breaks the HUD.
the Win32 flags that make a HUD feel like a HUD
Five behaviors stitched together by five flags
The interesting part is not that the HUD exists. It is that the HUD is built entirely out of existing Win32 knobs, in a combination most Windows GUI code never uses. Each card below is one knob and what it gets you.
Click-through, always
WS_EX_TRANSPARENT is the Win32 flag that excludes a window from hit testing. Paired with WS_EX_LAYERED and WS_EX_NOACTIVATE, it produces a full-screen HUD that shows on top of everything but cannot catch a click. Your mouse lands on the app underneath.
30% opaque
SetLayeredWindowAttributes(hwnd, COLORREF(0), 77, LWA_ALPHA). 77 out of 255. The background fills with white which at alpha 77 reads as a soft grey haze over the desktop. Black 32-pixel Segoe UI on that haze is reliably legible.
Spans every monitor
SM_XVIRTUALSCREEN and SM_CXVIRTUALSCREEN return the bounding rectangle of every connected display. The HUD is created at those coordinates. The status box always centers on the middle of the virtual desktop.
RAII lifetime, not manual show/hide
Each action function opens a scope, constructs an ActionOverlayGuard, does its work, and lets Rust's drop rules tear the HUD down on return. No leaks, no forgotten hide calls, no timer logic.
Env var off switch
TERMINATOR_ACTION_OVERLAY=0 in the environment at process start disables every show call via a Lazy that runs on first access. Used by CI and headless Windows Server runs.
The RAII guard is the whole lifetime API
There is no overlay.show()/overlay.hide() pair to forget. An action function opens a scope, constructs an ActionOverlayGuard, and the moment the action returns (normally, with an error, or via a panic) Rust's Drop impl runs and the HUD disappears. This is why you cannot find a single explicit hide call in the action methods.
action names that show up in the top line of the HUD
Anti-flash: three constants, one user-visible property
A fast YAML workflow can fire thirty actions in a second. Without guards, the overlay would mount and tear down the window thirty times, and the user would see an unreadable strobe. Three constants at the top of the file stop this from happening.
The lifecycle of one action, end to end
From function entry to HUD down, six stages
Action starts in the platform adapter
type_text, press_key, click, invoke, mouse_drag, scroll. Each one is a method on the Windows platform element. First line of the body constructs a local _overlay_guard via ActionOverlayGuard::new with the action name and the target element's description.
show_action_overlay gets called
Line 95. Checks ACTION_OVERLAY_ENABLED, the env-var lazy, and the 100ms anti-spam cooldown. If any of those blocks the call, returns without drawing. Otherwise updates OVERLAY_STATE with the new message and records OVERLAY_SHOWN_AT for the minimum-display-time guard.
Overlay thread spawns and creates the window
thread::spawn at line 142. In that thread, create_overlay_window registers the WNDCLASSEXW, computes SM_*VIRTUALSCREEN bounds, calls CreateWindowExW with the five extended styles, then SetLayeredWindowAttributes at 77/255 alpha. The calling thread sleeps 50ms so the HUD is visible before the action fires.
The real Windows action executes
SendInput for keyboard and mouse, or UIA invoke patterns. The agent clicks, types, scrolls. The user sees the desktop respond with the action narrated on top in 32-pixel Segoe UI, readable through the 30 percent alpha.
Message loop polls for close signal
Lines 361-381. PeekMessageW runs at 10ms intervals. If should_close flips true, the loop breaks and DestroyWindow takes down the HUD cleanly. Otherwise the message pump keeps serving WM_PAINT and WM_CLOSE in the overlay thread.
Action returns, Rust drops the guard
End of type_text or press_key. _overlay_guard goes out of scope. Drop impl calls hide_action_overlay. The MINIMUM_DISPLAY_MS guard may sleep the calling thread briefly to hit the 300ms floor, then PostMessageW(WM_CLOSE) reaches the overlay thread and the window is gone.
Who is talking to whom during one typing action
Three entities, one call path. The calling thread owns the action. The overlay thread owns the window and its message pump. Shared state lives in static locks, not in either thread. The sequence below is what happens when you call element.type_text("hello").
type_text on a Windows UIEdit
Where the data flows inside the crate
Action method on the left. Overlay pipeline in the middle. On-screen output on the right. The hub in the beam below is the single file that owns the whole HUD.
action_overlay.rs, the hub
The decisions the overlay took, versus the ones it refused
Every checkbox below is a design choice. The left column (unchecked) is what the HUD intentionally does not do. The right column (checked) is how it was actually shipped.
Rejected, by design
- Overlay steals keyboard focus when it appears
- User has to close a modal dialog to keep working
- Status window appears in Alt+Tab and interrupts window switching
- Fast action bursts flash the overlay dozens of times a second
- Sub-100ms actions show no overlay at all (too fast to see)
- HUD only draws on the primary monitor, invisible on secondary
- Status layer is opaque and covers the app you are watching
- Developer has to call hide_overlay manually at every action exit
Shipped
- Click-through via WS_EX_TRANSPARENT so mouse hits the app underneath
- WS_EX_NOACTIVATE prevents the overlay from stealing focus on show
- WS_EX_TOOLWINDOW keeps the HUD out of Alt+Tab
- OVERLAY_CHANGE_COOLDOWN_MS=100 collapses bursts into one visible HUD
- MINIMUM_DISPLAY_MS=300 guarantees a readable flash even on fast actions
- SM_XVIRTUALSCREEN + SM_CXVIRTUALSCREEN span every connected monitor
- 77/255 alpha leaves the app visible underneath the status text
- ActionOverlayGuard Drop impl hides the HUD the instant the action returns
The paint path, in order
The overlay's WM_PAINT handler lives at line 411. Every time Windows asks the HUD to redraw, this is what happens in order. Plain GDI, no third-party libs.
WM_PAINT handler, action_overlay.rs:411-535
BeginPaint + GetClientRect
Acquire the paint DC and read the window's client bounds so the centered box can be computed.
FillRect with a white brush
The entire client area gets filled white. At layered alpha 77/255 that reads as a faint grey haze, the uniform tint that sells the 'HUD over the desktop' look.
CreateFontW('Segoe UI', FW_BLACK, -32)
32-pixel bold Segoe UI, with CLEARTYPE_QUALITY antialiasing, for the action name.
Rectangle with a 2-pixel black pen
An 820x420 outlined box centered on the virtual desktop. No fill, so the desktop underneath stays visible through the interior.
DrawTextW the main message
DT_CENTER | DT_SINGLELINE | DT_VCENTER in a 60-pixel-tall rect near the top of the box. Typing, Clicking, Pressing key, and so on.
DrawTextW the element sub-message
If present, a smaller 20-pixel FW_NORMAL font, DT_CENTER | DT_WORDBREAK, for the role-and-name description of the element being acted on.
DeleteObject + EndPaint
Release the fonts, the pen, the brush, and the DC. GDI resource hygiene, so the HUD does not leak handles over long runs.
Why this matters if you are deploying an agent on a shared machine
Put yourself in the seat of an operations lead approving an AI agent to run on an analyst's workstation during business hours. What you want from the automation is obvious: a visible signal of what it is doing that does not take the analyst offline. The HUD is that signal. It shows up, narrates one line of action and one line of target, and vanishes. The analyst keeps typing.
Compare that to the alternative: the analyst sees their cursor jump, a window they did not open gets a new dropdown value, a modal dialog fires in front of their email. They either pull the plug on the agent or write an angry ticket. Terminator ships a translucent HUD so you never have to have that conversation.
Terminator vs other Windows automation programs, on the specific question of 'can the user see what it is doing'
Every row is a concrete property of the on-screen status behavior. Nothing about scripting language, YAML shape, or licensing.
| Feature | Power Automate / UiPath / AutoHotkey / SikuliX / PyAutoGUI | Terminator |
|---|---|---|
| Renders a click-through translucent HUD over the live desktop during each action | No on-screen overlay, logs in a side panel or silent | SetLayeredWindowAttributes at action_overlay.rs:347, alpha 77/255 |
| Full-screen HUD is click-through so keyboard and mouse reach the app below | Status windows either steal focus or must be dismissed | WS_EX_TRANSPARENT | WS_EX_NOACTIVATE, CreateWindowExW at :296 |
| RAII guard auto-hides the overlay when the action returns | Manual show/hide with explicit close or timers | ActionOverlayGuard Drop impl at action_overlay.rs:255 |
| Anti-flash cooldown and minimum display guard prevent flicker on bursts | Overlays flash frame-by-frame on fast sequences | 100ms cooldown, 300ms minimum display, constants at :25-28 |
| Overlay spans all monitors via SM_C*VIRTUALSCREEN | Primary monitor only, or breaks on mixed-DPI setups | GetSystemMetrics(SM_XVIRTUALSCREEN ...), action_overlay.rs:287-291 |
| Per-process opt-out via one environment variable | Requires configuration file edits or CLI flags | TERMINATOR_ACTION_OVERLAY=0, checked lazily at :37-44 |
| Source is MIT-licensed and auditable in one file | Closed-source UI status code | action_overlay.rs, 565 lines, github.com/mediar-ai/terminator |
Verify every number on this page
The HUD is one MIT-licensed file. Every alpha value, every millisecond constant, every style flag is in plain text and takes under a minute to confirm. Clone the repo, grep the names, done.
Want to see a click-through Windows HUD narrate your agent live?
Bring a workflow. We run it on a shared desktop with the overlay on, then show you where in action_overlay.rs to tune the alpha and timings for your ops team.
FAQ
Why do I care that a Windows automation program draws a translucent overlay?
Because the other option is flying blind. Most Windows automation programs either log in a side panel (Power Automate Desktop, UiPath Assistant) or run entirely silent (AutoHotkey, PyAutoGUI). When something goes wrong mid-workflow, the only way to know which step fired and on which element is to scroll through logs after the fact. Terminator paints the action name and the target element's description over the live desktop as the action fires, so you can watch the agent work without taking your hand off the mouse. The overlay is click-through, so you can keep using the machine while it is visible.
What exactly is on the HUD?
Two lines of text inside an 800-by-400 centered box with a 2-pixel black border. The top line is the action name in 32-pixel Segoe UI FW_BLACK weight. Examples: 'Typing', 'Clicking', 'Pressing key', 'Invoking'. The bottom line is the element description in 20-pixel Segoe UI FW_NORMAL. Examples: 'role:Button && name:Export CSV' or 'name:Compose a message' from Slack. The HUD draws on top of whatever is on screen, at alpha 77 out of 255, which is exactly 30 percent opaque. Everything else on the desktop stays visible underneath.
How is it click-through if it is rendered on top?
Three window extended styles, combined. WS_EX_LAYERED makes it a layered window so Windows will honor the alpha blend. WS_EX_TRANSPARENT makes it invisible to the hit-test so mouse input falls through to the window underneath. WS_EX_NOACTIVATE stops it from stealing focus when it appears. WS_EX_TOPMOST keeps it above everything. WS_EX_TOOLWINDOW keeps it out of Alt+Tab. All five are combined in a single CreateWindowExW call at crates/terminator/src/platforms/windows/action_overlay.rs line 296.
Does this slow down the automation?
Nowhere near a perceptible amount. The HUD runs on its own thread, spawned at action_overlay.rs line 142. The calling thread pays a 50ms window-creation barrier (WINDOW_CREATION_DELAY_MS at line 31) to ensure the HUD is up before the click lands, so a human watching the screen cannot miss the narration. On hide, the calling thread may block up to 300ms minus the elapsed display time to enforce MINIMUM_DISPLAY_MS (line 28), but only on fast actions; a 2-second type never hits that guard. For burst workflows the anti-spam cooldown at OVERLAY_CHANGE_COOLDOWN_MS=100 (line 25) drops further show requests rather than thrashing the window. On a 30-step YAML workflow the cumulative overhead is under a second.
Can I turn it off for headless runs?
Yes. Two ways. First, the environment variable TERMINATOR_ACTION_OVERLAY. Set it to 0, false, or off before the process starts and the OVERLAY_ENV_CHECKED Lazy on line 37 flips ACTION_OVERLAY_ENABLED to false the first time show_action_overlay is called. Second, the programmatic API: set_action_overlay_enabled(false) on line 72 toggles the flag at runtime and hides any active overlay. CI pipelines and headless Windows Servers usually set the env var; interactive desktop runs leave it on.
What happens on a multi-monitor setup?
The overlay spans every monitor. create_overlay_window at line 266 calls GetSystemMetrics with SM_XVIRTUALSCREEN, SM_YVIRTUALSCREEN, SM_CXVIRTUALSCREEN, and SM_CYVIRTUALSCREEN (lines 288-291) to get the virtual desktop bounds, which is the bounding rectangle of every connected display. The window is created at those absolute coordinates, so the status box is always centered across the whole virtual desktop. On a single monitor the message box sits in the middle of that display. On a three-monitor setup it sits in the middle of the center monitor, since that is the middle of the combined virtual screen.
What if an action triggers while the previous overlay is still fading?
LAST_OVERLAY_CHANGE at line 55 stores the Instant of the most recent show call. On the next show, the code at lines 110-121 checks whether that instant is less than OVERLAY_CHANGE_COOLDOWN_MS=100 milliseconds old; if so, it drops the new show request rather than flashing. For updates to an already-visible overlay the code takes a different path, update_action_overlay_message at line 208, which mutates OVERLAY_STATE and calls InvalidateRect to repaint without destroying the window.
How does the overlay know when to disappear?
ActionOverlayGuard. This is an RAII struct defined at action_overlay.rs line 242. Its constructor calls show_action_overlay; its Drop impl calls hide_action_overlay. Windows automation calls in the crate (type_text, press_key, click, invoke, drag, scroll) create an _overlay_guard at the top of their body and let Rust's scope rules handle the lifetime. The moment the action returns, the guard drops, the overlay hides. You can see the pattern in element.rs at lines 1153 (typing) and 1240 (pressing a key): let _overlay_guard = ActionOverlayGuard::new('Typing', Some(&element_info));
Is the overlay only available on Windows?
Yes. The file lives under crates/terminator/src/platforms/windows/ and uses windows::Win32::Graphics::Gdi and windows::Win32::UI::WindowsAndMessaging, both of which are Windows-only crates. The macOS platform adapter has its own status mechanism tied to NSApplication, and the Linux adapter currently does not render an overlay. If you need the same 'watch the agent work' experience on macOS or Linux you fall back to the framework's structured logging plus highlight_element, which draws colored borders around the elements it touches on every platform.
Can I change what the overlay says?
Yes, programmatically. update_action_overlay_message(message, sub_message) at line 208 replaces the text on an already-visible overlay and triggers a repaint via InvalidateRect. If you are scripting against Terminator from TypeScript or Python you do not normally call it directly; the platform-layer action functions pass the action name and element description to the guard automatically. If you are extending the framework with a custom action type, you create your own ActionOverlayGuard::new with your own strings at the top of your function.
Where can I see the source?
The full HUD implementation is crates/terminator/src/platforms/windows/action_overlay.rs in the mediar-ai/terminator repo on GitHub. 565 lines. RAII guard at line 242, show/hide at lines 95 and 157, window creation at line 266, the transparency call at line 347, and the paint routine at line 411. The env-var toggle is at lines 37-44. The call sites live in crates/terminator/src/platforms/windows/element.rs: search for ActionOverlayGuard::new and you will find one at the top of every action method.