Repository navigation
Releases: spilloid/kvm-it
Release list
kvm-it 0.4.8: polish, licences, Secure Boot boot drive (VM-verified)
kvm-it 0.4.8: polish ahead of 1.0. Desktop, CI and docs, plus a new boot-drive image; no protocol change. Reflash the adapter (Flash adapter…) to get the new boot drive; the pairing is kept.
What's new
- Windows: a real icon and version information on
kvmit.exeandkvmit-gui.exe, so Explorer, the taskbar, the Start menu and Properties > Details show kvm-it and its version; the installer shows the icon in Settings > Apps. - Bluetooth errors that say why: Bluetooth turned off, blocked (rfkill or airplane mode, or a hardware switch), no adapter at all (or one not passed through to a VM), or the
bluetoothdservice not running, each with what to do. - Licences in every package:
THIRD_PARTY_LICENSES.html(every Rust crate in the app, with its licence text) andTHIRD_PARTY_FIRMWARE_LICENSES.txt(everything the adapter firmware links, down to the headers of individual third-party source files). Both are generated and checked in CI. - Secure Boot for the boot drive: the drive now carries the iPXE project's signed pair (a Microsoft-signed shim and iPXE signed by the iPXE CA), so firmware that trusts Microsoft's third-party UEFI CA can start it with Secure Boot on.
- Road to 1.0:
docs/roadmap.mdnow has the plan and a 1.0 checklist.
Verified, and what is not
- Windows (real hardware, no Bluetooth adapter): the icon and every version field read correctly in Explorer;
kvmit scannames the missing adapter. - Linux: the CI-built app scans and finds a real adapter, and explains an absent one. The off/blocked/no-daemon messages are unit-tested, not yet provoked on real hardware; the same goes for macOS.
- Secure Boot: virtual machine only (OVMF with the stock Microsoft keys, Secure Boot on and off). No real PC with Secure Boot on has been tried, and the new image has not yet been flashed to a real adapter: tracked in #40. PCs that turn Microsoft's third-party CA off will refuse it; with Secure Boot off it works as before.
- Still open from earlier: capture-card unplug/no-signal (#31), recording with sound (#32), YUYV colour range (#33), wheel/touchpad on a target (#34).
Downloads and signing
- Windows:
.msiand.zip, code-signed in CI by Azure Artifact Signing (seeSIGNATURES.txt). - Linux:
kvm-it-0.4.8-x86_64.AppImage, not code-signed (check its.sha256). - macOS:
kvm-it-0.4.8-macos-arm64.dmg, ad-hoc signed and not notarized: the first time, allow it in System Settings > Privacy & Security > Open Anyway. - The boot drive's iPXE source archive is attached as usual.
Full details: CHANGELOG. Review log: rounds 31-35 in docs/dev-process.md.
kvm-it 0.4.7
kvm-it 0.4.7: macOS out of preview, and several adapters at once from the command line. Desktop, CI and docs only; no firmware or protocol change, so adapters on firmware 0.2.0 need nothing.
Verified on real hardware
- macOS (Apple silicon) and bare-metal Windows: an adapter and a live picture from an HDMI capture card, every button in the app's menu, and two boards reflashed from each and working afterwards (maintainer pass on 0.4.6).
- The Mac download drops its
-previewlabel:kvm-it-0.4.7-macos-arm64.dmg. - Not covered yet, still open: capture-card unplug / no-signal (#31), recording with sound (#32), YUYV colour range (#33), wheel and touchpad on a target (#34).
What's new
kvmit run --video <key|path>: run onekvmit --device <adapter> run script.toml --video <card>per target to drive several machines at once, each with its own log and exit status.kvmit video listprints each card's stable key.- No guessing which card is yours. Without
--video,runuses the card you last opened in the app, or the only one connected. Anything less certain stops the run before it types anything, because a wrong card would let screen waits pass on another machine's picture. With more than one target, always give--video. - Fixes: screen waits inside a
repeatnow get a capture card;--dry-runno longer needs one; settings saves are atomic, and the app and command line no longer overwrite each other's settings.
The multi-adapter command line is host-tested: two adapters have not yet run at once, and how many Bluetooth links one computer handles smoothly is not measured.
Downloads and signing
- Windows:
.msiand.zip, code-signed in CI by Azure Artifact Signing (seeSIGNATURES.txton this release). - Linux:
kvm-it-0.4.7-x86_64.AppImage, not code-signed (check its.sha256). - macOS:
kvm-it-0.4.7-macos-arm64.dmg, ad-hoc signed and not notarized: the first time, allow it in System Settings > Privacy & Security > Open Anyway.
Full details: CHANGELOG. Review log: rounds 24-30 in docs/dev-process.md.
v0.4.6 — macOS preview
kvm-it 0.4.6: macOS preview. Desktop and CI only; no firmware or protocol change, so adapters on firmware 0.2.0 need nothing.
What's new
- macOS as the controller (preview, Apple silicon).
- Capture cards work through AVFoundation; the first open asks for camera permission.
- The adapter is reached over CoreBluetooth, and macOS pairs it on first use, possibly with its own prompt.
- A Quartz keyboard grab sends Cmd+Tab, Cmd+Space and Cmd+Q to the target. It needs the Accessibility permission. Ctrl+Option+Esc releases it.
- The flasher and recording also work on macOS, and recording can take sound from an AVFoundation input.
- CI parity. Each release now gets a Linux AppImage and a macOS
.dmgbuilt and smoke-tested in CI, beside the signed Windows assets. scripts/bootstrap.shchecks a new dev machine and builds the build container.- All platforms: while a capture card is opening, the window keeps checking, so the first picture, or the reason it failed, shows without moving the mouse.
⚠️ Read before using the macOS build
kvm-it-0.4.6-macos-arm64-preview.dmg is built and CI-tested only. It compiles, its tests pass, and it packages and starts on a cloud Apple-silicon Mac. Nobody has run it with an adapter, a capture card or a real keyboard. Expect rough edges and please report them.
- Not notarized (ad-hoc signed only). The first time you open it, macOS blocks it. Allow it in System Settings → Privacy & Security → Open Anyway.
- Permissions. macOS asks for Bluetooth and Camera permission, and Microphone permission only if you record with sound. Keyboard capture needs Accessibility: add kvm-it in System Settings → Privacy & Security → Accessibility. Without it, the app says so once and captures keys in the window only.
- Adapter ids differ. macOS never exposes Bluetooth addresses, so a saved adapter on a Mac is identified by an id that macOS assigns.
- Known gap: on ISO Apple keyboards, the key left of 1 and the key left of Z are swapped, because the key table follows the ANSI layout.
Review and test status
- Adversarial review: three rounds on the macOS code with a second model (astra). Rounds 21 and 22 found 11 issues, all fixed; round 23 signed off. Details are in
docs/dev-process.md. - Windows: the executables and MSI are built, signed and checked by CI.
- Linux: the AppImage is built and smoke-tested in CI. It is not code-signed; verify it with its checksum.
- Hardware: nothing new in this release was hardware-verified. The open "Needs hardware" issues (#31–#34) still apply to 0.4.1–0.4.5.
kvm-it 0.4.5: recording, themes, honest video and buttons (host-tested)
Read this first
- Host-tested, not hardware-verified. Everything below was built and tested on a Linux host (unit tests, clippy, and the recorder against a real
ffmpegon synthetic frames). Nothing was run against a capture card, an adapter or a real target, and neither colour theme nor the Record button has been looked at on a real screen. The open hardware checks are tracked in issues #31–#34. - Signing:
kvmit.exe,kvmit-gui.exeand the MSI are code-signed by the release workflow (Azure Artifact Signing; Authenticode status Valid, signer CN=Joseph Spillers, fromSIGNATURES.txt), andverify-release.py --require-signedpasses on the published assets. There is no Linux AppImage in this release (the workflow does not build it); use 0.4.0's, or build from source. - No firmware change. Firmware 0.2.0 and protocol 1.1 are exactly 0.4.0's. The Secure Boot boot-drive work (PR #15) is not in this release.
- Review: four rounds of adversarial review (Codex
gpt-6-astra), every finding confirmed and fixed (docs/dev-process.md, rounds 17–20). The last fix (an atomic, no-replace publish of finished recordings) has not had a further review round, so no sign-off is claimed.
What's new (0.4.1 to 0.4.5, rolled into one release)
Session recording (new). ● Record… in the top bar: GIF (silent, about 12 fps, at most 960 px wide, stops after 30 s) or WebM (VP9, up to 30 fps, sound only if you pick an input, off by default). The popup warns that a recording cannot be redacted. While recording, a red ● REC mm:ss, click to stop chip stays live even while input is captured. Files go to ~/Videos/kvm-it/, timestamped, never overwriting; quitting finishes the file. Needs ffmpeg on your PATH.
Light and dark theme. A Theme button cycles auto (follows your system, the default), light and dark, and remembers the choice. Status colours are chosen per mode and held to WCAG AA contrast by tests.
The video chip tells the truth (Linux).
- A card that is unplugged or stops delivering is detected: the chip no longer stays green on a frozen frame, and a failed start is reported.
- Closing a stalled capture can no longer hang the window.
- The chip shows the frame rate the card was actually set to; the pixel format the driver applied is honoured, and YUYV rows are read with the driver's stride.
- A flat, blank picture shows an amber Video: no signal, and the picture area says so (a genuinely black screen reads the same; you can still record it).
- A fresh install no longer opens the first video device (often a webcam); only a remembered card opens, and it is remembered by a stable key, not
/dev/videoN.
Buttons and feedback.
- Type keeps your text if the run does not start, and flags characters it cannot type before you click.
- Capture with no adapter or during a script says why, instead of doing nothing.
- The run-log header says what happened: Finished, Dry run finished, Aborted: reason, or Failed: reason.
- Notices can be dismissed and expire; the adapter link error has its own line.
- Dry run works without an adapter. Target USB is a label, not a button. Disabled controls say why on hover.
- Esc aborts a running script and releases every key (not while you are typing in a text field). The mouse wheel honours touchpad pixel scrolling instead of sending dozens of notches.
Known limits
- Linux hardware behaviour of the video changes, recording from a real card, capture-card audio sync, the touchpad feel (40 points per notch is a guess) and the YUYV colour matrix are all unverified (#31–#34).
- A recording that cannot be encoded fast enough repeats frames to keep real time; the notice reports how many were dropped.
- Windows: recording and the new code paths are compile-checked for Windows only.
Full details: CHANGELOG.md (0.4.1–0.4.5). Source archive for the boot drive's GPL iPXE is attached by the release workflow as before.
kvm-it 0.4.0: network boot through the adapter
Signing: kvmit.exe, kvmit-gui.exe and the MSI are code-signed (Authenticode status Valid, signer CN=Joseph Spillers; from SIGNATURES.txt), and verify-release.py --require-signed passes on the published assets. The Linux AppImage is not code-signed (checksum only); its CLI was run (cli --version prints 0.4.0) and it carries the firmware bundle, but its GUI start was not run on this build. The upstream iPXE source archive (GPL v2) is attached (the automatic workflow failed on a relative-path bug, fixed after the tag; the archive was built with the fixed script and attached by hand). Review note: reviewed by Claude Opus 5.5 (a recorded deviation from the usual cross-model review); an independent Codex round over the final diff is still owed.
Firmware 0.2.0 and wire protocol 1.1 (backward compatible: one new message, one appended STATUS byte, one capability bit). The boot drive is OFF until you turn it on, with a
button in the app's Adapter popup or kvmit boot-drive on; an adapter that upgrades shows its target nothing new until then. Verification: see the README status table.
Added
- Network boot through the adapter (off by default). When turned on, the adapter also presents a 4 MiB read-only USB drive (a third USB interface, after the
keyboard and mouse) carrying iPXE and anautoexec.ipxe, so a UEFI target can boot from the network (WinPE, an installer, a rescue image). Adapter popup > Boot
drive: Turn on (restarts adapter), orkvmit boot-drive [on|off](kvmit statusshows it). The setting is stored in the adapter; the adapter restarts when it
changes, because a USB descriptor is fixed for a session (the target sees it re-plug, with the drive added or removed). The drive is write-protected by the
device, lives in its ownipxeflash partition (the settings partition did not move, so existing adapters upgrade in place and keep their pairing), and the USB
product id is303a:400a(was303a:4008) in both states, so a USB allow-list or VM passthrough keyed on the old product id needs updating; with the drive off the configuration is still just the keyboard and mouse interfaces. The setting is remembered across power cycles and re-flashes. - Safe default script: on the drive, iPXE waits five seconds for a key and otherwise exits with a failure status, so a target that boots this drive by accident
carries on down its boot order; nothing is fetched or run without a key press. A key press does DHCP and chains to the iPXE project's public demo menu over HTTPS
(replace the URL with your own boot server:docs/developing.md). A legacy BIOS that tries the disk moves on (its boot sector isINT 18h; untested). - Protocol 1.1:
SET_BOOT_DRIVE(0x61), a boot-drive byte appended toSTATUS, capability bit 4; an older controller or firmware simply lacks them (the app says
"needs firmware 0.2.0" and offers the flasher). - iPXE is built from unmodified upstream source at a pinned commit by
scripts/build-ipxe.sh(the binary is treated and shipped under the GNU GPL v2, with the licence
text and a source statement in every package); the upstream source archive is attached to the release by.github/workflows/ipxe-source.yml. SeeTHIRD_PARTY_NOTICES.md. - The flasher writes one extra part, the boot drive image: it must land exactly in a FAT data partition of the new table (never the settings or any other partition), fit
it, and carry a boot signature;scripts/refresh-firmware-release.pyrefreshesfirmware/releasefrom a build. tools/ipxe-test/boot-vm.sh: boots a UEFI VM from the real adapter (or a disk image) with Secure Boot off or on, for the Secure Boot work.
Verified (and what was not)
- Real adapter on a Linux host: enumerates as keyboard + mouse + a write-protected 4 MiB disk; the whole disk reads back byte-identical to the image; mounts read-only;
raw SCSI commands sent straight at the device (bypassing the host's write-protect flag) are refused (WRITE(10) as DATA PROTECT; WRITE(6)/(12), FORMAT UNIT, WRITE SAME
and UNMAP as invalid commands) and the disk is unchanged. These first ran on an early build of the image. On the final firmware (app sha256 6a5d0525…, image sha256 587650f8…) the following were re-run on the real
adapter (2026-10-05): the four-image flash over COM with the pairing kept (kvmit flash), boot with the drive off,kvmit boot-drive onover Bluetooth (from a
Windows 11 VM) giving the disk with identical readback, read-only, a raw WRITE(10) refused as DATA PROTECT, andoffreturning to keyboard and mouse only. - UEFI VM, Secure Boot off: with no key iPXE falls through to the firmware's boot menu; with a key it gets an address, fetches the demo over HTTPS and boots a network Linux.
- Not exercised: the app's Boot drive button (the CLI command was used), the flash wizard's four-image path in the packaged app, a real PC booting from the drive, a Windows host seeing the drive, legacy BIOS (unsupported). Secure Boot on refuses the unsigned iPXE ("Access
Denied", reproduced in an OVMF VM with the stock keys): a known limit, tracked for the 0.4.x releases. - Reviewed over several rounds; the review log (
docs/dev-process.md) records each finding. Claude Opus 5.5 reviewed in place of the usual reviewer while it was out of
quota (a recorded deviation from STD-001); a Codex round also ran over an earlier state.
Also in this release
- Screenshots of the real app on the website and in the README (overview, adapter, video, keys, type, scripts, run log, input captured, flash adapter), taken by an
automated harness (tools/screenshots) against a synthetic demo target and checked per scene; the demo picture's overlapping countdown text was fixed. - Documentation rewritten in a product voice with red-arrow annotated pictures: README, home page and getting-started now say what is true (a signed installer or an
AppImage, the adapter flashed from the app; no Rust, no Docker), and the build-from-source material moved todocs/developing.md.getting-started.mdhad still said
there were no packaged releases and sent people to Podman. - Roadmap: 0.4.0 is the iPXE network-boot drive (Secure Boot is validated heavily through the 0.4.x minors), the wired link over COM (drive the adapter over its UART with no Bluetooth) is the 0.5.0 candidate, a late "phone and Apple-silicon controllers" item (Android, iPhone/iPad, macOS) and a list of small lifts are
tracked next to them.
kvm-it 0.3.0: flash the adapter from the app
Firmware (0.1.0) and wire protocol (v1) are unchanged. The controller can now flash the adapter itself, on Linux and Windows.
Verification: see the README status table. On a physical board: kvmit flash on Linux (verified write, pairing kept by a
default flash and wiped by --erase-all, refusal while the adapter's USB port is plugged in, recovery from a write killed
halfway). In the Windows 11 VM with the board's COM bridge passed through: the CLI and the GUI wizard, and an MSI built, installed
and uninstalled. Not exercised: the Linux GUI wizard, bare-metal Windows, the signed release pipeline with the new packaging
before this tag. The flasher was adversarially reviewed over rounds 7-13 (docs/dev-process.md; round 13 by Claude Opus 5.5 because the usual reviewer was out of quota, a recorded deviation from STD-001, with a Codex round to follow); the adapter firmware now ships with the app.
Added
kvmit flash(in progress for 0.3.0): writes the adapter's firmware through its UART (COM) port with theespflashlibrary.
It validates the image first, refuses the board's native USB port (which would type into the flashing computer), keeps the
pairing and settings unless--erase-allis given, and asks for confirmation. Host-tested (image and port rules);
hardware-verified once on one board over its CH343 UART. Before writing it re-checks that the chosen port is still the same
device, that no Espressif USB device (which may be an adapter's own USB port: a keyboard and mouse, HID-only so not a serial port,
or the generic debug unit it shows in download mode) is plugged into the computer, that the chip is an ESP32-S3 with the image's flash size, and that the installed partition table keeps the settings
where they are; it validates the image (headers, partition table, file paths inside the firmware folder). Hardware
results: on Linux with a physical board the pairing survives a default flash and is wiped by--erase-all, and the native-USB
refusal works (also with--any-port); on Windows 11 (VM, the board's COM bridge passed through) the CLI and the GUI wizard
flash end to end. An interrupted flash (the Windows flasher killed halfway through the app) was recovered by flashing again, no BOOT button. Not exercised: the Linux GUI wizard, bare-metal Windows.- Flash adapter… in the GUI's Adapter popup (
flashwiz.rs): a window that finds the board's COM port, checks the firmware
folder, blocks while any Espressif native USB port is plugged into the computer, requires a second tick before erasing the
pairing, refuses while a script runs or the adapter is connected, flashes on a worker thread with a progress bar (no close button
and no app exit while writing), and says how to recover if it fails. Its rules are unit-tested; a flash through the window was run end to end in the
Windows 11 VM. Opening it cancels a pending connection attempt (which would otherwise block it with nothing to press). - The adapter firmware ships with the app:
firmware/release/(the exact images, with provenance and checksums) is installed
beside the program by the MSI, included in the zip and the AppImage, and found bykvmit flashand Flash adapter… with no
setup (KVMIT_FIRMWAREor--firmwareoverride it). CI checks that the bundled images are not older than the firmware sources;
verify-release.pychecks the zip carries them. Windows executables grew from ~21 MB to ~35 MB with the flasher (espflash). THIRD_PARTY_NOTICES.md(MPL-2.0 notice forserialport) ships in the zip, the MSI and the AppImage; the AppImage builder moved to Rust 1.99.
Changed
- Build toolchain is Rust 1.99 (was 1.90), needed for the in-app flasher planned for 0.3.0; clippy lints fixed (
as_chunks, an
always-trueminin a test, explicitf32for stroke widths). No behaviour change. The AppImage builder stays on 1.90 for now.
Changed (found cutting the release)
- The window title now carries the version ("kvm-it 0.3.0"), which also stamps it into
kvmit-gui.exe.scripts/verify-release.pychecks that stamp, and it had only
passed for 0.2.1 by coincidence (a dependency's source path in the executable happened to contain "0.2.1"): the GUI never embedded its own version before.
Known limitations
- Flashing: identical boards behind a USB bridge with no serial number cannot be told apart (the app says so and re-checks chip, flash size and settings layout); a USB device
the OS will not let the enumerator describe, one plugged in after the check, or an adapter's USB port running other firmware under another vendor id are not detected as
a reason to refuse. Firmware is not signed;KVMIT_FIRMWARE/--firmwarecan point at any valid ESP32-S3 image set. - BLE lifecycle around the flasher (review round 13, Low): pairing and then immediately connecting elsewhere can leave an idle OS-level link unowned, and a failed first
connection after a pairing may not close its link; both are dropped when the board is flashed (it resets). Planned for 0.3.1. - The Linux GUI wizard, Linux pairing with the new code and bare-metal Windows were not exercised.
Signing and assets
- Windows: code-signed.
SIGNATURES.txt(straight fromGet-AuthenticodeSignature) reportsValidforkvmit.exe,kvmit-gui.exeand the MSI, signer
CN=Joseph Spillers, signed in CI with Azure Artifact Signing (RFC 3161 timestamped).scripts/verify-release.py --require-signedpasses on the published assets
(hashes, zip contents, the bundled firmware byte-identical tofirmware/release, its checksums and source hash, PE headers, version stamps). - The signed MSI was installed in a Windows 11 VM: it places the firmware next to the app, both executables report a valid signature, and the installed
kvmit flashwrote the bundled firmware to a real board (verified write, pairing kept). - Linux:
kvm-it-0.3.0-x86_64.AppImage(+.sha256), built from the tag, not code-signed; check it against its checksum. Needs BlueZ, OpenGL and the usual
desktop libraries; glibc 2.35+. It carries the firmware too. Onlycli --versionandcli flash --listwere run from it; the GUI was not started from the AppImage. - Nothing here is verified on bare-metal Windows, on the Linux GUI wizard, or on Linux pairing with this release.
- The first publication attempt of this tag failed its own verifier (the GUI never embedded its version) before anything was attached; the tag was re-cut on the fixed
commit, which is why the tag is newer than the merge of the flasher work (details indocs/dev-process.md). - Review: the flasher went through thirteen adversarial review rounds; round 13 was done by Claude Opus 5.5 instead of the usual reviewer (out of quota), a recorded
deviation, and a Codex round over the same code is still to be run.
kvm-it 0.2.1
Patch release on 0.2.0; firmware (0.1.0) and wire protocol (v1) are unchanged. Verification: the video-switch fix was
VM-verified (Windows 11 VM, real capture card) before the round-6 review fixes; after them it is host-tested only (the
reopen click has no test). Nothing new was run on a Linux board or the Linux GUI; the AppImage was built and cli --version
run, the GUI not started from it. See the README status table.
Fixed
- Switching video devices in the GUI did nothing: the device dropdown was a second popup inside the Video popup, so opening it
counted as a click outside and closed the Video popup before anything was chosen. Devices are now rows in the popup (one click
switches), and a Rescan button re-reads the device list (it was only read at startup, so a card plugged in later never appeared).
Clicking the open device reopens it (a stalled card no longer needs an app restart), and the old capture is closed outside the
shared video lock so a card that will not stop cannot freeze scripts'screen().
Added
- A synthetic demo video source for documentation screenshots and tests: with
KVMIT_DEMO_VIDEO=<picture.png>the GUI lists a
"Demo target (synthetic picture)" device that shows that picture, so screenshots never need a real machine's screen. It is opt-in at open time too (a remembereddemo:path does nothing
without the variable, and is never saved as the preferred card), and refuses non-regular files and pictures over 8192 px a side. - The kvm-it logo: the GUI's window and taskbar icon, the site's favicon and Apple touch icon, and the README and site headers. The
small icons put the mark on a light tile so the navy monitor does not vanish on dark taskbars and browser tabs. - Linux AppImage packaging (
scripts/build-appimage.sh,installer/linux/): built in an Ubuntu 22.04 container so it runs on
glibc 2.35 and newer, with the AppImage tool and its embedded runtime pinned by checksum, the glibc floor enforced (and a failed inspection is an error), and
the host libraries it relies on named in the README. Not bit-for-bit reproducible (builder apt packages float). Assetkvm-it-0.2.1-x86_64.AppImage(with.sha256).
Signing and assets
- Windows: code-signed.
SIGNATURES.txt(straight fromGet-AuthenticodeSignature) reportsValidforkvmit.exe,kvmit-gui.exe
and the MSI, signer CN=Joseph Spillers, signed in CI with Azure Artifact Signing (RFC 3161 timestamped).scripts/verify-release.py --require-signedpasses on the published assets, and the SHA-256 files match. - Linux:
kvm-it-0.2.1-x86_64.AppImage(+.sha256), built from the tag. It is not code-signed; check it against its checksum.
Needs BlueZ, OpenGL and the usual desktop libraries; glibc 2.35+. Onlycli --versionwas run from it; the GUI was not started from the AppImage. - Nothing here is verified on bare-metal Windows or on a Linux board with this release.
kvm-it 0.2.0 (Windows controller)
The controller app now runs on Windows. Firmware and wire protocol are unchanged (firmware 0.1.0, protocol v1).
Verification: see the README status table. Everything Windows was run in a Windows 11 virtual machine on a Linux
host with the Bluetooth adapter and the HDMI capture card passed through over USB (real radio, real card); nothing
here is verified on bare-metal Windows. The shared client change (mouse motion) was not re-run on a Linux board.
The code was adversarially reviewed in two rounds (docs/dev-process.md, round 4: 16 findings, two deferred).
Added
- Windows controller:
kvmit.exe(CLI) andkvmit-gui.exe(GUI, no console window; a failed start shows a
dialog explaining it, e.g. when the graphics driver lacks OpenGL 2.0). - Windows pairing with no system dialog:
kvmit pair/unpairand the GUI's Pair & connect use the WinRT
custom-pairing API and accept only the Just Works ceremony. The adapter's physical pairing window is still required. - Windows video: a Media Foundation backend on the card's native MJPEG/YUY2 modes (same decode path as Linux).
- Keyboard grab on Windows: while input is captured, Win, Alt+Tab, Ctrl+Esc, Alt+F4 and the other keys the OS
would keep go to the target and not to the controller. Ctrl+Alt+Esc is the only chord that gives the keyboard
back, and is never forwarded. Ctrl+Alt+Del and Win+L cannot be intercepted by any program: use the Keys menu.
It runs in a small helper process (the app starts itself with a hidden flag) that stops swallowing if the GUI
stops responding for 3 s, handles the release chord itself, and passes keys that were already held before
capture through to the controller. - Accessibility tree: the GUI exposes its controls to screen readers and UI Automation (named buttons).
- Release packaging for Windows:
scripts/build-release.ps1(signed-if-configured exes, MSI, zip, SHA-256 files),
scripts/sign.ps1,scripts/verify-release.py,installer/kvmit.wxs, and a release runbook (docs/RELEASING.md). examples/linkstress(acked-request latency and failures under mouse-motion and video load) and
examples/hookcheck(keyboard grab on real Windows; prints counts only, never keys).
Changed
- GUI layout: the left sidebar is gone and the preview fills the window. Adapter, Target USB, Video and Input
are colour-coded status chips (green working, amber in progress, red broken, grey idle); Adapter, Video and Input
open the controls that used to be in the sidebar, and Keys, Type and Scripts are buttons with popups. A
running script's log moves to a bottom strip that stays visible while it runs. - Scanning returns as soon as the adapter is heard and waits up to 15 s by default on Windows (a 5 s scan found
it in about 2 of 5 runs there). - Mouse motion is sent in frames of at most 127 units per axis with a bounded backlog, and a click can no longer
overtake the movement before it (the firmware turns each motion frame into one USB report per 127 units inside its
Bluetooth handler, so huge frames stalled the link). This changes Linux behaviour too. - A failed release all keys at the end of capture now closes the session, so the adapter's own link-drop release
takes over instead of keepalives holding a stuck key.
Fixed
- On Windows the Bluetooth link collapsed under ordinary mouse movement (above about 30-60 motion frames per
second): Windows' default 60 ms connection interval cannot carry it. The app now requests the fast connection
mode (15 ms interval; round trip 120 ms to 30 ms in the VM) while connected. - A vanished capture card (Windows) now stops the capture and says so, instead of leaving a frozen frame that looks
live; stopping the capture can no longer hang the GUI; COM is initialised and released in balance. - Windows scancodes for Alt+PrintScreen and Ctrl+Pause are mapped.
Known limitations
- Modifier+click ordering can break while capturing on Windows if a GUI frame takes longer than the click: keyboard
and mouse buttons reach the app through two paths (planned fix: one ordered stream, review round 4 finding 6). - The end-to-end transport queue for mouse motion is not bounded; only the work per frame is (finding 8).
- Keys held across the instant capture begins are handled by virtual key, so an alias pair that shares one (Enter and
keypad Enter) can be misattributed once (review round 5, finding 4). - Windows 10, non-US keyboard layouts and IMEs, and bare-metal Windows are untested. The Linux GUI has not been
re-checked since the redesign. The V4L2 (Linux) backend still shows its last frame if the card vanishes. - Releases may be unsigned; the release notes say so. An unsigned build makes Windows SmartScreen warn.
Code signing: the Windows executables (kvmit.exe, kvmit-gui.exe) and the MSI are Authenticode-signed with Azure Artifact Signing (signer CN=Joseph Spillers) and RFC 3161 timestamped, by the release workflow. SIGNATURES.txt records Windows' own verdict. Verified in a Windows 11 VM only, not bare metal; see the README status table. Linux: a Linux AppImage (kvm-it-0.2.0-x86_64.AppImage, with its .sha256) is attached. It was built from this tag in an Ubuntu 22.04 container and runs on glibc 2.35 or newer (Debian 12 / Ubuntu 22.04 and later). Its command line and GUI were started on Linux (the GUI only inside a headless compositor); a Linux hardware pass with a board, and a check on other distributions, have not been run.