mirror of
https://github.com/luckfox-eng29/kvm.git
synced 2026-01-19 17:59:18 +01:00
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>
This commit is contained in:
144
webrtc.go
144
webrtc.go
@@ -7,12 +7,14 @@ import (
|
||||
"net"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/coder/websocket"
|
||||
"github.com/coder/websocket/wsjson"
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/jetkvm/kvm/internal/hidrpc"
|
||||
"github.com/jetkvm/kvm/internal/logging"
|
||||
"github.com/jetkvm/kvm/internal/usbgadget"
|
||||
"github.com/pion/webrtc/v4"
|
||||
"github.com/rs/zerolog"
|
||||
)
|
||||
@@ -27,9 +29,26 @@ type Session struct {
|
||||
|
||||
rpcQueue chan webrtc.DataChannelMessage
|
||||
|
||||
hidRPCAvailable bool
|
||||
hidQueueLock sync.Mutex
|
||||
hidQueue []chan webrtc.DataChannelMessage
|
||||
hidRPCAvailable bool
|
||||
lastKeepAliveArrivalTime time.Time // Track when last keep-alive packet arrived
|
||||
lastTimerResetTime time.Time // Track when auto-release timer was last reset
|
||||
keepAliveJitterLock sync.Mutex // Protect jitter compensation timing state
|
||||
hidQueueLock sync.Mutex
|
||||
hidQueue []chan hidQueueMessage
|
||||
|
||||
keysDownStateQueue chan usbgadget.KeysDownState
|
||||
}
|
||||
|
||||
func (s *Session) resetKeepAliveTime() {
|
||||
s.keepAliveJitterLock.Lock()
|
||||
defer s.keepAliveJitterLock.Unlock()
|
||||
s.lastKeepAliveArrivalTime = time.Time{} // Reset keep-alive timing tracking
|
||||
s.lastTimerResetTime = time.Time{} // Reset auto-release timer tracking
|
||||
}
|
||||
|
||||
type hidQueueMessage struct {
|
||||
webrtc.DataChannelMessage
|
||||
channel string
|
||||
}
|
||||
|
||||
type SessionConfig struct {
|
||||
@@ -78,16 +97,85 @@ func (s *Session) initQueues() {
|
||||
s.hidQueueLock.Lock()
|
||||
defer s.hidQueueLock.Unlock()
|
||||
|
||||
s.hidQueue = make([]chan webrtc.DataChannelMessage, 0)
|
||||
s.hidQueue = make([]chan hidQueueMessage, 0)
|
||||
for i := 0; i < 4; i++ {
|
||||
q := make(chan webrtc.DataChannelMessage, 256)
|
||||
q := make(chan hidQueueMessage, 256)
|
||||
s.hidQueue = append(s.hidQueue, q)
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Session) handleQueues(index int) {
|
||||
for msg := range s.hidQueue[index] {
|
||||
onHidMessage(msg.Data, s)
|
||||
onHidMessage(msg, s)
|
||||
}
|
||||
}
|
||||
|
||||
const keysDownStateQueueSize = 64
|
||||
|
||||
func (s *Session) initKeysDownStateQueue() {
|
||||
// serialise outbound key state reports so unreliable links can't stall input handling
|
||||
s.keysDownStateQueue = make(chan usbgadget.KeysDownState, keysDownStateQueueSize)
|
||||
go s.handleKeysDownStateQueue()
|
||||
}
|
||||
|
||||
func (s *Session) handleKeysDownStateQueue() {
|
||||
for state := range s.keysDownStateQueue {
|
||||
s.reportHidRPCKeysDownState(state)
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Session) enqueueKeysDownState(state usbgadget.KeysDownState) {
|
||||
if s == nil || s.keysDownStateQueue == nil {
|
||||
return
|
||||
}
|
||||
|
||||
select {
|
||||
case s.keysDownStateQueue <- state:
|
||||
default:
|
||||
hidRPCLogger.Warn().Msg("dropping keys down state update; queue full")
|
||||
}
|
||||
}
|
||||
|
||||
func getOnHidMessageHandler(session *Session, scopedLogger *zerolog.Logger, channel string) func(msg webrtc.DataChannelMessage) {
|
||||
return func(msg webrtc.DataChannelMessage) {
|
||||
l := scopedLogger.With().
|
||||
Str("channel", channel).
|
||||
Int("length", len(msg.Data)).
|
||||
Logger()
|
||||
// only log data if the log level is debug or lower
|
||||
if scopedLogger.GetLevel() > zerolog.DebugLevel {
|
||||
l = l.With().Str("data", string(msg.Data)).Logger()
|
||||
}
|
||||
|
||||
if msg.IsString {
|
||||
l.Warn().Msg("received string data in HID RPC message handler")
|
||||
return
|
||||
}
|
||||
|
||||
if len(msg.Data) < 1 {
|
||||
l.Warn().Msg("received empty data in HID RPC message handler")
|
||||
return
|
||||
}
|
||||
|
||||
l.Trace().Msg("received data in HID RPC message handler")
|
||||
|
||||
// Enqueue to ensure ordered processing
|
||||
queueIndex := hidrpc.GetQueueIndex(hidrpc.MessageType(msg.Data[0]))
|
||||
if queueIndex >= len(session.hidQueue) || queueIndex < 0 {
|
||||
l.Warn().Int("queueIndex", queueIndex).Msg("received data in HID RPC message handler, but queue index not found")
|
||||
queueIndex = 3
|
||||
}
|
||||
|
||||
queue := session.hidQueue[queueIndex]
|
||||
if queue != nil {
|
||||
queue <- hidQueueMessage{
|
||||
DataChannelMessage: msg,
|
||||
channel: channel,
|
||||
}
|
||||
} else {
|
||||
l.Warn().Int("queueIndex", queueIndex).Msg("received data in HID RPC message handler, but queue is nil")
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -133,6 +221,7 @@ func newSession(config SessionConfig) (*Session, error) {
|
||||
session := &Session{peerConnection: peerConnection}
|
||||
session.rpcQueue = make(chan webrtc.DataChannelMessage, 256)
|
||||
session.initQueues()
|
||||
session.initKeysDownStateQueue()
|
||||
|
||||
go func() {
|
||||
for msg := range session.rpcQueue {
|
||||
@@ -157,40 +246,12 @@ func newSession(config SessionConfig) (*Session, error) {
|
||||
switch d.Label() {
|
||||
case "hidrpc":
|
||||
session.HidChannel = d
|
||||
d.OnMessage(func(msg webrtc.DataChannelMessage) {
|
||||
l := scopedLogger.With().Int("length", len(msg.Data)).Logger()
|
||||
// only log data if the log level is debug or lower
|
||||
if scopedLogger.GetLevel() > zerolog.DebugLevel {
|
||||
l = l.With().Str("data", string(msg.Data)).Logger()
|
||||
}
|
||||
|
||||
if msg.IsString {
|
||||
l.Warn().Msg("received string data in HID RPC message handler")
|
||||
return
|
||||
}
|
||||
|
||||
if len(msg.Data) < 1 {
|
||||
l.Warn().Msg("received empty data in HID RPC message handler")
|
||||
return
|
||||
}
|
||||
|
||||
l.Trace().Msg("received data in HID RPC message handler")
|
||||
|
||||
// Enqueue to ensure ordered processing
|
||||
queueIndex := hidrpc.GetQueueIndex(hidrpc.MessageType(msg.Data[0]))
|
||||
if queueIndex >= len(session.hidQueue) || queueIndex < 0 {
|
||||
l.Warn().Int("queueIndex", queueIndex).Msg("received data in HID RPC message handler, but queue index not found")
|
||||
queueIndex = 3
|
||||
}
|
||||
|
||||
queue := session.hidQueue[queueIndex]
|
||||
if queue != nil {
|
||||
queue <- msg
|
||||
} else {
|
||||
l.Warn().Int("queueIndex", queueIndex).Msg("received data in HID RPC message handler, but queue is nil")
|
||||
return
|
||||
}
|
||||
})
|
||||
d.OnMessage(getOnHidMessageHandler(session, scopedLogger, "hidrpc"))
|
||||
// we won't send anything over the unreliable channels
|
||||
case "hidrpc-unreliable-ordered":
|
||||
d.OnMessage(getOnHidMessageHandler(session, scopedLogger, "hidrpc-unreliable-ordered"))
|
||||
case "hidrpc-unreliable-nonordered":
|
||||
d.OnMessage(getOnHidMessageHandler(session, scopedLogger, "hidrpc-unreliable-nonordered"))
|
||||
case "rpc":
|
||||
session.RPCChannel = d
|
||||
d.OnMessage(func(msg webrtc.DataChannelMessage) {
|
||||
@@ -282,6 +343,9 @@ func newSession(config SessionConfig) (*Session, error) {
|
||||
session.hidQueue[i] = nil
|
||||
}
|
||||
|
||||
close(session.keysDownStateQueue)
|
||||
session.keysDownStateQueue = nil
|
||||
|
||||
if session.shouldUmountVirtualMedia {
|
||||
if err := rpcUnmountImage(); err != nil {
|
||||
scopedLogger.Warn().Err(err).Msg("unmount image failed on connection close")
|
||||
|
||||
Reference in New Issue
Block a user