mirror of https://github.com/jetkvm/kvm.git
Compare commits
6 Commits
12210fac96
...
20a23de227
| Author | SHA1 | Date |
|---|---|---|
|
|
20a23de227 | |
|
|
4e5ae37bf4 | |
|
|
421666ccdc | |
|
|
11b3e8935f | |
|
|
9593a81cb8 | |
|
|
5cac4c4604 |
20
hidrpc.go
20
hidrpc.go
|
|
@ -25,11 +25,7 @@ func handleHidRPCMessage(message hidrpc.Message, session *Session) {
|
||||||
}
|
}
|
||||||
session.hidRPCAvailable = true
|
session.hidRPCAvailable = true
|
||||||
case hidrpc.TypeKeypressReport, hidrpc.TypeKeyboardReport:
|
case hidrpc.TypeKeypressReport, hidrpc.TypeKeyboardReport:
|
||||||
keysDownState, err := handleHidRPCKeyboardInput(message)
|
rpcErr = handleHidRPCKeyboardInput(message)
|
||||||
if keysDownState != nil {
|
|
||||||
session.reportHidRPCKeysDownState(*keysDownState)
|
|
||||||
}
|
|
||||||
rpcErr = err
|
|
||||||
case hidrpc.TypeKeypressKeepAliveReport:
|
case hidrpc.TypeKeypressKeepAliveReport:
|
||||||
gadget.DelayAutoRelease()
|
gadget.DelayAutoRelease()
|
||||||
case hidrpc.TypePointerReport:
|
case hidrpc.TypePointerReport:
|
||||||
|
|
@ -95,27 +91,25 @@ func onHidMessage(msg hidQueueMessage, session *Session) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func handleHidRPCKeyboardInput(message hidrpc.Message) (*usbgadget.KeysDownState, error) {
|
func handleHidRPCKeyboardInput(message hidrpc.Message) error {
|
||||||
switch message.Type() {
|
switch message.Type() {
|
||||||
case hidrpc.TypeKeypressReport:
|
case hidrpc.TypeKeypressReport:
|
||||||
keypressReport, err := message.KeypressReport()
|
keypressReport, err := message.KeypressReport()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Warn().Err(err).Msg("failed to get keypress report")
|
logger.Warn().Err(err).Msg("failed to get keypress report")
|
||||||
return nil, err
|
return err
|
||||||
}
|
}
|
||||||
keysDownState, rpcError := rpcKeypressReport(keypressReport.Key, keypressReport.Press)
|
return rpcKeypressReport(keypressReport.Key, keypressReport.Press)
|
||||||
return &keysDownState, rpcError
|
|
||||||
case hidrpc.TypeKeyboardReport:
|
case hidrpc.TypeKeyboardReport:
|
||||||
keyboardReport, err := message.KeyboardReport()
|
keyboardReport, err := message.KeyboardReport()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Warn().Err(err).Msg("failed to get keyboard report")
|
logger.Warn().Err(err).Msg("failed to get keyboard report")
|
||||||
return nil, err
|
return err
|
||||||
}
|
}
|
||||||
keysDownState, rpcError := rpcKeyboardReport(keyboardReport.Modifier, keyboardReport.Keys)
|
return rpcKeyboardReport(keyboardReport.Modifier, keyboardReport.Keys)
|
||||||
return &keysDownState, rpcError
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil, fmt.Errorf("unknown HID RPC message type: %d", message.Type())
|
return fmt.Errorf("unknown HID RPC message type: %d", message.Type())
|
||||||
}
|
}
|
||||||
|
|
||||||
func reportHidRPC(params any, session *Session) {
|
func reportHidRPC(params any, session *Session) {
|
||||||
|
|
|
||||||
|
|
@ -99,11 +99,3 @@ func NewKeydownStateMessage(state usbgadget.KeysDownState) *Message {
|
||||||
d: data,
|
d: data,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewKeypressKeepAliveMessage creates a new keypress keep alive message.
|
|
||||||
func NewKeypressKeepAliveMessage() *Message {
|
|
||||||
return &Message{
|
|
||||||
t: TypeKeypressKeepAliveReport,
|
|
||||||
d: []byte{},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,7 @@ import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/rs/xid"
|
"github.com/rs/xid"
|
||||||
|
|
@ -27,7 +28,7 @@ var keyboardConfig = gadgetConfigItem{
|
||||||
|
|
||||||
// macOS default: 15 * 15 = 225ms https://discussions.apple.com/thread/1316947?sortBy=rank
|
// macOS default: 15 * 15 = 225ms https://discussions.apple.com/thread/1316947?sortBy=rank
|
||||||
// Linux default: 250ms https://man.archlinux.org/man/kbdrate.8.en
|
// Linux default: 250ms https://man.archlinux.org/man/kbdrate.8.en
|
||||||
// Windows default: 1ms `HKEY_CURRENT_USER\Control Panel\Accessibility\Keyboard Response\AutoRepeatDelay`
|
// Windows default: 1s `HKEY_CURRENT_USER\Control Panel\Accessibility\Keyboard Response\AutoRepeatDelay`
|
||||||
const autoReleaseKeyboardInterval = time.Millisecond * 100
|
const autoReleaseKeyboardInterval = time.Millisecond * 100
|
||||||
|
|
||||||
// Source: https://www.kernel.org/doc/Documentation/usb/gadget_hid.txt
|
// Source: https://www.kernel.org/doc/Documentation/usb/gadget_hid.txt
|
||||||
|
|
@ -153,95 +154,79 @@ func (u *UsbGadget) GetKeysDownState() KeysDownState {
|
||||||
return u.keysDownState
|
return u.keysDownState
|
||||||
}
|
}
|
||||||
|
|
||||||
func (u *UsbGadget) updateKeyDownState(state KeysDownState) {
|
|
||||||
u.log.Trace().Interface("old", u.keysDownState).Interface("new", state).Msg("acquiring keyboardStateLock for updateKeyDownState")
|
|
||||||
|
|
||||||
// this is intentional to unlock keyboard state lock before onKeysDownChange callback
|
|
||||||
{
|
|
||||||
u.keyboardStateLock.Lock()
|
|
||||||
defer u.keyboardStateLock.Unlock()
|
|
||||||
|
|
||||||
if u.keysDownState.Modifier == state.Modifier &&
|
|
||||||
bytes.Equal(u.keysDownState.Keys, state.Keys) {
|
|
||||||
return // No change in key down state
|
|
||||||
}
|
|
||||||
|
|
||||||
u.log.Trace().Interface("old", u.keysDownState).Interface("new", state).Msg("keysDownState updated")
|
|
||||||
u.keysDownState = state
|
|
||||||
}
|
|
||||||
|
|
||||||
if u.onKeysDownChange != nil {
|
|
||||||
u.log.Trace().Interface("state", state).Msg("calling onKeysDownChange")
|
|
||||||
(*u.onKeysDownChange)(state)
|
|
||||||
u.log.Trace().Interface("state", state).Msg("onKeysDownChange called")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (u *UsbGadget) SetOnKeysDownChange(f func(state KeysDownState)) {
|
func (u *UsbGadget) SetOnKeysDownChange(f func(state KeysDownState)) {
|
||||||
u.onKeysDownChange = &f
|
u.onKeysDownChange = &f
|
||||||
}
|
}
|
||||||
|
|
||||||
func (u *UsbGadget) scheduleAutoRelease(key byte) {
|
func (u *UsbGadget) scheduleAutoRelease(key byte) {
|
||||||
u.log.Trace().Msg("scheduling autoRelease")
|
|
||||||
u.kbdAutoReleaseLock.Lock()
|
u.kbdAutoReleaseLock.Lock()
|
||||||
defer unlockWithLog(&u.kbdAutoReleaseLock, u.log, "autoRelease scheduled")
|
defer unlockWithLog(&u.kbdAutoReleaseLock, u.log, "autoRelease scheduled")
|
||||||
|
|
||||||
if u.kbdAutoReleaseTimer != nil {
|
if u.kbdAutoReleaseTimers[key] != nil {
|
||||||
u.kbdAutoReleaseTimer.Stop()
|
u.kbdAutoReleaseTimers[key].Stop()
|
||||||
}
|
}
|
||||||
|
|
||||||
u.kbdAutoReleaseTimer = time.AfterFunc(autoReleaseKeyboardInterval, func() {
|
u.kbdAutoReleaseTimers[key] = time.AfterFunc(autoReleaseKeyboardInterval, func() {
|
||||||
u.performAutoRelease()
|
u.performAutoRelease(key)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func (u *UsbGadget) cancelAutoRelease() {
|
func (u *UsbGadget) cancelAutoRelease(key byte) {
|
||||||
u.log.Trace().Msg("cancelling autoRelease")
|
|
||||||
u.kbdAutoReleaseLock.Lock()
|
u.kbdAutoReleaseLock.Lock()
|
||||||
defer unlockWithLog(&u.kbdAutoReleaseLock, u.log, "autoRelease cancelled")
|
defer unlockWithLog(&u.kbdAutoReleaseLock, u.log, "autoRelease cancelled")
|
||||||
|
|
||||||
if u.kbdAutoReleaseTimer != nil {
|
if timer := u.kbdAutoReleaseTimers[key]; timer != nil {
|
||||||
u.kbdAutoReleaseTimer.Stop()
|
timer.Stop()
|
||||||
|
u.kbdAutoReleaseTimers[key] = nil
|
||||||
|
delete(u.kbdAutoReleaseTimers, key)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (u *UsbGadget) DelayAutoRelease() {
|
func (u *UsbGadget) DelayAutoRelease() {
|
||||||
u.log.Trace().Msg("delaying autoRelease")
|
|
||||||
u.kbdAutoReleaseLock.Lock()
|
u.kbdAutoReleaseLock.Lock()
|
||||||
defer unlockWithLog(&u.kbdAutoReleaseLock, u.log, "autoRelease delayed")
|
defer unlockWithLog(&u.kbdAutoReleaseLock, u.log, "autoRelease delayed")
|
||||||
|
|
||||||
if u.kbdAutoReleaseTimer == nil {
|
if u.kbdAutoReleaseTimers == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
u.kbdAutoReleaseTimer.Reset(autoReleaseKeyboardInterval)
|
for _, timer := range u.kbdAutoReleaseTimers {
|
||||||
|
if timer != nil {
|
||||||
u.log.Trace().Msg("auto-release timer reset")
|
timer.Reset(autoReleaseKeyboardInterval)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (u *UsbGadget) performAutoRelease() {
|
func (u *UsbGadget) performAutoRelease(key byte) {
|
||||||
u.log.Trace().Msg("performing autoRelease")
|
|
||||||
u.kbdAutoReleaseLock.Lock()
|
u.kbdAutoReleaseLock.Lock()
|
||||||
defer unlockWithLog(&u.kbdAutoReleaseLock, u.log, "autoRelease unlocked")
|
|
||||||
|
|
||||||
key := u.kbdAutoReleaseLastKey
|
if u.kbdAutoReleaseTimers[key] == nil {
|
||||||
|
u.log.Warn().Uint8("key", key).Msg("autoRelease timer not found")
|
||||||
select {
|
u.kbdAutoReleaseLock.Unlock()
|
||||||
case <-u.keyboardStateCtx.Done():
|
|
||||||
return
|
return
|
||||||
default:
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// we just reset the keyboard state to 0 no matter what
|
u.kbdAutoReleaseTimers[key].Stop()
|
||||||
u.log.Trace().Uint8("key", key).Msg("auto-releasing keyboard key")
|
u.kbdAutoReleaseTimers[key] = nil
|
||||||
_, err := u.keypressReport(key, false, false)
|
delete(u.kbdAutoReleaseTimers, key)
|
||||||
if err != nil {
|
u.kbdAutoReleaseLock.Unlock()
|
||||||
u.log.Warn().Uint8("key", key).Msg("failed to auto-release keyboard key")
|
|
||||||
|
// Skip if already released
|
||||||
|
state := u.GetKeysDownState()
|
||||||
|
alreadyReleased := true
|
||||||
|
|
||||||
|
for i := range state.Keys {
|
||||||
|
if state.Keys[i] == key {
|
||||||
|
alreadyReleased = false
|
||||||
|
break
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
u.kbdAutoReleaseTimer = nil
|
if alreadyReleased {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
u.log.Trace().Uint8("key", key).Msg("auto release performed")
|
u.keypressReport(key, false)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (u *UsbGadget) listenKeyboardEvents() {
|
func (u *UsbGadget) listenKeyboardEvents() {
|
||||||
|
|
@ -313,7 +298,11 @@ func (u *UsbGadget) OpenKeyboardHidFile() error {
|
||||||
return u.openKeyboardHidFile()
|
return u.openKeyboardHidFile()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var keyboardWriteHidFileLock sync.Mutex
|
||||||
|
|
||||||
func (u *UsbGadget) keyboardWriteHidFile(modifier byte, keys []byte) error {
|
func (u *UsbGadget) keyboardWriteHidFile(modifier byte, keys []byte) error {
|
||||||
|
keyboardWriteHidFileLock.Lock()
|
||||||
|
defer keyboardWriteHidFileLock.Unlock()
|
||||||
if err := u.openKeyboardHidFile(); err != nil {
|
if err := u.openKeyboardHidFile(); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
@ -337,17 +326,29 @@ func (u *UsbGadget) UpdateKeysDown(modifier byte, keys []byte) KeysDownState {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
downState := KeysDownState{
|
state := KeysDownState{
|
||||||
Modifier: modifier,
|
Modifier: modifier,
|
||||||
Keys: []byte(keys[:]),
|
Keys: []byte(keys[:]),
|
||||||
}
|
}
|
||||||
u.updateKeyDownState(downState)
|
|
||||||
return downState
|
u.keyboardStateLock.Lock()
|
||||||
|
|
||||||
|
if u.keysDownState.Modifier == state.Modifier &&
|
||||||
|
bytes.Equal(u.keysDownState.Keys, state.Keys) {
|
||||||
|
u.keyboardStateLock.Unlock()
|
||||||
|
return state // No change in key down state
|
||||||
}
|
}
|
||||||
|
|
||||||
func (u *UsbGadget) KeyboardReport(modifier byte, keys []byte) (KeysDownState, error) {
|
u.keysDownState = state
|
||||||
u.keyboardLock.Lock()
|
u.keyboardStateLock.Unlock()
|
||||||
defer u.keyboardLock.Unlock()
|
|
||||||
|
if u.onKeysDownChange != nil {
|
||||||
|
(*u.onKeysDownChange)(state) // this enques to the outgoing hidrpc queue via usb.go → currentSession.enqueueKeysDownState(...)
|
||||||
|
}
|
||||||
|
return state
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *UsbGadget) KeyboardReport(modifier byte, keys []byte) error {
|
||||||
defer u.resetUserInputTime()
|
defer u.resetUserInputTime()
|
||||||
|
|
||||||
if len(keys) > hidKeyBufferSize {
|
if len(keys) > hidKeyBufferSize {
|
||||||
|
|
@ -362,7 +363,8 @@ func (u *UsbGadget) KeyboardReport(modifier byte, keys []byte) (KeysDownState, e
|
||||||
u.log.Warn().Uint8("modifier", modifier).Uints8("keys", keys).Msg("Could not write keyboard report to hidg0")
|
u.log.Warn().Uint8("modifier", modifier).Uints8("keys", keys).Msg("Could not write keyboard report to hidg0")
|
||||||
}
|
}
|
||||||
|
|
||||||
return u.UpdateKeysDown(modifier, keys), err
|
u.UpdateKeysDown(modifier, keys)
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
|
@ -402,10 +404,10 @@ var KeyCodeToMaskMap = map[byte]byte{
|
||||||
RightSuper: ModifierMaskRightSuper,
|
RightSuper: ModifierMaskRightSuper,
|
||||||
}
|
}
|
||||||
|
|
||||||
func (u *UsbGadget) keypressReportNonThreadSafe(key byte, press bool, autoRelease bool) (KeysDownState, error) {
|
func (u *UsbGadget) keypressReport(key byte, press bool) (KeysDownState, error) {
|
||||||
defer u.resetUserInputTime()
|
defer u.resetUserInputTime()
|
||||||
|
|
||||||
l := u.log.With().Uint8("key", key).Bool("press", press).Bool("autoRelease", autoRelease).Logger()
|
l := u.log.With().Uint8("key", key).Bool("press", press).Logger()
|
||||||
if l.GetLevel() <= zerolog.DebugLevel {
|
if l.GetLevel() <= zerolog.DebugLevel {
|
||||||
requestID := xid.New()
|
requestID := xid.New()
|
||||||
l = l.With().Str("requestID", requestID.String()).Logger()
|
l = l.With().Str("requestID", requestID.String()).Logger()
|
||||||
|
|
@ -470,64 +472,21 @@ func (u *UsbGadget) keypressReportNonThreadSafe(key byte, press bool, autoReleas
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if l.GetLevel() <= zerolog.DebugLevel {
|
|
||||||
l = l.With().Uint8("modifier", modifier).Uints8("keys", keys).Logger()
|
|
||||||
}
|
|
||||||
|
|
||||||
l.Trace().Msg("writing keypress report to hidg0")
|
|
||||||
|
|
||||||
err := u.keyboardWriteHidFile(modifier, keys)
|
err := u.keyboardWriteHidFile(modifier, keys)
|
||||||
if err != nil {
|
|
||||||
l.Warn().Msg("Could not write keypress report to hidg0")
|
|
||||||
}
|
|
||||||
|
|
||||||
l.Trace().Msg("keypress report written to hidg0")
|
|
||||||
|
|
||||||
if press {
|
|
||||||
{
|
|
||||||
l.Trace().Msg("acquiring kbdAutoReleaseLock to update last key")
|
|
||||||
u.kbdAutoReleaseLock.Lock()
|
|
||||||
u.kbdAutoReleaseLastKey = key
|
|
||||||
unlockWithLog(&u.kbdAutoReleaseLock, u.log, "last key updated")
|
|
||||||
}
|
|
||||||
|
|
||||||
if autoRelease {
|
|
||||||
u.scheduleAutoRelease(key)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if autoRelease {
|
|
||||||
u.cancelAutoRelease()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return u.UpdateKeysDown(modifier, keys), err
|
return u.UpdateKeysDown(modifier, keys), err
|
||||||
}
|
}
|
||||||
|
|
||||||
type keypressReportResult struct {
|
func (u *UsbGadget) KeypressReport(key byte, press bool) error {
|
||||||
KeysDownState KeysDownState
|
state, err := u.keypressReport(key, press)
|
||||||
Error error
|
isRolledOver := state.Keys[0] == hidErrorRollOver
|
||||||
|
|
||||||
|
if isRolledOver {
|
||||||
|
u.cancelAutoRelease(key)
|
||||||
|
} else if press {
|
||||||
|
u.scheduleAutoRelease(key)
|
||||||
|
} else {
|
||||||
|
u.cancelAutoRelease(key)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (u *UsbGadget) keypressReport(key byte, press bool, autoRelease bool) (KeysDownState, error) {
|
return err
|
||||||
u.keyboardLock.Lock()
|
|
||||||
defer u.keyboardLock.Unlock()
|
|
||||||
|
|
||||||
r := make(chan keypressReportResult)
|
|
||||||
go func() {
|
|
||||||
state, err := u.keypressReportNonThreadSafe(key, press, autoRelease)
|
|
||||||
r <- keypressReportResult{KeysDownState: state, Error: err}
|
|
||||||
}()
|
|
||||||
|
|
||||||
select {
|
|
||||||
case <-time.After(1 * time.Second):
|
|
||||||
u.log.Warn().Msg("keypressReport timed out, possibly stuck")
|
|
||||||
return u.keysDownState, fmt.Errorf("keypressReport timed out, possibly stuck")
|
|
||||||
case ret := <-r:
|
|
||||||
u.log.Debug().Msg("keypressReport handled")
|
|
||||||
return ret.KeysDownState, ret.Error
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (u *UsbGadget) KeypressReport(key byte, press bool) (KeysDownState, error) {
|
|
||||||
return u.keypressReport(key, press, true)
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -69,8 +69,7 @@ type UsbGadget struct {
|
||||||
keysDownState KeysDownState // keyboard dynamic state (modifier keys and pressed keys)
|
keysDownState KeysDownState // keyboard dynamic state (modifier keys and pressed keys)
|
||||||
|
|
||||||
kbdAutoReleaseLock sync.Mutex
|
kbdAutoReleaseLock sync.Mutex
|
||||||
kbdAutoReleaseTimer *time.Timer
|
kbdAutoReleaseTimers map[byte]*time.Timer
|
||||||
kbdAutoReleaseLastKey byte
|
|
||||||
|
|
||||||
keyboardStateLock sync.Mutex
|
keyboardStateLock sync.Mutex
|
||||||
keyboardStateCtx context.Context
|
keyboardStateCtx context.Context
|
||||||
|
|
@ -136,6 +135,7 @@ func newUsbGadget(name string, configMap map[string]gadgetConfigItem, enabledDev
|
||||||
keyboardStateCancel: keyboardCancel,
|
keyboardStateCancel: keyboardCancel,
|
||||||
keyboardState: 0,
|
keyboardState: 0,
|
||||||
keysDownState: KeysDownState{Modifier: 0, Keys: []byte{0, 0, 0, 0, 0, 0}}, // must be initialized to hidKeyBufferSize (6) zero bytes
|
keysDownState: KeysDownState{Modifier: 0, Keys: []byte{0, 0, 0, 0, 0, 0}}, // must be initialized to hidKeyBufferSize (6) zero bytes
|
||||||
|
kbdAutoReleaseTimers: make(map[byte]*time.Timer),
|
||||||
enabledDevices: *enabledDevices,
|
enabledDevices: *enabledDevices,
|
||||||
lastUserInput: time.Now(),
|
lastUserInput: time.Now(),
|
||||||
log: logger,
|
log: logger,
|
||||||
|
|
@ -163,10 +163,10 @@ func (u *UsbGadget) Close() error {
|
||||||
|
|
||||||
// Stop auto-release timer
|
// Stop auto-release timer
|
||||||
u.kbdAutoReleaseLock.Lock()
|
u.kbdAutoReleaseLock.Lock()
|
||||||
if u.kbdAutoReleaseTimer != nil {
|
for _, timer := range u.kbdAutoReleaseTimers {
|
||||||
u.kbdAutoReleaseTimer.Stop()
|
timer.Stop()
|
||||||
u.kbdAutoReleaseTimer = nil
|
|
||||||
}
|
}
|
||||||
|
u.kbdAutoReleaseTimers = make(map[byte]*time.Timer)
|
||||||
u.kbdAutoReleaseLock.Unlock()
|
u.kbdAutoReleaseLock.Unlock()
|
||||||
|
|
||||||
// Close HID files
|
// Close HID files
|
||||||
|
|
|
||||||
|
|
@ -1059,9 +1059,7 @@ var rpcHandlers = map[string]RPCHandler{
|
||||||
"getNetworkSettings": {Func: rpcGetNetworkSettings},
|
"getNetworkSettings": {Func: rpcGetNetworkSettings},
|
||||||
"setNetworkSettings": {Func: rpcSetNetworkSettings, Params: []string{"settings"}},
|
"setNetworkSettings": {Func: rpcSetNetworkSettings, Params: []string{"settings"}},
|
||||||
"renewDHCPLease": {Func: rpcRenewDHCPLease},
|
"renewDHCPLease": {Func: rpcRenewDHCPLease},
|
||||||
"keyboardReport": {Func: rpcKeyboardReport, Params: []string{"modifier", "keys"}},
|
|
||||||
"getKeyboardLedState": {Func: rpcGetKeyboardLedState},
|
"getKeyboardLedState": {Func: rpcGetKeyboardLedState},
|
||||||
"keypressReport": {Func: rpcKeypressReport, Params: []string{"key", "press"}},
|
|
||||||
"getKeyDownState": {Func: rpcGetKeysDownState},
|
"getKeyDownState": {Func: rpcGetKeysDownState},
|
||||||
"absMouseReport": {Func: rpcAbsMouseReport, Params: []string{"x", "y", "buttons"}},
|
"absMouseReport": {Func: rpcAbsMouseReport, Params: []string{"x", "y", "buttons"}},
|
||||||
"relMouseReport": {Func: rpcRelMouseReport, Params: []string{"dx", "dy", "buttons"}},
|
"relMouseReport": {Func: rpcRelMouseReport, Params: []string{"dx", "dy", "buttons"}},
|
||||||
|
|
|
||||||
|
|
@ -237,6 +237,7 @@ export default function WebRTCVideo() {
|
||||||
const keyDownHandler = useCallback(
|
const keyDownHandler = useCallback(
|
||||||
(e: KeyboardEvent) => {
|
(e: KeyboardEvent) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
|
if (e.repeat) return;
|
||||||
const code = getAdjustedKeyCode(e);
|
const code = getAdjustedKeyCode(e);
|
||||||
const hidKey = keys[code];
|
const hidKey = keys[code];
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -36,11 +36,16 @@ export function useHidRpc(onHidRpcMessage?: (payload: RpcMessage) => void) {
|
||||||
}, [rpcHidChannel, rpcHidProtocolVersion]);
|
}, [rpcHidChannel, rpcHidProtocolVersion]);
|
||||||
|
|
||||||
const rpcHidUnreliableReady = useMemo(() => {
|
const rpcHidUnreliableReady = useMemo(() => {
|
||||||
return rpcHidUnreliableChannel?.readyState === "open" && rpcHidProtocolVersion !== null;
|
return (
|
||||||
|
rpcHidUnreliableChannel?.readyState === "open" && rpcHidProtocolVersion !== null
|
||||||
|
);
|
||||||
}, [rpcHidUnreliableChannel, rpcHidProtocolVersion]);
|
}, [rpcHidUnreliableChannel, rpcHidProtocolVersion]);
|
||||||
|
|
||||||
const rpcHidUnreliableNonOrderedReady = useMemo(() => {
|
const rpcHidUnreliableNonOrderedReady = useMemo(() => {
|
||||||
return rpcHidUnreliableNonOrderedChannel?.readyState === "open" && rpcHidProtocolVersion !== null;
|
return (
|
||||||
|
rpcHidUnreliableNonOrderedChannel?.readyState === "open" &&
|
||||||
|
rpcHidProtocolVersion !== null
|
||||||
|
);
|
||||||
}, [rpcHidUnreliableNonOrderedChannel, rpcHidProtocolVersion]);
|
}, [rpcHidUnreliableNonOrderedChannel, rpcHidProtocolVersion]);
|
||||||
|
|
||||||
const rpcHidStatus = useMemo(() => {
|
const rpcHidStatus = useMemo(() => {
|
||||||
|
|
@ -50,7 +55,15 @@ export function useHidRpc(onHidRpcMessage?: (payload: RpcMessage) => void) {
|
||||||
return `ready (v${rpcHidProtocolVersion}${rpcHidUnreliableReady ? "+u" : ""})`;
|
return `ready (v${rpcHidProtocolVersion}${rpcHidUnreliableReady ? "+u" : ""})`;
|
||||||
}, [rpcHidChannel, rpcHidUnreliableReady, rpcHidProtocolVersion]);
|
}, [rpcHidChannel, rpcHidUnreliableReady, rpcHidProtocolVersion]);
|
||||||
|
|
||||||
const sendMessage = useCallback((message: RpcMessage, { ignoreHandshakeState, useUnreliableChannel, requireOrdered = true }: sendMessageParams = {}) => {
|
const sendMessage = useCallback(
|
||||||
|
(
|
||||||
|
message: RpcMessage,
|
||||||
|
{
|
||||||
|
ignoreHandshakeState,
|
||||||
|
useUnreliableChannel,
|
||||||
|
requireOrdered = true,
|
||||||
|
}: sendMessageParams = {},
|
||||||
|
) => {
|
||||||
if (rpcHidChannel?.readyState !== "open") return;
|
if (rpcHidChannel?.readyState !== "open") return;
|
||||||
if (!rpcHidReady && !ignoreHandshakeState) return;
|
if (!rpcHidReady && !ignoreHandshakeState) return;
|
||||||
|
|
||||||
|
|
@ -72,19 +85,22 @@ export function useHidRpc(onHidRpcMessage?: (payload: RpcMessage) => void) {
|
||||||
}
|
}
|
||||||
|
|
||||||
rpcHidChannel?.send(data as unknown as ArrayBuffer);
|
rpcHidChannel?.send(data as unknown as ArrayBuffer);
|
||||||
}, [
|
},
|
||||||
|
[
|
||||||
rpcHidChannel,
|
rpcHidChannel,
|
||||||
rpcHidUnreliableChannel,
|
rpcHidUnreliableChannel,
|
||||||
rpcHidUnreliableNonOrderedChannel,
|
rpcHidUnreliableNonOrderedChannel,
|
||||||
rpcHidReady,
|
rpcHidReady,
|
||||||
rpcHidUnreliableReady,
|
rpcHidUnreliableReady,
|
||||||
rpcHidUnreliableNonOrderedReady,
|
rpcHidUnreliableNonOrderedReady,
|
||||||
]);
|
],
|
||||||
|
);
|
||||||
|
|
||||||
const reportKeyboardEvent = useCallback(
|
const reportKeyboardEvent = useCallback(
|
||||||
(keys: number[], modifier: number) => {
|
(keys: number[], modifier: number) => {
|
||||||
sendMessage(new KeyboardReportMessage(keys, modifier));
|
sendMessage(new KeyboardReportMessage(keys, modifier));
|
||||||
}, [sendMessage],
|
},
|
||||||
|
[sendMessage],
|
||||||
);
|
);
|
||||||
|
|
||||||
const reportKeypressEvent = useCallback(
|
const reportKeypressEvent = useCallback(
|
||||||
|
|
@ -96,7 +112,9 @@ export function useHidRpc(onHidRpcMessage?: (payload: RpcMessage) => void) {
|
||||||
|
|
||||||
const reportAbsMouseEvent = useCallback(
|
const reportAbsMouseEvent = useCallback(
|
||||||
(x: number, y: number, buttons: number) => {
|
(x: number, y: number, buttons: number) => {
|
||||||
sendMessage(new PointerReportMessage(x, y, buttons), { useUnreliableChannel: true });
|
sendMessage(new PointerReportMessage(x, y, buttons), {
|
||||||
|
useUnreliableChannel: true,
|
||||||
|
});
|
||||||
},
|
},
|
||||||
[sendMessage],
|
[sendMessage],
|
||||||
);
|
);
|
||||||
|
|
@ -109,7 +127,7 @@ export function useHidRpc(onHidRpcMessage?: (payload: RpcMessage) => void) {
|
||||||
);
|
);
|
||||||
|
|
||||||
const reportKeypressKeepAlive = useCallback(() => {
|
const reportKeypressKeepAlive = useCallback(() => {
|
||||||
sendMessage(KEEPALIVE_MESSAGE, { useUnreliableChannel: true, requireOrdered: false });
|
sendMessage(KEEPALIVE_MESSAGE);
|
||||||
}, [sendMessage]);
|
}, [sendMessage]);
|
||||||
|
|
||||||
const sendHandshake = useCallback(() => {
|
const sendHandshake = useCallback(() => {
|
||||||
|
|
@ -119,7 +137,8 @@ export function useHidRpc(onHidRpcMessage?: (payload: RpcMessage) => void) {
|
||||||
sendMessage(new HandshakeMessage(HID_RPC_VERSION), { ignoreHandshakeState: true });
|
sendMessage(new HandshakeMessage(HID_RPC_VERSION), { ignoreHandshakeState: true });
|
||||||
}, [rpcHidChannel, rpcHidProtocolVersion, sendMessage]);
|
}, [rpcHidChannel, rpcHidProtocolVersion, sendMessage]);
|
||||||
|
|
||||||
const handleHandshake = useCallback((message: HandshakeMessage) => {
|
const handleHandshake = useCallback(
|
||||||
|
(message: HandshakeMessage) => {
|
||||||
if (!message.version) {
|
if (!message.version) {
|
||||||
console.error("Received handshake message without version", message);
|
console.error("Received handshake message without version", message);
|
||||||
return;
|
return;
|
||||||
|
|
@ -134,7 +153,9 @@ export function useHidRpc(onHidRpcMessage?: (payload: RpcMessage) => void) {
|
||||||
}
|
}
|
||||||
|
|
||||||
setRpcHidProtocolVersion(message.version);
|
setRpcHidProtocolVersion(message.version);
|
||||||
}, [setRpcHidProtocolVersion]);
|
},
|
||||||
|
[setRpcHidProtocolVersion],
|
||||||
|
);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!rpcHidChannel) return;
|
if (!rpcHidChannel) return;
|
||||||
|
|
@ -168,12 +189,12 @@ export function useHidRpc(onHidRpcMessage?: (payload: RpcMessage) => void) {
|
||||||
};
|
};
|
||||||
|
|
||||||
const openHandler = () => {
|
const openHandler = () => {
|
||||||
console.warn("HID RPC channel opened");
|
console.info("HID RPC channel opened");
|
||||||
sendHandshake();
|
sendHandshake();
|
||||||
};
|
};
|
||||||
|
|
||||||
const closeHandler = () => {
|
const closeHandler = () => {
|
||||||
console.warn("HID RPC channel closed");
|
console.info("HID RPC channel closed");
|
||||||
setRpcHidProtocolVersion(null);
|
setRpcHidProtocolVersion(null);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -186,15 +207,13 @@ export function useHidRpc(onHidRpcMessage?: (payload: RpcMessage) => void) {
|
||||||
rpcHidChannel.removeEventListener("close", closeHandler);
|
rpcHidChannel.removeEventListener("close", closeHandler);
|
||||||
rpcHidChannel.removeEventListener("open", openHandler);
|
rpcHidChannel.removeEventListener("open", openHandler);
|
||||||
};
|
};
|
||||||
},
|
}, [
|
||||||
[
|
|
||||||
rpcHidChannel,
|
rpcHidChannel,
|
||||||
onHidRpcMessage,
|
onHidRpcMessage,
|
||||||
setRpcHidProtocolVersion,
|
setRpcHidProtocolVersion,
|
||||||
sendHandshake,
|
sendHandshake,
|
||||||
handleHandshake,
|
handleHandshake,
|
||||||
],
|
]);
|
||||||
);
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
reportKeyboardEvent,
|
reportKeyboardEvent,
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,12 @@
|
||||||
import { useCallback, useRef } from "react";
|
import { useCallback, useRef } from "react";
|
||||||
|
|
||||||
import { hidErrorRollOver, hidKeyBufferSize, KeysDownState, useHidStore, useRTCStore } from "@/hooks/stores";
|
import {
|
||||||
|
hidErrorRollOver,
|
||||||
|
hidKeyBufferSize,
|
||||||
|
KeysDownState,
|
||||||
|
useHidStore,
|
||||||
|
useRTCStore,
|
||||||
|
} from "@/hooks/stores";
|
||||||
import { JsonRpcResponse, useJsonRpc } from "@/hooks/useJsonRpc";
|
import { JsonRpcResponse, useJsonRpc } from "@/hooks/useJsonRpc";
|
||||||
import { useHidRpc } from "@/hooks/useHidRpc";
|
import { useHidRpc } from "@/hooks/useHidRpc";
|
||||||
import { KeyboardLedStateMessage, KeysDownStateMessage } from "@/hooks/hidRpc";
|
import { KeyboardLedStateMessage, KeysDownStateMessage } from "@/hooks/hidRpc";
|
||||||
|
|
@ -31,7 +37,7 @@ export default function useKeyboard() {
|
||||||
reportKeypressEvent: sendKeypressEventHidRpc,
|
reportKeypressEvent: sendKeypressEventHidRpc,
|
||||||
reportKeypressKeepAlive: sendKeypressKeepAliveHidRpc,
|
reportKeypressKeepAlive: sendKeypressKeepAliveHidRpc,
|
||||||
rpcHidReady,
|
rpcHidReady,
|
||||||
} = useHidRpc((message) => {
|
} = useHidRpc(message => {
|
||||||
switch (message.constructor) {
|
switch (message.constructor) {
|
||||||
case KeysDownStateMessage:
|
case KeysDownStateMessage:
|
||||||
setKeysDownState((message as KeysDownStateMessage).keysDownState);
|
setKeysDownState((message as KeysDownStateMessage).keysDownState);
|
||||||
|
|
@ -52,7 +58,9 @@ export default function useKeyboard() {
|
||||||
async (state: KeysDownState) => {
|
async (state: KeysDownState) => {
|
||||||
if (rpcDataChannel?.readyState !== "open" && !rpcHidReady) return;
|
if (rpcDataChannel?.readyState !== "open" && !rpcHidReady) return;
|
||||||
|
|
||||||
console.debug(`Send keyboardReport keys: ${state.keys}, modifier: ${state.modifier}`);
|
console.debug(
|
||||||
|
`Send keyboardReport keys: ${state.keys}, modifier: ${state.modifier}`,
|
||||||
|
);
|
||||||
|
|
||||||
if (rpcHidReady) {
|
if (rpcHidReady) {
|
||||||
console.debug("Sending keyboard report via HidRPC");
|
console.debug("Sending keyboard report via HidRPC");
|
||||||
|
|
@ -60,20 +68,18 @@ export default function useKeyboard() {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
send("keyboardReport", { keys: state.keys, modifier: state.modifier }, (resp: JsonRpcResponse) => {
|
send(
|
||||||
|
"keyboardReport",
|
||||||
|
{ keys: state.keys, modifier: state.modifier },
|
||||||
|
(resp: JsonRpcResponse) => {
|
||||||
if ("error" in resp) {
|
if ("error" in resp) {
|
||||||
console.error(`Failed to send keyboard report ${state}`, resp.error);
|
console.error(`Failed to send keyboard report ${state}`, resp.error);
|
||||||
}
|
}
|
||||||
});
|
|
||||||
},
|
},
|
||||||
[
|
|
||||||
rpcDataChannel?.readyState,
|
|
||||||
rpcHidReady,
|
|
||||||
send,
|
|
||||||
sendKeyboardEventHidRpc,
|
|
||||||
],
|
|
||||||
);
|
);
|
||||||
|
},
|
||||||
|
[rpcDataChannel?.readyState, rpcHidReady, send, sendKeyboardEventHidRpc],
|
||||||
|
);
|
||||||
|
|
||||||
// executeMacro is used to execute a macro consisting of multiple steps.
|
// executeMacro is used to execute a macro consisting of multiple steps.
|
||||||
// Each step can have multiple keys, multiple modifiers and a delay.
|
// Each step can have multiple keys, multiple modifiers and a delay.
|
||||||
|
|
@ -81,10 +87,14 @@ export default function useKeyboard() {
|
||||||
// After the delay, the keys and modifiers are released and the next step is executed.
|
// After the delay, the keys and modifiers are released and the next step is executed.
|
||||||
// If a step has no keys or modifiers, it is treated as a delay-only step.
|
// If a step has no keys or modifiers, it is treated as a delay-only step.
|
||||||
// A small pause is added between steps to ensure that the device can process the events.
|
// A small pause is added between steps to ensure that the device can process the events.
|
||||||
const executeMacro = async (steps: { keys: string[] | null; modifiers: string[] | null; delay: number }[]) => {
|
const executeMacro = async (
|
||||||
|
steps: { keys: string[] | null; modifiers: string[] | null; delay: number }[],
|
||||||
|
) => {
|
||||||
for (const [index, step] of steps.entries()) {
|
for (const [index, step] of steps.entries()) {
|
||||||
const keyValues = (step.keys || []).map(key => keys[key]).filter(Boolean);
|
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);
|
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
|
// If the step has keys and/or modifiers, press them and hold for the delay
|
||||||
if (keyValues.length > 0 || modifierMask > 0) {
|
if (keyValues.length > 0 || modifierMask > 0) {
|
||||||
|
|
@ -119,6 +129,8 @@ export default function useKeyboard() {
|
||||||
clearInterval(keepAliveTimerRef.current);
|
clearInterval(keepAliveTimerRef.current);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
sendKeypressKeepAliveHidRpc();
|
||||||
|
|
||||||
// Create new interval timer
|
// Create new interval timer
|
||||||
keepAliveTimerRef.current = setInterval(() => {
|
keepAliveTimerRef.current = setInterval(() => {
|
||||||
sendKeypressKeepAliveHidRpc();
|
sendKeypressKeepAliveHidRpc();
|
||||||
|
|
@ -174,7 +186,11 @@ export default function useKeyboard() {
|
||||||
sendKeypress(key, press);
|
sendKeypress(key, press);
|
||||||
} else {
|
} else {
|
||||||
// if the keyPress api is not available, we need to handle the key locally
|
// if the keyPress api is not available, we need to handle the key locally
|
||||||
const downState = simulateDeviceSideKeyHandlingForLegacyDevices(keysDownState, key, press);
|
const downState = simulateDeviceSideKeyHandlingForLegacyDevices(
|
||||||
|
keysDownState,
|
||||||
|
key,
|
||||||
|
press,
|
||||||
|
);
|
||||||
sendKeyboardEvent(downState); // then we send the full state
|
sendKeyboardEvent(downState); // then we send the full state
|
||||||
|
|
||||||
// if we just sent ErrorRollOver, reset to empty state
|
// if we just sent ErrorRollOver, reset to empty state
|
||||||
|
|
@ -194,7 +210,11 @@ export default function useKeyboard() {
|
||||||
);
|
);
|
||||||
|
|
||||||
// IMPORTANT: See the keyPressReportApiAvailable comment above for the reason this exists
|
// IMPORTANT: See the keyPressReportApiAvailable comment above for the reason this exists
|
||||||
function simulateDeviceSideKeyHandlingForLegacyDevices(state: KeysDownState, key: number, press: boolean): KeysDownState {
|
function simulateDeviceSideKeyHandlingForLegacyDevices(
|
||||||
|
state: KeysDownState,
|
||||||
|
key: number,
|
||||||
|
press: boolean,
|
||||||
|
): KeysDownState {
|
||||||
// IMPORTANT: This code parallels the logic in the kernel's hid-gadget driver
|
// IMPORTANT: This code parallels the logic in the kernel's hid-gadget driver
|
||||||
// for handling key presses and releases. It ensures that the USB gadget
|
// for handling key presses and releases. It ensures that the USB gadget
|
||||||
// behaves similarly to a real USB HID keyboard. This logic is paralleled
|
// behaves similarly to a real USB HID keyboard. This logic is paralleled
|
||||||
|
|
@ -223,7 +243,7 @@ export default function useKeyboard() {
|
||||||
// and if we find a zero byte, we can place the key there (if press is true)
|
// and if we find a zero byte, we can place the key there (if press is true)
|
||||||
if (keys[i] === key || keys[i] === 0) {
|
if (keys[i] === key || keys[i] === 0) {
|
||||||
if (press) {
|
if (press) {
|
||||||
keys[i] = key // overwrites the zero byte or the same key if already pressed
|
keys[i] = key; // overwrites the zero byte or the same key if already pressed
|
||||||
} else {
|
} else {
|
||||||
// we are releasing the key, remove it from the buffer
|
// we are releasing the key, remove it from the buffer
|
||||||
if (keys[i] !== 0) {
|
if (keys[i] !== 0) {
|
||||||
|
|
@ -239,13 +259,15 @@ export default function useKeyboard() {
|
||||||
// If we reach here it means we didn't find an empty slot or the key in the buffer
|
// If we reach here it means we didn't find an empty slot or the key in the buffer
|
||||||
if (overrun) {
|
if (overrun) {
|
||||||
if (press) {
|
if (press) {
|
||||||
console.warn(`keyboard buffer overflow current keys ${keys}, key: ${key} not added`);
|
console.warn(
|
||||||
|
`keyboard buffer overflow current keys ${keys}, key: ${key} not added`,
|
||||||
|
);
|
||||||
// Fill all key slots with ErrorRollOver (0x01) to indicate overflow
|
// Fill all key slots with ErrorRollOver (0x01) to indicate overflow
|
||||||
keys.length = hidKeyBufferSize;
|
keys.length = hidKeyBufferSize;
|
||||||
keys.fill(hidErrorRollOver);
|
keys.fill(hidErrorRollOver);
|
||||||
} else {
|
} else {
|
||||||
// If we are releasing a key, and we didn't find it in a slot, who cares?
|
// If we are releasing a key, and we didn't find it in a slot, who cares?
|
||||||
console.debug(`key ${key} not found in buffer, nothing to release`)
|
console.debug(`key ${key} not found in buffer, nothing to release`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
6
usb.go
6
usb.go
|
|
@ -33,7 +33,7 @@ func initUsbGadget() {
|
||||||
|
|
||||||
gadget.SetOnKeysDownChange(func(state usbgadget.KeysDownState) {
|
gadget.SetOnKeysDownChange(func(state usbgadget.KeysDownState) {
|
||||||
if currentSession != nil {
|
if currentSession != nil {
|
||||||
currentSession.reportHidRPCKeysDownState(state)
|
currentSession.enqueueKeysDownState(state)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
@ -43,11 +43,11 @@ func initUsbGadget() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func rpcKeyboardReport(modifier byte, keys []byte) (usbgadget.KeysDownState, error) {
|
func rpcKeyboardReport(modifier byte, keys []byte) error {
|
||||||
return gadget.KeyboardReport(modifier, keys)
|
return gadget.KeyboardReport(modifier, keys)
|
||||||
}
|
}
|
||||||
|
|
||||||
func rpcKeypressReport(key byte, press bool) (usbgadget.KeysDownState, error) {
|
func rpcKeypressReport(key byte, press bool) error {
|
||||||
return gadget.KeypressReport(key, press)
|
return gadget.KeypressReport(key, press)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
30
webrtc.go
30
webrtc.go
|
|
@ -13,6 +13,7 @@ import (
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
"github.com/jetkvm/kvm/internal/hidrpc"
|
"github.com/jetkvm/kvm/internal/hidrpc"
|
||||||
"github.com/jetkvm/kvm/internal/logging"
|
"github.com/jetkvm/kvm/internal/logging"
|
||||||
|
"github.com/jetkvm/kvm/internal/usbgadget"
|
||||||
"github.com/pion/webrtc/v4"
|
"github.com/pion/webrtc/v4"
|
||||||
"github.com/rs/zerolog"
|
"github.com/rs/zerolog"
|
||||||
)
|
)
|
||||||
|
|
@ -30,6 +31,8 @@ type Session struct {
|
||||||
hidRPCAvailable bool
|
hidRPCAvailable bool
|
||||||
hidQueueLock sync.Mutex
|
hidQueueLock sync.Mutex
|
||||||
hidQueue []chan hidQueueMessage
|
hidQueue []chan hidQueueMessage
|
||||||
|
|
||||||
|
keysDownStateQueue chan usbgadget.KeysDownState
|
||||||
}
|
}
|
||||||
|
|
||||||
type hidQueueMessage struct {
|
type hidQueueMessage struct {
|
||||||
|
|
@ -96,6 +99,32 @@ func (s *Session) handleQueues(index int) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const keysDownStateQueueSize = 256
|
||||||
|
|
||||||
|
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) {
|
func getOnHidMessageHandler(session *Session, scopedLogger *zerolog.Logger, channel string) func(msg webrtc.DataChannelMessage) {
|
||||||
return func(msg webrtc.DataChannelMessage) {
|
return func(msg webrtc.DataChannelMessage) {
|
||||||
l := scopedLogger.With().
|
l := scopedLogger.With().
|
||||||
|
|
@ -181,6 +210,7 @@ func newSession(config SessionConfig) (*Session, error) {
|
||||||
session := &Session{peerConnection: peerConnection}
|
session := &Session{peerConnection: peerConnection}
|
||||||
session.rpcQueue = make(chan webrtc.DataChannelMessage, 256)
|
session.rpcQueue = make(chan webrtc.DataChannelMessage, 256)
|
||||||
session.initQueues()
|
session.initQueues()
|
||||||
|
session.initKeysDownStateQueue()
|
||||||
|
|
||||||
go func() {
|
go func() {
|
||||||
for msg := range session.rpcQueue {
|
for msg := range session.rpcQueue {
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue