1
1
package kvm
2
2
3
3
import (
4
+ "bytes"
4
5
"context"
5
6
"encoding/json"
6
7
"errors"
@@ -10,12 +11,14 @@ import (
10
11
"path/filepath"
11
12
"reflect"
12
13
"strconv"
14
+ "sync"
13
15
"time"
14
16
15
17
"github.com/pion/webrtc/v4"
16
18
"github.com/rs/zerolog"
17
19
"go.bug.st/serial"
18
20
21
+ "github.com/jetkvm/kvm/internal/hidrpc"
19
22
"github.com/jetkvm/kvm/internal/usbgadget"
20
23
"github.com/jetkvm/kvm/internal/utils"
21
24
)
@@ -1056,6 +1059,106 @@ func rpcSetLocalLoopbackOnly(enabled bool) error {
1056
1059
return nil
1057
1060
}
1058
1061
1062
+ var (
1063
+ keyboardMacroCancel context.CancelFunc
1064
+ keyboardMacroLock sync.Mutex
1065
+ )
1066
+
1067
+ // cancelKeyboardMacro cancels any ongoing keyboard macro execution
1068
+ func cancelKeyboardMacro () {
1069
+ keyboardMacroLock .Lock ()
1070
+ defer keyboardMacroLock .Unlock ()
1071
+
1072
+ if keyboardMacroCancel != nil {
1073
+ keyboardMacroCancel ()
1074
+ logger .Info ().Msg ("canceled keyboard macro" )
1075
+ keyboardMacroCancel = nil
1076
+ }
1077
+ }
1078
+
1079
+ func setKeyboardMacroCancel (cancel context.CancelFunc ) {
1080
+ keyboardMacroLock .Lock ()
1081
+ defer keyboardMacroLock .Unlock ()
1082
+
1083
+ keyboardMacroCancel = cancel
1084
+ }
1085
+
1086
+ func rpcExecuteKeyboardMacro (macro []hidrpc.KeyboardMacroStep ) (usbgadget.KeysDownState , error ) {
1087
+ cancelKeyboardMacro ()
1088
+
1089
+ ctx , cancel := context .WithCancel (context .Background ())
1090
+ setKeyboardMacroCancel (cancel )
1091
+
1092
+ s := hidrpc.KeyboardMacroState {
1093
+ State : true ,
1094
+ IsPaste : true ,
1095
+ }
1096
+
1097
+ if currentSession != nil {
1098
+ currentSession .reportHidRPCKeyboardMacroState (s )
1099
+ }
1100
+
1101
+ result , err := rpcDoExecuteKeyboardMacro (ctx , macro )
1102
+
1103
+ setKeyboardMacroCancel (nil )
1104
+
1105
+ s .State = false
1106
+ if currentSession != nil {
1107
+ currentSession .reportHidRPCKeyboardMacroState (s )
1108
+ }
1109
+
1110
+ return result , err
1111
+ }
1112
+
1113
+ func rpcCancelKeyboardMacro () {
1114
+ cancelKeyboardMacro ()
1115
+ }
1116
+
1117
+ var keyboardClearStateKeys = make ([]byte , hidrpc .HidKeyBufferSize )
1118
+
1119
+ func isClearKeyStep (step hidrpc.KeyboardMacroStep ) bool {
1120
+ return step .Modifier == 0 && bytes .Equal (step .Keys , keyboardClearStateKeys )
1121
+ }
1122
+
1123
+ func rpcDoExecuteKeyboardMacro (ctx context.Context , macro []hidrpc.KeyboardMacroStep ) (usbgadget.KeysDownState , error ) {
1124
+ var last usbgadget.KeysDownState
1125
+ var err error
1126
+
1127
+ logger .Debug ().Interface ("macro" , macro ).Msg ("Executing keyboard macro" )
1128
+
1129
+ for i , step := range macro {
1130
+ delay := time .Duration (step .Delay ) * time .Millisecond
1131
+
1132
+ last , err = rpcKeyboardReport (step .Modifier , step .Keys )
1133
+ if err != nil {
1134
+ logger .Warn ().Err (err ).Msg ("failed to execute keyboard macro" )
1135
+ return last , err
1136
+ }
1137
+
1138
+ // notify the device that the keyboard state is being cleared
1139
+ if isClearKeyStep (step ) {
1140
+ gadget .UpdateKeysDown (0 , keyboardClearStateKeys )
1141
+ }
1142
+
1143
+ // Use context-aware sleep that can be cancelled
1144
+ select {
1145
+ case <- time .After (delay ):
1146
+ // Sleep completed normally
1147
+ case <- ctx .Done ():
1148
+ // make sure keyboard state is reset
1149
+ _ , err := rpcKeyboardReport (0 , keyboardClearStateKeys )
1150
+ if err != nil {
1151
+ logger .Warn ().Err (err ).Msg ("failed to reset keyboard state" )
1152
+ }
1153
+
1154
+ logger .Debug ().Int ("step" , i ).Msg ("Keyboard macro cancelled during sleep" )
1155
+ return last , ctx .Err ()
1156
+ }
1157
+ }
1158
+
1159
+ return last , nil
1160
+ }
1161
+
1059
1162
var rpcHandlers = map [string ]RPCHandler {
1060
1163
"ping" : {Func : rpcPing },
1061
1164
"reboot" : {Func : rpcReboot , Params : []string {"force" }},
0 commit comments