Skip to content

Windows support: linkctl-panel native GUI control panel#6

Open
jfwoods wants to merge 1 commit into
mainfrom
claude/windows-camera-control-panel-ui2cgr
Open

Windows support: linkctl-panel native GUI control panel#6
jfwoods wants to merge 1 commit into
mainfrom
claude/windows-camera-control-panel-ui2cgr

Conversation

@jfwoods

@jfwoods jfwoods commented Jun 9, 2026

Copy link
Copy Markdown
Owner

Summary

Ports the project to Windows as a single lightweight native executable, host/linkctl-panel/ — a GUI control panel that replaces both the daemon and the CLI on Windows. Pure Win32 C, zero external dependencies, no installer; the stripped binary is ~100 KB. No viewfinder (MVP) — it's a controller, so the camera stays usable in Teams/OBS/etc. while the panel drives it.

[Insta360 Link] --USB/UVC--> [linkctl-panel.exe] --WiFi/WebSocket--> [ESP32 + Joystick]
                              GUI control panel    mDNS: _insta360ctrl._tcp

Camera control (camera.c)

Windows' usbvideo.sys owns the camera, so the macOS approach (raw class-specific control transfers via IOKit) is impossible. Instead:

  • Pan/tilt/center go through the UVC extension unit via kernel streaming: IKsControl::KsProperty with a KSP_NODE targeting the dev-specific topology node. Windows addresses XUs by guidExtensionCode rather than unit ID, so the GUID is discovered at runtime by reading the camera's USB configuration descriptor through its parent hub (SetupAPI + IOCTL_USB_GET_DESCRIPTOR_FROM_NODE_CONNECTION) and parsing the VC extension-unit descriptor for bUnitID 0x09 — nothing vendor-specific is hardcoded. The topology node is found via IKsTopologyInfo + a basic-support probe.
  • Zoom uses the standard UVC CT control via DirectShow IAMCameraControl, linearly mapped between the driver-reported range and the 100–400 protocol range.
  • All camera_* calls are serialized by an internal CRITICAL_SECTION (GUI thread vs. server thread), mirroring the Mac daemon's camera_mutex rule.

GUI (gui.c)

  • Hold-to-move D-pad buttons (subclassed for press/release) and arrow keys; pan & tilt speed sliders (protocol ranges 1–30 / 1–20)
  • Zoom slider + +/- keys; Center gimbal runs center → stop → zoom reset, the same sequence as linkctl center (per the drift gotcha); Stop button / Space / Esc
  • Status line (camera state, zoom, ESP32 client count), zoom slider stays in sync when the ESP32 changes zoom, camera auto-reconnect poll every 5 s
  • --console flag attaches a console for the [camera]/[server] logs

ESP32 keeps working

  • Background thread runs a hand-rolled RFC 6455 WebSocket server on port 9000 (Winsock select loop; own SHA-1/Base64 for the handshake; echoes the arduino subprotocol; handles ping/pong/close)
  • JSON command dispatch (pan_tilt, zoom, stop, center, status) and responses match the Mac daemon's server.c exactly, including the [0,1,0,1] stop-payload rule and the on-connect status push
  • mDNS advertisement of _insta360ctrl._tcp via Windows' built-in DnsServiceRegister (Win10 1809+) — no Bonjour install
  • The TCP/9001 control socket is intentionally not ported — the GUI replaces the CLI on Windows

CI / docs

  • windows-latest CI job (the commented-out placeholder, now real) builds with MSVC and uploads the .exe; the release workflow ships a versioned zip
  • README gets a Windows setup section; CLAUDE.md gets architecture/build/gotcha updates (e.g. winioctl.h before usbioctl.h, C-compatible local IKsControl declarations)

Testing

  • ✅ Full build cross-compiled with MinGW-w64, zero warnings; MSVC build will be exercised by the CI job on this PR
  • ✅ The portable protocol code (ws.c, json.c) is unit-tested natively — 31 checks including the RFC 6455 §1.3 worked example for the accept key, masked-frame decode, partial-frame handling, and ESP32-shaped JSON commands
  • ⚠️ Not yet tested on real hardware — this environment has no Windows machine or camera. The XU GUID discovery and KS node probe are the parts most worth watching on first run; linkctl-panel.exe --console will show exactly which stage fails if anything does.

https://claude.ai/code/session_019SjPJMQax1zDYcYSde7rT4


Generated by Claude Code

A single lightweight Win32 C executable (zero external dependencies)
that replaces both the daemon and the CLI on Windows:

- Camera control via DirectShow + kernel streaming: pan/tilt/center
  through the UVC extension unit (IKsControl/KSP_NODE), zoom through
  IAMCameraControl. The XU guidExtensionCode is discovered at runtime
  by parsing the USB config descriptor via the parent hub, so no
  vendor GUID is hardcoded.
- GUI: hold-to-move D-pad (mouse or arrow keys), pan/tilt speed
  sliders, zoom slider (+/- keys), center gimbal (center -> stop ->
  zoom reset, matching the CLI), stop, status line with camera state
  and ESP32 client count, 5s auto-reconnect.
- ESP32 keeps working: hand-rolled RFC 6455 WebSocket server on port
  9000 (own SHA-1/Base64, arduino subprotocol echo, ping/pong) with
  JSON dispatch identical to the Mac daemon, advertised over mDNS via
  Windows built-in DnsServiceRegister (Win10 1809+).
- CI builds the panel on windows-latest; releases ship a zip.

The portable pieces (ws.c framing/handshake incl. the RFC 6455 worked
example, json.c) are unit-tested; full build verified with MinGW-w64.

https://claude.ai/code/session_019SjPJMQax1zDYcYSde7rT4
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants