diff --git a/internal/usbgadget/hid_keyboard.go b/internal/usbgadget/hid_keyboard.go index 40dff0be..e3997ea6 100644 --- a/internal/usbgadget/hid_keyboard.go +++ b/internal/usbgadget/hid_keyboard.go @@ -22,6 +22,11 @@ var keyboardConfig = gadgetConfigItem{ reportDesc: keyboardReportDesc, } +// macOS default: 15 * 15 = 225ms https://discussions.apple.com/thread/1316947?sortBy=rank +// Linux default: 250ms https://man.archlinux.org/man/kbdrate.8.en +// Windows default: 1ms `HKEY_CURRENT_USER\Control Panel\Accessibility\Keyboard Response\AutoRepeatDelay` +const autoReleaseKeyboardInterval = time.Millisecond * 225 + // Source: https://www.kernel.org/doc/Documentation/usb/gadget_hid.txt var keyboardReportDesc = []byte{ 0x05, 0x01, /* USAGE_PAGE (Generic Desktop) */ @@ -173,8 +178,6 @@ func (u *UsbGadget) SetOnKeysDownChange(f func(state KeysDownState)) { u.onKeysDownChange = &f } -const autoReleaseKeyboardInterval = time.Millisecond * 450 - func (u *UsbGadget) scheduleAutoRelease(key byte) { u.kbdAutoReleaseLock.Lock() defer u.kbdAutoReleaseLock.Unlock() @@ -222,7 +225,8 @@ func (u *UsbGadget) performAutoRelease(key byte) { default: } - _, err := u.keypressReport(key, false, false) + // we just reset the keyboard state to 0 no matter what + _, err := u.keypressReport(0, false, false) if err != nil { u.log.Warn().Uint8("key", key).Msg("failed to auto-release keyboard key") } @@ -453,23 +457,6 @@ func (u *UsbGadget) keypressReport(key byte, press bool, autoRelease bool) (Keys } } - // if autoRelease { - // u.kbdAutoReleaseLock.Lock() - // u.kbdAutoReleaseLock.Unlock() - // ll.Uint8("key", key).Msg("locking kbdAutoReleaseLock, autoReleasLastKey reset") - - // defer func() { - // ll.Uint8("key", key).Msg("unlocked kbdAutoReleaseLock, autoReleasLastKey reset") - // }() - - // if u.kbdAutoReleaseLastKey == key { - // ll.Uint8("key", key).Msg("key already released by auto-release, skipping") - // u.kbdAutoReleaseLastKey = 0 - - // return u.UpdateKeysDown(modifier, keys), nil - // } - // } - err := u.keyboardWriteHidFile(modifier, keys) if err != nil { u.log.Warn().Uint8("modifier", modifier).Uints8("keys", keys).Msg("Could not write keypress report to hidg0") diff --git a/ui/src/hooks/useKeyboard.ts b/ui/src/hooks/useKeyboard.ts index ee3313b0..8c4c74da 100644 --- a/ui/src/hooks/useKeyboard.ts +++ b/ui/src/hooks/useKeyboard.ts @@ -220,6 +220,42 @@ export default function useKeyboard() { // we don't need to cancel it actually cancelOngoingKeyboardMacroHidRpc(); }, [rpcHidReady, cancelOngoingKeyboardMacroHidRpc, abortController]); + }; + + const KEEPALIVE_INTERVAL = 75; // 200ms interval + + const cancelKeepAlive = useCallback(() => { + if (keepAliveTimerRef.current) { + clearInterval(keepAliveTimerRef.current); + keepAliveTimerRef.current = null; + } + }, []); + + const scheduleKeepAlive = useCallback(() => { + // Clear existing timer if it exists + if (keepAliveTimerRef.current) { + clearInterval(keepAliveTimerRef.current); + } + + // Create new interval timer + keepAliveTimerRef.current = setInterval(() => { + sendKeypressKeepAliveHidRpc(); + }, KEEPALIVE_INTERVAL); + }, [sendKeypressKeepAliveHidRpc]); + + // resetKeyboardState is used to reset the keyboard state to no keys pressed and no modifiers. + // This is useful for macros and when the browser loses focus to ensure that the keyboard state + // is clean. + const resetKeyboardState = useCallback(async () => { + // Cancel keepalive since we're resetting the keyboard state + cancelKeepAlive(); + + // Reset the keys buffer to zeros and the modifier state to zero + keysDownState.keys.length = hidKeyBufferSize; + keysDownState.keys.fill(0); + keysDownState.modifier = 0; + sendKeyboardEvent(keysDownState); + }, [keysDownState, sendKeyboardEvent, cancelKeepAlive]); // handleKeyPress is used to handle a key press or release event. // This function handle both key press and key release events.