mirror of https://github.com/jetkvm/kvm.git
Run paste/macros in background on their own queue.
Also returns a token for cancellation. Fixed error in length check for macro key state. Removed redundant clear operation.
This commit is contained in:
parent
0321a7473a
commit
51d80bec65
2
cloud.go
2
cloud.go
|
|
@ -477,7 +477,7 @@ func handleSessionRequest(
|
||||||
cloudLogger.Trace().Interface("session", session).Msg("new session accepted")
|
cloudLogger.Trace().Interface("session", session).Msg("new session accepted")
|
||||||
|
|
||||||
// Cancel any ongoing keyboard macro when session changes
|
// Cancel any ongoing keyboard macro when session changes
|
||||||
cancelKeyboardMacro()
|
cancelAllRunningKeyboardMacros()
|
||||||
|
|
||||||
currentSession = session
|
currentSession = session
|
||||||
_ = wsjson.Write(context.Background(), c, gin.H{"type": "answer", "data": sd})
|
_ = wsjson.Write(context.Background(), c, gin.H{"type": "answer", "data": sd})
|
||||||
|
|
|
||||||
31
hidrpc.go
31
hidrpc.go
|
|
@ -26,20 +26,45 @@ func handleHidRPCMessage(message hidrpc.Message, session *Session) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
session.hidRPCAvailable = true
|
session.hidRPCAvailable = true
|
||||||
|
|
||||||
case hidrpc.TypeKeypressReport, hidrpc.TypeKeyboardReport:
|
case hidrpc.TypeKeypressReport, hidrpc.TypeKeyboardReport:
|
||||||
rpcErr = handleHidRPCKeyboardInput(message)
|
rpcErr = handleHidRPCKeyboardInput(message)
|
||||||
|
|
||||||
case hidrpc.TypeKeyboardMacroReport:
|
case hidrpc.TypeKeyboardMacroReport:
|
||||||
keyboardMacroReport, err := message.KeyboardMacroReport()
|
keyboardMacroReport, err := message.KeyboardMacroReport()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Warn().Err(err).Msg("failed to get keyboard macro report")
|
logger.Warn().Err(err).Msg("failed to get keyboard macro report")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
rpcErr = rpcExecuteKeyboardMacro(keyboardMacroReport.Steps)
|
token := rpcExecuteKeyboardMacro(keyboardMacroReport.IsPaste, keyboardMacroReport.Steps)
|
||||||
|
logger.Debug().Str("token", token.String()).Msg("started keyboard macro")
|
||||||
|
message, err := hidrpc.NewKeyboardMacroTokenMessage(token).Marshal()
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
logger.Warn().Err(err).Msg("failed to marshal running macro token message")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if err := session.HidChannel.Send(message); err != nil {
|
||||||
|
logger.Warn().Err(err).Msg("failed to send running macro token message")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
case hidrpc.TypeCancelKeyboardMacroReport:
|
case hidrpc.TypeCancelKeyboardMacroReport:
|
||||||
rpcCancelKeyboardMacro()
|
rpcCancelKeyboardMacro()
|
||||||
return
|
return
|
||||||
|
|
||||||
|
case hidrpc.TypeCancelKeyboardMacroByTokenReport:
|
||||||
|
token, err := message.KeyboardMacroToken()
|
||||||
|
if err != nil {
|
||||||
|
logger.Warn().Err(err).Msg("failed to get keyboard macro token")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
rpcCancelKeyboardMacroByToken(token)
|
||||||
|
return
|
||||||
|
|
||||||
case hidrpc.TypeKeypressKeepAliveReport:
|
case hidrpc.TypeKeypressKeepAliveReport:
|
||||||
rpcErr = handleHidRPCKeypressKeepAlive(session)
|
rpcErr = handleHidRPCKeypressKeepAlive(session)
|
||||||
|
|
||||||
case hidrpc.TypePointerReport:
|
case hidrpc.TypePointerReport:
|
||||||
pointerReport, err := message.PointerReport()
|
pointerReport, err := message.PointerReport()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
@ -47,6 +72,7 @@ func handleHidRPCMessage(message hidrpc.Message, session *Session) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
rpcErr = rpcAbsMouseReport(pointerReport.X, pointerReport.Y, pointerReport.Button)
|
rpcErr = rpcAbsMouseReport(pointerReport.X, pointerReport.Y, pointerReport.Button)
|
||||||
|
|
||||||
case hidrpc.TypeMouseReport:
|
case hidrpc.TypeMouseReport:
|
||||||
mouseReport, err := message.MouseReport()
|
mouseReport, err := message.MouseReport()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
@ -54,6 +80,7 @@ func handleHidRPCMessage(message hidrpc.Message, session *Session) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
rpcErr = rpcRelMouseReport(mouseReport.DX, mouseReport.DY, mouseReport.Button)
|
rpcErr = rpcRelMouseReport(mouseReport.DX, mouseReport.DY, mouseReport.Button)
|
||||||
|
|
||||||
default:
|
default:
|
||||||
logger.Warn().Uint8("type", uint8(message.Type())).Msg("unknown HID RPC message type")
|
logger.Warn().Uint8("type", uint8(message.Type())).Msg("unknown HID RPC message type")
|
||||||
}
|
}
|
||||||
|
|
@ -98,7 +125,7 @@ func onHidMessage(msg hidQueueMessage, session *Session) {
|
||||||
r <- nil
|
r <- nil
|
||||||
}()
|
}()
|
||||||
select {
|
select {
|
||||||
case <-time.After(30 * time.Second):
|
case <-time.After(1 * time.Second):
|
||||||
scopedLogger.Warn().Msg("HID RPC message timed out")
|
scopedLogger.Warn().Msg("HID RPC message timed out")
|
||||||
case <-r:
|
case <-r:
|
||||||
scopedLogger.Debug().Dur("duration", time.Since(t)).Msg("HID RPC message handled")
|
scopedLogger.Debug().Dur("duration", time.Since(t)).Msg("HID RPC message handled")
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@ package hidrpc
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/google/uuid"
|
||||||
"github.com/jetkvm/kvm/internal/usbgadget"
|
"github.com/jetkvm/kvm/internal/usbgadget"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -10,38 +11,46 @@ import (
|
||||||
type MessageType byte
|
type MessageType byte
|
||||||
|
|
||||||
const (
|
const (
|
||||||
TypeHandshake MessageType = 0x01
|
TypeHandshake MessageType = 0x01
|
||||||
TypeKeyboardReport MessageType = 0x02
|
TypeKeyboardReport MessageType = 0x02
|
||||||
TypePointerReport MessageType = 0x03
|
TypePointerReport MessageType = 0x03
|
||||||
TypeWheelReport MessageType = 0x04
|
TypeWheelReport MessageType = 0x04
|
||||||
TypeKeypressReport MessageType = 0x05
|
TypeKeypressReport MessageType = 0x05
|
||||||
TypeKeypressKeepAliveReport MessageType = 0x09
|
TypeKeypressKeepAliveReport MessageType = 0x09
|
||||||
TypeMouseReport MessageType = 0x06
|
TypeMouseReport MessageType = 0x06
|
||||||
TypeKeyboardMacroReport MessageType = 0x07
|
TypeKeyboardMacroReport MessageType = 0x07
|
||||||
TypeCancelKeyboardMacroReport MessageType = 0x08
|
TypeCancelKeyboardMacroReport MessageType = 0x08
|
||||||
TypeKeyboardLedState MessageType = 0x32
|
TypeKeyboardLedState MessageType = 0x32
|
||||||
TypeKeydownState MessageType = 0x33
|
TypeKeydownState MessageType = 0x33
|
||||||
TypeKeyboardMacroState MessageType = 0x34
|
TypeKeyboardMacroState MessageType = 0x34
|
||||||
|
TypeCancelKeyboardMacroByTokenReport MessageType = 0x35
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type QueueIndex int
|
||||||
|
|
||||||
const (
|
const (
|
||||||
Version byte = 0x01 // Version of the HID RPC protocol
|
Version byte = 0x01 // Version of the HID RPC protocol
|
||||||
|
HandshakeQueue int = 0 // Queue index for handshake messages
|
||||||
|
KeyboardQueue int = 1 // Queue index for keyboard and macro messages
|
||||||
|
MouseQueue int = 2 // Queue index for mouse messages
|
||||||
|
MacroQueue int = 3 // Queue index for macro cancel messages
|
||||||
|
OtherQueue int = 4 // Queue index for other messages
|
||||||
)
|
)
|
||||||
|
|
||||||
// GetQueueIndex returns the index of the queue to which the message should be enqueued.
|
// GetQueueIndex returns the index of the queue to which the message should be enqueued.
|
||||||
func GetQueueIndex(messageType MessageType) int {
|
func GetQueueIndex(messageType MessageType) int {
|
||||||
switch messageType {
|
switch messageType {
|
||||||
case TypeHandshake:
|
case TypeHandshake:
|
||||||
return 0
|
return HandshakeQueue
|
||||||
case TypeKeyboardReport, TypeKeypressReport, TypeKeyboardMacroReport, TypeKeyboardLedState, TypeKeydownState, TypeKeyboardMacroState:
|
case TypeKeyboardReport, TypeKeypressReport, TypeKeyboardLedState, TypeKeydownState, TypeKeyboardMacroState:
|
||||||
return 1
|
return KeyboardQueue
|
||||||
case TypePointerReport, TypeMouseReport, TypeWheelReport:
|
case TypePointerReport, TypeMouseReport, TypeWheelReport:
|
||||||
return 2
|
return MouseQueue
|
||||||
// we don't want to block the queue for this message
|
// we don't want to block the queue for these messages
|
||||||
case TypeCancelKeyboardMacroReport:
|
case TypeKeyboardMacroReport, TypeCancelKeyboardMacroReport, TypeCancelKeyboardMacroByTokenReport:
|
||||||
return 3
|
return MacroQueue
|
||||||
default:
|
default:
|
||||||
return 3
|
return OtherQueue
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -121,3 +130,13 @@ func NewKeyboardMacroStateMessage(state bool, isPaste bool) *Message {
|
||||||
d: data,
|
d: data,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// NewKeyboardMacroTokenMessage creates a new keyboard macro token message.
|
||||||
|
func NewKeyboardMacroTokenMessage(token uuid.UUID) *Message {
|
||||||
|
data, _ := token.MarshalBinary()
|
||||||
|
|
||||||
|
return &Message{
|
||||||
|
t: TypeKeyboardMacroState,
|
||||||
|
d: data,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,8 @@ package hidrpc
|
||||||
import (
|
import (
|
||||||
"encoding/binary"
|
"encoding/binary"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/google/uuid"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Message ..
|
// Message ..
|
||||||
|
|
@ -23,6 +25,9 @@ func (m *Message) Type() MessageType {
|
||||||
func (m *Message) String() string {
|
func (m *Message) String() string {
|
||||||
switch m.t {
|
switch m.t {
|
||||||
case TypeHandshake:
|
case TypeHandshake:
|
||||||
|
if len(m.d) != 0 {
|
||||||
|
return fmt.Sprintf("Handshake{Malformed: %v}", m.d)
|
||||||
|
}
|
||||||
return "Handshake"
|
return "Handshake"
|
||||||
case TypeKeypressReport:
|
case TypeKeypressReport:
|
||||||
if len(m.d) < 2 {
|
if len(m.d) < 2 {
|
||||||
|
|
@ -45,12 +50,45 @@ func (m *Message) String() string {
|
||||||
}
|
}
|
||||||
return fmt.Sprintf("MouseReport{DX: %d, DY: %d, Button: %d}", m.d[0], m.d[1], m.d[2])
|
return fmt.Sprintf("MouseReport{DX: %d, DY: %d, Button: %d}", m.d[0], m.d[1], m.d[2])
|
||||||
case TypeKeypressKeepAliveReport:
|
case TypeKeypressKeepAliveReport:
|
||||||
|
if len(m.d) != 0 {
|
||||||
|
return fmt.Sprintf("KeypressKeepAliveReport{Malformed: %v}", m.d)
|
||||||
|
}
|
||||||
return "KeypressKeepAliveReport"
|
return "KeypressKeepAliveReport"
|
||||||
|
case TypeWheelReport:
|
||||||
|
if len(m.d) < 3 {
|
||||||
|
return fmt.Sprintf("WheelReport{Malformed: %v}", m.d)
|
||||||
|
}
|
||||||
|
return fmt.Sprintf("WheelReport{Vertical: %d, Horizontal: %d}", int8(m.d[0]), int8(m.d[1]))
|
||||||
case TypeKeyboardMacroReport:
|
case TypeKeyboardMacroReport:
|
||||||
if len(m.d) < 5 {
|
if len(m.d) < 5 {
|
||||||
return fmt.Sprintf("KeyboardMacroReport{Malformed: %v}", m.d)
|
return fmt.Sprintf("KeyboardMacroReport{Malformed: %v}", m.d)
|
||||||
}
|
}
|
||||||
return fmt.Sprintf("KeyboardMacroReport{IsPaste: %v, Length: %d}", m.d[0] == uint8(1), binary.BigEndian.Uint32(m.d[1:5]))
|
return fmt.Sprintf("KeyboardMacroReport{IsPaste: %v, Length: %d}", m.d[0] == uint8(1), binary.BigEndian.Uint32(m.d[1:5]))
|
||||||
|
case TypeCancelKeyboardMacroReport:
|
||||||
|
if len(m.d) != 0 {
|
||||||
|
return fmt.Sprintf("CancelKeyboardMacroReport{Malformed: %v}", m.d)
|
||||||
|
}
|
||||||
|
return "CancelKeyboardMacroReport"
|
||||||
|
case TypeCancelKeyboardMacroByTokenReport:
|
||||||
|
if len(m.d) != 16 {
|
||||||
|
return fmt.Sprintf("CancelKeyboardMacroByTokenReport{Malformed: %v}", m.d)
|
||||||
|
}
|
||||||
|
return fmt.Sprintf("CancelKeyboardMacroByTokenReport{Token: %s}", uuid.Must(uuid.FromBytes(m.d)).String())
|
||||||
|
case TypeKeyboardLedState:
|
||||||
|
if len(m.d) < 1 {
|
||||||
|
return fmt.Sprintf("KeyboardLedState{Malformed: %v}", m.d)
|
||||||
|
}
|
||||||
|
return fmt.Sprintf("KeyboardLedState{State: %d}", m.d[0])
|
||||||
|
case TypeKeydownState:
|
||||||
|
if len(m.d) < 1 {
|
||||||
|
return fmt.Sprintf("KeydownState{Malformed: %v}", m.d)
|
||||||
|
}
|
||||||
|
return fmt.Sprintf("KeydownState{State: %d}", m.d[0])
|
||||||
|
case TypeKeyboardMacroState:
|
||||||
|
if len(m.d) < 2 {
|
||||||
|
return fmt.Sprintf("KeyboardMacroState{Malformed: %v}", m.d)
|
||||||
|
}
|
||||||
|
return fmt.Sprintf("KeyboardMacroState{State: %v, IsPaste: %v}", m.d[0] == uint8(1), m.d[1] == uint8(1))
|
||||||
default:
|
default:
|
||||||
return fmt.Sprintf("Unknown{Type: %d, Data: %v}", m.t, m.d)
|
return fmt.Sprintf("Unknown{Type: %d, Data: %v}", m.t, m.d)
|
||||||
}
|
}
|
||||||
|
|
@ -67,7 +105,9 @@ func (m *Message) KeypressReport() (KeypressReport, error) {
|
||||||
if m.t != TypeKeypressReport {
|
if m.t != TypeKeypressReport {
|
||||||
return KeypressReport{}, fmt.Errorf("invalid message type: %d", m.t)
|
return KeypressReport{}, fmt.Errorf("invalid message type: %d", m.t)
|
||||||
}
|
}
|
||||||
|
if len(m.d) < 2 {
|
||||||
|
return KeypressReport{}, fmt.Errorf("invalid message data length: %d", len(m.d))
|
||||||
|
}
|
||||||
return KeypressReport{
|
return KeypressReport{
|
||||||
Key: m.d[0],
|
Key: m.d[0],
|
||||||
Press: m.d[1] == uint8(1),
|
Press: m.d[1] == uint8(1),
|
||||||
|
|
@ -95,7 +135,7 @@ func (m *Message) KeyboardReport() (KeyboardReport, error) {
|
||||||
// Macro ..
|
// Macro ..
|
||||||
type KeyboardMacroStep struct {
|
type KeyboardMacroStep struct {
|
||||||
Modifier byte // 1 byte
|
Modifier byte // 1 byte
|
||||||
Keys []byte // 6 bytes: hidKeyBufferSize
|
Keys []byte // 6 bytes: HidKeyBufferSize
|
||||||
Delay uint16 // 2 bytes
|
Delay uint16 // 2 bytes
|
||||||
}
|
}
|
||||||
type KeyboardMacroReport struct {
|
type KeyboardMacroReport struct {
|
||||||
|
|
@ -105,7 +145,7 @@ type KeyboardMacroReport struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
// HidKeyBufferSize is the size of the keys buffer in the keyboard report.
|
// HidKeyBufferSize is the size of the keys buffer in the keyboard report.
|
||||||
const HidKeyBufferSize = 6
|
const HidKeyBufferSize int = 6
|
||||||
|
|
||||||
// KeyboardMacroReport returns the keyboard macro report from the message.
|
// KeyboardMacroReport returns the keyboard macro report from the message.
|
||||||
func (m *Message) KeyboardMacroReport() (KeyboardMacroReport, error) {
|
func (m *Message) KeyboardMacroReport() (KeyboardMacroReport, error) {
|
||||||
|
|
@ -205,3 +245,20 @@ func (m *Message) KeyboardMacroState() (KeyboardMacroState, error) {
|
||||||
IsPaste: m.d[1] == uint8(1),
|
IsPaste: m.d[1] == uint8(1),
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// KeyboardMacroToken returns the keyboard macro token UUID from the message.
|
||||||
|
func (m *Message) KeyboardMacroToken() (uuid.UUID, error) {
|
||||||
|
if m.t != TypeCancelKeyboardMacroByTokenReport {
|
||||||
|
return uuid.Nil, fmt.Errorf("invalid message type: %d", m.t)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(m.d) == 0 {
|
||||||
|
return uuid.Nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(m.d) != 16 {
|
||||||
|
return uuid.Nil, fmt.Errorf("invalid UUID length: %d", len(m.d))
|
||||||
|
}
|
||||||
|
|
||||||
|
return uuid.FromBytes(m.d)
|
||||||
|
}
|
||||||
|
|
|
||||||
134
jsonrpc.go
134
jsonrpc.go
|
|
@ -1,7 +1,6 @@
|
||||||
package kvm
|
package kvm
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
|
||||||
"context"
|
"context"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
|
|
@ -14,6 +13,7 @@ import (
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/google/uuid"
|
||||||
"github.com/pion/webrtc/v4"
|
"github.com/pion/webrtc/v4"
|
||||||
"github.com/rs/zerolog"
|
"github.com/rs/zerolog"
|
||||||
"go.bug.st/serial"
|
"go.bug.st/serial"
|
||||||
|
|
@ -1070,68 +1070,121 @@ func rpcSetLocalLoopbackOnly(enabled bool) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type RunningMacro struct {
|
||||||
|
cancel context.CancelFunc
|
||||||
|
isPaste bool
|
||||||
|
}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
keyboardMacroCancel context.CancelFunc
|
keyboardMacroCancelMap map[uuid.UUID]RunningMacro
|
||||||
keyboardMacroLock sync.Mutex
|
keyboardMacroLock sync.Mutex
|
||||||
)
|
)
|
||||||
|
|
||||||
// cancelKeyboardMacro cancels any ongoing keyboard macro execution
|
func init() {
|
||||||
func cancelKeyboardMacro() {
|
keyboardMacroCancelMap = make(map[uuid.UUID]RunningMacro)
|
||||||
|
}
|
||||||
|
|
||||||
|
func addKeyboardMacro(isPaste bool, cancel context.CancelFunc) uuid.UUID {
|
||||||
keyboardMacroLock.Lock()
|
keyboardMacroLock.Lock()
|
||||||
defer keyboardMacroLock.Unlock()
|
defer keyboardMacroLock.Unlock()
|
||||||
|
|
||||||
if keyboardMacroCancel != nil {
|
token := uuid.New() // Generate a unique token
|
||||||
keyboardMacroCancel()
|
keyboardMacroCancelMap[token] = RunningMacro{
|
||||||
logger.Info().Msg("canceled keyboard macro")
|
isPaste: isPaste,
|
||||||
keyboardMacroCancel = nil
|
cancel: cancel,
|
||||||
|
}
|
||||||
|
return token
|
||||||
|
}
|
||||||
|
|
||||||
|
func removeRunningKeyboardMacro(token uuid.UUID) {
|
||||||
|
keyboardMacroLock.Lock()
|
||||||
|
defer keyboardMacroLock.Unlock()
|
||||||
|
|
||||||
|
delete(keyboardMacroCancelMap, token)
|
||||||
|
}
|
||||||
|
|
||||||
|
func cancelRunningKeyboardMacro(token uuid.UUID) {
|
||||||
|
keyboardMacroLock.Lock()
|
||||||
|
defer keyboardMacroLock.Unlock()
|
||||||
|
|
||||||
|
if runningMacro, exists := keyboardMacroCancelMap[token]; exists {
|
||||||
|
runningMacro.cancel()
|
||||||
|
delete(keyboardMacroCancelMap, token)
|
||||||
|
logger.Info().Interface("token", token).Msg("canceled keyboard macro by token")
|
||||||
|
} else {
|
||||||
|
logger.Debug().Interface("token", token).Msg("no running keyboard macro found for token")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func setKeyboardMacroCancel(cancel context.CancelFunc) {
|
func cancelAllRunningKeyboardMacros() {
|
||||||
keyboardMacroLock.Lock()
|
keyboardMacroLock.Lock()
|
||||||
defer keyboardMacroLock.Unlock()
|
defer keyboardMacroLock.Unlock()
|
||||||
|
|
||||||
keyboardMacroCancel = cancel
|
for token, runningMacro := range keyboardMacroCancelMap {
|
||||||
|
runningMacro.cancel()
|
||||||
|
delete(keyboardMacroCancelMap, token)
|
||||||
|
logger.Info().Interface("token", token).Msg("cancelled keyboard macro")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func rpcExecuteKeyboardMacro(macro []hidrpc.KeyboardMacroStep) error {
|
func reportRunningMacrosState() {
|
||||||
cancelKeyboardMacro()
|
if currentSession != nil {
|
||||||
|
keyboardMacroLock.Lock()
|
||||||
|
defer keyboardMacroLock.Unlock()
|
||||||
|
|
||||||
|
isPaste := false
|
||||||
|
anyRunning := false
|
||||||
|
for _, runningMacro := range keyboardMacroCancelMap {
|
||||||
|
anyRunning = true
|
||||||
|
if runningMacro.isPaste {
|
||||||
|
isPaste = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
state := hidrpc.KeyboardMacroState{
|
||||||
|
State: anyRunning,
|
||||||
|
IsPaste: isPaste,
|
||||||
|
}
|
||||||
|
|
||||||
|
currentSession.reportHidRPCKeyboardMacroState(state)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func rpcExecuteKeyboardMacro(isPaste bool, macro []hidrpc.KeyboardMacroStep) uuid.UUID {
|
||||||
ctx, cancel := context.WithCancel(context.Background())
|
ctx, cancel := context.WithCancel(context.Background())
|
||||||
setKeyboardMacroCancel(cancel)
|
token := addKeyboardMacro(isPaste, cancel)
|
||||||
|
reportRunningMacrosState()
|
||||||
|
|
||||||
s := hidrpc.KeyboardMacroState{
|
go func() {
|
||||||
State: true,
|
defer reportRunningMacrosState() // this executes last, so the map is already updated
|
||||||
IsPaste: true,
|
defer removeRunningKeyboardMacro(token) // this executes first, to update the map
|
||||||
}
|
|
||||||
|
|
||||||
if currentSession != nil {
|
err := executeKeyboardMacro(ctx, isPaste, macro)
|
||||||
currentSession.reportHidRPCKeyboardMacroState(s)
|
if err != nil {
|
||||||
}
|
logger.Error().Err(err).Interface("token", token).Bool("isPaste", isPaste).Msg("keyboard macro execution failed")
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
err := rpcDoExecuteKeyboardMacro(ctx, macro)
|
return token
|
||||||
|
|
||||||
setKeyboardMacroCancel(nil)
|
|
||||||
|
|
||||||
s.State = false
|
|
||||||
if currentSession != nil {
|
|
||||||
currentSession.reportHidRPCKeyboardMacroState(s)
|
|
||||||
}
|
|
||||||
|
|
||||||
return err
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func rpcCancelKeyboardMacro() {
|
func rpcCancelKeyboardMacro() {
|
||||||
cancelKeyboardMacro()
|
defer reportRunningMacrosState()
|
||||||
|
cancelAllRunningKeyboardMacros()
|
||||||
}
|
}
|
||||||
|
|
||||||
var keyboardClearStateKeys = make([]byte, hidrpc.HidKeyBufferSize)
|
func rpcCancelKeyboardMacroByToken(token uuid.UUID) {
|
||||||
|
defer reportRunningMacrosState()
|
||||||
|
|
||||||
func isClearKeyStep(step hidrpc.KeyboardMacroStep) bool {
|
if token == uuid.Nil {
|
||||||
return step.Modifier == 0 && bytes.Equal(step.Keys, keyboardClearStateKeys)
|
cancelAllRunningKeyboardMacros()
|
||||||
|
} else {
|
||||||
|
cancelRunningKeyboardMacro(token)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func rpcDoExecuteKeyboardMacro(ctx context.Context, macro []hidrpc.KeyboardMacroStep) error {
|
func executeKeyboardMacro(ctx context.Context, isPaste bool, macro []hidrpc.KeyboardMacroStep) error {
|
||||||
logger.Debug().Int("macro_steps", len(macro)).Msg("Executing keyboard macro")
|
logger.Debug().Int("macro_steps", len(macro)).Msg("Executing keyboard macro")
|
||||||
|
|
||||||
// don't report keyboard state changes while executing the macro
|
// don't report keyboard state changes while executing the macro
|
||||||
|
|
@ -1143,15 +1196,10 @@ func rpcDoExecuteKeyboardMacro(ctx context.Context, macro []hidrpc.KeyboardMacro
|
||||||
|
|
||||||
err := rpcKeyboardReport(step.Modifier, step.Keys)
|
err := rpcKeyboardReport(step.Modifier, step.Keys)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Warn().Err(err).Msg("failed to execute keyboard macro")
|
logger.Warn().Err(err).Int("step", i).Msg("failed to execute keyboard macro")
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// notify the device that the keyboard state is being cleared
|
|
||||||
if isClearKeyStep(step) {
|
|
||||||
gadget.UpdateKeysDown(0, keyboardClearStateKeys)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Use context-aware sleep that can be cancelled
|
// Use context-aware sleep that can be cancelled
|
||||||
select {
|
select {
|
||||||
case <-time.After(delay):
|
case <-time.After(delay):
|
||||||
|
|
@ -1159,7 +1207,7 @@ func rpcDoExecuteKeyboardMacro(ctx context.Context, macro []hidrpc.KeyboardMacro
|
||||||
case <-ctx.Done():
|
case <-ctx.Done():
|
||||||
// make sure keyboard state is reset and the client gets notified
|
// make sure keyboard state is reset and the client gets notified
|
||||||
gadget.ResumeSuspendKeyDownMessages()
|
gadget.ResumeSuspendKeyDownMessages()
|
||||||
err := rpcKeyboardReport(0, keyboardClearStateKeys)
|
err := rpcKeyboardReport(0, make([]byte, hidrpc.HidKeyBufferSize))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Warn().Err(err).Msg("failed to reset keyboard state")
|
logger.Warn().Err(err).Msg("failed to reset keyboard state")
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -37,6 +37,7 @@
|
||||||
"recharts": "^3.2.1",
|
"recharts": "^3.2.1",
|
||||||
"tailwind-merge": "^3.3.1",
|
"tailwind-merge": "^3.3.1",
|
||||||
"usehooks-ts": "^3.1.1",
|
"usehooks-ts": "^3.1.1",
|
||||||
|
"uuid": "^13.0.0",
|
||||||
"validator": "^13.15.15",
|
"validator": "^13.15.15",
|
||||||
"zustand": "^4.5.2"
|
"zustand": "^4.5.2"
|
||||||
},
|
},
|
||||||
|
|
@ -6880,6 +6881,19 @@
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/uuid": {
|
||||||
|
"version": "13.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/uuid/-/uuid-13.0.0.tgz",
|
||||||
|
"integrity": "sha512-XQegIaBTVUjSHliKqcnFqYypAd4S+WCYt5NIeRs6w/UAry7z8Y9j5ZwRRL4kzq9U3sD6v+85er9FvkEaBpji2w==",
|
||||||
|
"funding": [
|
||||||
|
"https://github.com/sponsors/broofa",
|
||||||
|
"https://github.com/sponsors/ctavan"
|
||||||
|
],
|
||||||
|
"license": "MIT",
|
||||||
|
"bin": {
|
||||||
|
"uuid": "dist-node/bin/uuid"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/validator": {
|
"node_modules/validator": {
|
||||||
"version": "13.15.15",
|
"version": "13.15.15",
|
||||||
"resolved": "https://registry.npmjs.org/validator/-/validator-13.15.15.tgz",
|
"resolved": "https://registry.npmjs.org/validator/-/validator-13.15.15.tgz",
|
||||||
|
|
|
||||||
|
|
@ -48,6 +48,7 @@
|
||||||
"recharts": "^3.2.1",
|
"recharts": "^3.2.1",
|
||||||
"tailwind-merge": "^3.3.1",
|
"tailwind-merge": "^3.3.1",
|
||||||
"usehooks-ts": "^3.1.1",
|
"usehooks-ts": "^3.1.1",
|
||||||
|
"uuid": "^13.0.0",
|
||||||
"validator": "^13.15.15",
|
"validator": "^13.15.15",
|
||||||
"zustand": "^4.5.2"
|
"zustand": "^4.5.2"
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,5 @@
|
||||||
|
import { parse as uuidParse , stringify as uuidStringify } from "uuid";
|
||||||
|
|
||||||
import { hidKeyBufferSize, KeyboardLedState, KeysDownState } from "./stores";
|
import { hidKeyBufferSize, KeyboardLedState, KeysDownState } from "./stores";
|
||||||
|
|
||||||
export const HID_RPC_MESSAGE_TYPES = {
|
export const HID_RPC_MESSAGE_TYPES = {
|
||||||
|
|
@ -13,6 +15,7 @@ export const HID_RPC_MESSAGE_TYPES = {
|
||||||
KeyboardLedState: 0x32,
|
KeyboardLedState: 0x32,
|
||||||
KeysDownState: 0x33,
|
KeysDownState: 0x33,
|
||||||
KeyboardMacroState: 0x34,
|
KeyboardMacroState: 0x34,
|
||||||
|
CancelKeyboardMacroByTokenReport: 0x35,
|
||||||
}
|
}
|
||||||
|
|
||||||
export type HidRpcMessageType = typeof HID_RPC_MESSAGE_TYPES[keyof typeof HID_RPC_MESSAGE_TYPES];
|
export type HidRpcMessageType = typeof HID_RPC_MESSAGE_TYPES[keyof typeof HID_RPC_MESSAGE_TYPES];
|
||||||
|
|
@ -299,7 +302,7 @@ export class KeyboardMacroStateMessage extends RpcMessage {
|
||||||
}
|
}
|
||||||
|
|
||||||
public static unmarshal(data: Uint8Array): KeyboardMacroStateMessage | undefined {
|
public static unmarshal(data: Uint8Array): KeyboardMacroStateMessage | undefined {
|
||||||
if (data.length < 1) {
|
if (data.length < 2) {
|
||||||
throw new Error(`Invalid keyboard macro state report message length: ${data.length}`);
|
throw new Error(`Invalid keyboard macro state report message length: ${data.length}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -378,13 +381,30 @@ export class PointerReportMessage extends RpcMessage {
|
||||||
}
|
}
|
||||||
|
|
||||||
export class CancelKeyboardMacroReportMessage extends RpcMessage {
|
export class CancelKeyboardMacroReportMessage extends RpcMessage {
|
||||||
|
token: string;
|
||||||
|
|
||||||
constructor() {
|
constructor(token: string) {
|
||||||
super(HID_RPC_MESSAGE_TYPES.CancelKeyboardMacroReport);
|
super(HID_RPC_MESSAGE_TYPES.CancelKeyboardMacroReport);
|
||||||
|
this.token = (token == null || token === undefined || token === "")
|
||||||
|
? "00000000-0000-0000-0000-000000000000"
|
||||||
|
: token;
|
||||||
}
|
}
|
||||||
|
|
||||||
marshal(): Uint8Array {
|
marshal(): Uint8Array {
|
||||||
return new Uint8Array([this.messageType]);
|
const tokenBytes = uuidParse(this.token);
|
||||||
|
return new Uint8Array([this.messageType, ...tokenBytes]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static unmarshal(data: Uint8Array): CancelKeyboardMacroReportMessage | undefined {
|
||||||
|
if (data.length == 0) {
|
||||||
|
return new CancelKeyboardMacroReportMessage("00000000-0000-0000-0000-000000000000");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (data.length != 16) {
|
||||||
|
throw new Error(`Invalid cancel message length: ${data.length}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
return new CancelKeyboardMacroReportMessage(uuidStringify(data.slice(0, 16)));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -430,6 +450,7 @@ export const messageRegistry = {
|
||||||
[HID_RPC_MESSAGE_TYPES.CancelKeyboardMacroReport]: CancelKeyboardMacroReportMessage,
|
[HID_RPC_MESSAGE_TYPES.CancelKeyboardMacroReport]: CancelKeyboardMacroReportMessage,
|
||||||
[HID_RPC_MESSAGE_TYPES.KeyboardMacroState]: KeyboardMacroStateMessage,
|
[HID_RPC_MESSAGE_TYPES.KeyboardMacroState]: KeyboardMacroStateMessage,
|
||||||
[HID_RPC_MESSAGE_TYPES.KeypressKeepAliveReport]: KeypressKeepAliveMessage,
|
[HID_RPC_MESSAGE_TYPES.KeypressKeepAliveReport]: KeypressKeepAliveMessage,
|
||||||
|
[HID_RPC_MESSAGE_TYPES.CancelKeyboardMacroByTokenReport]: CancelKeyboardMacroReportMessage,
|
||||||
}
|
}
|
||||||
|
|
||||||
export const unmarshalHidRpcMessage = (data: Uint8Array): RpcMessage | undefined => {
|
export const unmarshalHidRpcMessage = (data: Uint8Array): RpcMessage | undefined => {
|
||||||
|
|
|
||||||
|
|
@ -142,7 +142,7 @@ export function useHidRpc(onHidRpcMessage?: (payload: RpcMessage) => void) {
|
||||||
|
|
||||||
const cancelOngoingKeyboardMacro = useCallback(
|
const cancelOngoingKeyboardMacro = useCallback(
|
||||||
() => {
|
() => {
|
||||||
sendMessage(new CancelKeyboardMacroReportMessage());
|
sendMessage(new CancelKeyboardMacroReportMessage(""));
|
||||||
},
|
},
|
||||||
[sendMessage],
|
[sendMessage],
|
||||||
);
|
);
|
||||||
|
|
|
||||||
2
web.go
2
web.go
|
|
@ -230,7 +230,7 @@ func handleWebRTCSession(c *gin.Context) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Cancel any ongoing keyboard macro when session changes
|
// Cancel any ongoing keyboard macro when session changes
|
||||||
cancelKeyboardMacro()
|
cancelAllRunningKeyboardMacros()
|
||||||
|
|
||||||
currentSession = session
|
currentSession = session
|
||||||
c.JSON(http.StatusOK, gin.H{"sd": sd})
|
c.JSON(http.StatusOK, gin.H{"sd": sd})
|
||||||
|
|
|
||||||
|
|
@ -98,7 +98,7 @@ func (s *Session) initQueues() {
|
||||||
defer s.hidQueueLock.Unlock()
|
defer s.hidQueueLock.Unlock()
|
||||||
|
|
||||||
s.hidQueue = make([]chan hidQueueMessage, 0)
|
s.hidQueue = make([]chan hidQueueMessage, 0)
|
||||||
for i := 0; i < 4; i++ {
|
for i := 0; i <= hidrpc.OtherQueue; i++ {
|
||||||
q := make(chan hidQueueMessage, 256)
|
q := make(chan hidQueueMessage, 256)
|
||||||
s.hidQueue = append(s.hidQueue, q)
|
s.hidQueue = append(s.hidQueue, q)
|
||||||
}
|
}
|
||||||
|
|
@ -163,7 +163,7 @@ func getOnHidMessageHandler(session *Session, scopedLogger *zerolog.Logger, chan
|
||||||
queueIndex := hidrpc.GetQueueIndex(hidrpc.MessageType(msg.Data[0]))
|
queueIndex := hidrpc.GetQueueIndex(hidrpc.MessageType(msg.Data[0]))
|
||||||
if queueIndex >= len(session.hidQueue) || queueIndex < 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")
|
l.Warn().Int("queueIndex", queueIndex).Msg("received data in HID RPC message handler, but queue index not found")
|
||||||
queueIndex = 3
|
queueIndex = hidrpc.OtherQueue
|
||||||
}
|
}
|
||||||
|
|
||||||
queue := session.hidQueue[queueIndex]
|
queue := session.hidQueue[queueIndex]
|
||||||
|
|
@ -328,7 +328,7 @@ func newSession(config SessionConfig) (*Session, error) {
|
||||||
scopedLogger.Debug().Msg("ICE Connection State is closed, unmounting virtual media")
|
scopedLogger.Debug().Msg("ICE Connection State is closed, unmounting virtual media")
|
||||||
if session == currentSession {
|
if session == currentSession {
|
||||||
// Cancel any ongoing keyboard report multi when session closes
|
// Cancel any ongoing keyboard report multi when session closes
|
||||||
cancelKeyboardMacro()
|
cancelAllRunningKeyboardMacros()
|
||||||
currentSession = nil
|
currentSession = nil
|
||||||
}
|
}
|
||||||
// Stop RPC processor
|
// Stop RPC processor
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue