mirror of https://github.com/jetkvm/kvm.git
Compare commits
3 Commits
c46f545b3c
...
89229c2f1c
| Author | SHA1 | Date |
|---|---|---|
|
|
89229c2f1c | |
|
|
9438ab7778 | |
|
|
5c1c665ddb |
2
cloud.go
2
cloud.go
|
|
@ -478,7 +478,7 @@ func handleSessionRequest(
|
|||
cloudLogger.Trace().Interface("session", session).Msg("new session accepted")
|
||||
|
||||
// Cancel any ongoing keyboard macro when session changes
|
||||
cancelKeyboardMacro()
|
||||
cancelAllRunningKeyboardMacros()
|
||||
|
||||
currentSession = session
|
||||
_ = wsjson.Write(context.Background(), c, gin.H{"type": "answer", "data": sd})
|
||||
|
|
|
|||
42
hidrpc.go
42
hidrpc.go
|
|
@ -26,20 +26,45 @@ func handleHidRPCMessage(message hidrpc.Message, session *Session) {
|
|||
return
|
||||
}
|
||||
session.hidRPCAvailable = true
|
||||
|
||||
case hidrpc.TypeKeypressReport, hidrpc.TypeKeyboardReport:
|
||||
rpcErr = handleHidRPCKeyboardInput(message)
|
||||
|
||||
case hidrpc.TypeKeyboardMacroReport:
|
||||
keyboardMacroReport, err := message.KeyboardMacroReport()
|
||||
if err != nil {
|
||||
logger.Warn().Err(err).Msg("failed to get keyboard macro report")
|
||||
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:
|
||||
rpcCancelKeyboardMacro()
|
||||
return
|
||||
|
||||
case hidrpc.TypeKeyboardMacroTokenState:
|
||||
tokenState, err := message.KeyboardMacroTokenState()
|
||||
if err != nil {
|
||||
logger.Warn().Err(err).Msg("failed to get keyboard macro token")
|
||||
return
|
||||
}
|
||||
rpcCancelKeyboardMacroByToken(tokenState.Token)
|
||||
return
|
||||
|
||||
case hidrpc.TypeKeypressKeepAliveReport:
|
||||
rpcErr = handleHidRPCKeypressKeepAlive(session)
|
||||
|
||||
case hidrpc.TypePointerReport:
|
||||
pointerReport, err := message.PointerReport()
|
||||
if err != nil {
|
||||
|
|
@ -47,6 +72,7 @@ func handleHidRPCMessage(message hidrpc.Message, session *Session) {
|
|||
return
|
||||
}
|
||||
rpcErr = rpcAbsMouseReport(pointerReport.X, pointerReport.Y, pointerReport.Button)
|
||||
|
||||
case hidrpc.TypeMouseReport:
|
||||
mouseReport, err := message.MouseReport()
|
||||
if err != nil {
|
||||
|
|
@ -54,6 +80,7 @@ func handleHidRPCMessage(message hidrpc.Message, session *Session) {
|
|||
return
|
||||
}
|
||||
rpcErr = rpcRelMouseReport(mouseReport.DX, mouseReport.DY, mouseReport.Button)
|
||||
|
||||
default:
|
||||
logger.Warn().Uint8("type", uint8(message.Type())).Msg("unknown HID RPC message type")
|
||||
}
|
||||
|
|
@ -65,15 +92,18 @@ func handleHidRPCMessage(message hidrpc.Message, session *Session) {
|
|||
|
||||
func onHidMessage(msg hidQueueMessage, session *Session) {
|
||||
data := msg.Data
|
||||
dataLen := len(data)
|
||||
|
||||
scopedLogger := hidRPCLogger.With().
|
||||
Str("channel", msg.channel).
|
||||
Bytes("data", data).
|
||||
Dur("timelimit", msg.timelimit).
|
||||
Int("data_len", dataLen).
|
||||
Bytes("data", data[:min(dataLen, 32)]).
|
||||
Logger()
|
||||
scopedLogger.Debug().Msg("HID RPC message received")
|
||||
|
||||
if len(data) < 1 {
|
||||
scopedLogger.Warn().Int("length", len(data)).Msg("received empty data in HID RPC message handler")
|
||||
if dataLen < 1 {
|
||||
scopedLogger.Warn().Msg("received empty data in HID RPC message handler")
|
||||
return
|
||||
}
|
||||
|
||||
|
|
@ -96,7 +126,7 @@ func onHidMessage(msg hidQueueMessage, session *Session) {
|
|||
r <- nil
|
||||
}()
|
||||
select {
|
||||
case <-time.After(1 * time.Second):
|
||||
case <-time.After(msg.timelimit * time.Second):
|
||||
scopedLogger.Warn().Msg("HID RPC message timed out")
|
||||
case <-r:
|
||||
scopedLogger.Debug().Dur("duration", time.Since(t)).Msg("HID RPC message handled")
|
||||
|
|
@ -212,6 +242,8 @@ func reportHidRPC(params any, session *Session) {
|
|||
message, err = hidrpc.NewKeydownStateMessage(params).Marshal()
|
||||
case hidrpc.KeyboardMacroState:
|
||||
message, err = hidrpc.NewKeyboardMacroStateMessage(params.State, params.IsPaste).Marshal()
|
||||
case hidrpc.KeyboardMacroTokenState:
|
||||
message, err = hidrpc.NewKeyboardMacroTokenMessage(params.Token).Marshal()
|
||||
default:
|
||||
err = fmt.Errorf("unknown HID RPC message type: %T", params)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,7 +2,9 @@ package hidrpc
|
|||
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"github.com/jetkvm/kvm/internal/usbgadget"
|
||||
)
|
||||
|
||||
|
|
@ -22,26 +24,34 @@ const (
|
|||
TypeKeyboardLedState MessageType = 0x32
|
||||
TypeKeydownState MessageType = 0x33
|
||||
TypeKeyboardMacroState MessageType = 0x34
|
||||
TypeKeyboardMacroTokenState MessageType = 0x35
|
||||
)
|
||||
|
||||
type QueueIndex int
|
||||
|
||||
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 messages
|
||||
MouseQueue int = 2 // Queue index for mouse messages
|
||||
MacroQueue int = 3 // Queue index for macro messages
|
||||
OtherQueue int = 4 // Queue index for other messages
|
||||
)
|
||||
|
||||
// GetQueueIndex returns the index of the queue to which the message should be enqueued.
|
||||
func GetQueueIndex(messageType MessageType) int {
|
||||
func GetQueueIndex(messageType MessageType) (int, time.Duration) {
|
||||
switch messageType {
|
||||
case TypeHandshake:
|
||||
return 0
|
||||
case TypeKeyboardReport, TypeKeypressReport, TypeKeyboardMacroReport, TypeKeyboardLedState, TypeKeydownState, TypeKeyboardMacroState:
|
||||
return 1
|
||||
return HandshakeQueue, 1
|
||||
case TypeKeyboardReport, TypeKeypressReport, TypeKeyboardLedState, TypeKeydownState, TypeKeyboardMacroState:
|
||||
return KeyboardQueue, 1
|
||||
case TypePointerReport, TypeMouseReport, TypeWheelReport:
|
||||
return 2
|
||||
// we don't want to block the queue for this message
|
||||
case TypeCancelKeyboardMacroReport:
|
||||
return 3
|
||||
return MouseQueue, 1
|
||||
// we don't want to block the queue for these messages
|
||||
case TypeKeyboardMacroReport, TypeCancelKeyboardMacroReport, TypeKeyboardMacroTokenState:
|
||||
return MacroQueue, 60 // 1 minute timeout
|
||||
default:
|
||||
return 3
|
||||
return OtherQueue, 5
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -121,3 +131,13 @@ func NewKeyboardMacroStateMessage(state bool, isPaste bool) *Message {
|
|||
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 (
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
|
||||
"github.com/google/uuid"
|
||||
)
|
||||
|
||||
// Message ..
|
||||
|
|
@ -23,6 +25,9 @@ func (m *Message) Type() MessageType {
|
|||
func (m *Message) String() string {
|
||||
switch m.t {
|
||||
case TypeHandshake:
|
||||
if len(m.d) != 0 {
|
||||
return fmt.Sprintf("Handshake{Malformed: %v}", m.d)
|
||||
}
|
||||
return "Handshake"
|
||||
case TypeKeypressReport:
|
||||
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])
|
||||
case TypeKeypressKeepAliveReport:
|
||||
if len(m.d) != 0 {
|
||||
return fmt.Sprintf("KeypressKeepAliveReport{Malformed: %v}", m.d)
|
||||
}
|
||||
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:
|
||||
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]))
|
||||
case TypeCancelKeyboardMacroReport:
|
||||
if len(m.d) != 0 {
|
||||
return fmt.Sprintf("CancelKeyboardMacroReport{Malformed: %v}", m.d)
|
||||
}
|
||||
return "CancelKeyboardMacroReport"
|
||||
case TypeKeyboardMacroTokenState:
|
||||
if len(m.d) != 16 {
|
||||
return fmt.Sprintf("KeyboardMacroTokenState{Malformed: %v}", m.d)
|
||||
}
|
||||
return fmt.Sprintf("KeyboardMacroTokenState{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:
|
||||
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 {
|
||||
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{
|
||||
Key: m.d[0],
|
||||
Press: m.d[1] == uint8(1),
|
||||
|
|
@ -95,7 +135,7 @@ func (m *Message) KeyboardReport() (KeyboardReport, error) {
|
|||
// Macro ..
|
||||
type KeyboardMacroStep struct {
|
||||
Modifier byte // 1 byte
|
||||
Keys []byte // 6 bytes: hidKeyBufferSize
|
||||
Keys []byte // 6 bytes: HidKeyBufferSize
|
||||
Delay uint16 // 2 bytes
|
||||
}
|
||||
type KeyboardMacroReport struct {
|
||||
|
|
@ -105,7 +145,7 @@ type KeyboardMacroReport struct {
|
|||
}
|
||||
|
||||
// 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.
|
||||
func (m *Message) KeyboardMacroReport() (KeyboardMacroReport, error) {
|
||||
|
|
@ -205,3 +245,29 @@ func (m *Message) KeyboardMacroState() (KeyboardMacroState, error) {
|
|||
IsPaste: m.d[1] == uint8(1),
|
||||
}, nil
|
||||
}
|
||||
|
||||
type KeyboardMacroTokenState struct {
|
||||
Token uuid.UUID
|
||||
}
|
||||
|
||||
// KeyboardMacroTokenState returns the keyboard macro token UUID from the message.
|
||||
func (m *Message) KeyboardMacroTokenState() (KeyboardMacroTokenState, error) {
|
||||
if m.t != TypeKeyboardMacroTokenState {
|
||||
return KeyboardMacroTokenState{}, fmt.Errorf("invalid message type: %d", m.t)
|
||||
}
|
||||
|
||||
if len(m.d) == 0 {
|
||||
return KeyboardMacroTokenState{Token: uuid.Nil}, nil
|
||||
}
|
||||
|
||||
if len(m.d) != 16 {
|
||||
return KeyboardMacroTokenState{}, fmt.Errorf("invalid UUID length: %d", len(m.d))
|
||||
}
|
||||
|
||||
token, err := uuid.FromBytes(m.d)
|
||||
if err != nil {
|
||||
return KeyboardMacroTokenState{}, fmt.Errorf("invalid UUID: %v", err)
|
||||
}
|
||||
|
||||
return KeyboardMacroTokenState{Token: token}, nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -31,6 +31,8 @@ var keyboardReportDesc = []byte{
|
|||
0x05, 0x01, /* USAGE_PAGE (Generic Desktop) */
|
||||
0x09, 0x06, /* USAGE (Keyboard) */
|
||||
0xa1, 0x01, /* COLLECTION (Application) */
|
||||
|
||||
/* 8 modifier bits */
|
||||
0x05, 0x07, /* USAGE_PAGE (Keyboard) */
|
||||
0x19, 0xe0, /* USAGE_MINIMUM (Keyboard LeftControl) */
|
||||
0x29, 0xe7, /* USAGE_MAXIMUM (Keyboard Right GUI) */
|
||||
|
|
@ -39,27 +41,47 @@ var keyboardReportDesc = []byte{
|
|||
0x75, 0x01, /* REPORT_SIZE (1) */
|
||||
0x95, 0x08, /* REPORT_COUNT (8) */
|
||||
0x81, 0x02, /* INPUT (Data,Var,Abs) */
|
||||
|
||||
/* 8 bits of padding */
|
||||
0x95, 0x01, /* REPORT_COUNT (1) */
|
||||
0x75, 0x08, /* REPORT_SIZE (8) */
|
||||
0x81, 0x03, /* INPUT (Cnst,Var,Abs) */
|
||||
|
||||
/* 6 key codes for the 104 key keyboard */
|
||||
0x95, 0x06, /* REPORT_COUNT (6) */
|
||||
0x75, 0x08, /* REPORT_SIZE (8) */
|
||||
0x15, 0x00, /* LOGICAL_MINIMUM (0) */
|
||||
0x25, 0xE7, /* LOGICAL_MAXIMUM (104-key HID) */
|
||||
0x05, 0x07, /* USAGE_PAGE (Keyboard) */
|
||||
0x19, 0x00, /* USAGE_MINIMUM (Reserved) */
|
||||
0x29, 0xE7, /* USAGE_MAXIMUM (Keyboard Right GUI) */
|
||||
0x81, 0x00, /* INPUT (Data,Ary,Abs) */
|
||||
|
||||
/* LED report 5 bits for Num Lock through Kana */
|
||||
0x95, 0x05, /* REPORT_COUNT (5) */
|
||||
0x75, 0x01, /* REPORT_SIZE (1) */
|
||||
|
||||
0x05, 0x08, /* USAGE_PAGE (LEDs) */
|
||||
0x19, 0x01, /* USAGE_MINIMUM (Num Lock) */
|
||||
0x29, 0x05, /* USAGE_MAXIMUM (Kana) */
|
||||
0x91, 0x02, /* OUTPUT (Data,Var,Abs) */
|
||||
|
||||
/* 1 bit of padding for the Power LED (ignored) */
|
||||
0x95, 0x01, /* REPORT_COUNT (1) */
|
||||
0x75, 0x03, /* REPORT_SIZE (3) */
|
||||
0x75, 0x03, /* REPORT_SIZE (1) */
|
||||
0x91, 0x03, /* OUTPUT (Cnst,Var,Abs) */
|
||||
|
||||
/* LED report 1 bit for Shift */
|
||||
0x95, 0x01, /* REPORT_COUNT (1) */
|
||||
0x75, 0x01, /* REPORT_SIZE (1) */
|
||||
0x05, 0x08, /* USAGE_PAGE (LEDs) */
|
||||
0x19, 0x07, /* USAGE_MINIMUM (Shift) */
|
||||
0x29, 0x07, /* USAGE_MAXIMUM (Shift) */
|
||||
0x91, 0x02, /* OUTPUT (Data,Var,Abs) */
|
||||
|
||||
/* 1 bit of padding for the rest of the byte */
|
||||
0x95, 0x01, /* REPORT_COUNT (1) */
|
||||
0x75, 0x03, /* REPORT_SIZE (1) */
|
||||
0x91, 0x03, /* OUTPUT (Cnst,Var,Abs) */
|
||||
0x95, 0x06, /* REPORT_COUNT (6) */
|
||||
0x75, 0x08, /* REPORT_SIZE (8) */
|
||||
0x15, 0x00, /* LOGICAL_MINIMUM (0) */
|
||||
0x25, 0x65, /* LOGICAL_MAXIMUM (101) */
|
||||
0x05, 0x07, /* USAGE_PAGE (Keyboard) */
|
||||
0x19, 0x00, /* USAGE_MINIMUM (Reserved) */
|
||||
0x29, 0x65, /* USAGE_MAXIMUM (Keyboard Application) */
|
||||
0x81, 0x00, /* INPUT (Data,Ary,Abs) */
|
||||
0xc0, /* END_COLLECTION */
|
||||
}
|
||||
|
||||
|
|
@ -153,6 +175,16 @@ func (u *UsbGadget) SetOnKeysDownChange(f func(state KeysDownState)) {
|
|||
u.onKeysDownChange = &f
|
||||
}
|
||||
|
||||
var suspendedKeyDownMessages bool = false
|
||||
|
||||
func (u *UsbGadget) SuspendKeyDownMessages() {
|
||||
suspendedKeyDownMessages = true
|
||||
}
|
||||
|
||||
func (u *UsbGadget) ResumeSuspendKeyDownMessages() {
|
||||
suspendedKeyDownMessages = false
|
||||
}
|
||||
|
||||
func (u *UsbGadget) SetOnKeepAliveReset(f func()) {
|
||||
u.onKeepAliveReset = &f
|
||||
}
|
||||
|
|
@ -169,9 +201,9 @@ func (u *UsbGadget) scheduleAutoRelease(key byte) {
|
|||
}
|
||||
|
||||
// TODO: make this configurable
|
||||
// We currently hardcode the duration to 100ms
|
||||
// We currently hardcode the duration to the default of 100ms
|
||||
// However, it should be the same as the duration of the keep-alive reset called baseExtension.
|
||||
u.kbdAutoReleaseTimers[key] = time.AfterFunc(100*time.Millisecond, func() {
|
||||
u.kbdAutoReleaseTimers[key] = time.AfterFunc(DefaultAutoReleaseDuration, func() {
|
||||
u.performAutoRelease(key)
|
||||
})
|
||||
}
|
||||
|
|
@ -314,6 +346,7 @@ var keyboardWriteHidFileLock sync.Mutex
|
|||
func (u *UsbGadget) keyboardWriteHidFile(modifier byte, keys []byte) error {
|
||||
keyboardWriteHidFileLock.Lock()
|
||||
defer keyboardWriteHidFileLock.Unlock()
|
||||
|
||||
if err := u.openKeyboardHidFile(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
|
@ -353,7 +386,7 @@ func (u *UsbGadget) UpdateKeysDown(modifier byte, keys []byte) KeysDownState {
|
|||
u.keysDownState = state
|
||||
u.keyboardStateLock.Unlock()
|
||||
|
||||
if u.onKeysDownChange != nil {
|
||||
if u.onKeysDownChange != nil && !suspendedKeyDownMessages {
|
||||
(*u.onKeysDownChange)(state) // this enques to the outgoing hidrpc queue via usb.go → currentSession.enqueueKeysDownState(...)
|
||||
}
|
||||
return state
|
||||
|
|
@ -484,6 +517,10 @@ func (u *UsbGadget) keypressReport(key byte, press bool) (KeysDownState, error)
|
|||
}
|
||||
|
||||
err := u.keyboardWriteHidFile(modifier, keys)
|
||||
if err != nil {
|
||||
u.log.Warn().Uint8("modifier", modifier).Uints8("keys", keys).Msg("Could not write keyboard report to hidg0")
|
||||
}
|
||||
|
||||
return u.UpdateKeysDown(modifier, keys), err
|
||||
}
|
||||
|
||||
|
|
|
|||
153
jsonrpc.go
153
jsonrpc.go
|
|
@ -1,7 +1,6 @@
|
|||
package kvm
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
|
|
@ -14,6 +13,7 @@ import (
|
|||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"github.com/pion/webrtc/v4"
|
||||
"github.com/rs/zerolog"
|
||||
"go.bug.st/serial"
|
||||
|
|
@ -1084,91 +1084,154 @@ func rpcSetLocalLoopbackOnly(enabled bool) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
type RunningMacro struct {
|
||||
cancel context.CancelFunc
|
||||
isPaste bool
|
||||
}
|
||||
|
||||
var (
|
||||
keyboardMacroCancel context.CancelFunc
|
||||
keyboardMacroLock sync.Mutex
|
||||
keyboardMacroCancelMap map[uuid.UUID]RunningMacro
|
||||
keyboardMacroLock sync.Mutex
|
||||
keyboardMacroOnce sync.Once
|
||||
)
|
||||
|
||||
// cancelKeyboardMacro cancels any ongoing keyboard macro execution
|
||||
func cancelKeyboardMacro() {
|
||||
func getKeyboardMacroCancelMap() map[uuid.UUID]RunningMacro {
|
||||
keyboardMacroOnce.Do(func() {
|
||||
keyboardMacroCancelMap = make(map[uuid.UUID]RunningMacro)
|
||||
})
|
||||
return keyboardMacroCancelMap
|
||||
}
|
||||
|
||||
func addKeyboardMacro(isPaste bool, cancel context.CancelFunc) uuid.UUID {
|
||||
keyboardMacroLock.Lock()
|
||||
defer keyboardMacroLock.Unlock()
|
||||
cancelMap := getKeyboardMacroCancelMap()
|
||||
|
||||
if keyboardMacroCancel != nil {
|
||||
keyboardMacroCancel()
|
||||
logger.Info().Msg("canceled keyboard macro")
|
||||
keyboardMacroCancel = nil
|
||||
token := uuid.New() // Generate a unique token
|
||||
cancelMap[token] = RunningMacro{
|
||||
isPaste: isPaste,
|
||||
cancel: cancel,
|
||||
}
|
||||
return token
|
||||
}
|
||||
|
||||
func removeRunningKeyboardMacro(token uuid.UUID) {
|
||||
keyboardMacroLock.Lock()
|
||||
defer keyboardMacroLock.Unlock()
|
||||
cancelMap := getKeyboardMacroCancelMap()
|
||||
|
||||
delete(cancelMap, token)
|
||||
}
|
||||
|
||||
func cancelRunningKeyboardMacro(token uuid.UUID) {
|
||||
keyboardMacroLock.Lock()
|
||||
defer keyboardMacroLock.Unlock()
|
||||
cancelMap := getKeyboardMacroCancelMap()
|
||||
|
||||
if runningMacro, exists := cancelMap[token]; exists {
|
||||
runningMacro.cancel()
|
||||
delete(cancelMap, 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()
|
||||
defer keyboardMacroLock.Unlock()
|
||||
cancelMap := getKeyboardMacroCancelMap()
|
||||
|
||||
keyboardMacroCancel = cancel
|
||||
for token, runningMacro := range cancelMap {
|
||||
runningMacro.cancel()
|
||||
delete(cancelMap, token)
|
||||
logger.Info().Interface("token", token).Msg("cancelled keyboard macro")
|
||||
}
|
||||
}
|
||||
|
||||
func rpcExecuteKeyboardMacro(macro []hidrpc.KeyboardMacroStep) error {
|
||||
cancelKeyboardMacro()
|
||||
func reportRunningMacrosState() {
|
||||
if currentSession != nil {
|
||||
keyboardMacroLock.Lock()
|
||||
defer keyboardMacroLock.Unlock()
|
||||
cancelMap := getKeyboardMacroCancelMap()
|
||||
|
||||
isPaste := false
|
||||
for _, runningMacro := range cancelMap {
|
||||
if runningMacro.isPaste {
|
||||
isPaste = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
state := hidrpc.KeyboardMacroState{
|
||||
State: len(cancelMap) > 0,
|
||||
IsPaste: isPaste,
|
||||
}
|
||||
|
||||
currentSession.reportHidRPCKeyboardMacroState(state)
|
||||
}
|
||||
}
|
||||
|
||||
func rpcExecuteKeyboardMacro(isPaste bool, macro []hidrpc.KeyboardMacroStep) uuid.UUID {
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
setKeyboardMacroCancel(cancel)
|
||||
token := addKeyboardMacro(isPaste, cancel)
|
||||
reportRunningMacrosState()
|
||||
|
||||
s := hidrpc.KeyboardMacroState{
|
||||
State: true,
|
||||
IsPaste: true,
|
||||
}
|
||||
go func() {
|
||||
defer reportRunningMacrosState() // this executes last, so the map is already updated
|
||||
defer removeRunningKeyboardMacro(token) // this executes first, to update the map
|
||||
|
||||
if currentSession != nil {
|
||||
currentSession.reportHidRPCKeyboardMacroState(s)
|
||||
}
|
||||
err := executeKeyboardMacro(ctx, isPaste, macro)
|
||||
if err != nil {
|
||||
logger.Error().Err(err).Interface("token", token).Bool("isPaste", isPaste).Msg("keyboard macro execution failed")
|
||||
}
|
||||
}()
|
||||
|
||||
err := rpcDoExecuteKeyboardMacro(ctx, macro)
|
||||
|
||||
setKeyboardMacroCancel(nil)
|
||||
|
||||
s.State = false
|
||||
if currentSession != nil {
|
||||
currentSession.reportHidRPCKeyboardMacroState(s)
|
||||
}
|
||||
|
||||
return err
|
||||
return token
|
||||
}
|
||||
|
||||
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 {
|
||||
return step.Modifier == 0 && bytes.Equal(step.Keys, keyboardClearStateKeys)
|
||||
if token == uuid.Nil {
|
||||
cancelAllRunningKeyboardMacros()
|
||||
} else {
|
||||
cancelRunningKeyboardMacro(token)
|
||||
}
|
||||
}
|
||||
|
||||
func rpcDoExecuteKeyboardMacro(ctx context.Context, macro []hidrpc.KeyboardMacroStep) error {
|
||||
logger.Debug().Interface("macro", macro).Msg("Executing keyboard macro")
|
||||
func executeKeyboardMacro(ctx context.Context, isPaste bool, macro []hidrpc.KeyboardMacroStep) error {
|
||||
logger.Debug().
|
||||
Int("macro_steps", len(macro)).
|
||||
Bool("isPaste", isPaste).
|
||||
Msg("Executing keyboard macro")
|
||||
|
||||
// don't report keyboard state changes while executing the macro
|
||||
gadget.SuspendKeyDownMessages()
|
||||
defer gadget.ResumeSuspendKeyDownMessages()
|
||||
|
||||
for i, step := range macro {
|
||||
delay := time.Duration(step.Delay) * time.Millisecond
|
||||
|
||||
err := rpcKeyboardReport(step.Modifier, step.Keys)
|
||||
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
|
||||
}
|
||||
|
||||
// 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
|
||||
select {
|
||||
case <-time.After(delay):
|
||||
// Sleep completed normally
|
||||
case <-ctx.Done():
|
||||
// make sure keyboard state is reset
|
||||
err := rpcKeyboardReport(0, keyboardClearStateKeys)
|
||||
// make sure keyboard state is reset and the client gets notified
|
||||
gadget.ResumeSuspendKeyDownMessages()
|
||||
err := rpcKeyboardReport(0, make([]byte, hidrpc.HidKeyBufferSize))
|
||||
if err != nil {
|
||||
logger.Warn().Err(err).Msg("failed to reset keyboard state")
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,14 +1,14 @@
|
|||
{
|
||||
"name": "kvm-ui",
|
||||
"version": "2025.09.23.0000",
|
||||
"version": "2025.09.26.01300",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "kvm-ui",
|
||||
"version": "2025.09.23.0000",
|
||||
"version": "2025.09.26.01300",
|
||||
"dependencies": {
|
||||
"@headlessui/react": "^2.2.8",
|
||||
"@headlessui/react": "^2.2.9",
|
||||
"@headlessui/tailwindcss": "^0.2.2",
|
||||
"@heroicons/react": "^2.2.0",
|
||||
"@vitejs/plugin-basic-ssl": "^2.1.0",
|
||||
|
|
@ -22,7 +22,7 @@
|
|||
"dayjs": "^1.11.18",
|
||||
"eslint-import-resolver-alias": "^1.1.2",
|
||||
"focus-trap-react": "^11.0.4",
|
||||
"framer-motion": "^12.23.18",
|
||||
"framer-motion": "^12.23.22",
|
||||
"lodash.throttle": "^4.1.1",
|
||||
"mini-svg-data-uri": "^1.4.4",
|
||||
"react": "^19.1.1",
|
||||
|
|
@ -30,13 +30,14 @@
|
|||
"react-dom": "^19.1.1",
|
||||
"react-hot-toast": "^2.6.0",
|
||||
"react-icons": "^5.5.0",
|
||||
"react-router": "^7.9.1",
|
||||
"react-router": "^7.9.3",
|
||||
"react-simple-keyboard": "^3.8.122",
|
||||
"react-use-websocket": "^4.13.0",
|
||||
"react-xtermjs": "^1.0.10",
|
||||
"recharts": "^3.2.1",
|
||||
"tailwind-merge": "^3.3.1",
|
||||
"usehooks-ts": "^3.1.1",
|
||||
"uuid": "^13.0.0",
|
||||
"validator": "^13.15.15",
|
||||
"zustand": "^4.5.2"
|
||||
},
|
||||
|
|
@ -46,9 +47,9 @@
|
|||
"@eslint/js": "^9.36.0",
|
||||
"@tailwindcss/forms": "^0.5.10",
|
||||
"@tailwindcss/postcss": "^4.1.13",
|
||||
"@tailwindcss/typography": "^0.5.18",
|
||||
"@tailwindcss/typography": "^0.5.19",
|
||||
"@tailwindcss/vite": "^4.1.13",
|
||||
"@types/react": "^19.1.13",
|
||||
"@types/react": "^19.1.14",
|
||||
"@types/react-dom": "^19.1.9",
|
||||
"@types/semver": "^7.7.1",
|
||||
"@types/validator": "^13.15.3",
|
||||
|
|
@ -61,7 +62,7 @@
|
|||
"eslint-plugin-import": "^2.32.0",
|
||||
"eslint-plugin-react": "^7.37.5",
|
||||
"eslint-plugin-react-hooks": "^5.2.0",
|
||||
"eslint-plugin-react-refresh": "^0.4.21",
|
||||
"eslint-plugin-react-refresh": "^0.4.22",
|
||||
"globals": "^16.4.0",
|
||||
"postcss": "^8.5.6",
|
||||
"prettier": "^3.6.2",
|
||||
|
|
@ -723,9 +724,9 @@
|
|||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@headlessui/react": {
|
||||
"version": "2.2.8",
|
||||
"resolved": "https://registry.npmjs.org/@headlessui/react/-/react-2.2.8.tgz",
|
||||
"integrity": "sha512-vkiZulDC0lFeTrZTbA4tHvhZHvkUb2PFh5xJ1BvWAZdRK0fayMKO1QEO4inWkXxK1i0I1rcwwu1d6mo0K7Pcbw==",
|
||||
"version": "2.2.9",
|
||||
"resolved": "https://registry.npmjs.org/@headlessui/react/-/react-2.2.9.tgz",
|
||||
"integrity": "sha512-Mb+Un58gwBn0/yWZfyrCh0TJyurtT+dETj7YHleylHk5od3dv2XqETPGWMyQ5/7sYN7oWdyM1u9MvC0OC8UmzQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@floating-ui/react": "^0.26.16",
|
||||
|
|
@ -1043,9 +1044,9 @@
|
|||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@rollup/rollup-android-arm-eabi": {
|
||||
"version": "4.52.0",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.52.0.tgz",
|
||||
"integrity": "sha512-VxDYCDqOaR7NXzAtvRx7G1u54d2kEHopb28YH/pKzY6y0qmogP3gG7CSiWsq9WvDFxOQMpNEyjVAHZFXfH3o/A==",
|
||||
"version": "4.52.2",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.52.2.tgz",
|
||||
"integrity": "sha512-o3pcKzJgSGt4d74lSZ+OCnHwkKBeAbFDmbEm5gg70eA8VkyCuC/zV9TwBnmw6VjDlRdF4Pshfb+WE9E6XY1PoQ==",
|
||||
"cpu": [
|
||||
"arm"
|
||||
],
|
||||
|
|
@ -1056,9 +1057,9 @@
|
|||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-android-arm64": {
|
||||
"version": "4.52.0",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.52.0.tgz",
|
||||
"integrity": "sha512-pqDirm8koABIKvzL59YI9W9DWbRlTX7RWhN+auR8HXJxo89m4mjqbah7nJZjeKNTNYopqL+yGg+0mhCpf3xZtQ==",
|
||||
"version": "4.52.2",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.52.2.tgz",
|
||||
"integrity": "sha512-cqFSWO5tX2vhC9hJTK8WAiPIm4Q8q/cU8j2HQA0L3E1uXvBYbOZMhE2oFL8n2pKB5sOCHY6bBuHaRwG7TkfJyw==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
|
|
@ -1069,9 +1070,9 @@
|
|||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-darwin-arm64": {
|
||||
"version": "4.52.0",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.52.0.tgz",
|
||||
"integrity": "sha512-YCdWlY/8ltN6H78HnMsRHYlPiKvqKagBP1r+D7SSylxX+HnsgXGCmLiV3Y4nSyY9hW8qr8U9LDUx/Lo7M6MfmQ==",
|
||||
"version": "4.52.2",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.52.2.tgz",
|
||||
"integrity": "sha512-vngduywkkv8Fkh3wIZf5nFPXzWsNsVu1kvtLETWxTFf/5opZmflgVSeLgdHR56RQh71xhPhWoOkEBvbehwTlVA==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
|
|
@ -1082,9 +1083,9 @@
|
|||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-darwin-x64": {
|
||||
"version": "4.52.0",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.52.0.tgz",
|
||||
"integrity": "sha512-z4nw6y1j+OOSGzuVbSWdIp1IUks9qNw4dc7z7lWuWDKojY38VMWBlEN7F9jk5UXOkUcp97vA1N213DF+Lz8BRg==",
|
||||
"version": "4.52.2",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.52.2.tgz",
|
||||
"integrity": "sha512-h11KikYrUCYTrDj6h939hhMNlqU2fo/X4NB0OZcys3fya49o1hmFaczAiJWVAFgrM1NCP6RrO7lQKeVYSKBPSQ==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
|
|
@ -1095,9 +1096,9 @@
|
|||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-freebsd-arm64": {
|
||||
"version": "4.52.0",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.52.0.tgz",
|
||||
"integrity": "sha512-Q/dv9Yvyr5rKlK8WQJZVrp5g2SOYeZUs9u/t2f9cQ2E0gJjYB/BWoedXfUT0EcDJefi2zzVfhcOj8drWCzTviw==",
|
||||
"version": "4.52.2",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.52.2.tgz",
|
||||
"integrity": "sha512-/eg4CI61ZUkLXxMHyVlmlGrSQZ34xqWlZNW43IAU4RmdzWEx0mQJ2mN/Cx4IHLVZFL6UBGAh+/GXhgvGb+nVxw==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
|
|
@ -1108,9 +1109,9 @@
|
|||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-freebsd-x64": {
|
||||
"version": "4.52.0",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.52.0.tgz",
|
||||
"integrity": "sha512-kdBsLs4Uile/fbjZVvCRcKB4q64R+1mUq0Yd7oU1CMm1Av336ajIFqNFovByipciuUQjBCPMxwJhCgfG2re3rg==",
|
||||
"version": "4.52.2",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.52.2.tgz",
|
||||
"integrity": "sha512-QOWgFH5X9+p+S1NAfOqc0z8qEpJIoUHf7OWjNUGOeW18Mx22lAUOiA9b6r2/vpzLdfxi/f+VWsYjUOMCcYh0Ng==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
|
|
@ -1121,9 +1122,9 @@
|
|||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-linux-arm-gnueabihf": {
|
||||
"version": "4.52.0",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.52.0.tgz",
|
||||
"integrity": "sha512-aL6hRwu0k7MTUESgkg7QHY6CoqPgr6gdQXRJI1/VbFlUMwsSzPGSR7sG5d+MCbYnJmJwThc2ol3nixj1fvI/zQ==",
|
||||
"version": "4.52.2",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.52.2.tgz",
|
||||
"integrity": "sha512-kDWSPafToDd8LcBYd1t5jw7bD5Ojcu12S3uT372e5HKPzQt532vW+rGFFOaiR0opxePyUkHrwz8iWYEyH1IIQA==",
|
||||
"cpu": [
|
||||
"arm"
|
||||
],
|
||||
|
|
@ -1134,9 +1135,9 @@
|
|||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-linux-arm-musleabihf": {
|
||||
"version": "4.52.0",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.52.0.tgz",
|
||||
"integrity": "sha512-BTs0M5s1EJejgIBJhCeiFo7GZZ2IXWkFGcyZhxX4+8usnIo5Mti57108vjXFIQmmJaRyDwmV59Tw64Ap1dkwMw==",
|
||||
"version": "4.52.2",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.52.2.tgz",
|
||||
"integrity": "sha512-gKm7Mk9wCv6/rkzwCiUC4KnevYhlf8ztBrDRT9g/u//1fZLapSRc+eDZj2Eu2wpJ+0RzUKgtNijnVIB4ZxyL+w==",
|
||||
"cpu": [
|
||||
"arm"
|
||||
],
|
||||
|
|
@ -1147,9 +1148,9 @@
|
|||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-linux-arm64-gnu": {
|
||||
"version": "4.52.0",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.52.0.tgz",
|
||||
"integrity": "sha512-uj672IVOU9m08DBGvoPKPi/J8jlVgjh12C9GmjjBxCTQc3XtVmRkRKyeHSmIKQpvJ7fIm1EJieBUcnGSzDVFyw==",
|
||||
"version": "4.52.2",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.52.2.tgz",
|
||||
"integrity": "sha512-66lA8vnj5mB/rtDNwPgrrKUOtCLVQypkyDa2gMfOefXK6rcZAxKLO9Fy3GkW8VkPnENv9hBkNOFfGLf6rNKGUg==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
|
|
@ -1160,9 +1161,9 @@
|
|||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-linux-arm64-musl": {
|
||||
"version": "4.52.0",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.52.0.tgz",
|
||||
"integrity": "sha512-/+IVbeDMDCtB/HP/wiWsSzduD10SEGzIZX2945KSgZRNi4TSkjHqRJtNTVtVb8IRwhJ65ssI56krlLik+zFWkw==",
|
||||
"version": "4.52.2",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.52.2.tgz",
|
||||
"integrity": "sha512-s+OPucLNdJHvuZHuIz2WwncJ+SfWHFEmlC5nKMUgAelUeBUnlB4wt7rXWiyG4Zn07uY2Dd+SGyVa9oyLkVGOjA==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
|
|
@ -1173,9 +1174,9 @@
|
|||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-linux-loong64-gnu": {
|
||||
"version": "4.52.0",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.52.0.tgz",
|
||||
"integrity": "sha512-U1vVzvSWtSMWKKrGoROPBXMh3Vwn93TA9V35PldokHGqiUbF6erSzox/5qrSMKp6SzakvyjcPiVF8yB1xKr9Pg==",
|
||||
"version": "4.52.2",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.52.2.tgz",
|
||||
"integrity": "sha512-8wTRM3+gVMDLLDdaT6tKmOE3lJyRy9NpJUS/ZRWmLCmOPIJhVyXwjBo+XbrrwtV33Em1/eCTd5TuGJm4+DmYjw==",
|
||||
"cpu": [
|
||||
"loong64"
|
||||
],
|
||||
|
|
@ -1186,9 +1187,9 @@
|
|||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-linux-ppc64-gnu": {
|
||||
"version": "4.52.0",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.52.0.tgz",
|
||||
"integrity": "sha512-X/4WfuBAdQRH8cK3DYl8zC00XEE6aM472W+QCycpQJeLWVnHfkv7RyBFVaTqNUMsTgIX8ihMjCvFF9OUgeABzw==",
|
||||
"version": "4.52.2",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.52.2.tgz",
|
||||
"integrity": "sha512-6yqEfgJ1anIeuP2P/zhtfBlDpXUb80t8DpbYwXQ3bQd95JMvUaqiX+fKqYqUwZXqdJDd8xdilNtsHM2N0cFm6A==",
|
||||
"cpu": [
|
||||
"ppc64"
|
||||
],
|
||||
|
|
@ -1199,9 +1200,9 @@
|
|||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-linux-riscv64-gnu": {
|
||||
"version": "4.52.0",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.52.0.tgz",
|
||||
"integrity": "sha512-xIRYc58HfWDBZoLmWfWXg2Sq8VCa2iJ32B7mqfWnkx5mekekl0tMe7FHpY8I72RXEcUkaWawRvl3qA55og+cwQ==",
|
||||
"version": "4.52.2",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.52.2.tgz",
|
||||
"integrity": "sha512-sshYUiYVSEI2B6dp4jMncwxbrUqRdNApF2c3bhtLAU0qA8Lrri0p0NauOsTWh3yCCCDyBOjESHMExonp7Nzc0w==",
|
||||
"cpu": [
|
||||
"riscv64"
|
||||
],
|
||||
|
|
@ -1212,9 +1213,9 @@
|
|||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-linux-riscv64-musl": {
|
||||
"version": "4.52.0",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.52.0.tgz",
|
||||
"integrity": "sha512-mbsoUey05WJIOz8U1WzNdf+6UMYGwE3fZZnQqsM22FZ3wh1N887HT6jAOjXs6CNEK3Ntu2OBsyQDXfIjouI4dw==",
|
||||
"version": "4.52.2",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.52.2.tgz",
|
||||
"integrity": "sha512-duBLgd+3pqC4MMwBrKkFxaZerUxZcYApQVC5SdbF5/e/589GwVvlRUnyqMFbM8iUSb1BaoX/3fRL7hB9m2Pj8Q==",
|
||||
"cpu": [
|
||||
"riscv64"
|
||||
],
|
||||
|
|
@ -1225,9 +1226,9 @@
|
|||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-linux-s390x-gnu": {
|
||||
"version": "4.52.0",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.52.0.tgz",
|
||||
"integrity": "sha512-qP6aP970bucEi5KKKR4AuPFd8aTx9EF6BvutvYxmZuWLJHmnq4LvBfp0U+yFDMGwJ+AIJEH5sIP+SNypauMWzg==",
|
||||
"version": "4.52.2",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.52.2.tgz",
|
||||
"integrity": "sha512-tzhYJJidDUVGMgVyE+PmxENPHlvvqm1KILjjZhB8/xHYqAGeizh3GBGf9u6WdJpZrz1aCpIIHG0LgJgH9rVjHQ==",
|
||||
"cpu": [
|
||||
"s390x"
|
||||
],
|
||||
|
|
@ -1238,9 +1239,9 @@
|
|||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-linux-x64-gnu": {
|
||||
"version": "4.52.0",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.52.0.tgz",
|
||||
"integrity": "sha512-nmSVN+F2i1yKZ7rJNKO3G7ZzmxJgoQBQZ/6c4MuS553Grmr7WqR7LLDcYG53Z2m9409z3JLt4sCOhLdbKQ3HmA==",
|
||||
"version": "4.52.2",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.52.2.tgz",
|
||||
"integrity": "sha512-opH8GSUuVcCSSyHHcl5hELrmnk4waZoVpgn/4FDao9iyE4WpQhyWJ5ryl5M3ocp4qkRuHfyXnGqg8M9oKCEKRA==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
|
|
@ -1251,9 +1252,9 @@
|
|||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-linux-x64-musl": {
|
||||
"version": "4.52.0",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.52.0.tgz",
|
||||
"integrity": "sha512-2d0qRo33G6TfQVjaMR71P+yJVGODrt5V6+T0BDYH4EMfGgdC/2HWDVjSSFw888GSzAZUwuska3+zxNUCDco6rQ==",
|
||||
"version": "4.52.2",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.52.2.tgz",
|
||||
"integrity": "sha512-LSeBHnGli1pPKVJ79ZVJgeZWWZXkEe/5o8kcn23M8eMKCUANejchJbF/JqzM4RRjOJfNRhKJk8FuqL1GKjF5oQ==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
|
|
@ -1264,9 +1265,9 @@
|
|||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-openharmony-arm64": {
|
||||
"version": "4.52.0",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.52.0.tgz",
|
||||
"integrity": "sha512-A1JalX4MOaFAAyGgpO7XP5khquv/7xKzLIyLmhNrbiCxWpMlnsTYr8dnsWM7sEeotNmxvSOEL7F65j0HXFcFsw==",
|
||||
"version": "4.52.2",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.52.2.tgz",
|
||||
"integrity": "sha512-uPj7MQ6/s+/GOpolavm6BPo+6CbhbKYyZHUDvZ/SmJM7pfDBgdGisFX3bY/CBDMg2ZO4utfhlApkSfZ92yXw7Q==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
|
|
@ -1277,9 +1278,9 @@
|
|||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-win32-arm64-msvc": {
|
||||
"version": "4.52.0",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.52.0.tgz",
|
||||
"integrity": "sha512-YQugafP/rH0eOOHGjmNgDURrpYHrIX0yuojOI8bwCyXwxC9ZdTd3vYkmddPX0oHONLXu9Rb1dDmT0VNpjkzGGw==",
|
||||
"version": "4.52.2",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.52.2.tgz",
|
||||
"integrity": "sha512-Z9MUCrSgIaUeeHAiNkm3cQyst2UhzjPraR3gYYfOjAuZI7tcFRTOD+4cHLPoS/3qinchth+V56vtqz1Tv+6KPA==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
|
|
@ -1290,9 +1291,9 @@
|
|||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-win32-ia32-msvc": {
|
||||
"version": "4.52.0",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.52.0.tgz",
|
||||
"integrity": "sha512-zYdUYhi3Qe2fndujBqL5FjAFzvNeLxtIqfzNEVKD1I7C37/chv1VxhscWSQHTNfjPCrBFQMnynwA3kpZpZ8w4A==",
|
||||
"version": "4.52.2",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.52.2.tgz",
|
||||
"integrity": "sha512-+GnYBmpjldD3XQd+HMejo+0gJGwYIOfFeoBQv32xF/RUIvccUz20/V6Otdv+57NE70D5pa8W/jVGDoGq0oON4A==",
|
||||
"cpu": [
|
||||
"ia32"
|
||||
],
|
||||
|
|
@ -1303,9 +1304,9 @@
|
|||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-win32-x64-gnu": {
|
||||
"version": "4.52.0",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.52.0.tgz",
|
||||
"integrity": "sha512-fGk03kQylNaCOQ96HDMeT7E2n91EqvCDd3RwvT5k+xNdFCeMGnj5b5hEgTGrQuyidqSsD3zJDQ21QIaxXqTBJw==",
|
||||
"version": "4.52.2",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.52.2.tgz",
|
||||
"integrity": "sha512-ApXFKluSB6kDQkAqZOKXBjiaqdF1BlKi+/eqnYe9Ee7U2K3pUDKsIyr8EYm/QDHTJIM+4X+lI0gJc3TTRhd+dA==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
|
|
@ -1316,9 +1317,9 @@
|
|||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-win32-x64-msvc": {
|
||||
"version": "4.52.0",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.52.0.tgz",
|
||||
"integrity": "sha512-6iKDCVSIUQ8jPMoIV0OytRKniaYyy5EbY/RRydmLW8ZR3cEBhxbWl5ro0rkUNe0ef6sScvhbY79HrjRm8i3vDQ==",
|
||||
"version": "4.52.2",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.52.2.tgz",
|
||||
"integrity": "sha512-ARz+Bs8kY6FtitYM96PqPEVvPXqEZmPZsSkXvyX19YzDqkCaIlhCieLLMI5hxO9SRZ2XtCtm8wxhy0iJ2jxNfw==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
|
|
@ -1347,15 +1348,15 @@
|
|||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@swc/core": {
|
||||
"version": "1.13.5",
|
||||
"resolved": "https://registry.npmjs.org/@swc/core/-/core-1.13.5.tgz",
|
||||
"integrity": "sha512-WezcBo8a0Dg2rnR82zhwoR6aRNxeTGfK5QCD6TQ+kg3xx/zNT02s/0o+81h/3zhvFSB24NtqEr8FTw88O5W/JQ==",
|
||||
"version": "1.13.19",
|
||||
"resolved": "https://registry.npmjs.org/@swc/core/-/core-1.13.19.tgz",
|
||||
"integrity": "sha512-V1r4wFdjaZIUIZZrV2Mb/prEeu03xvSm6oatPxsvnXKF9lNh5Jtk9QvUdiVfD9rrvi7bXrAVhg9Wpbmv/2Fl1g==",
|
||||
"dev": true,
|
||||
"hasInstallScript": true,
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@swc/counter": "^0.1.3",
|
||||
"@swc/types": "^0.1.24"
|
||||
"@swc/types": "^0.1.25"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
|
|
@ -1365,16 +1366,16 @@
|
|||
"url": "https://opencollective.com/swc"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"@swc/core-darwin-arm64": "1.13.5",
|
||||
"@swc/core-darwin-x64": "1.13.5",
|
||||
"@swc/core-linux-arm-gnueabihf": "1.13.5",
|
||||
"@swc/core-linux-arm64-gnu": "1.13.5",
|
||||
"@swc/core-linux-arm64-musl": "1.13.5",
|
||||
"@swc/core-linux-x64-gnu": "1.13.5",
|
||||
"@swc/core-linux-x64-musl": "1.13.5",
|
||||
"@swc/core-win32-arm64-msvc": "1.13.5",
|
||||
"@swc/core-win32-ia32-msvc": "1.13.5",
|
||||
"@swc/core-win32-x64-msvc": "1.13.5"
|
||||
"@swc/core-darwin-arm64": "1.13.19",
|
||||
"@swc/core-darwin-x64": "1.13.19",
|
||||
"@swc/core-linux-arm-gnueabihf": "1.13.19",
|
||||
"@swc/core-linux-arm64-gnu": "1.13.19",
|
||||
"@swc/core-linux-arm64-musl": "1.13.19",
|
||||
"@swc/core-linux-x64-gnu": "1.13.19",
|
||||
"@swc/core-linux-x64-musl": "1.13.19",
|
||||
"@swc/core-win32-arm64-msvc": "1.13.19",
|
||||
"@swc/core-win32-ia32-msvc": "1.13.19",
|
||||
"@swc/core-win32-x64-msvc": "1.13.19"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@swc/helpers": ">=0.5.17"
|
||||
|
|
@ -1386,9 +1387,9 @@
|
|||
}
|
||||
},
|
||||
"node_modules/@swc/core-darwin-arm64": {
|
||||
"version": "1.13.5",
|
||||
"resolved": "https://registry.npmjs.org/@swc/core-darwin-arm64/-/core-darwin-arm64-1.13.5.tgz",
|
||||
"integrity": "sha512-lKNv7SujeXvKn16gvQqUQI5DdyY8v7xcoO3k06/FJbHJS90zEwZdQiMNRiqpYw/orU543tPaWgz7cIYWhbopiQ==",
|
||||
"version": "1.13.19",
|
||||
"resolved": "https://registry.npmjs.org/@swc/core-darwin-arm64/-/core-darwin-arm64-1.13.19.tgz",
|
||||
"integrity": "sha512-NxDyte9tCJSJ8+R62WDtqwg8eI57lubD52sHyGOfezpJBOPr36bUSGGLyO3Vod9zTGlOu2CpkuzA/2iVw92u1g==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
|
|
@ -1403,9 +1404,9 @@
|
|||
}
|
||||
},
|
||||
"node_modules/@swc/core-darwin-x64": {
|
||||
"version": "1.13.5",
|
||||
"resolved": "https://registry.npmjs.org/@swc/core-darwin-x64/-/core-darwin-x64-1.13.5.tgz",
|
||||
"integrity": "sha512-ILd38Fg/w23vHb0yVjlWvQBoE37ZJTdlLHa8LRCFDdX4WKfnVBiblsCU9ar4QTMNdeTBEX9iUF4IrbNWhaF1Ng==",
|
||||
"version": "1.13.19",
|
||||
"resolved": "https://registry.npmjs.org/@swc/core-darwin-x64/-/core-darwin-x64-1.13.19.tgz",
|
||||
"integrity": "sha512-+w5DYrJndSygFFRDcuPYmx5BljD6oYnAohZ15K1L6SfORHp/BTSIbgSFRKPoyhjuIkDiq3W0um8RoMTOBAcQjQ==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
|
|
@ -1420,9 +1421,9 @@
|
|||
}
|
||||
},
|
||||
"node_modules/@swc/core-linux-arm-gnueabihf": {
|
||||
"version": "1.13.5",
|
||||
"resolved": "https://registry.npmjs.org/@swc/core-linux-arm-gnueabihf/-/core-linux-arm-gnueabihf-1.13.5.tgz",
|
||||
"integrity": "sha512-Q6eS3Pt8GLkXxqz9TAw+AUk9HpVJt8Uzm54MvPsqp2yuGmY0/sNaPPNVqctCX9fu/Nu8eaWUen0si6iEiCsazQ==",
|
||||
"version": "1.13.19",
|
||||
"resolved": "https://registry.npmjs.org/@swc/core-linux-arm-gnueabihf/-/core-linux-arm-gnueabihf-1.13.19.tgz",
|
||||
"integrity": "sha512-7LlfgpdwwYq2q7himNkAAFo4q6jysMLFNoBH6GRP7WL29NcSsl5mPMJjmYZymK+sYq/9MTVieDTQvChzYDsapw==",
|
||||
"cpu": [
|
||||
"arm"
|
||||
],
|
||||
|
|
@ -1437,9 +1438,9 @@
|
|||
}
|
||||
},
|
||||
"node_modules/@swc/core-linux-arm64-gnu": {
|
||||
"version": "1.13.5",
|
||||
"resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-gnu/-/core-linux-arm64-gnu-1.13.5.tgz",
|
||||
"integrity": "sha512-aNDfeN+9af+y+M2MYfxCzCy/VDq7Z5YIbMqRI739o8Ganz6ST+27kjQFd8Y/57JN/hcnUEa9xqdS3XY7WaVtSw==",
|
||||
"version": "1.13.19",
|
||||
"resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-gnu/-/core-linux-arm64-gnu-1.13.19.tgz",
|
||||
"integrity": "sha512-ml3I6Lm2marAQ3UC/TS9t/yILBh/eDSVHAdPpikp652xouWAVW1znUeV6bBSxe1sSZIenv+p55ubKAWq/u84sQ==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
|
|
@ -1454,9 +1455,9 @@
|
|||
}
|
||||
},
|
||||
"node_modules/@swc/core-linux-arm64-musl": {
|
||||
"version": "1.13.5",
|
||||
"resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-musl/-/core-linux-arm64-musl-1.13.5.tgz",
|
||||
"integrity": "sha512-9+ZxFN5GJag4CnYnq6apKTnnezpfJhCumyz0504/JbHLo+Ue+ZtJnf3RhyA9W9TINtLE0bC4hKpWi8ZKoETyOQ==",
|
||||
"version": "1.13.19",
|
||||
"resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-musl/-/core-linux-arm64-musl-1.13.19.tgz",
|
||||
"integrity": "sha512-M/otFc3/rWWkbF6VgbOXVzUKVoE7MFcphTaStxJp4bwb7oP5slYlxMZN51Dk/OTOfvCDo9pTAFDKNyixbkXMDQ==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
|
|
@ -1471,9 +1472,9 @@
|
|||
}
|
||||
},
|
||||
"node_modules/@swc/core-linux-x64-gnu": {
|
||||
"version": "1.13.5",
|
||||
"resolved": "https://registry.npmjs.org/@swc/core-linux-x64-gnu/-/core-linux-x64-gnu-1.13.5.tgz",
|
||||
"integrity": "sha512-WD530qvHrki8Ywt/PloKUjaRKgstQqNGvmZl54g06kA+hqtSE2FTG9gngXr3UJxYu/cNAjJYiBifm7+w4nbHbA==",
|
||||
"version": "1.13.19",
|
||||
"resolved": "https://registry.npmjs.org/@swc/core-linux-x64-gnu/-/core-linux-x64-gnu-1.13.19.tgz",
|
||||
"integrity": "sha512-NoMUKaOJEdouU4tKF88ggdDHFiRRING+gYLxDqnTfm+sUXaizB5OGBRzvSVDYSXQb1SuUuChnXFPFzwTWbt3ZQ==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
|
|
@ -1488,9 +1489,9 @@
|
|||
}
|
||||
},
|
||||
"node_modules/@swc/core-linux-x64-musl": {
|
||||
"version": "1.13.5",
|
||||
"resolved": "https://registry.npmjs.org/@swc/core-linux-x64-musl/-/core-linux-x64-musl-1.13.5.tgz",
|
||||
"integrity": "sha512-Luj8y4OFYx4DHNQTWjdIuKTq2f5k6uSXICqx+FSabnXptaOBAbJHNbHT/06JZh6NRUouaf0mYXN0mcsqvkhd7Q==",
|
||||
"version": "1.13.19",
|
||||
"resolved": "https://registry.npmjs.org/@swc/core-linux-x64-musl/-/core-linux-x64-musl-1.13.19.tgz",
|
||||
"integrity": "sha512-r6krlZwyu8SBaw24QuS1lau2I9q8M+eJV6ITz0rpb6P1Bx0elf9ii5Bhh8ddmIqXXH8kOGSjC/dwcdHbZqAhgw==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
|
|
@ -1505,9 +1506,9 @@
|
|||
}
|
||||
},
|
||||
"node_modules/@swc/core-win32-arm64-msvc": {
|
||||
"version": "1.13.5",
|
||||
"resolved": "https://registry.npmjs.org/@swc/core-win32-arm64-msvc/-/core-win32-arm64-msvc-1.13.5.tgz",
|
||||
"integrity": "sha512-cZ6UpumhF9SDJvv4DA2fo9WIzlNFuKSkZpZmPG1c+4PFSEMy5DFOjBSllCvnqihCabzXzpn6ykCwBmHpy31vQw==",
|
||||
"version": "1.13.19",
|
||||
"resolved": "https://registry.npmjs.org/@swc/core-win32-arm64-msvc/-/core-win32-arm64-msvc-1.13.19.tgz",
|
||||
"integrity": "sha512-awcZSIuxyVn0Dw28VjMvgk1qiDJ6CeQwHkZNUjg2UxVlq23zE01NMMp+zkoGFypmLG9gaGmJSzuoqvk/WCQ5tw==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
|
|
@ -1522,9 +1523,9 @@
|
|||
}
|
||||
},
|
||||
"node_modules/@swc/core-win32-ia32-msvc": {
|
||||
"version": "1.13.5",
|
||||
"resolved": "https://registry.npmjs.org/@swc/core-win32-ia32-msvc/-/core-win32-ia32-msvc-1.13.5.tgz",
|
||||
"integrity": "sha512-C5Yi/xIikrFUzZcyGj9L3RpKljFvKiDMtyDzPKzlsDrKIw2EYY+bF88gB6oGY5RGmv4DAX8dbnpRAqgFD0FMEw==",
|
||||
"version": "1.13.19",
|
||||
"resolved": "https://registry.npmjs.org/@swc/core-win32-ia32-msvc/-/core-win32-ia32-msvc-1.13.19.tgz",
|
||||
"integrity": "sha512-H5d+KO7ISoLNgYvTbOcCQjJZNM3R7yaYlrMAF13lUr6GSiOUX+92xtM31B+HvzAWI7HtvVe74d29aC1b1TpXFA==",
|
||||
"cpu": [
|
||||
"ia32"
|
||||
],
|
||||
|
|
@ -1539,9 +1540,9 @@
|
|||
}
|
||||
},
|
||||
"node_modules/@swc/core-win32-x64-msvc": {
|
||||
"version": "1.13.5",
|
||||
"resolved": "https://registry.npmjs.org/@swc/core-win32-x64-msvc/-/core-win32-x64-msvc-1.13.5.tgz",
|
||||
"integrity": "sha512-YrKdMVxbYmlfybCSbRtrilc6UA8GF5aPmGKBdPvjrarvsmf4i7ZHGCEnLtfOMd3Lwbs2WUZq3WdMbozYeLU93Q==",
|
||||
"version": "1.13.19",
|
||||
"resolved": "https://registry.npmjs.org/@swc/core-win32-x64-msvc/-/core-win32-x64-msvc-1.13.19.tgz",
|
||||
"integrity": "sha512-qNoyCpXvv2O3JqXKanRIeoMn03Fho/As+N4Fhe7u0FsYh4VYqGQah4DGDzEP/yjl4Gx1IElhqLGDhCCGMwWaDw==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
|
|
@ -1871,9 +1872,9 @@
|
|||
}
|
||||
},
|
||||
"node_modules/@tailwindcss/typography": {
|
||||
"version": "0.5.18",
|
||||
"resolved": "https://registry.npmjs.org/@tailwindcss/typography/-/typography-0.5.18.tgz",
|
||||
"integrity": "sha512-dDIgwZOlf+tVkZ7A029VvQ1+ngKATENDjMEx2N35s2yPjfTS05RWSM8ilhEWSa5DMJ6ci2Ha9WNZEd2GQjrdQg==",
|
||||
"version": "0.5.19",
|
||||
"resolved": "https://registry.npmjs.org/@tailwindcss/typography/-/typography-0.5.19.tgz",
|
||||
"integrity": "sha512-w31dd8HOx3k9vPtcQh5QHP9GwKcgbMp87j58qi6xgiBnFFtKEAgCWnDw4qUT8aHwkCp8bKvb/KGKWWHedP0AAg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
|
|
@ -2007,9 +2008,9 @@
|
|||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@types/react": {
|
||||
"version": "19.1.13",
|
||||
"resolved": "https://registry.npmjs.org/@types/react/-/react-19.1.13.tgz",
|
||||
"integrity": "sha512-hHkbU/eoO3EG5/MZkuFSKmYqPbSVk5byPFa3e7y/8TybHiLMACgI8seVYlicwk7H5K/rI2px9xrQp/C+AUDTiQ==",
|
||||
"version": "19.1.14",
|
||||
"resolved": "https://registry.npmjs.org/@types/react/-/react-19.1.14.tgz",
|
||||
"integrity": "sha512-ukd93VGzaNPMAUPy0gRDSC57UuQbnH9Kussp7HBjM06YFi9uZTFhOvMSO2OKqXm1rSgzOE+pVx1k1PYHGwlc8Q==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"csstype": "^3.0.2"
|
||||
|
|
@ -2679,9 +2680,9 @@
|
|||
"license": "MIT"
|
||||
},
|
||||
"node_modules/baseline-browser-mapping": {
|
||||
"version": "2.8.6",
|
||||
"resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.8.6.tgz",
|
||||
"integrity": "sha512-wrH5NNqren/QMtKUEEJf7z86YjfqW/2uw3IL3/xpqZUC95SSVIFXYQeeGjL6FT/X68IROu6RMehZQS5foy2BXw==",
|
||||
"version": "2.8.7",
|
||||
"resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.8.7.tgz",
|
||||
"integrity": "sha512-bxxN2M3a4d1CRoQC//IqsR5XrLh0IJ8TCv2x6Y9N0nckNz/rTjZB3//GGscZziZOxmjP55rzxg/ze7usFI9FqQ==",
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"bin": {
|
||||
|
|
@ -2802,9 +2803,9 @@
|
|||
}
|
||||
},
|
||||
"node_modules/caniuse-lite": {
|
||||
"version": "1.0.30001743",
|
||||
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001743.tgz",
|
||||
"integrity": "sha512-e6Ojr7RV14Un7dz6ASD0aZDmQPT/A+eZU+nuTNfjqmRrmkmQlnTNWH0SKmqagx9PeW87UVqapSurtAXifmtdmw==",
|
||||
"version": "1.0.30001745",
|
||||
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001745.tgz",
|
||||
"integrity": "sha512-ywt6i8FzvdgrrrGbr1jZVObnVv6adj+0if2/omv9cmR2oiZs30zL4DIyaptKcbOrBdOIc74QTMoJvSE2QHh5UQ==",
|
||||
"dev": true,
|
||||
"funding": [
|
||||
{
|
||||
|
|
@ -3185,9 +3186,9 @@
|
|||
}
|
||||
},
|
||||
"node_modules/detect-libc": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.0.tgz",
|
||||
"integrity": "sha512-vEtk+OcP7VBRtQZ1EJ3bdgzSfBjgnEalLTp5zjJrS+2Z1w2KZly4SBdac/WDU3hhsNAZ9E8SC96ME4Ey8MZ7cg==",
|
||||
"version": "2.1.1",
|
||||
"resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.1.tgz",
|
||||
"integrity": "sha512-ecqj/sy1jcK1uWrwpR67UhYrIFQ+5WlGxth34WquCbamhFA6hkkwiu37o6J5xCHdo1oixJRfVRw+ywV+Hq/0Aw==",
|
||||
"devOptional": true,
|
||||
"license": "Apache-2.0",
|
||||
"engines": {
|
||||
|
|
@ -3221,9 +3222,9 @@
|
|||
}
|
||||
},
|
||||
"node_modules/electron-to-chromium": {
|
||||
"version": "1.5.222",
|
||||
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.222.tgz",
|
||||
"integrity": "sha512-gA7psSwSwQRE60CEoLz6JBCQPIxNeuzB2nL8vE03GK/OHxlvykbLyeiumQy1iH5C2f3YbRAZpGCMT12a/9ih9w==",
|
||||
"version": "1.5.224",
|
||||
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.224.tgz",
|
||||
"integrity": "sha512-kWAoUu/bwzvnhpdZSIc6KUyvkI1rbRXMT0Eq8pKReyOyaPZcctMli+EgvcN1PAvwVc7Tdo4Fxi2PsLNDU05mdg==",
|
||||
"dev": true,
|
||||
"license": "ISC"
|
||||
},
|
||||
|
|
@ -3716,9 +3717,9 @@
|
|||
}
|
||||
},
|
||||
"node_modules/eslint-plugin-react-refresh": {
|
||||
"version": "0.4.21",
|
||||
"resolved": "https://registry.npmjs.org/eslint-plugin-react-refresh/-/eslint-plugin-react-refresh-0.4.21.tgz",
|
||||
"integrity": "sha512-MWDWTtNC4voTcWDxXbdmBNe8b/TxfxRFUL6hXgKWJjN9c1AagYEmpiFWBWzDw+5H3SulWUe1pJKTnoSdmk88UA==",
|
||||
"version": "0.4.22",
|
||||
"resolved": "https://registry.npmjs.org/eslint-plugin-react-refresh/-/eslint-plugin-react-refresh-0.4.22.tgz",
|
||||
"integrity": "sha512-atkAG6QaJMGoTLc4MDAP+rqZcfwQuTIh2IqHWFLy2TEjxr0MOK+5BSG4RzL2564AAPpZkDRsZXAUz68kjnU6Ug==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peerDependencies": {
|
||||
|
|
@ -4055,12 +4056,12 @@
|
|||
}
|
||||
},
|
||||
"node_modules/framer-motion": {
|
||||
"version": "12.23.18",
|
||||
"resolved": "https://registry.npmjs.org/framer-motion/-/framer-motion-12.23.18.tgz",
|
||||
"integrity": "sha512-HBVXBL5x3nk/0WrYM5G4VgjBey99ytVYET5AX17s/pcnlH90cyaxVUqgoN8cpF4+PqZRVOhwWsv28F+hxA9Tzg==",
|
||||
"version": "12.23.22",
|
||||
"resolved": "https://registry.npmjs.org/framer-motion/-/framer-motion-12.23.22.tgz",
|
||||
"integrity": "sha512-ZgGvdxXCw55ZYvhoZChTlG6pUuehecgvEAJz0BHoC5pQKW1EC5xf1Mul1ej5+ai+pVY0pylyFfdl45qnM1/GsA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"motion-dom": "^12.23.18",
|
||||
"motion-dom": "^12.23.21",
|
||||
"motion-utils": "^12.23.6",
|
||||
"tslib": "^2.4.0"
|
||||
},
|
||||
|
|
@ -5296,9 +5297,9 @@
|
|||
}
|
||||
},
|
||||
"node_modules/motion-dom": {
|
||||
"version": "12.23.18",
|
||||
"resolved": "https://registry.npmjs.org/motion-dom/-/motion-dom-12.23.18.tgz",
|
||||
"integrity": "sha512-9piw3uOcP6DpS0qpnDF95bLDzmgMxLOg/jghLnHwYJ0YFizzuvbH/L8106dy39JNgHYmXFUTztoP9JQvUqlBwQ==",
|
||||
"version": "12.23.21",
|
||||
"resolved": "https://registry.npmjs.org/motion-dom/-/motion-dom-12.23.21.tgz",
|
||||
"integrity": "sha512-5xDXx/AbhrfgsQmSE7YESMn4Dpo6x5/DTZ4Iyy4xqDvVHWvFVoV+V2Ri2S/ksx+D40wrZ7gPYiMWshkdoqNgNQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"motion-utils": "^12.23.6"
|
||||
|
|
@ -5903,9 +5904,9 @@
|
|||
}
|
||||
},
|
||||
"node_modules/react-router": {
|
||||
"version": "7.9.1",
|
||||
"resolved": "https://registry.npmjs.org/react-router/-/react-router-7.9.1.tgz",
|
||||
"integrity": "sha512-pfAByjcTpX55mqSDGwGnY9vDCpxqBLASg0BMNAuMmpSGESo/TaOUG6BllhAtAkCGx8Rnohik/XtaqiYUJtgW2g==",
|
||||
"version": "7.9.3",
|
||||
"resolved": "https://registry.npmjs.org/react-router/-/react-router-7.9.3.tgz",
|
||||
"integrity": "sha512-4o2iWCFIwhI/eYAIL43+cjORXYn/aRQPgtFRRZb3VzoyQ5Uej0Bmqj7437L97N9NJW4wnicSwLOLS+yCXfAPgg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"cookie": "^1.0.1",
|
||||
|
|
@ -6080,9 +6081,9 @@
|
|||
}
|
||||
},
|
||||
"node_modules/rollup": {
|
||||
"version": "4.52.0",
|
||||
"resolved": "https://registry.npmjs.org/rollup/-/rollup-4.52.0.tgz",
|
||||
"integrity": "sha512-+IuescNkTJQgX7AkIDtITipZdIGcWF0pnVvZTWStiazUmcGA2ag8dfg0urest2XlXUi9kuhfQ+qmdc5Stc3z7g==",
|
||||
"version": "4.52.2",
|
||||
"resolved": "https://registry.npmjs.org/rollup/-/rollup-4.52.2.tgz",
|
||||
"integrity": "sha512-I25/2QgoROE1vYV+NQ1En9T9UFB9Cmfm2CJ83zZOlaDpvz29wGQSZXWKw7MiNXau7wYgB/T9fVIdIuEQ+KbiiA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@types/estree": "1.0.8"
|
||||
|
|
@ -6095,28 +6096,28 @@
|
|||
"npm": ">=8.0.0"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"@rollup/rollup-android-arm-eabi": "4.52.0",
|
||||
"@rollup/rollup-android-arm64": "4.52.0",
|
||||
"@rollup/rollup-darwin-arm64": "4.52.0",
|
||||
"@rollup/rollup-darwin-x64": "4.52.0",
|
||||
"@rollup/rollup-freebsd-arm64": "4.52.0",
|
||||
"@rollup/rollup-freebsd-x64": "4.52.0",
|
||||
"@rollup/rollup-linux-arm-gnueabihf": "4.52.0",
|
||||
"@rollup/rollup-linux-arm-musleabihf": "4.52.0",
|
||||
"@rollup/rollup-linux-arm64-gnu": "4.52.0",
|
||||
"@rollup/rollup-linux-arm64-musl": "4.52.0",
|
||||
"@rollup/rollup-linux-loong64-gnu": "4.52.0",
|
||||
"@rollup/rollup-linux-ppc64-gnu": "4.52.0",
|
||||
"@rollup/rollup-linux-riscv64-gnu": "4.52.0",
|
||||
"@rollup/rollup-linux-riscv64-musl": "4.52.0",
|
||||
"@rollup/rollup-linux-s390x-gnu": "4.52.0",
|
||||
"@rollup/rollup-linux-x64-gnu": "4.52.0",
|
||||
"@rollup/rollup-linux-x64-musl": "4.52.0",
|
||||
"@rollup/rollup-openharmony-arm64": "4.52.0",
|
||||
"@rollup/rollup-win32-arm64-msvc": "4.52.0",
|
||||
"@rollup/rollup-win32-ia32-msvc": "4.52.0",
|
||||
"@rollup/rollup-win32-x64-gnu": "4.52.0",
|
||||
"@rollup/rollup-win32-x64-msvc": "4.52.0",
|
||||
"@rollup/rollup-android-arm-eabi": "4.52.2",
|
||||
"@rollup/rollup-android-arm64": "4.52.2",
|
||||
"@rollup/rollup-darwin-arm64": "4.52.2",
|
||||
"@rollup/rollup-darwin-x64": "4.52.2",
|
||||
"@rollup/rollup-freebsd-arm64": "4.52.2",
|
||||
"@rollup/rollup-freebsd-x64": "4.52.2",
|
||||
"@rollup/rollup-linux-arm-gnueabihf": "4.52.2",
|
||||
"@rollup/rollup-linux-arm-musleabihf": "4.52.2",
|
||||
"@rollup/rollup-linux-arm64-gnu": "4.52.2",
|
||||
"@rollup/rollup-linux-arm64-musl": "4.52.2",
|
||||
"@rollup/rollup-linux-loong64-gnu": "4.52.2",
|
||||
"@rollup/rollup-linux-ppc64-gnu": "4.52.2",
|
||||
"@rollup/rollup-linux-riscv64-gnu": "4.52.2",
|
||||
"@rollup/rollup-linux-riscv64-musl": "4.52.2",
|
||||
"@rollup/rollup-linux-s390x-gnu": "4.52.2",
|
||||
"@rollup/rollup-linux-x64-gnu": "4.52.2",
|
||||
"@rollup/rollup-linux-x64-musl": "4.52.2",
|
||||
"@rollup/rollup-openharmony-arm64": "4.52.2",
|
||||
"@rollup/rollup-win32-arm64-msvc": "4.52.2",
|
||||
"@rollup/rollup-win32-ia32-msvc": "4.52.2",
|
||||
"@rollup/rollup-win32-x64-gnu": "4.52.2",
|
||||
"@rollup/rollup-win32-x64-msvc": "4.52.2",
|
||||
"fsevents": "~2.3.2"
|
||||
}
|
||||
},
|
||||
|
|
@ -6559,9 +6560,9 @@
|
|||
}
|
||||
},
|
||||
"node_modules/tar": {
|
||||
"version": "7.4.4",
|
||||
"resolved": "https://registry.npmjs.org/tar/-/tar-7.4.4.tgz",
|
||||
"integrity": "sha512-O1z7ajPkjTgEgmTGz0v9X4eqeEXTDREPTO77pVC1Nbs86feBU1Zhdg+edzavPmYW1olxkwsqA2v4uOw6E8LeDg==",
|
||||
"version": "7.5.1",
|
||||
"resolved": "https://registry.npmjs.org/tar/-/tar-7.5.1.tgz",
|
||||
"integrity": "sha512-nlGpxf+hv0v7GkWBK2V9spgactGOp0qvfWRxUMjqHyzrt3SgwE48DIv/FhqPHJYLHpgW1opq3nERbz5Anq7n1g==",
|
||||
"dev": true,
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
|
|
@ -6880,6 +6881,19 @@
|
|||
"dev": true,
|
||||
"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": {
|
||||
"version": "13.15.15",
|
||||
"resolved": "https://registry.npmjs.org/validator/-/validator-13.15.15.tgz",
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"name": "kvm-ui",
|
||||
"private": true,
|
||||
"version": "2025.09.23.0000",
|
||||
"version": "2025.09.26.01300",
|
||||
"type": "module",
|
||||
"engines": {
|
||||
"node": "^22.15.0"
|
||||
|
|
@ -19,7 +19,7 @@
|
|||
"preview": "vite preview"
|
||||
},
|
||||
"dependencies": {
|
||||
"@headlessui/react": "^2.2.8",
|
||||
"@headlessui/react": "^2.2.9",
|
||||
"@headlessui/tailwindcss": "^0.2.2",
|
||||
"@heroicons/react": "^2.2.0",
|
||||
"@vitejs/plugin-basic-ssl": "^2.1.0",
|
||||
|
|
@ -33,7 +33,7 @@
|
|||
"dayjs": "^1.11.18",
|
||||
"eslint-import-resolver-alias": "^1.1.2",
|
||||
"focus-trap-react": "^11.0.4",
|
||||
"framer-motion": "^12.23.18",
|
||||
"framer-motion": "^12.23.22",
|
||||
"lodash.throttle": "^4.1.1",
|
||||
"mini-svg-data-uri": "^1.4.4",
|
||||
"react": "^19.1.1",
|
||||
|
|
@ -41,13 +41,14 @@
|
|||
"react-dom": "^19.1.1",
|
||||
"react-hot-toast": "^2.6.0",
|
||||
"react-icons": "^5.5.0",
|
||||
"react-router": "^7.9.1",
|
||||
"react-router": "^7.9.3",
|
||||
"react-simple-keyboard": "^3.8.122",
|
||||
"react-use-websocket": "^4.13.0",
|
||||
"react-xtermjs": "^1.0.10",
|
||||
"recharts": "^3.2.1",
|
||||
"tailwind-merge": "^3.3.1",
|
||||
"usehooks-ts": "^3.1.1",
|
||||
"uuid": "^13.0.0",
|
||||
"validator": "^13.15.15",
|
||||
"zustand": "^4.5.2"
|
||||
},
|
||||
|
|
@ -57,9 +58,9 @@
|
|||
"@eslint/js": "^9.36.0",
|
||||
"@tailwindcss/forms": "^0.5.10",
|
||||
"@tailwindcss/postcss": "^4.1.13",
|
||||
"@tailwindcss/typography": "^0.5.18",
|
||||
"@tailwindcss/typography": "^0.5.19",
|
||||
"@tailwindcss/vite": "^4.1.13",
|
||||
"@types/react": "^19.1.13",
|
||||
"@types/react": "^19.1.14",
|
||||
"@types/react-dom": "^19.1.9",
|
||||
"@types/semver": "^7.7.1",
|
||||
"@types/validator": "^13.15.3",
|
||||
|
|
@ -72,7 +73,7 @@
|
|||
"eslint-plugin-import": "^2.32.0",
|
||||
"eslint-plugin-react": "^7.37.5",
|
||||
"eslint-plugin-react-hooks": "^5.2.0",
|
||||
"eslint-plugin-react-refresh": "^0.4.21",
|
||||
"eslint-plugin-react-refresh": "^0.4.22",
|
||||
"globals": "^16.4.0",
|
||||
"postcss": "^8.5.6",
|
||||
"prettier": "^3.6.2",
|
||||
|
|
|
|||
|
|
@ -0,0 +1,40 @@
|
|||
import { cx } from "@/cva.config";
|
||||
import LoadingSpinner from "@components/LoadingSpinner";
|
||||
|
||||
interface SettingsItemProps {
|
||||
readonly title: string;
|
||||
readonly description: string | React.ReactNode;
|
||||
readonly badge?: string;
|
||||
readonly className?: string;
|
||||
readonly loading?: boolean;
|
||||
readonly children?: React.ReactNode;
|
||||
}
|
||||
|
||||
export function SettingsItem(props: SettingsItemProps) {
|
||||
const { title, description, badge, children, className, loading } = props;
|
||||
|
||||
return (
|
||||
<label
|
||||
className={cx(
|
||||
"flex select-none items-center justify-between gap-x-8 rounded",
|
||||
className,
|
||||
)}
|
||||
>
|
||||
<div className="space-y-0.5">
|
||||
<div className="flex items-center gap-x-2">
|
||||
<div className="flex items-center text-base font-semibold text-black dark:text-white">
|
||||
{title}
|
||||
{badge && (
|
||||
<span className="ml-2 rounded-full bg-red-500 px-2 py-1 text-[10px] font-medium leading-none text-white dark:border dark:border-red-700 dark:bg-red-800 dark:text-red-50">
|
||||
{badge}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
{loading && <LoadingSpinner className="h-4 w-4 text-blue-500" />}
|
||||
</div>
|
||||
<div className="text-sm text-slate-700 dark:text-slate-300">{description}</div>
|
||||
</div>
|
||||
{children ? <div>{children}</div> : null}
|
||||
</label>
|
||||
);
|
||||
}
|
||||
|
|
@ -1,8 +1,9 @@
|
|||
import { useCallback , useEffect, useState } from "react";
|
||||
|
||||
import { SettingsItem } from "@components/SettingsItem";
|
||||
|
||||
import { JsonRpcResponse, useJsonRpc } from "../hooks/useJsonRpc";
|
||||
import notifications from "../notifications";
|
||||
import { SettingsItem } from "../routes/devices.$id.settings";
|
||||
|
||||
import Checkbox from "./Checkbox";
|
||||
import { Button } from "./Button";
|
||||
|
|
|
|||
|
|
@ -1,12 +1,11 @@
|
|||
import { useMemo , useCallback , useEffect, useState } from "react";
|
||||
|
||||
import { Button } from "@components/Button";
|
||||
|
||||
import { SettingsItem } from "@components/SettingsItem";
|
||||
|
||||
import { UsbConfigState } from "../hooks/stores";
|
||||
import { JsonRpcResponse, useJsonRpc } from "../hooks/useJsonRpc";
|
||||
import notifications from "../notifications";
|
||||
import { SettingsItem } from "../routes/devices.$id.settings";
|
||||
|
||||
import { InputFieldWithLabel } from "./InputField";
|
||||
import { SelectMenuBasic } from "./SelectMenuBasic";
|
||||
|
|
|
|||
|
|
@ -188,18 +188,18 @@ export default function PasteModal() {
|
|||
type="number"
|
||||
label="Delay between keys"
|
||||
placeholder="Delay between keys"
|
||||
min={50}
|
||||
min={0}
|
||||
max={65534}
|
||||
value={delayValue}
|
||||
onChange={e => {
|
||||
setDelayValue(parseInt(e.target.value, 10));
|
||||
}}
|
||||
/>
|
||||
{delayValue < 50 || delayValue > 65534 && (
|
||||
{delayValue < defaultDelay || delayValue > 65534 && (
|
||||
<div className="mt-2 flex items-center gap-x-2">
|
||||
<ExclamationCircleIcon className="h-4 w-4 text-red-500 dark:text-red-400" />
|
||||
<span className="text-xs text-red-500 dark:text-red-400">
|
||||
Delay must be between 50 and 65534
|
||||
Delay should be between 20 and 65534
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
|
|
|
|||
|
|
@ -1,3 +1,5 @@
|
|||
import { parse as uuidParse , stringify as uuidStringify } from "uuid";
|
||||
|
||||
import { hidKeyBufferSize, KeyboardLedState, KeysDownState } from "./stores";
|
||||
|
||||
export const HID_RPC_MESSAGE_TYPES = {
|
||||
|
|
@ -13,6 +15,7 @@ export const HID_RPC_MESSAGE_TYPES = {
|
|||
KeyboardLedState: 0x32,
|
||||
KeysDownState: 0x33,
|
||||
KeyboardMacroState: 0x34,
|
||||
CancelKeyboardMacroByTokenReport: 0x35,
|
||||
}
|
||||
|
||||
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 {
|
||||
if (data.length < 1) {
|
||||
if (data.length < 2) {
|
||||
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 {
|
||||
token: string;
|
||||
|
||||
constructor() {
|
||||
constructor(token: string) {
|
||||
super(HID_RPC_MESSAGE_TYPES.CancelKeyboardMacroReport);
|
||||
this.token = (token == null || token === undefined || token === "")
|
||||
? "00000000-0000-0000-0000-000000000000"
|
||||
: token;
|
||||
}
|
||||
|
||||
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.KeyboardMacroState]: KeyboardMacroStateMessage,
|
||||
[HID_RPC_MESSAGE_TYPES.KeypressKeepAliveReport]: KeypressKeepAliveMessage,
|
||||
[HID_RPC_MESSAGE_TYPES.CancelKeyboardMacroByTokenReport]: CancelKeyboardMacroReportMessage,
|
||||
}
|
||||
|
||||
export const unmarshalHidRpcMessage = (data: Uint8Array): RpcMessage | undefined => {
|
||||
|
|
|
|||
|
|
@ -142,7 +142,7 @@ export function useHidRpc(onHidRpcMessage?: (payload: RpcMessage) => void) {
|
|||
|
||||
const cancelOngoingKeyboardMacro = useCallback(
|
||||
() => {
|
||||
sendMessage(new CancelKeyboardMacroReportMessage());
|
||||
sendMessage(new CancelKeyboardMacroReportMessage(""));
|
||||
},
|
||||
[sendMessage],
|
||||
);
|
||||
|
|
|
|||
|
|
@ -277,7 +277,6 @@ export default function useKeyboard() {
|
|||
cancelKeepAlive();
|
||||
}, [cancelKeepAlive]);
|
||||
|
||||
|
||||
// executeMacro is used to execute a macro consisting of multiple steps.
|
||||
// Each step can have multiple keys, multiple modifiers and a delay.
|
||||
// The keys and modifiers are pressed together and held for the delay duration.
|
||||
|
|
@ -292,9 +291,7 @@ export default function useKeyboard() {
|
|||
for (const [_, step] of steps.entries()) {
|
||||
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);
|
||||
|
||||
// If the step has keys and/or modifiers, press them and hold for the delay
|
||||
|
|
@ -306,6 +303,7 @@ export default function useKeyboard() {
|
|||
|
||||
sendKeyboardMacroEventHidRpc(macro);
|
||||
}, [sendKeyboardMacroEventHidRpc]);
|
||||
|
||||
const executeMacroClientSide = useCallback(async (steps: MacroSteps) => {
|
||||
const promises: (() => Promise<void>)[] = [];
|
||||
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ import { GridCard } from "@/components/Card";
|
|||
import { Button, LinkButton } from "@/components/Button";
|
||||
import { InputFieldWithLabel } from "@/components/InputField";
|
||||
import { SelectMenuBasic } from "@/components/SelectMenuBasic";
|
||||
import { SettingsItem } from "@components/SettingsItem";
|
||||
import { SettingsSectionHeader } from "@/components/SettingsSectionHeader";
|
||||
import { useDeviceUiNavigation } from "@/hooks/useAppNavigation";
|
||||
import notifications from "@/notifications";
|
||||
|
|
@ -18,7 +19,6 @@ import { isOnDevice } from "@/main";
|
|||
import { TextAreaWithLabel } from "@components/TextArea";
|
||||
|
||||
import { LocalDevice } from "./devices.$id";
|
||||
import { SettingsItem } from "./devices.$id.settings";
|
||||
import { CloudState } from "./adopt";
|
||||
|
||||
export interface TLSState {
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
import { useCallback, useEffect, useState } from "react";
|
||||
|
||||
import { GridCard } from "@components/Card";
|
||||
import { SettingsItem } from "@components/SettingsItem";
|
||||
|
||||
import { Button } from "../components/Button";
|
||||
import Checkbox from "../components/Checkbox";
|
||||
|
|
@ -12,8 +13,6 @@ import { JsonRpcResponse, useJsonRpc } from "../hooks/useJsonRpc";
|
|||
import { isOnDevice } from "../main";
|
||||
import notifications from "../notifications";
|
||||
|
||||
import { SettingsItem } from "./devices.$id.settings";
|
||||
|
||||
export default function SettingsAdvancedRoute() {
|
||||
const { send } = useJsonRpc();
|
||||
|
||||
|
|
|
|||
|
|
@ -1,10 +1,10 @@
|
|||
import { useCallback, useState } from "react";
|
||||
|
||||
import { SettingsItem } from "@components/SettingsItem";
|
||||
|
||||
import { SettingsPageHeader } from "../components/SettingsPageheader";
|
||||
import { SelectMenuBasic } from "../components/SelectMenuBasic";
|
||||
|
||||
import { SettingsItem } from "./devices.$id.settings";
|
||||
|
||||
export default function SettingsAppearanceRoute() {
|
||||
const [currentTheme, setCurrentTheme] = useState(() => {
|
||||
return localStorage.theme || "system";
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
|
||||
import { useState , useEffect } from "react";
|
||||
|
||||
import { SettingsItem } from "@components/SettingsItem";
|
||||
import { JsonRpcResponse, useJsonRpc } from "@/hooks/useJsonRpc";
|
||||
|
||||
import { SettingsPageHeader } from "../components/SettingsPageheader";
|
||||
|
|
@ -10,7 +11,6 @@ import Checkbox from "../components/Checkbox";
|
|||
import { useDeviceUiNavigation } from "../hooks/useAppNavigation";
|
||||
import { useDeviceStore } from "../hooks/stores";
|
||||
|
||||
import { SettingsItem } from "./devices.$id.settings";
|
||||
|
||||
export default function SettingsGeneralRoute() {
|
||||
const { send } = useJsonRpc();
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import { useEffect } from "react";
|
||||
|
||||
import { SettingsItem } from "@components/SettingsItem";
|
||||
import { SettingsPageHeader } from "@components/SettingsPageheader";
|
||||
import { SettingsItem } from "@routes/devices.$id.settings";
|
||||
import { BacklightSettings, useSettingsStore } from "@/hooks/stores";
|
||||
import { JsonRpcResponse, useJsonRpc } from "@/hooks/useJsonRpc";
|
||||
import { SelectMenuBasic } from "@components/SelectMenuBasic";
|
||||
|
|
|
|||
|
|
@ -3,13 +3,12 @@ import { useCallback, useEffect } from "react";
|
|||
import { useSettingsStore } from "@/hooks/stores";
|
||||
import { JsonRpcResponse, useJsonRpc } from "@/hooks/useJsonRpc";
|
||||
import useKeyboardLayout from "@/hooks/useKeyboardLayout";
|
||||
import { SettingsItem } from "@components/SettingsItem";
|
||||
import { SettingsPageHeader } from "@components/SettingsPageheader";
|
||||
import { Checkbox } from "@/components/Checkbox";
|
||||
import { SelectMenuBasic } from "@/components/SelectMenuBasic";
|
||||
import notifications from "@/notifications";
|
||||
|
||||
import { SettingsItem } from "./devices.$id.settings";
|
||||
|
||||
export default function SettingsKeyboardRoute() {
|
||||
const { setKeyboardLayout } = useSettingsStore();
|
||||
const { showPressedKeys, setShowPressedKeys } = useSettingsStore();
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ import { GridCard } from "@/components/Card";
|
|||
import { Checkbox } from "@/components/Checkbox";
|
||||
import { useSettingsStore } from "@/hooks/stores";
|
||||
import { JsonRpcResponse, useJsonRpc } from "@/hooks/useJsonRpc";
|
||||
import { SettingsItem } from "@components/SettingsItem";
|
||||
import { SettingsPageHeader } from "@components/SettingsPageheader";
|
||||
import { SelectMenuBasic } from "@components/SelectMenuBasic";
|
||||
import { JigglerSetting } from "@components/JigglerSetting";
|
||||
|
|
@ -15,8 +16,6 @@ import { cx } from "../cva.config";
|
|||
import notifications from "../notifications";
|
||||
import SettingsNestedSection from "../components/SettingsNestedSection";
|
||||
|
||||
import { SettingsItem } from "./devices.$id.settings";
|
||||
|
||||
export interface JigglerConfig {
|
||||
inactivity_limit_seconds: number;
|
||||
jitter_percentage: number;
|
||||
|
|
|
|||
|
|
@ -21,6 +21,7 @@ import { SelectMenuBasic } from "@/components/SelectMenuBasic";
|
|||
import { SettingsPageHeader } from "@/components/SettingsPageheader";
|
||||
import Fieldset from "@/components/Fieldset";
|
||||
import { ConfirmDialog } from "@/components/ConfirmDialog";
|
||||
import { SettingsItem } from "@components/SettingsItem";
|
||||
import notifications from "@/notifications";
|
||||
|
||||
import Ipv6NetworkCard from "../components/Ipv6NetworkCard";
|
||||
|
|
@ -28,8 +29,6 @@ import EmptyCard from "../components/EmptyCard";
|
|||
import AutoHeight from "../components/AutoHeight";
|
||||
import DhcpLeaseCard from "../components/DhcpLeaseCard";
|
||||
|
||||
import { SettingsItem } from "./devices.$id.settings";
|
||||
|
||||
dayjs.extend(relativeTime);
|
||||
|
||||
const defaultNetworkSettings: NetworkSettings = {
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
import React, { useEffect, useRef, useState } from "react";
|
||||
import { NavLink, Outlet, useLocation } from "react-router";
|
||||
import {
|
||||
LuSettings,
|
||||
|
|
@ -12,17 +13,14 @@ import {
|
|||
LuCommand,
|
||||
LuNetwork,
|
||||
} from "react-icons/lu";
|
||||
import React, { useEffect, useRef, useState } from "react";
|
||||
import { useResizeObserver } from "usehooks-ts";
|
||||
|
||||
import Card from "@/components/Card";
|
||||
import { LinkButton } from "@/components/Button";
|
||||
import { FeatureFlag } from "@/components/FeatureFlag";
|
||||
import LoadingSpinner from "@/components/LoadingSpinner";
|
||||
import { cx } from "@/cva.config";
|
||||
import Card from "@components/Card";
|
||||
import { LinkButton } from "@components/Button";
|
||||
import { FeatureFlag } from "@components/FeatureFlag";
|
||||
import { useUiStore } from "@/hooks/stores";
|
||||
|
||||
import { cx } from "../cva.config";
|
||||
|
||||
/* TODO: Migrate to using URLs instead of the global state. To simplify the refactoring, we'll keep the global state for now. */
|
||||
export default function SettingsRoute() {
|
||||
const location = useLocation();
|
||||
|
|
@ -257,40 +255,3 @@ export default function SettingsRoute() {
|
|||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
interface SettingsItemProps {
|
||||
readonly title: string;
|
||||
readonly description: string | React.ReactNode;
|
||||
readonly badge?: string;
|
||||
readonly className?: string;
|
||||
readonly loading?: boolean;
|
||||
readonly children?: React.ReactNode;
|
||||
}
|
||||
export function SettingsItem(props: SettingsItemProps) {
|
||||
const { title, description, badge, children, className, loading } = props;
|
||||
|
||||
return (
|
||||
<label
|
||||
className={cx(
|
||||
"flex select-none items-center justify-between gap-x-8 rounded",
|
||||
className,
|
||||
)}
|
||||
>
|
||||
<div className="space-y-0.5">
|
||||
<div className="flex items-center gap-x-2">
|
||||
<div className="flex items-center text-base font-semibold text-black dark:text-white">
|
||||
{title}
|
||||
{badge && (
|
||||
<span className="ml-2 rounded-full bg-red-500 px-2 py-1 text-[10px] font-medium leading-none text-white dark:border dark:border-red-700 dark:bg-red-800 dark:text-red-50">
|
||||
{badge}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
{loading && <LoadingSpinner className="h-4 w-4 text-blue-500" />}
|
||||
</div>
|
||||
<div className="text-sm text-slate-700 dark:text-slate-300">{description}</div>
|
||||
</div>
|
||||
{children ? <div>{children}</div> : null}
|
||||
</label>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,14 +3,13 @@ import { useCallback, useEffect, useState } from "react";
|
|||
import { Button } from "@/components/Button";
|
||||
import { TextAreaWithLabel } from "@/components/TextArea";
|
||||
import { JsonRpcResponse, useJsonRpc } from "@/hooks/useJsonRpc";
|
||||
import { SettingsItem } from "@components/SettingsItem";
|
||||
import { SettingsPageHeader } from "@components/SettingsPageheader";
|
||||
import { useSettingsStore } from "@/hooks/stores";
|
||||
import { SelectMenuBasic } from "@components/SelectMenuBasic";
|
||||
import Fieldset from "@components/Fieldset";
|
||||
import notifications from "@/notifications";
|
||||
|
||||
import { SettingsItem } from "./devices.$id.settings";
|
||||
|
||||
const defaultEdid =
|
||||
"00ffffffffffff0052620188008888881c150103800000780a0dc9a05747982712484c00000001010101010101010101010101010101023a801871382d40582c4500c48e2100001e011d007251d01e206e285500c48e2100001e000000fc00543734392d6648443732300a20000000fd00147801ff1d000a202020202020017b";
|
||||
const edids = [
|
||||
|
|
|
|||
2
web.go
2
web.go
|
|
@ -230,7 +230,7 @@ func handleWebRTCSession(c *gin.Context) {
|
|||
}
|
||||
|
||||
// Cancel any ongoing keyboard macro when session changes
|
||||
cancelKeyboardMacro()
|
||||
cancelAllRunningKeyboardMacros()
|
||||
|
||||
currentSession = session
|
||||
c.JSON(http.StatusOK, gin.H{"sd": sd})
|
||||
|
|
|
|||
56
webrtc.go
56
webrtc.go
|
|
@ -34,7 +34,7 @@ type Session struct {
|
|||
lastTimerResetTime time.Time // Track when auto-release timer was last reset
|
||||
keepAliveJitterLock sync.Mutex // Protect jitter compensation timing state
|
||||
hidQueueLock sync.Mutex
|
||||
hidQueue []chan hidQueueMessage
|
||||
hidQueues []chan hidQueueMessage
|
||||
|
||||
keysDownStateQueue chan usbgadget.KeysDownState
|
||||
}
|
||||
|
|
@ -48,7 +48,8 @@ func (s *Session) resetKeepAliveTime() {
|
|||
|
||||
type hidQueueMessage struct {
|
||||
webrtc.DataChannelMessage
|
||||
channel string
|
||||
channel string
|
||||
timelimit time.Duration
|
||||
}
|
||||
|
||||
type SessionConfig struct {
|
||||
|
|
@ -93,19 +94,20 @@ func (s *Session) ExchangeOffer(offerStr string) (string, error) {
|
|||
return base64.StdEncoding.EncodeToString(localDescription), nil
|
||||
}
|
||||
|
||||
func (s *Session) initQueues() {
|
||||
func (s *Session) initHidQueues() {
|
||||
s.hidQueueLock.Lock()
|
||||
defer s.hidQueueLock.Unlock()
|
||||
|
||||
s.hidQueue = make([]chan hidQueueMessage, 0)
|
||||
for i := 0; i < 4; i++ {
|
||||
q := make(chan hidQueueMessage, 256)
|
||||
s.hidQueue = append(s.hidQueue, q)
|
||||
}
|
||||
s.hidQueues = make([]chan hidQueueMessage, hidrpc.OtherQueue+1)
|
||||
s.hidQueues[hidrpc.HandshakeQueue] = make(chan hidQueueMessage, 2) // we don't really want to queue many handshake messages
|
||||
s.hidQueues[hidrpc.KeyboardQueue] = make(chan hidQueueMessage, 256)
|
||||
s.hidQueues[hidrpc.MouseQueue] = make(chan hidQueueMessage, 256)
|
||||
s.hidQueues[hidrpc.MacroQueue] = make(chan hidQueueMessage, 10) // macros can be long, but we don't want to queue too many
|
||||
s.hidQueues[hidrpc.OtherQueue] = make(chan hidQueueMessage, 256)
|
||||
}
|
||||
|
||||
func (s *Session) handleQueues(index int) {
|
||||
for msg := range s.hidQueue[index] {
|
||||
func (s *Session) handleQueue(queue chan hidQueueMessage) {
|
||||
for msg := range queue {
|
||||
onHidMessage(msg, s)
|
||||
}
|
||||
}
|
||||
|
|
@ -160,17 +162,18 @@ func getOnHidMessageHandler(session *Session, scopedLogger *zerolog.Logger, chan
|
|||
l.Trace().Msg("received data in HID RPC message handler")
|
||||
|
||||
// Enqueue to ensure ordered processing
|
||||
queueIndex := hidrpc.GetQueueIndex(hidrpc.MessageType(msg.Data[0]))
|
||||
if queueIndex >= len(session.hidQueue) || queueIndex < 0 {
|
||||
queueIndex, timelimit := hidrpc.GetQueueIndex(hidrpc.MessageType(msg.Data[0]))
|
||||
if queueIndex >= len(session.hidQueues) || queueIndex < 0 {
|
||||
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.hidQueues[queueIndex]
|
||||
if queue != nil {
|
||||
queue <- hidQueueMessage{
|
||||
DataChannelMessage: msg,
|
||||
channel: channel,
|
||||
timelimit: timelimit,
|
||||
}
|
||||
} else {
|
||||
l.Warn().Int("queueIndex", queueIndex).Msg("received data in HID RPC message handler, but queue is nil")
|
||||
|
|
@ -220,7 +223,7 @@ func newSession(config SessionConfig) (*Session, error) {
|
|||
|
||||
session := &Session{peerConnection: peerConnection}
|
||||
session.rpcQueue = make(chan webrtc.DataChannelMessage, 256)
|
||||
session.initQueues()
|
||||
session.initHidQueues()
|
||||
session.initKeysDownStateQueue()
|
||||
|
||||
go func() {
|
||||
|
|
@ -230,8 +233,8 @@ func newSession(config SessionConfig) (*Session, error) {
|
|||
}
|
||||
}()
|
||||
|
||||
for i := 0; i < len(session.hidQueue); i++ {
|
||||
go session.handleQueues(i)
|
||||
for queue := range session.hidQueues {
|
||||
go session.handleQueue(session.hidQueues[queue])
|
||||
}
|
||||
|
||||
peerConnection.OnDataChannel(func(d *webrtc.DataChannel) {
|
||||
|
|
@ -256,7 +259,11 @@ func newSession(config SessionConfig) (*Session, error) {
|
|||
session.RPCChannel = d
|
||||
d.OnMessage(func(msg webrtc.DataChannelMessage) {
|
||||
// Enqueue to ensure ordered processing
|
||||
session.rpcQueue <- msg
|
||||
if session.rpcQueue != nil {
|
||||
session.rpcQueue <- msg
|
||||
} else {
|
||||
scopedLogger.Warn().Msg("RPC message received but rpcQueue is nil")
|
||||
}
|
||||
})
|
||||
triggerOTAStateUpdate()
|
||||
triggerVideoStateUpdate()
|
||||
|
|
@ -325,22 +332,23 @@ func newSession(config SessionConfig) (*Session, error) {
|
|||
_ = peerConnection.Close()
|
||||
}
|
||||
if connectionState == webrtc.ICEConnectionStateClosed {
|
||||
scopedLogger.Debug().Msg("ICE Connection State is closed, unmounting virtual media")
|
||||
scopedLogger.Debug().Msg("ICE Connection State is closed, tearing down session")
|
||||
if session == currentSession {
|
||||
// Cancel any ongoing keyboard report multi when session closes
|
||||
cancelKeyboardMacro()
|
||||
cancelAllRunningKeyboardMacros()
|
||||
currentSession = nil
|
||||
}
|
||||
|
||||
// Stop RPC processor
|
||||
if session.rpcQueue != nil {
|
||||
close(session.rpcQueue)
|
||||
session.rpcQueue = nil
|
||||
}
|
||||
|
||||
// Stop HID RPC processor
|
||||
for i := 0; i < len(session.hidQueue); i++ {
|
||||
close(session.hidQueue[i])
|
||||
session.hidQueue[i] = nil
|
||||
// Stop HID RPC processors
|
||||
for i := 0; i < len(session.hidQueues); i++ {
|
||||
close(session.hidQueues[i])
|
||||
session.hidQueues[i] = nil
|
||||
}
|
||||
|
||||
close(session.keysDownStateQueue)
|
||||
|
|
|
|||
Loading…
Reference in New Issue