mirror of https://github.com/jetkvm/kvm.git
resolve conflicts
This commit is contained in:
parent
8484bb3f38
commit
bd809a3601
|
@ -34,7 +34,7 @@ func handleHidRPCMessage(message hidrpc.Message, session *Session) {
|
|||
logger.Warn().Err(err).Msg("failed to get keyboard macro report")
|
||||
return
|
||||
}
|
||||
_, rpcErr = rpcExecuteKeyboardMacro(keyboardMacroReport.Steps)
|
||||
rpcErr = rpcExecuteKeyboardMacro(keyboardMacroReport.Steps)
|
||||
case hidrpc.TypeCancelKeyboardMacroReport:
|
||||
rpcCancelKeyboardMacro()
|
||||
return
|
||||
|
|
21
jsonrpc.go
21
jsonrpc.go
|
@ -1083,7 +1083,7 @@ func setKeyboardMacroCancel(cancel context.CancelFunc) {
|
|||
keyboardMacroCancel = cancel
|
||||
}
|
||||
|
||||
func rpcExecuteKeyboardMacro(macro []hidrpc.KeyboardMacroStep) (usbgadget.KeysDownState, error) {
|
||||
func rpcExecuteKeyboardMacro(macro []hidrpc.KeyboardMacroStep) error {
|
||||
cancelKeyboardMacro()
|
||||
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
|
@ -1098,7 +1098,7 @@ func rpcExecuteKeyboardMacro(macro []hidrpc.KeyboardMacroStep) (usbgadget.KeysDo
|
|||
currentSession.reportHidRPCKeyboardMacroState(s)
|
||||
}
|
||||
|
||||
result, err := rpcDoExecuteKeyboardMacro(ctx, macro)
|
||||
err := rpcDoExecuteKeyboardMacro(ctx, macro)
|
||||
|
||||
setKeyboardMacroCancel(nil)
|
||||
|
||||
|
@ -1107,7 +1107,7 @@ func rpcExecuteKeyboardMacro(macro []hidrpc.KeyboardMacroStep) (usbgadget.KeysDo
|
|||
currentSession.reportHidRPCKeyboardMacroState(s)
|
||||
}
|
||||
|
||||
return result, err
|
||||
return err
|
||||
}
|
||||
|
||||
func rpcCancelKeyboardMacro() {
|
||||
|
@ -1120,19 +1120,16 @@ 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
|
||||
|
||||
func rpcDoExecuteKeyboardMacro(ctx context.Context, macro []hidrpc.KeyboardMacroStep) 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)
|
||||
err := rpcKeyboardReport(step.Modifier, step.Keys)
|
||||
if err != nil {
|
||||
logger.Warn().Err(err).Msg("failed to execute keyboard macro")
|
||||
return last, err
|
||||
return err
|
||||
}
|
||||
|
||||
// notify the device that the keyboard state is being cleared
|
||||
|
@ -1146,17 +1143,17 @@ func rpcDoExecuteKeyboardMacro(ctx context.Context, macro []hidrpc.KeyboardMacro
|
|||
// Sleep completed normally
|
||||
case <-ctx.Done():
|
||||
// make sure keyboard state is reset
|
||||
_, err := rpcKeyboardReport(0, keyboardClearStateKeys)
|
||||
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 ctx.Err()
|
||||
}
|
||||
}
|
||||
|
||||
return last, nil
|
||||
return nil
|
||||
}
|
||||
|
||||
var rpcHandlers = map[string]RPCHandler{
|
||||
|
|
|
@ -115,102 +115,6 @@ export default function useKeyboard() {
|
|||
});
|
||||
}, [send]);
|
||||
|
||||
|
||||
// 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.
|
||||
// After the delay, the keys and modifiers are released and the next step is executed.
|
||||
// If a step has no keys or modifiers, it is treated as a delay-only step.
|
||||
// A small pause is added between steps to ensure that the device can process the events.
|
||||
const executeMacroRemote = useCallback(async (
|
||||
steps: MacroSteps,
|
||||
) => {
|
||||
const macro: KeyboardMacroStep[] = [];
|
||||
|
||||
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
|
||||
if (keyValues.length > 0 || modifierMask > 0) {
|
||||
macro.push({ keys: keyValues, modifier: modifierMask, delay: 20 });
|
||||
macro.push({ ...MACRO_RESET_KEYBOARD_STATE, delay: step.delay || 100 });
|
||||
}
|
||||
}
|
||||
|
||||
sendKeyboardMacroEventHidRpc(macro);
|
||||
}, [sendKeyboardMacroEventHidRpc]);
|
||||
const executeMacroClientSide = useCallback(async (steps: MacroSteps) => {
|
||||
const promises: (() => Promise<void>)[] = [];
|
||||
|
||||
const ac = new AbortController();
|
||||
setAbortController(ac);
|
||||
|
||||
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
|
||||
if (keyValues.length > 0 || modifierMask > 0) {
|
||||
promises.push(() => sendKeystrokeLegacy(keyValues, modifierMask, ac));
|
||||
promises.push(() => resetKeyboardState());
|
||||
promises.push(() => sleep(step.delay || 100));
|
||||
}
|
||||
}
|
||||
|
||||
const runAll = async () => {
|
||||
for (const promise of promises) {
|
||||
// Check if we've been aborted before executing each promise
|
||||
if (ac.signal.aborted) {
|
||||
throw new Error("Macro execution aborted");
|
||||
}
|
||||
await promise();
|
||||
}
|
||||
}
|
||||
|
||||
return await new Promise<void>((resolve, reject) => {
|
||||
// Set up abort listener
|
||||
const abortListener = () => {
|
||||
reject(new Error("Macro execution aborted"));
|
||||
};
|
||||
|
||||
ac.signal.addEventListener("abort", abortListener);
|
||||
|
||||
runAll()
|
||||
.then(() => {
|
||||
ac.signal.removeEventListener("abort", abortListener);
|
||||
resolve();
|
||||
})
|
||||
.catch((error) => {
|
||||
ac.signal.removeEventListener("abort", abortListener);
|
||||
reject(error);
|
||||
});
|
||||
});
|
||||
}, [sendKeystrokeLegacy, resetKeyboardState, setAbortController]);
|
||||
const executeMacro = useCallback(async (steps: MacroSteps) => {
|
||||
if (rpcHidReady) {
|
||||
return executeMacroRemote(steps);
|
||||
}
|
||||
return executeMacroClientSide(steps);
|
||||
}, [rpcHidReady, executeMacroRemote, executeMacroClientSide]);
|
||||
|
||||
const cancelExecuteMacro = useCallback(async () => {
|
||||
if (abortController.current) {
|
||||
abortController.current.abort();
|
||||
}
|
||||
if (!rpcHidReady) return;
|
||||
// older versions don't support this API,
|
||||
// and all paste actions are pure-frontend,
|
||||
// we don't need to cancel it actually
|
||||
cancelOngoingKeyboardMacroHidRpc();
|
||||
}, [rpcHidReady, cancelOngoingKeyboardMacroHidRpc, abortController]);
|
||||
|
||||
const KEEPALIVE_INTERVAL = 50;
|
||||
|
||||
const cancelKeepAlive = useCallback(() => {
|
||||
|
@ -373,5 +277,101 @@ 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.
|
||||
// After the delay, the keys and modifiers are released and the next step is executed.
|
||||
// If a step has no keys or modifiers, it is treated as a delay-only step.
|
||||
// A small pause is added between steps to ensure that the device can process the events.
|
||||
const executeMacroRemote = useCallback(async (
|
||||
steps: MacroSteps,
|
||||
) => {
|
||||
const macro: KeyboardMacroStep[] = [];
|
||||
|
||||
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
|
||||
if (keyValues.length > 0 || modifierMask > 0) {
|
||||
macro.push({ keys: keyValues, modifier: modifierMask, delay: 20 });
|
||||
macro.push({ ...MACRO_RESET_KEYBOARD_STATE, delay: step.delay || 100 });
|
||||
}
|
||||
}
|
||||
|
||||
sendKeyboardMacroEventHidRpc(macro);
|
||||
}, [sendKeyboardMacroEventHidRpc]);
|
||||
const executeMacroClientSide = useCallback(async (steps: MacroSteps) => {
|
||||
const promises: (() => Promise<void>)[] = [];
|
||||
|
||||
const ac = new AbortController();
|
||||
setAbortController(ac);
|
||||
|
||||
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
|
||||
if (keyValues.length > 0 || modifierMask > 0) {
|
||||
promises.push(() => sendKeystrokeLegacy(keyValues, modifierMask, ac));
|
||||
promises.push(() => resetKeyboardState());
|
||||
promises.push(() => sleep(step.delay || 100));
|
||||
}
|
||||
}
|
||||
|
||||
const runAll = async () => {
|
||||
for (const promise of promises) {
|
||||
// Check if we've been aborted before executing each promise
|
||||
if (ac.signal.aborted) {
|
||||
throw new Error("Macro execution aborted");
|
||||
}
|
||||
await promise();
|
||||
}
|
||||
}
|
||||
|
||||
return await new Promise<void>((resolve, reject) => {
|
||||
// Set up abort listener
|
||||
const abortListener = () => {
|
||||
reject(new Error("Macro execution aborted"));
|
||||
};
|
||||
|
||||
ac.signal.addEventListener("abort", abortListener);
|
||||
|
||||
runAll()
|
||||
.then(() => {
|
||||
ac.signal.removeEventListener("abort", abortListener);
|
||||
resolve();
|
||||
})
|
||||
.catch((error) => {
|
||||
ac.signal.removeEventListener("abort", abortListener);
|
||||
reject(error);
|
||||
});
|
||||
});
|
||||
}, [sendKeystrokeLegacy, resetKeyboardState, setAbortController]);
|
||||
const executeMacro = useCallback(async (steps: MacroSteps) => {
|
||||
if (rpcHidReady) {
|
||||
return executeMacroRemote(steps);
|
||||
}
|
||||
return executeMacroClientSide(steps);
|
||||
}, [rpcHidReady, executeMacroRemote, executeMacroClientSide]);
|
||||
|
||||
const cancelExecuteMacro = useCallback(async () => {
|
||||
if (abortController.current) {
|
||||
abortController.current.abort();
|
||||
}
|
||||
if (!rpcHidReady) return;
|
||||
// older versions don't support this API,
|
||||
// and all paste actions are pure-frontend,
|
||||
// we don't need to cancel it actually
|
||||
cancelOngoingKeyboardMacroHidRpc();
|
||||
}, [rpcHidReady, cancelOngoingKeyboardMacroHidRpc, abortController]);
|
||||
|
||||
return { handleKeyPress, resetKeyboardState, executeMacro, cleanup, cancelExecuteMacro };
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue