mirror of https://github.com/jetkvm/kvm.git
Compare commits
2 Commits
5ec9716877
...
efdd72ce87
| Author | SHA1 | Date |
|---|---|---|
|
|
efdd72ce87 | |
|
|
854cdc8e82 |
|
|
@ -1,7 +1,6 @@
|
||||||
package kvm
|
package kvm
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
|
|
@ -32,13 +31,6 @@ func handleHidRPCMessage(message hidrpc.Message, session *Session) {
|
||||||
session.reportHidRPCKeysDownState(*keysDownState)
|
session.reportHidRPCKeysDownState(*keysDownState)
|
||||||
}
|
}
|
||||||
rpcErr = err
|
rpcErr = err
|
||||||
case hidrpc.TypeKeyboardMacroReport:
|
|
||||||
keyboardMacroReport, err := message.KeyboardMacroReport()
|
|
||||||
if err != nil {
|
|
||||||
logger.Warn().Err(err).Msg("failed to get keyboard macro report")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
_, rpcErr = rpcKeyboardReportMulti(context.Background(), keyboardMacroReport.Macro)
|
|
||||||
case hidrpc.TypePointerReport:
|
case hidrpc.TypePointerReport:
|
||||||
pointerReport, err := message.PointerReport()
|
pointerReport, err := message.PointerReport()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
||||||
|
|
@ -10,17 +10,14 @@ 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
|
||||||
TypeMouseReport MessageType = 0x06
|
TypeMouseReport MessageType = 0x06
|
||||||
TypeKeyboardMacroReport MessageType = 0x07
|
TypeKeyboardLedState MessageType = 0x32
|
||||||
TypeCancelKeyboardMacroReport MessageType = 0x08
|
TypeKeydownState MessageType = 0x33
|
||||||
TypeKeyboardLedState MessageType = 0x32
|
|
||||||
TypeKeydownState MessageType = 0x33
|
|
||||||
TypeKeyboardMacroStateReport MessageType = 0x34
|
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
|
@ -32,7 +29,7 @@ func GetQueueIndex(messageType MessageType) int {
|
||||||
switch messageType {
|
switch messageType {
|
||||||
case TypeHandshake:
|
case TypeHandshake:
|
||||||
return 0
|
return 0
|
||||||
case TypeKeyboardReport, TypeKeypressReport, TypeKeyboardLedState, TypeKeydownState, TypeKeyboardMacroStateReport:
|
case TypeKeyboardReport, TypeKeypressReport, TypeKeyboardLedState, TypeKeydownState:
|
||||||
return 1
|
return 1
|
||||||
case TypePointerReport, TypeMouseReport, TypeWheelReport:
|
case TypePointerReport, TypeMouseReport, TypeWheelReport:
|
||||||
return 2
|
return 2
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,6 @@
|
||||||
package hidrpc
|
package hidrpc
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/binary"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -44,11 +43,6 @@ func (m *Message) String() string {
|
||||||
return fmt.Sprintf("MouseReport{Malformed: %v}", m.d)
|
return fmt.Sprintf("MouseReport{Malformed: %v}", m.d)
|
||||||
}
|
}
|
||||||
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 TypeKeyboardMacroReport:
|
|
||||||
if len(m.d) < 5 {
|
|
||||||
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]))
|
|
||||||
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)
|
||||||
}
|
}
|
||||||
|
|
@ -90,51 +84,6 @@ func (m *Message) KeyboardReport() (KeyboardReport, error) {
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Macro ..
|
|
||||||
type KeyboardMacro struct {
|
|
||||||
Modifier byte // 1 byte
|
|
||||||
Keys []byte // 6 bytes, to make things easier, the keys length is fixed to 6
|
|
||||||
Delay uint16 // 2 bytes
|
|
||||||
}
|
|
||||||
type KeyboardMacroReport struct {
|
|
||||||
IsPaste bool
|
|
||||||
Length uint32
|
|
||||||
Macro []KeyboardMacro
|
|
||||||
}
|
|
||||||
|
|
||||||
// KeyboardMacroReport returns the keyboard macro report from the message.
|
|
||||||
func (m *Message) KeyboardMacroReport() (KeyboardMacroReport, error) {
|
|
||||||
if m.t != TypeKeyboardMacroReport {
|
|
||||||
return KeyboardMacroReport{}, fmt.Errorf("invalid message type: %d", m.t)
|
|
||||||
}
|
|
||||||
|
|
||||||
isPaste := m.d[0] == uint8(1)
|
|
||||||
length := binary.BigEndian.Uint32(m.d[1:5])
|
|
||||||
|
|
||||||
// check total length
|
|
||||||
expectedLength := int(length)*9 + 5
|
|
||||||
if len(m.d) != expectedLength {
|
|
||||||
return KeyboardMacroReport{}, fmt.Errorf("invalid length: %d, expected: %d", len(m.d), expectedLength)
|
|
||||||
}
|
|
||||||
|
|
||||||
macro := make([]KeyboardMacro, 0, int(length))
|
|
||||||
for i := 0; i < int(length); i++ {
|
|
||||||
offset := 5 + i*9
|
|
||||||
|
|
||||||
macro = append(macro, KeyboardMacro{
|
|
||||||
Modifier: m.d[offset],
|
|
||||||
Keys: m.d[offset+1 : offset+7],
|
|
||||||
Delay: binary.BigEndian.Uint16(m.d[offset+7 : offset+9]),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
return KeyboardMacroReport{
|
|
||||||
IsPaste: isPaste,
|
|
||||||
Macro: macro,
|
|
||||||
Length: length,
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// PointerReport ..
|
// PointerReport ..
|
||||||
type PointerReport struct {
|
type PointerReport struct {
|
||||||
X int
|
X int
|
||||||
|
|
|
||||||
284
jsonrpc.go
284
jsonrpc.go
|
|
@ -10,13 +10,13 @@ import (
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"reflect"
|
"reflect"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"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"
|
||||||
|
|
||||||
"github.com/jetkvm/kvm/internal/hidrpc"
|
|
||||||
"github.com/jetkvm/kvm/internal/usbgadget"
|
"github.com/jetkvm/kvm/internal/usbgadget"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -1050,56 +1050,52 @@ func rpcSetLocalLoopbackOnly(enabled bool) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// cancelKeyboardReportMulti cancels any ongoing keyboard report multi execution
|
||||||
func cancelKeyboardReportMulti() {
|
func cancelKeyboardReportMulti() {
|
||||||
|
keyboardReportMultiLock.Lock()
|
||||||
|
defer keyboardReportMultiLock.Unlock()
|
||||||
|
|
||||||
|
if keyboardReportMultiCancel != nil {
|
||||||
|
keyboardReportMultiCancel()
|
||||||
|
logger.Info().Msg("canceled keyboard report multi")
|
||||||
|
keyboardReportMultiCancel = nil
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// // cancelKeyboardReportMulti cancels any ongoing keyboard report multi execution
|
func setKeyboardReportMultiCancel(cancel context.CancelFunc) {
|
||||||
// func cancelKeyboardReportMulti() {
|
keyboardReportMultiLock.Lock()
|
||||||
// keyboardReportMultiLock.Lock()
|
defer keyboardReportMultiLock.Unlock()
|
||||||
// defer keyboardReportMultiLock.Unlock()
|
|
||||||
|
|
||||||
// if keyboardReportMultiCancel != nil {
|
keyboardReportMultiCancel = cancel
|
||||||
// keyboardReportMultiCancel()
|
}
|
||||||
// logger.Info().Msg("canceled keyboard report multi")
|
|
||||||
// keyboardReportMultiCancel = nil
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
// func setKeyboardReportMultiCancel(cancel context.CancelFunc) {
|
func rpcKeyboardReportMultiWrapper(macro []map[string]any) (usbgadget.KeysDownState, error) {
|
||||||
// keyboardReportMultiLock.Lock()
|
cancelKeyboardReportMulti()
|
||||||
// defer keyboardReportMultiLock.Unlock()
|
|
||||||
|
|
||||||
// keyboardReportMultiCancel = cancel
|
ctx, cancel := context.WithCancel(context.Background())
|
||||||
// }
|
setKeyboardReportMultiCancel(cancel)
|
||||||
|
|
||||||
// func rpcKeyboardReportMultiWrapper(macro []map[string]any) (usbgadget.KeysDownState, error) {
|
writeJSONRPCEvent("keyboardReportMultiState", true, currentSession)
|
||||||
// // cancelKeyboardReportMulti()
|
|
||||||
|
|
||||||
// // ctx, cancel := context.WithCancel(context.Background())
|
result, err := rpcKeyboardReportMulti(ctx, macro)
|
||||||
// // setKeyboardReportMultiCancel(cancel)
|
|
||||||
|
|
||||||
// // writeJSONRPCEvent("keyboardReportMultiState", true, currentSession)
|
setKeyboardReportMultiCancel(nil)
|
||||||
|
|
||||||
// // result, err := rpcKeyboardReportMulti(ctx, macro)
|
writeJSONRPCEvent("keyboardReportMultiState", false, currentSession)
|
||||||
|
|
||||||
// // setKeyboardReportMultiCancel(nil)
|
return result, err
|
||||||
|
}
|
||||||
|
|
||||||
// // writeJSONRPCEvent("keyboardReportMultiState", false, currentSession)
|
var (
|
||||||
|
keyboardReportMultiCancel context.CancelFunc
|
||||||
|
keyboardReportMultiLock sync.Mutex
|
||||||
|
)
|
||||||
|
|
||||||
// // return result, err
|
func rpcCancelKeyboardReportMulti() {
|
||||||
// }
|
cancelKeyboardReportMulti()
|
||||||
|
}
|
||||||
|
|
||||||
// var (
|
func rpcKeyboardReportMulti(ctx context.Context, macro []map[string]any) (usbgadget.KeysDownState, error) {
|
||||||
// keyboardReportMultiCancel context.CancelFunc
|
|
||||||
// keyboardReportMultiLock sync.Mutex
|
|
||||||
// )
|
|
||||||
|
|
||||||
// func rpcCancelKeyboardReportMulti() {
|
|
||||||
// cancelKeyboardReportMulti()
|
|
||||||
// }
|
|
||||||
|
|
||||||
func rpcKeyboardReportMulti(ctx context.Context, macro []hidrpc.KeyboardMacro) (usbgadget.KeysDownState, error) {
|
|
||||||
var last usbgadget.KeysDownState
|
var last usbgadget.KeysDownState
|
||||||
var err error
|
var err error
|
||||||
|
|
||||||
|
|
@ -1114,112 +1110,144 @@ func rpcKeyboardReportMulti(ctx context.Context, macro []hidrpc.KeyboardMacro) (
|
||||||
default:
|
default:
|
||||||
}
|
}
|
||||||
|
|
||||||
delay := time.Duration(step.Delay) * time.Millisecond
|
var modifier byte
|
||||||
logger.Info().Int("step", i).Uint16("delay", step.Delay).Msg("Keyboard report multi delay")
|
switch m := step["modifier"].(type) {
|
||||||
|
case uint8:
|
||||||
|
modifier = m
|
||||||
|
case int:
|
||||||
|
modifier = byte(m)
|
||||||
|
case float64:
|
||||||
|
modifier = byte(int(m))
|
||||||
|
default:
|
||||||
|
return last, fmt.Errorf("invalid modifier type: %T", m)
|
||||||
|
}
|
||||||
|
|
||||||
last, err = rpcKeyboardReport(step.Modifier, step.Keys)
|
var keys []byte
|
||||||
if err != nil {
|
switch k := step["keys"].(type) {
|
||||||
logger.Warn().Err(err).Msg("failed to execute keyboard report multi")
|
case []byte:
|
||||||
return last, err
|
keys = k
|
||||||
|
case []any:
|
||||||
|
arr := k
|
||||||
|
keys = make([]byte, 0, len(arr))
|
||||||
|
for _, v := range arr {
|
||||||
|
switch f := v.(type) {
|
||||||
|
case uint8:
|
||||||
|
keys = append(keys, f)
|
||||||
|
case int:
|
||||||
|
keys = append(keys, byte(f))
|
||||||
|
case float64:
|
||||||
|
keys = append(keys, byte(int(f)))
|
||||||
|
default:
|
||||||
|
return last, fmt.Errorf("invalid key type: %T", f)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
return last, fmt.Errorf("invalid keys type: %T", k)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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(100 * time.Millisecond):
|
||||||
// Sleep completed normally
|
// Sleep completed normally
|
||||||
case <-ctx.Done():
|
case <-ctx.Done():
|
||||||
logger.Debug().Int("step", i).Msg("Keyboard report multi cancelled during sleep")
|
logger.Debug().Int("step", i).Msg("Keyboard report multi cancelled during sleep")
|
||||||
return last, ctx.Err()
|
return last, ctx.Err()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
last, err = rpcKeyboardReport(modifier, keys)
|
||||||
|
if err != nil {
|
||||||
|
logger.Warn().Err(err).Msg("failed to execute keyboard report multi")
|
||||||
|
return last, err
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return last, nil
|
return last, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
var rpcHandlers = map[string]RPCHandler{
|
var rpcHandlers = map[string]RPCHandler{
|
||||||
"ping": {Func: rpcPing},
|
"ping": {Func: rpcPing},
|
||||||
"reboot": {Func: rpcReboot, Params: []string{"force"}},
|
"reboot": {Func: rpcReboot, Params: []string{"force"}},
|
||||||
"getDeviceID": {Func: rpcGetDeviceID},
|
"getDeviceID": {Func: rpcGetDeviceID},
|
||||||
"deregisterDevice": {Func: rpcDeregisterDevice},
|
"deregisterDevice": {Func: rpcDeregisterDevice},
|
||||||
"getCloudState": {Func: rpcGetCloudState},
|
"getCloudState": {Func: rpcGetCloudState},
|
||||||
"getNetworkState": {Func: rpcGetNetworkState},
|
"getNetworkState": {Func: rpcGetNetworkState},
|
||||||
"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"}},
|
"keyboardReport": {Func: rpcKeyboardReport, Params: []string{"modifier", "keys"}},
|
||||||
// "keyboardReportMulti": {Func: rpcKeyboardReportMultiWrapper, Params: []string{"macro"}},
|
"keyboardReportMulti": {Func: rpcKeyboardReportMultiWrapper, Params: []string{"macro"}},
|
||||||
// "cancelKeyboardReportMulti": {Func: rpcCancelKeyboardReportMulti},
|
"cancelKeyboardReportMulti": {Func: rpcCancelKeyboardReportMulti},
|
||||||
"getKeyboardLedState": {Func: rpcGetKeyboardLedState},
|
"getKeyboardLedState": {Func: rpcGetKeyboardLedState},
|
||||||
"keypressReport": {Func: rpcKeypressReport, Params: []string{"key", "press"}},
|
"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"}},
|
||||||
"wheelReport": {Func: rpcWheelReport, Params: []string{"wheelY"}},
|
"wheelReport": {Func: rpcWheelReport, Params: []string{"wheelY"}},
|
||||||
"getVideoState": {Func: rpcGetVideoState},
|
"getVideoState": {Func: rpcGetVideoState},
|
||||||
"getUSBState": {Func: rpcGetUSBState},
|
"getUSBState": {Func: rpcGetUSBState},
|
||||||
"unmountImage": {Func: rpcUnmountImage},
|
"unmountImage": {Func: rpcUnmountImage},
|
||||||
"rpcMountBuiltInImage": {Func: rpcMountBuiltInImage, Params: []string{"filename"}},
|
"rpcMountBuiltInImage": {Func: rpcMountBuiltInImage, Params: []string{"filename"}},
|
||||||
"setJigglerState": {Func: rpcSetJigglerState, Params: []string{"enabled"}},
|
"setJigglerState": {Func: rpcSetJigglerState, Params: []string{"enabled"}},
|
||||||
"getJigglerState": {Func: rpcGetJigglerState},
|
"getJigglerState": {Func: rpcGetJigglerState},
|
||||||
"setJigglerConfig": {Func: rpcSetJigglerConfig, Params: []string{"jigglerConfig"}},
|
"setJigglerConfig": {Func: rpcSetJigglerConfig, Params: []string{"jigglerConfig"}},
|
||||||
"getJigglerConfig": {Func: rpcGetJigglerConfig},
|
"getJigglerConfig": {Func: rpcGetJigglerConfig},
|
||||||
"getTimezones": {Func: rpcGetTimezones},
|
"getTimezones": {Func: rpcGetTimezones},
|
||||||
"sendWOLMagicPacket": {Func: rpcSendWOLMagicPacket, Params: []string{"macAddress"}},
|
"sendWOLMagicPacket": {Func: rpcSendWOLMagicPacket, Params: []string{"macAddress"}},
|
||||||
"getStreamQualityFactor": {Func: rpcGetStreamQualityFactor},
|
"getStreamQualityFactor": {Func: rpcGetStreamQualityFactor},
|
||||||
"setStreamQualityFactor": {Func: rpcSetStreamQualityFactor, Params: []string{"factor"}},
|
"setStreamQualityFactor": {Func: rpcSetStreamQualityFactor, Params: []string{"factor"}},
|
||||||
"getAutoUpdateState": {Func: rpcGetAutoUpdateState},
|
"getAutoUpdateState": {Func: rpcGetAutoUpdateState},
|
||||||
"setAutoUpdateState": {Func: rpcSetAutoUpdateState, Params: []string{"enabled"}},
|
"setAutoUpdateState": {Func: rpcSetAutoUpdateState, Params: []string{"enabled"}},
|
||||||
"getEDID": {Func: rpcGetEDID},
|
"getEDID": {Func: rpcGetEDID},
|
||||||
"setEDID": {Func: rpcSetEDID, Params: []string{"edid"}},
|
"setEDID": {Func: rpcSetEDID, Params: []string{"edid"}},
|
||||||
"getDevChannelState": {Func: rpcGetDevChannelState},
|
"getDevChannelState": {Func: rpcGetDevChannelState},
|
||||||
"setDevChannelState": {Func: rpcSetDevChannelState, Params: []string{"enabled"}},
|
"setDevChannelState": {Func: rpcSetDevChannelState, Params: []string{"enabled"}},
|
||||||
"getUpdateStatus": {Func: rpcGetUpdateStatus},
|
"getUpdateStatus": {Func: rpcGetUpdateStatus},
|
||||||
"tryUpdate": {Func: rpcTryUpdate},
|
"tryUpdate": {Func: rpcTryUpdate},
|
||||||
"getDevModeState": {Func: rpcGetDevModeState},
|
"getDevModeState": {Func: rpcGetDevModeState},
|
||||||
"setDevModeState": {Func: rpcSetDevModeState, Params: []string{"enabled"}},
|
"setDevModeState": {Func: rpcSetDevModeState, Params: []string{"enabled"}},
|
||||||
"getSSHKeyState": {Func: rpcGetSSHKeyState},
|
"getSSHKeyState": {Func: rpcGetSSHKeyState},
|
||||||
"setSSHKeyState": {Func: rpcSetSSHKeyState, Params: []string{"sshKey"}},
|
"setSSHKeyState": {Func: rpcSetSSHKeyState, Params: []string{"sshKey"}},
|
||||||
"getTLSState": {Func: rpcGetTLSState},
|
"getTLSState": {Func: rpcGetTLSState},
|
||||||
"setTLSState": {Func: rpcSetTLSState, Params: []string{"state"}},
|
"setTLSState": {Func: rpcSetTLSState, Params: []string{"state"}},
|
||||||
"setMassStorageMode": {Func: rpcSetMassStorageMode, Params: []string{"mode"}},
|
"setMassStorageMode": {Func: rpcSetMassStorageMode, Params: []string{"mode"}},
|
||||||
"getMassStorageMode": {Func: rpcGetMassStorageMode},
|
"getMassStorageMode": {Func: rpcGetMassStorageMode},
|
||||||
"isUpdatePending": {Func: rpcIsUpdatePending},
|
"isUpdatePending": {Func: rpcIsUpdatePending},
|
||||||
"getUsbEmulationState": {Func: rpcGetUsbEmulationState},
|
"getUsbEmulationState": {Func: rpcGetUsbEmulationState},
|
||||||
"setUsbEmulationState": {Func: rpcSetUsbEmulationState, Params: []string{"enabled"}},
|
"setUsbEmulationState": {Func: rpcSetUsbEmulationState, Params: []string{"enabled"}},
|
||||||
"getUsbConfig": {Func: rpcGetUsbConfig},
|
"getUsbConfig": {Func: rpcGetUsbConfig},
|
||||||
"setUsbConfig": {Func: rpcSetUsbConfig, Params: []string{"usbConfig"}},
|
"setUsbConfig": {Func: rpcSetUsbConfig, Params: []string{"usbConfig"}},
|
||||||
"checkMountUrl": {Func: rpcCheckMountUrl, Params: []string{"url"}},
|
"checkMountUrl": {Func: rpcCheckMountUrl, Params: []string{"url"}},
|
||||||
"getVirtualMediaState": {Func: rpcGetVirtualMediaState},
|
"getVirtualMediaState": {Func: rpcGetVirtualMediaState},
|
||||||
"getStorageSpace": {Func: rpcGetStorageSpace},
|
"getStorageSpace": {Func: rpcGetStorageSpace},
|
||||||
"mountWithHTTP": {Func: rpcMountWithHTTP, Params: []string{"url", "mode"}},
|
"mountWithHTTP": {Func: rpcMountWithHTTP, Params: []string{"url", "mode"}},
|
||||||
"mountWithStorage": {Func: rpcMountWithStorage, Params: []string{"filename", "mode"}},
|
"mountWithStorage": {Func: rpcMountWithStorage, Params: []string{"filename", "mode"}},
|
||||||
"listStorageFiles": {Func: rpcListStorageFiles},
|
"listStorageFiles": {Func: rpcListStorageFiles},
|
||||||
"deleteStorageFile": {Func: rpcDeleteStorageFile, Params: []string{"filename"}},
|
"deleteStorageFile": {Func: rpcDeleteStorageFile, Params: []string{"filename"}},
|
||||||
"startStorageFileUpload": {Func: rpcStartStorageFileUpload, Params: []string{"filename", "size"}},
|
"startStorageFileUpload": {Func: rpcStartStorageFileUpload, Params: []string{"filename", "size"}},
|
||||||
"getWakeOnLanDevices": {Func: rpcGetWakeOnLanDevices},
|
"getWakeOnLanDevices": {Func: rpcGetWakeOnLanDevices},
|
||||||
"setWakeOnLanDevices": {Func: rpcSetWakeOnLanDevices, Params: []string{"params"}},
|
"setWakeOnLanDevices": {Func: rpcSetWakeOnLanDevices, Params: []string{"params"}},
|
||||||
"resetConfig": {Func: rpcResetConfig},
|
"resetConfig": {Func: rpcResetConfig},
|
||||||
"setDisplayRotation": {Func: rpcSetDisplayRotation, Params: []string{"params"}},
|
"setDisplayRotation": {Func: rpcSetDisplayRotation, Params: []string{"params"}},
|
||||||
"getDisplayRotation": {Func: rpcGetDisplayRotation},
|
"getDisplayRotation": {Func: rpcGetDisplayRotation},
|
||||||
"setBacklightSettings": {Func: rpcSetBacklightSettings, Params: []string{"params"}},
|
"setBacklightSettings": {Func: rpcSetBacklightSettings, Params: []string{"params"}},
|
||||||
"getBacklightSettings": {Func: rpcGetBacklightSettings},
|
"getBacklightSettings": {Func: rpcGetBacklightSettings},
|
||||||
"getDCPowerState": {Func: rpcGetDCPowerState},
|
"getDCPowerState": {Func: rpcGetDCPowerState},
|
||||||
"setDCPowerState": {Func: rpcSetDCPowerState, Params: []string{"enabled"}},
|
"setDCPowerState": {Func: rpcSetDCPowerState, Params: []string{"enabled"}},
|
||||||
"setDCRestoreState": {Func: rpcSetDCRestoreState, Params: []string{"state"}},
|
"setDCRestoreState": {Func: rpcSetDCRestoreState, Params: []string{"state"}},
|
||||||
"getActiveExtension": {Func: rpcGetActiveExtension},
|
"getActiveExtension": {Func: rpcGetActiveExtension},
|
||||||
"setActiveExtension": {Func: rpcSetActiveExtension, Params: []string{"extensionId"}},
|
"setActiveExtension": {Func: rpcSetActiveExtension, Params: []string{"extensionId"}},
|
||||||
"getATXState": {Func: rpcGetATXState},
|
"getATXState": {Func: rpcGetATXState},
|
||||||
"setATXPowerAction": {Func: rpcSetATXPowerAction, Params: []string{"action"}},
|
"setATXPowerAction": {Func: rpcSetATXPowerAction, Params: []string{"action"}},
|
||||||
"getSerialSettings": {Func: rpcGetSerialSettings},
|
"getSerialSettings": {Func: rpcGetSerialSettings},
|
||||||
"setSerialSettings": {Func: rpcSetSerialSettings, Params: []string{"settings"}},
|
"setSerialSettings": {Func: rpcSetSerialSettings, Params: []string{"settings"}},
|
||||||
"getUsbDevices": {Func: rpcGetUsbDevices},
|
"getUsbDevices": {Func: rpcGetUsbDevices},
|
||||||
"setUsbDevices": {Func: rpcSetUsbDevices, Params: []string{"devices"}},
|
"setUsbDevices": {Func: rpcSetUsbDevices, Params: []string{"devices"}},
|
||||||
"setUsbDeviceState": {Func: rpcSetUsbDeviceState, Params: []string{"device", "enabled"}},
|
"setUsbDeviceState": {Func: rpcSetUsbDeviceState, Params: []string{"device", "enabled"}},
|
||||||
"setCloudUrl": {Func: rpcSetCloudUrl, Params: []string{"apiUrl", "appUrl"}},
|
"setCloudUrl": {Func: rpcSetCloudUrl, Params: []string{"apiUrl", "appUrl"}},
|
||||||
"getKeyboardLayout": {Func: rpcGetKeyboardLayout},
|
"getKeyboardLayout": {Func: rpcGetKeyboardLayout},
|
||||||
"setKeyboardLayout": {Func: rpcSetKeyboardLayout, Params: []string{"layout"}},
|
"setKeyboardLayout": {Func: rpcSetKeyboardLayout, Params: []string{"layout"}},
|
||||||
"getKeyboardMacros": {Func: getKeyboardMacros},
|
"getKeyboardMacros": {Func: getKeyboardMacros},
|
||||||
"setKeyboardMacros": {Func: setKeyboardMacros, Params: []string{"params"}},
|
"setKeyboardMacros": {Func: setKeyboardMacros, Params: []string{"params"}},
|
||||||
"getLocalLoopbackOnly": {Func: rpcGetLocalLoopbackOnly},
|
"getLocalLoopbackOnly": {Func: rpcGetLocalLoopbackOnly},
|
||||||
"setLocalLoopbackOnly": {Func: rpcSetLocalLoopbackOnly, Params: []string{"enabled"}},
|
"setLocalLoopbackOnly": {Func: rpcSetLocalLoopbackOnly, Params: []string{"enabled"}},
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,6 @@ export const HID_RPC_MESSAGE_TYPES = {
|
||||||
WheelReport: 0x04,
|
WheelReport: 0x04,
|
||||||
KeypressReport: 0x05,
|
KeypressReport: 0x05,
|
||||||
MouseReport: 0x06,
|
MouseReport: 0x06,
|
||||||
KeyboardMacroReport: 0x07,
|
|
||||||
KeyboardLedState: 0x32,
|
KeyboardLedState: 0x32,
|
||||||
KeysDownState: 0x33,
|
KeysDownState: 0x33,
|
||||||
}
|
}
|
||||||
|
|
@ -33,30 +32,6 @@ const fromInt32toUint8 = (n: number) => {
|
||||||
]);
|
]);
|
||||||
};
|
};
|
||||||
|
|
||||||
const fromUint16toUint8 = (n: number) => {
|
|
||||||
if (n > 65535 || n < 0) {
|
|
||||||
throw new Error(`Number ${n} is not within the uint16 range`);
|
|
||||||
}
|
|
||||||
|
|
||||||
return new Uint8Array([
|
|
||||||
(n >> 8) & 0xFF,
|
|
||||||
(n >> 0) & 0xFF,
|
|
||||||
]);
|
|
||||||
};
|
|
||||||
|
|
||||||
const fromUint32toUint8 = (n: number) => {
|
|
||||||
if (n > 4294967295 || n < 0) {
|
|
||||||
throw new Error(`Number ${n} is not within the uint32 range`);
|
|
||||||
}
|
|
||||||
|
|
||||||
return new Uint8Array([
|
|
||||||
(n >> 24) & 0xFF,
|
|
||||||
(n >> 16) & 0xFF,
|
|
||||||
(n >> 8) & 0xFF,
|
|
||||||
(n >> 0) & 0xFF,
|
|
||||||
]);
|
|
||||||
};
|
|
||||||
|
|
||||||
const fromInt8ToUint8 = (n: number) => {
|
const fromInt8ToUint8 = (n: number) => {
|
||||||
if (n < -128 || n > 127) {
|
if (n < -128 || n > 127) {
|
||||||
throw new Error(`Number ${n} is not within the int8 range`);
|
throw new Error(`Number ${n} is not within the int8 range`);
|
||||||
|
|
@ -211,64 +186,6 @@ export class KeyboardReportMessage extends RpcMessage {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface KeyboardMacro extends KeysDownState {
|
|
||||||
delay: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
export class KeyboardMacroReportMessage extends RpcMessage {
|
|
||||||
isPaste: boolean;
|
|
||||||
length: number;
|
|
||||||
macro: KeyboardMacro[];
|
|
||||||
|
|
||||||
KEYS_LENGTH = 6;
|
|
||||||
|
|
||||||
constructor(isPaste: boolean, length: number, macro: KeyboardMacro[]) {
|
|
||||||
super(HID_RPC_MESSAGE_TYPES.KeyboardMacroReport);
|
|
||||||
this.isPaste = isPaste;
|
|
||||||
this.length = length;
|
|
||||||
this.macro = macro;
|
|
||||||
}
|
|
||||||
|
|
||||||
marshal(): Uint8Array {
|
|
||||||
const dataHeader = new Uint8Array([
|
|
||||||
this.messageType,
|
|
||||||
this.isPaste ? 1 : 0,
|
|
||||||
...fromUint32toUint8(this.length),
|
|
||||||
]);
|
|
||||||
|
|
||||||
let dataBody = new Uint8Array();
|
|
||||||
|
|
||||||
for (const step of this.macro) {
|
|
||||||
if (!withinUint8Range(step.modifier)) {
|
|
||||||
throw new Error(`Modifier ${step.modifier} is not within the uint8 range`);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Ensure the keys are within the KEYS_LENGTH range
|
|
||||||
const keys = step.keys;
|
|
||||||
if (keys.length > this.KEYS_LENGTH) {
|
|
||||||
throw new Error(`Keys ${keys} is not within the hidKeyBufferSize range`);
|
|
||||||
} else if (keys.length < this.KEYS_LENGTH) {
|
|
||||||
keys.push(...Array(this.KEYS_LENGTH - keys.length).fill(0));
|
|
||||||
}
|
|
||||||
|
|
||||||
for (const key of keys) {
|
|
||||||
if (!withinUint8Range(key)) {
|
|
||||||
throw new Error(`Key ${key} is not within the uint8 range`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const macroBinary = new Uint8Array([
|
|
||||||
step.modifier,
|
|
||||||
...keys,
|
|
||||||
...fromUint16toUint8(step.delay),
|
|
||||||
]);
|
|
||||||
|
|
||||||
dataBody = new Uint8Array([...dataBody, ...macroBinary]);
|
|
||||||
}
|
|
||||||
return new Uint8Array([...dataHeader, ...dataBody]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export class KeyboardLedStateMessage extends RpcMessage {
|
export class KeyboardLedStateMessage extends RpcMessage {
|
||||||
keyboardLedState: KeyboardLedState;
|
keyboardLedState: KeyboardLedState;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -5,8 +5,6 @@ import { useRTCStore } from "@/hooks/stores";
|
||||||
import {
|
import {
|
||||||
HID_RPC_VERSION,
|
HID_RPC_VERSION,
|
||||||
HandshakeMessage,
|
HandshakeMessage,
|
||||||
KeyboardMacro,
|
|
||||||
KeyboardMacroReportMessage,
|
|
||||||
KeyboardReportMessage,
|
KeyboardReportMessage,
|
||||||
KeypressReportMessage,
|
KeypressReportMessage,
|
||||||
MouseReportMessage,
|
MouseReportMessage,
|
||||||
|
|
@ -70,15 +68,6 @@ export function useHidRpc(onHidRpcMessage?: (payload: RpcMessage) => void) {
|
||||||
[sendMessage],
|
[sendMessage],
|
||||||
);
|
);
|
||||||
|
|
||||||
const reportKeyboardMacroEvent = useCallback(
|
|
||||||
(macro: KeyboardMacro[]) => {
|
|
||||||
const d = new KeyboardMacroReportMessage(false, macro.length, macro);
|
|
||||||
sendMessage(d);
|
|
||||||
console.log("Sent keyboard macro report", d, d.marshal());
|
|
||||||
},
|
|
||||||
[sendMessage],
|
|
||||||
);
|
|
||||||
|
|
||||||
const sendHandshake = useCallback(() => {
|
const sendHandshake = useCallback(() => {
|
||||||
if (rpcHidProtocolVersion) return;
|
if (rpcHidProtocolVersion) return;
|
||||||
if (!rpcHidChannel) return;
|
if (!rpcHidChannel) return;
|
||||||
|
|
@ -154,7 +143,6 @@ export function useHidRpc(onHidRpcMessage?: (payload: RpcMessage) => void) {
|
||||||
reportKeypressEvent,
|
reportKeypressEvent,
|
||||||
reportAbsMouseEvent,
|
reportAbsMouseEvent,
|
||||||
reportRelMouseEvent,
|
reportRelMouseEvent,
|
||||||
reportKeyboardMacroEvent,
|
|
||||||
rpcHidProtocolVersion,
|
rpcHidProtocolVersion,
|
||||||
rpcHidReady,
|
rpcHidReady,
|
||||||
rpcHidStatus,
|
rpcHidStatus,
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
import { useCallback, useMemo } from "react";
|
import { useCallback } from "react";
|
||||||
|
|
||||||
import {
|
import {
|
||||||
hidErrorRollOver,
|
hidErrorRollOver,
|
||||||
|
|
@ -9,7 +9,7 @@ import {
|
||||||
} from "@/hooks/stores";
|
} 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, KeyboardMacro, KeysDownStateMessage } from "@/hooks/hidRpc";
|
import { KeyboardLedStateMessage, KeysDownStateMessage } from "@/hooks/hidRpc";
|
||||||
import { hidKeyToModifierMask, keys, modifiers } from "@/keyboardMappings";
|
import { hidKeyToModifierMask, keys, modifiers } from "@/keyboardMappings";
|
||||||
|
|
||||||
export default function useKeyboard() {
|
export default function useKeyboard() {
|
||||||
|
|
@ -32,7 +32,6 @@ export default function useKeyboard() {
|
||||||
const {
|
const {
|
||||||
reportKeyboardEvent: sendKeyboardEventHidRpc,
|
reportKeyboardEvent: sendKeyboardEventHidRpc,
|
||||||
reportKeypressEvent: sendKeypressEventHidRpc,
|
reportKeypressEvent: sendKeypressEventHidRpc,
|
||||||
reportKeyboardMacroEvent: sendKeyboardMacroEventHidRpc,
|
|
||||||
rpcHidReady,
|
rpcHidReady,
|
||||||
} = useHidRpc(message => {
|
} = useHidRpc(message => {
|
||||||
switch (message.constructor) {
|
switch (message.constructor) {
|
||||||
|
|
@ -78,19 +77,16 @@ export default function useKeyboard() {
|
||||||
[rpcDataChannel?.readyState, rpcHidReady, send, sendKeyboardEventHidRpc],
|
[rpcDataChannel?.readyState, rpcHidReady, send, sendKeyboardEventHidRpc],
|
||||||
);
|
);
|
||||||
|
|
||||||
const MACRO_RESET_KEYBOARD_STATE = useMemo(() => ({
|
|
||||||
keys: new Array(hidKeyBufferSize).fill(0),
|
|
||||||
modifier: 0,
|
|
||||||
delay: 0,
|
|
||||||
}), []);
|
|
||||||
|
|
||||||
// resetKeyboardState is used to reset the keyboard state to no keys pressed and no modifiers.
|
// resetKeyboardState is used to reset the keyboard state to no keys pressed and no modifiers.
|
||||||
// This is useful for macros and when the browser loses focus to ensure that the keyboard state
|
// This is useful for macros and when the browser loses focus to ensure that the keyboard state
|
||||||
// is clean.
|
// is clean.
|
||||||
const resetKeyboardState = useCallback(async () => {
|
const resetKeyboardState = useCallback(async () => {
|
||||||
// Reset the keys buffer to zeros and the modifier state to zero
|
// Reset the keys buffer to zeros and the modifier state to zero
|
||||||
sendKeyboardEvent(MACRO_RESET_KEYBOARD_STATE);
|
keysDownState.keys.length = hidKeyBufferSize;
|
||||||
}, [sendKeyboardEvent, MACRO_RESET_KEYBOARD_STATE]);
|
keysDownState.keys.fill(0);
|
||||||
|
keysDownState.modifier = 0;
|
||||||
|
sendKeyboardEvent(keysDownState);
|
||||||
|
}, [keysDownState, sendKeyboardEvent]);
|
||||||
|
|
||||||
// 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.
|
||||||
|
|
@ -101,7 +97,7 @@ export default function useKeyboard() {
|
||||||
const executeMacro = async (
|
const executeMacro = async (
|
||||||
steps: { keys: string[] | null; modifiers: string[] | null; delay: number }[],
|
steps: { keys: string[] | null; modifiers: string[] | null; delay: number }[],
|
||||||
) => {
|
) => {
|
||||||
const macro: KeyboardMacro[] = [];
|
const macro: KeysDownState[] = [];
|
||||||
|
|
||||||
for (const [_, step] of steps.entries()) {
|
for (const [_, step] of steps.entries()) {
|
||||||
const keyValues = (step.keys || []).map(key => keys[key]).filter(Boolean);
|
const keyValues = (step.keys || []).map(key => keys[key]).filter(Boolean);
|
||||||
|
|
@ -111,12 +107,19 @@ export default function useKeyboard() {
|
||||||
|
|
||||||
// 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) {
|
||||||
macro.push({ keys: keyValues, modifier: modifierMask, delay: 50 });
|
macro.push({ keys: keyValues, modifier: modifierMask });
|
||||||
macro.push({ ...MACRO_RESET_KEYBOARD_STATE, delay: 200 });
|
keysDownState.keys.length = hidKeyBufferSize;
|
||||||
|
keysDownState.keys.fill(0);
|
||||||
|
keysDownState.modifier = 0;
|
||||||
|
macro.push(keysDownState);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// KeyboardReportMessage
|
||||||
sendKeyboardMacroEventHidRpc(macro);
|
send("keyboardReportMulti", { macro }, (resp: JsonRpcResponse) => {
|
||||||
|
if ("error" in resp) {
|
||||||
|
console.error(`Failed to send keyboard report ${macro}`, resp.error);
|
||||||
|
}
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const cancelExecuteMacro = useCallback(async () => {
|
const cancelExecuteMacro = useCallback(async () => {
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue