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)
This commit is contained in:
Marc Brooks 2025-09-24 19:20:51 -05:00
parent 703625d59a
commit 704d65bde6
No known key found for this signature in database
GPG Key ID: 583A6AF2D6AE1DC6
5 changed files with 37 additions and 17 deletions

View File

@ -65,15 +65,17 @@ func handleHidRPCMessage(message hidrpc.Message, session *Session) {
func onHidMessage(msg hidQueueMessage, session *Session) { func onHidMessage(msg hidQueueMessage, session *Session) {
data := msg.Data data := msg.Data
dataLen := len(data)
scopedLogger := hidRPCLogger.With(). scopedLogger := hidRPCLogger.With().
Str("channel", msg.channel). Str("channel", msg.channel).
Bytes("data", data). Int("data_len", dataLen).
Bytes("data", data[:min(dataLen, 32)]).
Logger() Logger()
scopedLogger.Debug().Msg("HID RPC message received") scopedLogger.Debug().Msg("HID RPC message received")
if len(data) < 1 { if dataLen < 1 {
scopedLogger.Warn().Int("length", len(data)).Msg("received empty data in HID RPC message handler") scopedLogger.Warn().Msg("received empty data in HID RPC message handler")
return return
} }
@ -96,7 +98,7 @@ func onHidMessage(msg hidQueueMessage, session *Session) {
r <- nil r <- nil
}() }()
select { select {
case <-time.After(1 * time.Second): case <-time.After(30 * time.Second):
scopedLogger.Warn().Msg("HID RPC message timed out") scopedLogger.Warn().Msg("HID RPC message timed out")
case <-r: case <-r:
scopedLogger.Debug().Dur("duration", time.Since(t)).Msg("HID RPC message handled") scopedLogger.Debug().Dur("duration", time.Since(t)).Msg("HID RPC message handled")

View File

@ -55,10 +55,10 @@ var keyboardReportDesc = []byte{
0x95, 0x06, /* REPORT_COUNT (6) */ 0x95, 0x06, /* REPORT_COUNT (6) */
0x75, 0x08, /* REPORT_SIZE (8) */ 0x75, 0x08, /* REPORT_SIZE (8) */
0x15, 0x00, /* LOGICAL_MINIMUM (0) */ 0x15, 0x00, /* LOGICAL_MINIMUM (0) */
0x25, 0x65, /* LOGICAL_MAXIMUM (101) */ 0x25, 104, /* LOGICAL_MAXIMUM (104-key) */
0x05, 0x07, /* USAGE_PAGE (Keyboard) */ 0x05, 0x07, /* USAGE_PAGE (Keyboard) */
0x19, 0x00, /* USAGE_MINIMUM (Reserved) */ 0x19, 0x00, /* USAGE_MINIMUM (Reserved) */
0x29, 0x65, /* USAGE_MAXIMUM (Keyboard Application) */ 0x29, 0xE7, /* USAGE_MAXIMUM (Keyboard Right GUI) */
0x81, 0x00, /* INPUT (Data,Ary,Abs) */ 0x81, 0x00, /* INPUT (Data,Ary,Abs) */
0xc0, /* END_COLLECTION */ 0xc0, /* END_COLLECTION */
} }
@ -153,6 +153,16 @@ func (u *UsbGadget) SetOnKeysDownChange(f func(state KeysDownState)) {
u.onKeysDownChange = &f u.onKeysDownChange = &f
} }
var suspendedKeyDownMessages bool = false
func (u *UsbGadget) SuspendKeyDownMessages() {
suspendedKeyDownMessages = true
}
func (u *UsbGadget) ResumeSuspendKeyDownMessages() {
suspendedKeyDownMessages = false
}
func (u *UsbGadget) SetOnKeepAliveReset(f func()) { func (u *UsbGadget) SetOnKeepAliveReset(f func()) {
u.onKeepAliveReset = &f u.onKeepAliveReset = &f
} }
@ -169,9 +179,9 @@ func (u *UsbGadget) scheduleAutoRelease(key byte) {
} }
// TODO: make this configurable // TODO: make this configurable
// We currently hardcode the duration to 100ms // We currently hardcode the duration to the default of 100ms
// However, it should be the same as the duration of the keep-alive reset called baseExtension. // However, it should be the same as the duration of the keep-alive reset called baseExtension.
u.kbdAutoReleaseTimers[key] = time.AfterFunc(100*time.Millisecond, func() { u.kbdAutoReleaseTimers[key] = time.AfterFunc(DefaultAutoReleaseDuration, func() {
u.performAutoRelease(key) u.performAutoRelease(key)
}) })
} }
@ -314,6 +324,7 @@ var keyboardWriteHidFileLock sync.Mutex
func (u *UsbGadget) keyboardWriteHidFile(modifier byte, keys []byte) error { func (u *UsbGadget) keyboardWriteHidFile(modifier byte, keys []byte) error {
keyboardWriteHidFileLock.Lock() keyboardWriteHidFileLock.Lock()
defer keyboardWriteHidFileLock.Unlock() defer keyboardWriteHidFileLock.Unlock()
if err := u.openKeyboardHidFile(); err != nil { if err := u.openKeyboardHidFile(); err != nil {
return err return err
} }
@ -353,7 +364,7 @@ func (u *UsbGadget) UpdateKeysDown(modifier byte, keys []byte) KeysDownState {
u.keysDownState = state u.keysDownState = state
u.keyboardStateLock.Unlock() u.keyboardStateLock.Unlock()
if u.onKeysDownChange != nil { if u.onKeysDownChange != nil && !suspendedKeyDownMessages {
(*u.onKeysDownChange)(state) // this enques to the outgoing hidrpc queue via usb.go → currentSession.enqueueKeysDownState(...) (*u.onKeysDownChange)(state) // this enques to the outgoing hidrpc queue via usb.go → currentSession.enqueueKeysDownState(...)
} }
return state return state
@ -484,6 +495,10 @@ func (u *UsbGadget) keypressReport(key byte, press bool) (KeysDownState, error)
} }
err := u.keyboardWriteHidFile(modifier, keys) err := u.keyboardWriteHidFile(modifier, keys)
if err != nil {
u.log.Warn().Uint8("modifier", modifier).Uints8("keys", keys).Msg("Could not write keyboard report to hidg0")
}
return u.UpdateKeysDown(modifier, keys), err return u.UpdateKeysDown(modifier, keys), err
} }

