diff --git a/internal/usbgadget/hid_keyboard.go b/internal/usbgadget/hid_keyboard.go index fb710c20..8b7080a7 100644 --- a/internal/usbgadget/hid_keyboard.go +++ b/internal/usbgadget/hid_keyboard.go @@ -173,6 +173,50 @@ func (u *UsbGadget) SetOnKeysDownChange(f func(state KeysDownState)) { u.onKeysDownChange = &f } +const autoReleaseKeyboardInterval = time.Millisecond * 300 + +func (u *UsbGadget) scheduleAutoRelease(key byte) { + u.keysAutoReleaseLock.Lock() + defer u.keysAutoReleaseLock.Unlock() + + if u.keysAutoReleaseTimer != nil { + u.keysAutoReleaseTimer.Stop() + } + + u.keysAutoReleaseTimer = time.AfterFunc(autoReleaseKeyboardInterval, func() { + u.performAutoRelease(key) + }) +} + +func (u *UsbGadget) cancelAutoRelease() { + u.keysAutoReleaseLock.Lock() + defer u.keysAutoReleaseLock.Unlock() + + if u.keysAutoReleaseTimer != nil { + u.keysAutoReleaseTimer.Stop() + } +} + +func (u *UsbGadget) performAutoRelease(key byte) { + u.keysAutoReleaseLock.Lock() + defer u.keysAutoReleaseLock.Unlock() + + select { + case <-u.keyboardStateCtx.Done(): + return + default: + } + + _, err := u.KeypressReport(key, false) + if err != nil { + u.log.Warn().Uint8("key", key).Msg("failed to auto-release keyboard key") + } + + u.keysAutoReleaseTimer = nil + + u.log.Trace().Uint8("key", key).Msg("auto release performed") +} + func (u *UsbGadget) listenKeyboardEvents() { var path string if u.keyboardHidFile != nil { @@ -398,5 +442,11 @@ func (u *UsbGadget) KeypressReport(key byte, press bool) (KeysDownState, error) u.log.Warn().Uint8("modifier", modifier).Uints8("keys", keys).Msg("Could not write keypress report to hidg0") } + if press { + u.scheduleAutoRelease(key) + } else { + u.cancelAutoRelease() + } + return u.UpdateKeysDown(modifier, keys), err } diff --git a/internal/usbgadget/usbgadget.go b/internal/usbgadget/usbgadget.go index 3a01a447..7485fc20 100644 --- a/internal/usbgadget/usbgadget.go +++ b/internal/usbgadget/usbgadget.go @@ -68,6 +68,9 @@ type UsbGadget struct { keyboardState byte // keyboard latched state (NumLock, CapsLock, ScrollLock, Compose, Kana) keysDownState KeysDownState // keyboard dynamic state (modifier keys and pressed keys) + keysAutoReleaseLock sync.Mutex + keysAutoReleaseTimer *time.Timer + keyboardStateLock sync.Mutex keyboardStateCtx context.Context keyboardStateCancel context.CancelFunc @@ -149,3 +152,35 @@ func newUsbGadget(name string, configMap map[string]gadgetConfigItem, enabledDev return g } + +// Close cleans up resources used by the USB gadget +func (u *UsbGadget) Close() error { + // Cancel keyboard state context + if u.keyboardStateCancel != nil { + u.keyboardStateCancel() + } + + // Stop auto-release timer + u.keysAutoReleaseLock.Lock() + if u.keysAutoReleaseTimer != nil { + u.keysAutoReleaseTimer.Stop() + u.keysAutoReleaseTimer = nil + } + u.keysAutoReleaseLock.Unlock() + + // Close HID files + if u.keyboardHidFile != nil { + u.keyboardHidFile.Close() + u.keyboardHidFile = nil + } + if u.absMouseHidFile != nil { + u.absMouseHidFile.Close() + u.absMouseHidFile = nil + } + if u.relMouseHidFile != nil { + u.relMouseHidFile.Close() + u.relMouseHidFile = nil + } + + return nil +}