diff --git a/hidrpc.go b/hidrpc.go index ebe03daa..abf3bd73 100644 --- a/hidrpc.go +++ b/hidrpc.go @@ -65,15 +65,17 @@ func handleHidRPCMessage(message hidrpc.Message, session *Session) { func onHidMessage(msg hidQueueMessage, session *Session) { data := msg.Data + dataLen := len(data) scopedLogger := hidRPCLogger.With(). Str("channel", msg.channel). - Bytes("data", data). + Int("data_len", dataLen). + Bytes("data", data[:min(dataLen, 32)]). Logger() scopedLogger.Debug().Msg("HID RPC message received") - if len(data) < 1 { - scopedLogger.Warn().Int("length", len(data)).Msg("received empty data in HID RPC message handler") + if dataLen < 1 { + scopedLogger.Warn().Msg("received empty data in HID RPC message handler") return } @@ -96,7 +98,7 @@ func onHidMessage(msg hidQueueMessage, session *Session) { r <- nil }() select { - case <-time.After(1 * time.Second): + case <-time.After(30 * time.Second): scopedLogger.Warn().Msg("HID RPC message timed out") case <-r: scopedLogger.Debug().Dur("duration", time.Since(t)).Msg("HID RPC message handled") diff --git a/internal/usbgadget/hid_keyboard.go b/internal/usbgadget/hid_keyboard.go index 74cf76f9..1deb7c95 100644 --- a/internal/usbgadget/hid_keyboard.go +++ b/internal/usbgadget/hid_keyboard.go @@ -55,10 +55,10 @@ var keyboardReportDesc = []byte{ 0x95, 0x06, /* REPORT_COUNT (6) */ 0x75, 0x08, /* REPORT_SIZE (8) */ 0x15, 0x00, /* LOGICAL_MINIMUM (0) */ - 0x25, 0x65, /* LOGICAL_MAXIMUM (101) */ + 0x25, 104, /* LOGICAL_MAXIMUM (104-key) */ 0x05, 0x07, /* USAGE_PAGE (Keyboard) */ 0x19, 0x00, /* USAGE_MINIMUM (Reserved) */ - 0x29, 0x65, /* USAGE_MAXIMUM (Keyboard Application) */ + 0x29, 0xE7, /* USAGE_MAXIMUM (Keyboard Right GUI) */ 0x81, 0x00, /* INPUT (Data,Ary,Abs) */ 0xc0, /* END_COLLECTION */ } @@ -153,6 +153,16 @@ func (u *UsbGadget) SetOnKeysDownChange(f func(state KeysDownState)) { 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()) { u.onKeepAliveReset = &f } @@ -169,9 +179,9 @@ func (u *UsbGadget) scheduleAutoRelease(key byte) { } // 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. - u.kbdAutoReleaseTimers[key] = time.AfterFunc(100*time.Millisecond, func() { + u.kbdAutoReleaseTimers[key] = time.AfterFunc(DefaultAutoReleaseDuration, func() { u.performAutoRelease(key) }) } @@ -314,6 +324,7 @@ var keyboardWriteHidFileLock sync.Mutex func (u *UsbGadget) keyboardWriteHidFile(modifier byte, keys []byte) error { keyboardWriteHidFileLock.Lock() defer keyboardWriteHidFileLock.Unlock() + if err := u.openKeyboardHidFile(); err != nil { return err } @@ -353,7 +364,7 @@ func (u *UsbGadget) UpdateKeysDown(modifier byte, keys []byte) KeysDownState { u.keysDownState = state 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(...) } return state @@ -484,6 +495,10 @@ func (u *UsbGadget) keypressReport(key byte, press bool) (KeysDownState, error) } 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 } diff --git a/jsonrpc.go b/jsonrpc.go index 3e3d9c94..e3db0fac 100644 --- a/jsonrpc.go +++ b/jsonrpc.go @@ -1132,7 +1132,11 @@ func isClearKeyStep(step hidrpc.KeyboardMacroStep) bool { } 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 { delay := time.Duration(step.Delay) * time.Millisecond @@ -1153,7 +1157,8 @@ func rpcDoExecuteKeyboardMacro(ctx context.Context, macro []hidrpc.KeyboardMacro case <-time.After(delay): // Sleep completed normally 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) if err != nil { logger.Warn().Err(err).Msg("failed to reset keyboard state") diff --git a/ui/src/components/popovers/PasteModal.tsx b/ui/src/components/popovers/PasteModal.tsx index ac97e29b..4b2ed49a 100644 --- a/ui/src/components/popovers/PasteModal.tsx +++ b/ui/src/components/popovers/PasteModal.tsx @@ -188,18 +188,18 @@ export default function PasteModal() { type="number" label="Delay between keys" placeholder="Delay between keys" - min={50} + min={0} max={65534} value={delayValue} onChange={e => { setDelayValue(parseInt(e.target.value, 10)); }} /> - {delayValue < 50 || delayValue > 65534 && ( + {delayValue < defaultDelay || delayValue > 65534 && (
- Delay must be between 50 and 65534 + Delay should be between 20 and 65534
)} diff --git a/ui/src/hooks/useKeyboard.ts b/ui/src/hooks/useKeyboard.ts index 8d101b3b..48a2cdfe 100644 --- a/ui/src/hooks/useKeyboard.ts +++ b/ui/src/hooks/useKeyboard.ts @@ -277,7 +277,6 @@ export default function useKeyboard() { cancelKeepAlive(); }, [cancelKeepAlive]); - // executeMacro is used to execute a macro consisting of multiple steps. // Each step can have multiple keys, multiple modifiers and a delay. // 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()) { const keyValues = (step.keys || []).map(key => keys[key]).filter(Boolean); const modifierMask: number = (step.modifiers || []) - .map(mod => modifiers[mod]) - .reduce((acc, val) => acc + val, 0); // 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]); + const executeMacroClientSide = useCallback(async (steps: MacroSteps) => { const promises: (() => Promise)[] = [];