View File

@ -1132,7 +1132,11 @@ func isClearKeyStep(step hidrpc.KeyboardMacroStep) bool {
} }
func rpcDoExecuteKeyboardMacro(ctx context.Context, macro []hidrpc.KeyboardMacroStep) error { func rpcDoExecuteKeyboardMacro(ctx context.Context, macro []hidrpc.KeyboardMacroStep) error {
logger.Debug().Interface("macro", macro).Msg("Executing keyboard macro") logger.Debug().Int("macro_steps", len(macro)).Msg("Executing keyboard macro")
// don't report keyboard state changes while executing the macro
gadget.SuspendKeyDownMessages()
defer gadget.ResumeSuspendKeyDownMessages()
for i, step := range macro { for i, step := range macro {
delay := time.Duration(step.Delay) * time.Millisecond delay := time.Duration(step.Delay) * time.Millisecond
@ -1153,7 +1157,8 @@ func rpcDoExecuteKeyboardMacro(ctx context.Context, macro []hidrpc.KeyboardMacro
case <-time.After(delay): case <-time.After(delay):
// Sleep completed normally // Sleep completed normally
case <-ctx.Done(): case <-ctx.Done():
// make sure keyboard state is reset // make sure keyboard state is reset and the client gets notified
gadget.ResumeSuspendKeyDownMessages()
err := rpcKeyboardReport(0, keyboardClearStateKeys) err := rpcKeyboardReport(0, keyboardClearStateKeys)
if err != nil { if err != nil {
logger.Warn().Err(err).Msg("failed to reset keyboard state") logger.Warn().Err(err).Msg("failed to reset keyboard state")

View File

@ -188,18 +188,18 @@ export default function PasteModal() {
type="number" type="number"
label="Delay between keys" label="Delay between keys"
placeholder="Delay between keys" placeholder="Delay between keys"
min={50} min={0}
max={65534} max={65534}
value={delayValue} value={delayValue}
onChange={e => { onChange={e => {
setDelayValue(parseInt(e.target.value, 10)); setDelayValue(parseInt(e.target.value, 10));
}} }}
/> />
{delayValue < 50 || delayValue > 65534 && ( {delayValue < defaultDelay || delayValue > 65534 && (
<div className="mt-2 flex items-center gap-x-2"> <div className="mt-2 flex items-center gap-x-2">
<ExclamationCircleIcon className="h-4 w-4 text-red-500 dark:text-red-400" /> <ExclamationCircleIcon className="h-4 w-4 text-red-500 dark:text-red-400" />
<span className="text-xs text-red-500 dark:text-red-400"> <span className="text-xs text-red-500 dark:text-red-400">
Delay must be between 50 and 65534 Delay should be between 20 and 65534
</span> </span>
</div> </div>
)} )}

View File

@ -277,7 +277,6 @@ export default function useKeyboard() {
cancelKeepAlive(); cancelKeepAlive();
}, [cancelKeepAlive]); }, [cancelKeepAlive]);
// executeMacro is used to execute a macro consisting of multiple steps. // executeMacro is used to execute a macro consisting of multiple steps.
// Each step can have multiple keys, multiple modifiers and a delay. // Each step can have multiple keys, multiple modifiers and a delay.
// The keys and modifiers are pressed together and held for the delay duration. // The keys and modifiers are pressed together and held for the delay duration.
@ -292,9 +291,7 @@ export default function useKeyboard() {
for (const [_, step] of steps.entries()) { for (const [_, step] of steps.entries()) {
const keyValues = (step.keys || []).map(key => keys[key]).filter(Boolean); const keyValues = (step.keys || []).map(key => keys[key]).filter(Boolean);
const modifierMask: number = (step.modifiers || []) const modifierMask: number = (step.modifiers || [])
.map(mod => modifiers[mod]) .map(mod => modifiers[mod])
.reduce((acc, val) => acc + val, 0); .reduce((acc, val) => acc + val, 0);
// If the step has keys and/or modifiers, press them and hold for the delay // If the step has keys and/or modifiers, press them and hold for the delay
@ -306,6 +303,7 @@ export default function useKeyboard() {
sendKeyboardMacroEventHidRpc(macro); sendKeyboardMacroEventHidRpc(macro);
}, [sendKeyboardMacroEventHidRpc]); }, [sendKeyboardMacroEventHidRpc]);
const executeMacroClientSide = useCallback(async (steps: MacroSteps) => { const executeMacroClientSide = useCallback(async (steps: MacroSteps) => {
const promises: (() => Promise<void>)[] = []; const promises: (() => Promise<void>)[] = [];