Stream Deck plugin (Windows) that moves Zoom's dual-monitor second window between two saved positions
  • TypeScript 58.6%
  • PowerShell 29.3%
  • JavaScript 6.5%
  • HTML 2.3%
  • Shell 2.1%
  • Other 1.2%
Find a file
Repository files (latest commit first)
Filename Latest commit message Latest commit date
2026-11-04 11:38:07 +10:00
.forgejo/workflows CI: fix trailing-whitespace failure and check the whole tree 2026-11-02 10:29:40 +10:00
assets Initial commit 2026-11-01 15:27:04 +10:00
scripts Build: verify koffi's Windows binaries against package-lock.json; release install steps 2026-11-03 22:09:05 +10:00
src Review nits: comment accuracy (Alt tap ordering, injection time outside the JW deadline) 2026-11-04 11:25:40 +10:00
test Gate 2 fix: input injection on a worker thread, bounded to 250 ms 2026-11-04 11:21:05 +10:00
testing VM test results for 0.2.1 2026-11-04 11:35:59 +10:00
videos Tester-log fixes, full VM re-run and new demo video 2026-11-03 21:23:25 +10:00
xyz.acyber.jwzoomswitcher.sdPlugin v0.2.1: focus hand-back, faster switch-back, smaller support file 2026-11-04 11:16:28 +10:00
.gitignore Add cover/restore mode with automatic Zoom and JW Library detection 2026-11-01 19:20:40 +10:00
INSTALL.md v0.2.1: focus hand-back, faster switch-back, smaller support file 2026-11-04 11:16:28 +10:00
LICENSE Add Stream Deck plugin to move Zoom's dual-monitor second window 2026-11-01 15:48:44 +10:00
package-lock.json v0.2.1: focus hand-back, faster switch-back, smaller support file 2026-11-04 11:16:28 +10:00
package.json v0.2.1: focus hand-back, faster switch-back, smaller support file 2026-11-04 11:16:28 +10:00
README.md VM test results for 0.2.1 2026-11-04 11:35:59 +10:00
rollup.config.mjs Add Stream Deck plugin to move Zoom's dual-monitor second window 2026-11-01 15:48:44 +10:00
rollup.harness.config.mjs Add cover/restore mode with automatic Zoom and JW Library detection 2026-11-01 19:20:40 +10:00
tsconfig.json Add Stream Deck plugin to move Zoom's dual-monitor second window 2026-11-01 15:48:44 +10:00

JW Zoom Switcher

A Stream Deck plugin for Windows: one key puts Zoom's dual-monitor second window (the participant) full screen on the TV that shows JW Library's media, and the next press puts everything back exactly as it was. Zoom's main meeting window is never moved.

Built for a Kingdom Hall AV PC running Zoom (dual monitors), JW Library (media on a second display) and OnlyT. Current version: 0.2.1.

Download and install

  1. Download xyz.acyber.jwzoomswitcher.streamDeckPlugin from the latest release.
  2. Optional but recommended: check the file against the SHA-256 in SHA256SUMS.txt on the same release (PowerShell: (Get-FileHash .\xyz.acyber.jwzoomswitcher.streamDeckPlugin).Hash).
  3. Double-click it with the Stream Deck app running and click Install. It replaces an older version in place; the key stays where it is.
  4. Drag JW Zoom Switcher → Zoom participant → TV (toggle) onto a key.

Step-by-step guide, updating, uninstalling and troubleshooting: INSTALL.md.

Requirements: Windows 10 or 11 (x64 or ARM64), the Elgato Stream Deck app 7.1 or later (it supplies the Node.js 24 runtime the plugin runs in), Zoom with Use dual monitors on, and JW Library with Play video on second display on. Tested with Zoom Workplace 7.2.5, JW Library 15.10.9 and Stream Deck 7.6.

Demo (107 s): videos/jwzoom-v0.2.0-demo-1920x540.mp4.

