mirror of
https://github.com/luckfox-eng29/kvm.git
synced 2026-01-18 03:28:19 +01:00
feat: send all paste keystrokes to backend (#789)
* feat: send all paste keystrokes to backend * feat: cancel paste mode * wip: send macro using hidRPC channel * add delay * feat: allow paste progress to be cancelled * allow user to override delay * chore: clear keysDownState * fix: use currentSession.reportHidRPCKeyboardMacroState * fix: jsonrpc.go:1142:21: Error return value is not checked (errcheck) * fix: performance issue of Uint8Array concat * chore: hide delay option when debugMode isn't enabled * feat: use clientSide macro if backend doesn't support macros * fix: update keysDownState handling * minor issues * refactor * fix: send duplicated keyDownState * chore: add max length for paste text --------- Co-authored-by: Adam Shiervani <adam.shiervani@gmail.com>
This commit is contained in:
103
jsonrpc.go
103
jsonrpc.go
@@ -1,6 +1,7 @@
|
||||
package kvm
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
@@ -10,12 +11,14 @@ import (
|
||||
"path/filepath"
|
||||
"reflect"
|
||||
"strconv"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/pion/webrtc/v4"
|
||||
"github.com/rs/zerolog"
|
||||
"go.bug.st/serial"
|
||||
|
||||
"github.com/jetkvm/kvm/internal/hidrpc"
|
||||
"github.com/jetkvm/kvm/internal/usbgadget"
|
||||
"github.com/jetkvm/kvm/internal/utils"
|
||||
)
|
||||
@@ -1056,6 +1059,106 @@ func rpcSetLocalLoopbackOnly(enabled bool) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
var (
|
||||
keyboardMacroCancel context.CancelFunc
|
||||
keyboardMacroLock sync.Mutex
|
||||
)
|
||||
|
||||
// cancelKeyboardMacro cancels any ongoing keyboard macro execution
|
||||
func cancelKeyboardMacro() {
|
||||
keyboardMacroLock.Lock()
|
||||
defer keyboardMacroLock.Unlock()
|
||||
|
||||
if keyboardMacroCancel != nil {
|
||||
keyboardMacroCancel()
|
||||
logger.Info().Msg("canceled keyboard macro")
|
||||
keyboardMacroCancel = nil
|
||||
}
|
||||
}
|
||||
|
||||
func setKeyboardMacroCancel(cancel context.CancelFunc) {
|
||||
keyboardMacroLock.Lock()
|
||||
defer keyboardMacroLock.Unlock()
|
||||
|
||||
keyboardMacroCancel = cancel
|
||||
}
|
||||
|
||||
func rpcExecuteKeyboardMacro(macro []hidrpc.KeyboardMacroStep) (usbgadget.KeysDownState, error) {
|
||||
cancelKeyboardMacro()
|
||||
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
setKeyboardMacroCancel(cancel)
|
||||
|
||||
s := hidrpc.KeyboardMacroState{
|
||||
State: true,
|
||||
IsPaste: true,
|
||||
}
|
||||
|
||||
if currentSession != nil {
|
||||
currentSession.reportHidRPCKeyboardMacroState(s)
|
||||
}
|
||||
|
||||
result, err := rpcDoExecuteKeyboardMacro(ctx, macro)
|
||||
|
||||
setKeyboardMacroCancel(nil)
|
||||
|
||||
s.State = false
|
||||
if currentSession != nil {
|
||||
currentSession.reportHidRPCKeyboardMacroState(s)
|
||||
}
|
||||
|
||||
return result, err
|
||||
}
|
||||
|
||||
func rpcCancelKeyboardMacro() {
|
||||
cancelKeyboardMacro()
|
||||
}
|
||||
|
||||
var keyboardClearStateKeys = make([]byte, hidrpc.HidKeyBufferSize)
|
||||
|
||||
func isClearKeyStep(step hidrpc.KeyboardMacroStep) bool {
|
||||
return step.Modifier == 0 && bytes.Equal(step.Keys, keyboardClearStateKeys)
|
||||
}
|
||||
|
||||
func rpcDoExecuteKeyboardMacro(ctx context.Context, macro []hidrpc.KeyboardMacroStep) (usbgadget.KeysDownState, error) {
|
||||
var last usbgadget.KeysDownState
|
||||
var err error
|
||||
|
||||
logger.Debug().Interface("macro", macro).Msg("Executing keyboard macro")
|
||||
|
||||
for i, step := range macro {
|
||||
delay := time.Duration(step.Delay) * time.Millisecond
|
||||
|
||||
last, err = rpcKeyboardReport(step.Modifier, step.Keys)
|
||||
if err != nil {
|
||||
logger.Warn().Err(err).Msg("failed to execute keyboard macro")
|
||||
return last, 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)
|
||||
if err != nil {
|
||||
logger.Warn().Err(err).Msg("failed to reset keyboard state")
|
||||
}
|
||||
|
||||
logger.Debug().Int("step", i).Msg("Keyboard macro cancelled during sleep")
|
||||
return last, ctx.Err()
|
||||
}
|
||||
}
|
||||
|
||||
return last, nil
|
||||
}
|
||||
|
||||
var rpcHandlers = map[string]RPCHandler{
|
||||
"ping": {Func: rpcPing},
|
||||
"reboot": {Func: rpcReboot, Params: []string{"force"}},
|
||||
|
||||
Reference in New Issue
Block a user