Commit 05a6350

bryfry <bryon@fryer.io>
2026-01-23 18:15:10
fragmented clipboard
1 parent cfe71ab
Changed files (2)
.gitignore
@@ -1,3 +1,4 @@
 .govnc_history
 clipboard_in
 clipboard_out
+clipboard
main.go
@@ -173,6 +173,10 @@ func run() error {
 
 // drainFrames reads and logs incoming VNC messages
 func drainFrames(ctx context.Context, c *websocket.Conn, width, height uint16) error {
+	// Buffer for reassembling fragmented messages
+	var pendingBuf []byte
+	var pendingExpected int
+
 	for {
 		_, b, err := c.Read(ctx)
 		if err != nil {
@@ -184,6 +188,25 @@ func drainFrames(ctx context.Context, c *websocket.Conn, width, height uint16) e
 			continue
 		}
 
+		// If we're waiting for more data on a fragmented message, append it
+		if pendingBuf != nil {
+			pendingBuf = append(pendingBuf, b...)
+			slog.Debug("reassembly", slog.Int("have", len(pendingBuf)), slog.Int("need", pendingExpected))
+
+			if len(pendingBuf) >= pendingExpected {
+				// We have enough data, process the complete message
+				clipboardText := string(pendingBuf[8:pendingExpected])
+				slog.Info("clipboard recv", slog.Int("len", len(clipboardText)))
+				slog.Debug("clipboard", slog.String("text", clipboardText))
+				if err := saveClipboard(clipboardText); err != nil {
+					slog.Warn("failed to save clipboard", slog.String("error", err.Error()))
+				}
+				pendingBuf = nil
+				pendingExpected = 0
+			}
+			continue
+		}
+
 		msgType := b[0]
 		switch msgType {
 		case 0: // FramebufferUpdate
@@ -200,15 +223,32 @@ func drainFrames(ctx context.Context, c *websocket.Conn, width, height uint16) e
 		case 3: // ServerCutText (clipboard)
 			if len(b) >= 8 {
 				textLen := binary.BigEndian.Uint32(b[4:8])
-				if len(b) >= 8+int(textLen) {
-					clipboardText := string(b[8 : 8+textLen])
+
+				// Check for extended clipboard (high bit set = negative signed int32)
+				if int32(textLen) < 0 {
+					slog.Debug("recv", slog.String("type", "ExtendedClipboard"), slog.Int("flags", int(textLen&0x7FFFFFFF)))
+					// Extended clipboard - for now just log it
+					// TODO: implement extended clipboard request/response
+					continue
+				}
+
+				expectedLen := 8 + int(textLen)
+				if len(b) >= expectedLen {
+					clipboardText := string(b[8:expectedLen])
 					slog.Info("clipboard recv", slog.Int("len", int(textLen)))
 					slog.Debug("clipboard", slog.String("text", clipboardText))
 					if err := saveClipboard(clipboardText); err != nil {
 						slog.Warn("failed to save clipboard", slog.String("error", err.Error()))
 					}
 				} else {
-					slog.Info("recv", slog.String("type", "ServerCutText"), slog.Int("textLen", int(textLen)), slog.String("note", "incomplete"))
+					// Start buffering for reassembly
+					slog.Info("recv", slog.String("type", "ServerCutText"),
+						slog.Int("textLen", int(textLen)),
+						slog.Int("have", len(b)),
+						slog.String("note", "fragmented, buffering"))
+					pendingBuf = make([]byte, len(b))
+					copy(pendingBuf, b)
+					pendingExpected = expectedLen
 				}
 			}
 		default: