Feature(Go): Web terminal relay with PTY mode and graceful close (Phase 6)

This commit is contained in:
yuanyuanxiang
2026-05-18 15:03:42 +02:00
committed by yuanyuanxiang
parent 6485e800d6
commit d7f38ecfdb
7 changed files with 696 additions and 96 deletions

View File

@@ -107,19 +107,24 @@ const (
CommandScreenSpy byte = 16 // COMMAND_SCREEN_SPY - start screen capture
CommandScreenControl byte = 20 // COMMAND_SCREEN_CONTROL - mouse/keyboard input (MSG64 batches)
CommandNext byte = 30 // COMMAND_NEXT - "control-side dialog is open, you may stream"
CommandShell byte = 40 // COMMAND_SHELL - ask device to open a shell sub-connection
CommandTerminalRsize byte = 81 // CMD_TERMINAL_RESIZE - [cmd:1][cols:2 LE][rows:2 LE]
CommandBye byte = 204 // COMMAND_BYE - disconnect
CommandHeartbeat byte = 216 // CMD_HEARTBEAT_ACK
// Client -> Server tokens
TokenAuth byte = 100 // TOKEN_AUTH - authorization required
TokenHeartbeat byte = 101 // TOKEN_HEARTBEAT
TokenLogin byte = 102 // TOKEN_LOGIN - login packet
TokenBitmapInfo byte = 115 // TOKEN_BITMAPINFO - screen sub-connection header
TokenFirstScreen byte = 116 // TOKEN_FIRSTSCREEN - raw BGRA baseline frame (NOT H264)
TokenNextScreen byte = 117 // TOKEN_NEXTSCREEN - non-keyframe H264 (P-frame)
TokenKeyframe byte = 134 // TOKEN_KEYFRAME - H264 IDR (sent on GOP boundary)
TokenConnAuth byte = 246 // TOKEN_CONN_AUTH - sub-connection identity handshake
CmdCursorImage byte = 93 // CMD_CURSOR_IMAGE - custom cursor bitmap (Phase 5+ feature)
TokenAuth byte = 100 // TOKEN_AUTH - authorization required
TokenHeartbeat byte = 101 // TOKEN_HEARTBEAT
TokenLogin byte = 102 // TOKEN_LOGIN - login packet
TokenBitmapInfo byte = 115 // TOKEN_BITMAPINFO - screen sub-connection header
TokenFirstScreen byte = 116 // TOKEN_FIRSTSCREEN - raw BGRA baseline frame (NOT H264)
TokenNextScreen byte = 117 // TOKEN_NEXTSCREEN - non-keyframe H264 (P-frame)
TokenShellStart byte = 128 // TOKEN_SHELL_START - legacy cmd-pipe shell sub-conn open
TokenKeyframe byte = 134 // TOKEN_KEYFRAME - H264 IDR (sent on GOP boundary)
TokenTerminalStart byte = 232 // TOKEN_TERMINAL_START - modern PTY shell sub-conn open
TokenTerminalClose byte = 233 // TOKEN_TERMINAL_CLOSE - shell exited / close ack
TokenConnAuth byte = 246 // TOKEN_CONN_AUTH - sub-connection identity handshake
CmdCursorImage byte = 93 // CMD_CURSOR_IMAGE - custom cursor bitmap (Phase 5+ feature)
)
// Sub-connection authentication (matches common/commands.h ConnAuth* structs).
@@ -128,7 +133,13 @@ const (
const (
ConnAuthPacketSize = 512
ConnAuthAckSize = 256
// ConnAuthAck field offsets within the 256-byte buffer.
// ConnAuthPacket field offsets within the inbound 512-byte buffer.
// Layout (from common/commands.h::ConnAuthPacket):
// [token:1][clientID:8 LE][timestamp:8 LE][nonce:16][signature:64][reserved:415]
ConnAuthOffClientID = 1 // uint64 LE — pin to the sub-conn so later
// // 1-byte tokens (TOKEN_TERMINAL_START etc.) can
// // resolve the parent device.
// ConnAuthAck field offsets within the outbound 256-byte buffer.
ConnAuthAckOffStatus = 1 // uint8
ConnAuthAckOffServerTime = 2 // uint64 LE
// Status codes.
@@ -245,6 +256,27 @@ func BuildScreenControlPacket(message, wParam, lParam uint64, ptX, ptY int32, ti
return buf
}
// TerminalBinaryMagic is the 4-byte prefix the web UI uses to demultiplex
// terminal output from screen frames over the single WebSocket. Matches
// the C++ side at server/2015Remote/WebService.cpp:2013 ("TRM1"). Screen
// frames lead with a uint32 LE device ID, so collisions with this exact
// magic are astronomically rare in practice.
var TerminalBinaryMagic = [4]byte{'T', 'R', 'M', '1'}
// BuildTerminalResize encodes the 5-byte CMD_TERMINAL_RESIZE packet the
// client's ConPTYManager/TerminalManager expects on the shell sub-conn:
//
// [CMD_TERMINAL_RESIZE:1][cols:2 LE][rows:2 LE]
//
// cols/rows are signed int16 on the wire (the C++ side casts to `short`).
func BuildTerminalResize(cols, rows int) []byte {
buf := make([]byte, 5)
buf[0] = CommandTerminalRsize
binary.LittleEndian.PutUint16(buf[1:3], uint16(int16(cols)))
binary.LittleEndian.PutUint16(buf[3:5], uint16(int16(rows)))
return buf
}
// MakeLParam packs x into the low word and y into the high word — the
// Windows MAKELPARAM macro the client expects in mouse-message lParams.
func MakeLParam(x, y int32) uint64 {