Skip to content

Commit 832106c

Browse files
committed
Reduce traffic during pastes
Suspend KeyDownMessages while processing a macro. Make sure we don't emit huge debugging traces. Allow 30 seconds for RPC to finish (not ideal) Reduced default delay between keys (and allow as low as 0) Move the HID keyboard descriptor LED state as it seems to interfere with boot mode Run paste/macros in background on their own queue and return a token for cancellation. Fixed error in length check for macro key state. Removed redundant clear operation. Use Once instead of init() Add a time limit for each message type/queue.
1 parent 9438ab7 commit 832106c

File tree

14 files changed

+367
-107
lines changed

14 files changed

+367
-107
lines changed

cloud.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -478,7 +478,7 @@ func handleSessionRequest(
478478
cloudLogger.Trace().Interface("session", session).Msg("new session accepted")
479479

480480
// Cancel any ongoing keyboard macro when session changes
481-
cancelKeyboardMacro()
481+
cancelAllRunningKeyboardMacros()
482482

483483
currentSession = session
484484
_ = wsjson.Write(context.Background(), c, gin.H{"type": "answer", "data": sd})

hidrpc.go

Lines changed: 37 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -26,34 +26,61 @@ func handleHidRPCMessage(message hidrpc.Message, session *Session) {
2626
return
2727
}
2828
session.hidRPCAvailable = true
29+
2930
case hidrpc.TypeKeypressReport, hidrpc.TypeKeyboardReport:
3031
rpcErr = handleHidRPCKeyboardInput(message)
32+
3133
case hidrpc.TypeKeyboardMacroReport:
3234
keyboardMacroReport, err := message.KeyboardMacroReport()
3335
if err != nil {
3436
logger.Warn().Err(err).Msg("failed to get keyboard macro report")
3537
return
3638
}
37-
rpcErr = rpcExecuteKeyboardMacro(keyboardMacroReport.Steps)
39+
token := rpcExecuteKeyboardMacro(keyboardMacroReport.IsPaste, keyboardMacroReport.Steps)
40+
logger.Debug().Str("token", token.String()).Msg("started keyboard macro")
41+
message, err := hidrpc.NewKeyboardMacroTokenMessage(token).Marshal()
42+
43+
if err != nil {
44+
logger.Warn().Err(err).Msg("failed to marshal running macro token message")
45+
return
46+
}
47+
if err := session.HidChannel.Send(message); err != nil {
48+
logger.Warn().Err(err).Msg("failed to send running macro token message")
49+
return
50+
}
51+
3852
case hidrpc.TypeCancelKeyboardMacroReport:
3953
rpcCancelKeyboardMacro()
4054
return
55+
56+
case hidrpc.TypeKeyboardMacroTokenState:
57+
tokenState, err := message.KeyboardMacroTokenState()
58+
if err != nil {
59+
logger.Warn().Err(err).Msg("failed to get keyboard macro token")
60+
return
61+
}
62+
rpcCancelKeyboardMacroByToken(tokenState.Token)
63+
return
64+
4165
case hidrpc.TypeKeypressKeepAliveReport:
4266
rpcErr = handleHidRPCKeypressKeepAlive(session)
67+
4368
case hidrpc.TypePointerReport:
4469
pointerReport, err := message.PointerReport()
4570
if err != nil {
4671
logger.Warn().Err(err).Msg("failed to get pointer report")
4772
return
4873
}
4974
rpcErr = rpcAbsMouseReport(pointerReport.X, pointerReport.Y, pointerReport.Button)
75+
5076
case hidrpc.TypeMouseReport:
5177
mouseReport, err := message.MouseReport()
5278
if err != nil {
5379
logger.Warn().Err(err).Msg("failed to get mouse report")
5480
return
5581
}
5682
rpcErr = rpcRelMouseReport(mouseReport.DX, mouseReport.DY, mouseReport.Button)
83+
5784
default:
5885
logger.Warn().Uint8("type", uint8(message.Type())).Msg("unknown HID RPC message type")
5986
}
@@ -65,15 +92,18 @@ func handleHidRPCMessage(message hidrpc.Message, session *Session) {
6592

6693
func onHidMessage(msg hidQueueMessage, session *Session) {
6794
data := msg.Data
95+
dataLen := len(data)
6896

6997
scopedLogger := hidRPCLogger.With().
7098
Str("channel", msg.channel).
71-
Bytes("data", data).
99+
Dur("timelimit", msg.timelimit).
100+
Int("data_len", dataLen).
101+
Bytes("data", data[:min(dataLen, 32)]).
72102
Logger()
73103
scopedLogger.Debug().Msg("HID RPC message received")
74104

75-
if len(data) < 1 {
76-
scopedLogger.Warn().Int("length", len(data)).Msg("received empty data in HID RPC message handler")
105+
if dataLen < 1 {
106+
scopedLogger.Warn().Msg("received empty data in HID RPC message handler")
77107
return
78108
}
79109

@@ -96,7 +126,7 @@ func onHidMessage(msg hidQueueMessage, session *Session) {
96126
r <- nil
97127
}()
98128
select {
99-
case <-time.After(1 * time.Second):
129+
case <-time.After(msg.timelimit * time.Second):
100130
scopedLogger.Warn().Msg("HID RPC message timed out")
101131
case <-r:
102132
scopedLogger.Debug().Dur("duration", time.Since(t)).Msg("HID RPC message handled")
@@ -212,6 +242,8 @@ func reportHidRPC(params any, session *Session) {
212242
message, err = hidrpc.NewKeydownStateMessage(params).Marshal()
213243
case hidrpc.KeyboardMacroState:
214244
message, err = hidrpc.NewKeyboardMacroStateMessage(params.State, params.IsPaste).Marshal()
245+
case hidrpc.KeyboardMacroTokenState:
246+
message, err = hidrpc.NewKeyboardMacroTokenMessage(params.Token).Marshal()
215247
default:
216248
err = fmt.Errorf("unknown HID RPC message type: %T", params)
217249
}

internal/hidrpc/hidrpc.go

Lines changed: 30 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,9 @@ package hidrpc
22

33
import (
44
"fmt"
5+
"time"
56

7+
"github.com/google/uuid"
68
"github.com/jetkvm/kvm/internal/usbgadget"
79
)
810

@@ -22,26 +24,34 @@ const (
2224
TypeKeyboardLedState MessageType = 0x32
2325
TypeKeydownState MessageType = 0x33
2426
TypeKeyboardMacroState MessageType = 0x34
27+
TypeKeyboardMacroTokenState MessageType = 0x35
2528
)
2629

30+
type QueueIndex int
31+
2732
const (
28-
Version byte = 0x01 // Version of the HID RPC protocol
33+
Version byte = 0x01 // Version of the HID RPC protocol
34+
HandshakeQueue int = 0 // Queue index for handshake messages
35+
KeyboardQueue int = 1 // Queue index for keyboard messages
36+
MouseQueue int = 2 // Queue index for mouse messages
37+
MacroQueue int = 3 // Queue index for macro messages
38+
OtherQueue int = 4 // Queue index for other messages
2939
)
3040

3141
// GetQueueIndex returns the index of the queue to which the message should be enqueued.
32-
func GetQueueIndex(messageType MessageType) int {
42+
func GetQueueIndex(messageType MessageType) (int, time.Duration) {
3343
switch messageType {
3444
case TypeHandshake:
35-
return 0
36-
case TypeKeyboardReport, TypeKeypressReport, TypeKeyboardMacroReport, TypeKeyboardLedState, TypeKeydownState, TypeKeyboardMacroState:
37-
return 1
45+
return HandshakeQueue, 1
46+
case TypeKeyboardReport, TypeKeypressReport, TypeKeyboardLedState, TypeKeydownState, TypeKeyboardMacroState:
47+
return KeyboardQueue, 1
3848
case TypePointerReport, TypeMouseReport, TypeWheelReport:
39-
return 2
40-
// we don't want to block the queue for this message
41-
case TypeCancelKeyboardMacroReport:
42-
return 3
49+
return MouseQueue, 1
50+
// we don't want to block the queue for these messages
51+
case TypeKeyboardMacroReport, TypeCancelKeyboardMacroReport, TypeKeyboardMacroTokenState:
52+
return MacroQueue, 60 // 1 minute timeout
4353
default:
44-
return 3
54+
return OtherQueue, 5
4555
}
4656
}
4757

@@ -121,3 +131,13 @@ func NewKeyboardMacroStateMessage(state bool, isPaste bool) *Message {
121131
d: data,
122132
}
123133
}
134+
135+
// NewKeyboardMacroTokenMessage creates a new keyboard macro token message.
136+
func NewKeyboardMacroTokenMessage(token uuid.UUID) *Message {
137+
data, _ := token.MarshalBinary()
138+
139+
return &Message{
140+
t: TypeKeyboardMacroState,
141+
d: data,
142+
}
143+
}

internal/hidrpc/message.go

Lines changed: 69 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@ package hidrpc
33
import (
44
"encoding/binary"
55
"fmt"
6+
7+
"github.com/google/uuid"
68
)
79

810
// Message ..
@@ -23,6 +25,9 @@ func (m *Message) Type() MessageType {
2325
func (m *Message) String() string {
2426
switch m.t {
2527
case TypeHandshake:
28+
if len(m.d) != 0 {
29+
return fmt.Sprintf("Handshake{Malformed: %v}", m.d)
30+
}
2631
return "Handshake"
2732
case TypeKeypressReport:
2833
if len(m.d) < 2 {
@@ -45,12 +50,45 @@ func (m *Message) String() string {
4550
}
4651
return fmt.Sprintf("MouseReport{DX: %d, DY: %d, Button: %d}", m.d[0], m.d[1], m.d[2])
4752
case TypeKeypressKeepAliveReport:
53+
if len(m.d) != 0 {
54+
return fmt.Sprintf("KeypressKeepAliveReport{Malformed: %v}", m.d)
55+
}
4856
return "KeypressKeepAliveReport"
57+
case TypeWheelReport:
58+
if len(m.d) < 3 {
59+
return fmt.Sprintf("WheelReport{Malformed: %v}", m.d)
60+
}
61+
return fmt.Sprintf("WheelReport{Vertical: %d, Horizontal: %d}", int8(m.d[0]), int8(m.d[1]))
4962
case TypeKeyboardMacroReport:
5063
if len(m.d) < 5 {
5164
return fmt.Sprintf("KeyboardMacroReport{Malformed: %v}", m.d)
5265
}
5366
return fmt.Sprintf("KeyboardMacroReport{IsPaste: %v, Length: %d}", m.d[0] == uint8(1), binary.BigEndian.Uint32(m.d[1:5]))
67+
case TypeCancelKeyboardMacroReport:
68+
if len(m.d) != 0 {
69+
return fmt.Sprintf("CancelKeyboardMacroReport{Malformed: %v}", m.d)
70+
}
71+
return "CancelKeyboardMacroReport"
72+
case TypeKeyboardMacroTokenState:
73+
if len(m.d) != 16 {
74+
return fmt.Sprintf("KeyboardMacroTokenState{Malformed: %v}", m.d)
75+
}
76+
return fmt.Sprintf("KeyboardMacroTokenState{Token: %s}", uuid.Must(uuid.FromBytes(m.d)).String())
77+
case TypeKeyboardLedState:
78+
if len(m.d) < 1 {
79+
return fmt.Sprintf("KeyboardLedState{Malformed: %v}", m.d)
80+
}
81+
return fmt.Sprintf("KeyboardLedState{State: %d}", m.d[0])
82+
case TypeKeydownState:
83+
if len(m.d) < 1 {
84+
return fmt.Sprintf("KeydownState{Malformed: %v}", m.d)
85+
}
86+
return fmt.Sprintf("KeydownState{State: %d}", m.d[0])
87+
case TypeKeyboardMacroState:
88+
if len(m.d) < 2 {
89+
return fmt.Sprintf("KeyboardMacroState{Malformed: %v}", m.d)
90+
}
91+
return fmt.Sprintf("KeyboardMacroState{State: %v, IsPaste: %v}", m.d[0] == uint8(1), m.d[1] == uint8(1))
5492
default:
5593
return fmt.Sprintf("Unknown{Type: %d, Data: %v}", m.t, m.d)
5694
}
@@ -67,7 +105,9 @@ func (m *Message) KeypressReport() (KeypressReport, error) {
67105
if m.t != TypeKeypressReport {
68106
return KeypressReport{}, fmt.Errorf("invalid message type: %d", m.t)
69107
}
70-
108+
if len(m.d) < 2 {
109+
return KeypressReport{}, fmt.Errorf("invalid message data length: %d", len(m.d))
110+
}
71111
return KeypressReport{
72112
Key: m.d[0],
73113
Press: m.d[1] == uint8(1),
@@ -95,7 +135,7 @@ func (m *Message) KeyboardReport() (KeyboardReport, error) {
95135
// Macro ..
96136
type KeyboardMacroStep struct {
97137
Modifier byte // 1 byte
98-
Keys []byte // 6 bytes: hidKeyBufferSize
138+
Keys []byte // 6 bytes: HidKeyBufferSize
99139
Delay uint16 // 2 bytes
100140
}
101141
type KeyboardMacroReport struct {
@@ -105,7 +145,7 @@ type KeyboardMacroReport struct {
105145
}
106146

107147
// HidKeyBufferSize is the size of the keys buffer in the keyboard report.
108-
const HidKeyBufferSize = 6
148+
const HidKeyBufferSize int = 6
109149

110150
// KeyboardMacroReport returns the keyboard macro report from the message.
111151
func (m *Message) KeyboardMacroReport() (KeyboardMacroReport, error) {
@@ -205,3 +245,29 @@ func (m *Message) KeyboardMacroState() (KeyboardMacroState, error) {
205245
IsPaste: m.d[1] == uint8(1),
206246
}, nil
207247
}
248+
249+
type KeyboardMacroTokenState struct {
250+
Token uuid.UUID
251+
}
252+
253+
// KeyboardMacroTokenState returns the keyboard macro token UUID from the message.
254+
func (m *Message) KeyboardMacroTokenState() (KeyboardMacroTokenState, error) {
255+
if m.t != TypeKeyboardMacroTokenState {
256+
return KeyboardMacroTokenState{}, fmt.Errorf("invalid message type: %d", m.t)
257+
}
258+
259+
if len(m.d) == 0 {
260+
return KeyboardMacroTokenState{Token: uuid.Nil}, nil
261+
}
262+
263+
if len(m.d) != 16 {
264+
return KeyboardMacroTokenState{}, fmt.Errorf("invalid UUID length: %d", len(m.d))
265+
}
266+
267+
token, err := uuid.FromBytes(m.d)
268+
if err != nil {
269+
return KeyboardMacroTokenState{}, fmt.Errorf("invalid UUID: %v", err)
270+
}
271+
272+
return KeyboardMacroTokenState{Token: token}, nil
273+
}

0 commit comments

Comments
 (0)