Feature: Multi-device screen wall on the Go server
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
This commit is contained in:
1 parent
8eab26c1f6
commit
1922f87dd2
7 files changed
+502
-80
No files matched your search
+10
-5
@@ -138,11 +138,16 @@ func (h *MyHandler) OnReceive(ctx *connection.Context, data []byte) {
|
||||
case protocol.TokenClipboardText:
|
||||
h.handleClipboardText(ctx, data)
|
||||
case protocol.TokenKeyframe:
|
||||
// Sent by the client only when frameID % m_GOP == 0; the client's
|
||||
// DEFAULT_GOP is 0x7FFFFFFF (effectively infinite), so this token
|
||||
// is essentially unused in practice. Treat as a no-op for now —
|
||||
// IDRs always arrive in-band via TOKEN_NEXTSCREEN and we catch
|
||||
// them via the H264 NAL scan in handleScreenFrame.
|
||||
// The client switches to this token when it emits an IDR on request
|
||||
// (COMMAND_SCREEN_KEYFRAME_REQ) or on a GOP boundary. The header
|
||||
// layout is identical to TOKEN_NEXTSCREEN, but the payload is always
|
||||
// an IDR, so relay it as frameType=1 without sniffing. Dropping it
|
||||
// (the old behaviour, harmless while the client's DEFAULT_GOP of
|
||||
// 0x7FFFFFFF meant this token never appeared) breaks keyframe
|
||||
// recovery: the browser keeps decoding P-frames against a reference
|
||||
// chain it never received, so the picture stays corrupt until the
|
||||
// screen session restarts.
|
||||
h.handleScreenFrame(ctx, data, true)
|
||||
case protocol.CmdCursorImage:
|
||||
// Custom cursor bitmaps — relayed in Phase 5+ when the web cursor
|
||||
// overlay learns to render arbitrary BGRA images. Drop silently for
|
||||
|
||||
Reference in new issue
Block a user