Files
kvm/usb.go
Aveline afb146d78c feat: release keyPress automatically (#796)
* feat: release keyPress automatically

* send keepalive when pressing the key

* remove logging

* clean up logging

* chore: use unreliable channel to send keepalive events

* chore: use ordered unreliable channel for pointer events

* chore: adjust auto release key interval

* chore: update logging for kbdAutoReleaseLock

* chore: update comment for KEEPALIVE_INTERVAL

* fix: should cancelAutorelease when pressed is true

* fix: handshake won't happen if webrtc reconnects

* chore: add trace log for writeWithTimeout

* chore: add timeout for KeypressReport

* chore: use the proper key to send release command

* refactor: simplify HID RPC keyboard input handling and improve key state management

- Updated `handleHidRPCKeyboardInput` to return errors directly instead of keys down state.
- Refactored `rpcKeyboardReport` and `rpcKeypressReport` to return errors instead of states.
- Introduced a queue for managing key down state updates in the `Session` struct to prevent input handling stalls.
- Adjusted the `UpdateKeysDown` method to handle state changes more efficiently.
- Removed unnecessary logging and commented-out code for clarity.

* refactor: enhance keyboard auto-release functionality and key state management

* fix: correct Windows default auto-repeat delay comment from 1ms to 1s

* refactor: send keypress as early as possible

* refactor: replace console.warn with console.info for HID RPC channel events

* refactor: remove unused NewKeypressKeepAliveMessage function from HID RPC

* fix: handle error in key release process and log warnings

* fix: log warning on keypress report failure

* fix: update auto-release keyboard interval to 225

* refactor: enhance keep-alive handling and jitter compensation in HID RPC

- Implemented staleness guard to ignore outdated keep-alive packets.
- Added jitter compensation logic to adjust timer extensions based on packet arrival times.
- Introduced new methods for managing keep-alive state and reset functionality in the Session struct.
- Updated auto-release delay mechanism to use dynamic durations based on keep-alive timing.
- Adjusted keep-alive interval in the UI to improve responsiveness.

* gofmt

* clean up code

* chore: use dynamic duration for scheduleAutoRelease

* Use harcoded timer reset value for now

* fix: prevent nil pointer dereference when stopping timers in Close method

* refactor: remove nil check for kbdAutoReleaseTimers in DelayAutoReleaseWithDuration

* refactor: optimize dependencies in useHidRpc hooks

* refactor: streamline keep-alive timer management in useKeyboard hook

* refactor: clarify comments in useKeyboard hook for resetKeyboardState function

* refactor: reduce keysDownStateQueueSize

* refactor: close and reset keysDownStateQueue in newSession function

* chore: resolve conflicts

* resolve conflicts

---------

Co-authored-by: Adam Shiervani <adam.shiervani@gmail.com>
2025-09-18 13:35:47 +02:00

107 lines
2.3 KiB
Go

package kvm
import (
"time"
"github.com/jetkvm/kvm/internal/usbgadget"
)
var gadget *usbgadget.UsbGadget
// initUsbGadget initializes the USB gadget.
// call it only after the config is loaded.
func initUsbGadget() {
gadget = usbgadget.NewUsbGadget(
"jetkvm",
config.UsbDevices,
config.UsbConfig,
usbLogger,
)
go func() {
for {
checkUSBState()
time.Sleep(500 * time.Millisecond)
}
}()
gadget.SetOnKeyboardStateChange(func(state usbgadget.KeyboardState) {
if currentSession != nil {
currentSession.reportHidRPCKeyboardLedState(state)
}
})
gadget.SetOnKeysDownChange(func(state usbgadget.KeysDownState) {
if currentSession != nil {
currentSession.enqueueKeysDownState(state)
}
})
gadget.SetOnKeepAliveReset(func() {
if currentSession != nil {
currentSession.resetKeepAliveTime()
}
})
// open the keyboard hid file to listen for keyboard events
if err := gadget.OpenKeyboardHidFile(); err != nil {
usbLogger.Error().Err(err).Msg("failed to open keyboard hid file")
}
}
func rpcKeyboardReport(modifier byte, keys []byte) error {
return gadget.KeyboardReport(modifier, keys)
}
func rpcKeypressReport(key byte, press bool) error {
return gadget.KeypressReport(key, press)
}
func rpcAbsMouseReport(x int, y int, buttons uint8) error {
return gadget.AbsMouseReport(x, y, buttons)
}
func rpcRelMouseReport(dx int8, dy int8, buttons uint8) error {
return gadget.RelMouseReport(dx, dy, buttons)
}
func rpcWheelReport(wheelY int8) error {
return gadget.AbsMouseWheelReport(wheelY)
}
func rpcGetKeyboardLedState() (state usbgadget.KeyboardState) {
return gadget.GetKeyboardState()
}
func rpcGetKeysDownState() (state usbgadget.KeysDownState) {
return gadget.GetKeysDownState()
}
var usbState = "unknown"
func rpcGetUSBState() (state string) {
return gadget.GetUsbState()
}
func triggerUSBStateUpdate() {
go func() {
if currentSession == nil {
usbLogger.Info().Msg("No active RPC session, skipping USB state update")
return
}
writeJSONRPCEvent("usbState", usbState, currentSession)
}()
}
func checkUSBState() {
newState := gadget.GetUsbState()
if newState == usbState {
return
}
usbLogger.Info().Str("from", usbState).Str("to", newState).Msg("USB state changed")
usbState = newState
requestDisplayUpdate(true)
triggerUSBStateUpdate()
}