Using it

  • ZOOM → TV: ready. Press: JW Library's media window is minimised and Zoom's second window fills the TV, borderless.
  • BACK TO JW: Zoom is on the TV. Press: Zoom goes back exactly where it was (position, size, maximised/minimised, frame) and JW Library's media is back on the TV.
  • WORKING: a switch is running. Presses during a switch and for 1 s after it are ignored, so an accidental double or triple tap can't flip everything straight back.
  • BACK TO JW while nothing was covered: JW Library's media window got minimised or hidden, so the TV is blank. The press only brings JW Library's media back; Zoom isn't touched.
  • Amber badge with a short reason: a press wouldn't work right now (see below). The plugin checks every couple of seconds, so this shows before anyone presses. While Zoom is on the TV, an amber badge means something changed underneath (Zoom moved or closed, JW Library on top again, displays changed); one press still puts everything back.

Both windows are found automatically, on any display; there is nothing to configure.

When it refuses

Nothing is moved, and the key shows an amber badge with a short reason, when it can't be sure:

Key Meaning
No Zoom No Zoom meeting window is open.
Zoom x1 Only one meeting window: turn on Zoom's dual-monitor mode.
Zoom 3+ / Zoom ? More than two meeting windows, or main and second can't be told apart (unexpected Zoom state).
Zoom min Zoom's main meeting window is minimised. Restore it.
No JW JW Library's media window isn't showing: turn on "Play video on second display".
JW min / JW ? A JW Library window is minimised but isn't recognisably the media window, or more than one full-screen JW window.
Blocked Windows refused access, usually because Zoom or JW Library runs as administrator, or Zoom isn't responding.
Changed A window changed between finding it and moving it; everything was put back.
Check Stream Deck's settings didn't answer in time; the next press only finishes an undo.
Press again Something changed mid-way; the undo record is kept and the next press finishes it.

Settings panel

Click the key in the Stream Deck app:

  • Status (live) and Last press (the full result message).
  • Save support file: one text file on the Desktop with diagnostics and the recent plugin logs, shown in Explorer, ready to attach to a message. Meeting names, other apps' window titles and the Windows user name are removed. Diagnostics shows the same snapshot with a Copy button.
  • Advanced detection: only for after a Zoom or JW Library update. Dropdowns for Zoom's window classes, Zoom's toolbar class and JW Library's window title, filled with what this PC shows, or a custom value.

What it installs and touches

What Where
Plugin files %APPDATA%\Elgato\StreamDeck\Plugins\xyz.acyber.jwzoomswitcher.sdPlugin\ (installed and removed by Stream Deck)
Logs logs\ inside that folder, including a numbered step-by-step record of every key press; Stream Deck rotates them
Settings Stream Deck's global settings for the plugin: the undo record while Zoom is on the TV, and any detection overrides
Support files JWZoomSwitcher-support-<date>-<time>.txt on the Desktop (or Documents/Downloads) when you ask for one; up to 10 diagnostics-*.json copies in logs\
  • No network access and no telemetry: the plugin talks only to the Stream Deck app on the same PC.
  • Runs with the user's normal rights. It only moves, minimises and restores Zoom's second meeting window and JW Library's media window, re-identifying each one (handle, process, process start time, class) immediately before every change. It never waits on an app that isn't responding.
  • The only program it starts is Explorer, to show a support file.
  • Native code: koffi for the Windows API. The build only ships koffi binaries whose SHA-512 matches package-lock.json.

How it works

In Zoom's dual-monitor mode both meeting windows share the same Win32 class (ConfMultiTabContentWndClass / ZPContentViewWndClass), title and (lack of) owner, so generic window movers can't tell them apart. Only Zoom's main meeting window contains the meeting toolbar (ZPControlPanelClass); the other is the second window. JW Library's media window is a monitor-sized ApplicationFrameWindow hosting JWLibrary.exe, in a z-band ordinary windows can't be raised above, so it is minimised (and later restored) rather than covered.

Each app has a second detection path, used only when the first finds nothing and only when it gives one clear answer:

  • Zoom without the toolbar window: the main meeting window is the one with at least two of ZPMeetingInfoClass, VideoPaginationBarWndCls and ZappLeftMeetingWindow, the other having none.
  • JW Library rebuilt as a desktop app: a visible window titled "JW Library", owned by JWLibrary.exe, exactly filling a display.

