Bring the Go web backend up to parity with the MFC server's screen wall.
A browser tab can now stream several devices over one WebSocket: the
single `watching` field becomes a per-connection watch set guarded by the
hub mutex, capped at 32 devices per socket. The challenge advertises
`caps.multiWatch` and `caps.keyframeRequest`, binary frames stay keyed by
the low 32 bits of the device ID, and connect/disconnect replies echo the
device id and request id so the wall can route concurrent responses.
Input commands (mouse, key, clipboard, rdp_reset) resolve their target
from the watch set: an explicit id must be a current subscription, and an
absent id is accepted only while exactly one device is watched. Legacy
frontends that do not send `multiWatch` keep replace-all semantics, so a
repeated device switch cannot grow the set without bound. Offline
devices are removed from every watch set, and teardown is computed from a
snapshot taken outside the hub lock to avoid re-entrant locking.
Add the `keyframe` command: it replays the cached IDR, then asks the
device to emit a fresh one, rate-limited to one request per device per
second. This exposed a Go-only corruption bug: the client answers the
request with TOKEN_KEYFRAME (134), which the dispatcher treated as a
no-op. That token only appears on forced IDRs because the client's GOP is
effectively infinite, so the dropped IDR left the browser decoding
P-frames against a reference chain it never received, producing
persistent garbling. Relay it as a keyframe instead. Also drop the
cached IDR when the resolution changes, mirroring the C++ server, so a
late joiner cannot receive an old-geometry keyframe.
Add tests for the watch-set transitions and for relaying TOKEN_KEYFRAME.
Co-Authored-By: deepseek-flash
Add keyboard-shortcut (Ctrl+C / Ctrl+X / Ctrl+V) text clipboard transfer
between the browser and the remote desktop, reusing the clipboard protocol
that already exists in the native viewer (COMMAND_SCREEN_GET_CLIPBOARD=24,
COMMAND_SCREEN_SET_CLIPBOARD=25, TOKEN_CLIPBOARD_TEXT=118) so the client
needs no change.
Local -> remote: pressing Ctrl+V reads the browser clipboard, pushes it to
the remote clipboard (UTF-8 -> GBK, matching the client's CF_TEXT/ANSI
handling) and then synthesizes a remote Ctrl+V so the pasted text is the
newly delivered one.
Remote -> local: Ctrl+C or Ctrl+X is forwarded to the remote as usual and,
after a short delay, the remote clipboard is pulled back and written to the
browser clipboard for a later local Ctrl+V.
Both the MFC and Go servers get the same three clipboard signaling paths
(clipboard_set, clipboard_get, clipboard_text) so the shared single-page
frontend behaves identically on either backend.
Co-Authored-By: deepseek-v4-pro
SOCKET_DLLLOADER (210) is the shellcode / memory-DLL loader asking the
server for the full DLL payload before it can load the client. The MFC
master console serves it via SendServerDll; the Go web device port has no
payload to hand out, so every memory-DLL client startup/reconnect fell
into the default dispatch branch and logged "Unhandled command 210",
flooding the log. Add a protocol constant and drop the command silently.
While auditing the device-port logging surface:
- Downgrade wrapTLS's per-connection hex-dump lines and the screen-spy
"[timing]" cold-start line from Info to Debug (they fire once per TCP
connection / screen view and are diagnostics, not operational events).
- Distinguish a sub-connection hijack attempt from a plain orphan: when a
screen/terminal sub-conn claims a MasterID from a source IP other than
the device's login conn, log a dedicated Warn instead of mislabeling it
as "orphan ... unknown device".
Co-Authored-By: deepseek-v4-pro