Before changing anything the plugin saves an undo record and confirms it was stored. A window that can't be read is never treated as gone. Zoom and JW Library are restored independently, and the record is cleared only once both are verified back, so a crash, a Stream Deck restart or a reboot mid-meeting is put right by the next press. If the displays changed while covering, Zoom gets its frame back and the closest on-screen position Windows allows.

Tested

On a Windows 11 VM with five 1920×1080 displays, Zoom Workplace 7.2.5, JW Library 15.10.9, OnlyT and Stream Deck 7.6 with an emulated USB Stream Deck MK.2 (all tooling in testing/):

  • Exact cover and restore from ten starting positions of Zoom's second window, including maximised, minimised and spanning two displays; Zoom's main window never touched.
  • A lifecycle suite through real key presses: upgrade from 0.1 while covering; triple taps; JW Library's media minimised or hidden (also after a reboot); Zoom's main window minimised; Stream Deck started first, in the middle and last, and killed while covering; Zoom leaving the meeting while covering; JW Library restarted; a real reboot while covering; an unrelated display and the TV itself unplugged while covering; the JW desktop path and the Zoom fallback signal.
  • 125 unit tests, including fault injection at every step, crashes, display changes, stale records and an unresponsive Zoom.

Results: testing/results/vm-lifecycle-2026-11-03.md; 0.2.1 re-run: testing/results/vm-v0.2.1-2026-11-04.md. The 0.1 test recording is videos/jwzoom-toggle-test-1920x540.mp4.

Known limits

  • Not tested: Zoom screen sharing, JW Library while a video plays, Windows lock/sleep, Zoom or JW Library running as administrator (the VM runs everything elevated; covered by unit tests).
  • After JW Library has to be re-activated, giving keyboard focus back to the previous app couldn't be verified in the VM, and failed on a tester's PC with 0.2.0. 0.2.1 adds another method (an empty injected input before SetForegroundWindow) and logs which method worked, or a warning if none did.
  • If the TV is unplugged or switched off and on, JW Library may move its media window off the TV; turn "Play video on second display" off and on again.

Develop

Requires Node 24+ (any OS: the build stages koffi's Windows binaries itself).

npm ci
npm run check        # typecheck, unit tests, build, manifest validation
npm run pack         # → dist/xyz.acyber.jwzoomswitcher.streamDeckPlugin

CI on Forgejo (.forgejo/workflows/ci.yml) runs a secrets scan, then typecheck, tests, build, validation, packaging and a whitespace check on every push to main and every pull request, and keeps the package as an artifact. Releases are published on the releases page with a SHA256SUMS.txt.

Path What
src/plugin.ts Entry point: wiring, settings-panel requests, key images
src/actions/zoom-second-window.ts The Stream Deck action: key presses, readiness polling, key rendering
src/cover.ts The toggle: journaled switch and switch-back, busy guard, rollback
src/window-ops.ts Identity re-checks (gone / replaced / unreadable), bounded waits, transaction log
src/jw-recovery.ts Bringing JW Library's window back, including the suspended-Store-app case (capped at 4 s)
src/health.ts Read-only readiness and while-covering health checks
src/roles.ts Detection: Zoom main/second (toolbar, fallback signal) and JW Library's media window (Store and desktop paths)
src/classify.ts, src/geometry.ts Zoom window filter and identity; workspace/screen coordinates and display layout
src/detection.ts, src/detection-options.ts Detection defaults, validated overrides and the dropdown choices
src/settings.ts Global settings: one owner, serialised transactions with timeouts, read-back confirmation
src/key-view.ts, src/codes.ts What the key shows, and the short reason labels
src/diagnose.ts, src/support.ts Redacted diagnostics and the one-click support file
src/win32.ts koffi bindings for the Windows API
src/harness.ts Windows test harness (press, roles, matrix, check, diagnose, support, bench, refocus, …)
scripts/stage-koffi.mjs Stages koffi with only its Windows binaries, verified against package-lock.json
testing/ Test VM rig, lifecycle tests, emulated Stream Deck MK.2 and video recording

License

MIT, see LICENSE.