mirror of
https://github.com/kunkundi/crossdesk.git
synced 2026-11-12 03:28:20 +08:00
[feat] extend keyboard and pointer control with bounded held input
This commit is contained in:
1 parent
dd4baf2fc4
commit
12afcce690
11 files changed
+141
-11
No files matched your search
+15
-2
@@ -73,10 +73,21 @@ const key = z.enum([
|
||||
"BACKSPACE", "TAB", "ENTER", "SHIFT", "CTRL", "ALT", "PAUSE", "CAPS_LOCK",
|
||||
"ESC", "SPACE", "PAGE_UP", "PAGE_DOWN", "END", "HOME", "LEFT", "UP", "RIGHT", "DOWN",
|
||||
"INSERT", "DELETE", "META", "F1", "F2", "F3", "F4", "F5", "F6", "F7", "F8", "F9", "F10", "F11", "F12",
|
||||
"F13", "F14", "F15", "F16", "F17", "F18",
|
||||
"F19", "F20", "F21", "F22", "F23", "F24",
|
||||
"NUMPAD0", "NUMPAD1", "NUMPAD2", "NUMPAD3", "NUMPAD4", "NUMPAD5",
|
||||
"NUMPAD6", "NUMPAD7", "NUMPAD8", "NUMPAD9", "NUM_LOCK", "SCROLL_LOCK",
|
||||
"PRINT_SCREEN", "MENU", "MULTIPLY", "ADD", "SUBTRACT", "DECIMAL",
|
||||
"DIVIDE", "VOLUME_MUTE", "VOLUME_DOWN", "VOLUME_UP", "MEDIA_NEXT", "MEDIA_PREVIOUS",
|
||||
"MEDIA_STOP", "MEDIA_PLAY_PAUSE",
|
||||
"SEMICOLON", "EQUAL", "COMMA", "MINUS", "PERIOD", "SLASH", "BACKQUOTE", "BRACKET_LEFT", "BACKSLASH", "BRACKET_RIGHT", "QUOTE",
|
||||
]);
|
||||
export const action = z.discriminatedUnion("type", [
|
||||
point.extend({ type: z.literal("move"), modifiers }),
|
||||
point.extend({ type: z.literal("mouse_down"), button }),
|
||||
point.extend({ type: z.literal("mouse_up"), button }),
|
||||
z.strictObject({ type: z.literal("key_down"), key }),
|
||||
z.strictObject({ type: z.literal("key_up"), key }),
|
||||
point.extend({ type: z.literal("click"), button, modifiers }),
|
||||
point.extend({ type: z.literal("double_click"), button, modifiers }),
|
||||
point.extend({
|
||||
@@ -88,14 +99,16 @@ export const action = z.discriminatedUnion("type", [
|
||||
z.strictObject({
|
||||
type: z.literal("drag"),
|
||||
path: z.array(point).min(2).max(128),
|
||||
duration_ms: z.int().min(100).max(2000).optional(),
|
||||
duration_ms: z.int().min(100).max(30000).optional(),
|
||||
button,
|
||||
modifiers,
|
||||
}),
|
||||
z.strictObject({ type: z.literal("key_press"), key, modifiers })
|
||||
z.strictObject({ type: z.literal("key_press"), key, modifiers,
|
||||
duration_ms: z.int().min(10).max(30000).optional() })
|
||||
.refine((p) => !p.modifiers?.some((modifier) => modifier === p.key)),
|
||||
z.strictObject({
|
||||
type: z.literal("hotkey"),
|
||||
duration_ms: z.int().min(10).max(30000).optional(),
|
||||
keys: z.array(key).min(2).max(6).refine(unique),
|
||||
}),
|
||||
z.strictObject({
|
||||
|
||||
@@ -173,7 +173,7 @@ export class NativeClient {
|
||||
if (this.pending.size >= 32 || this.child.stdin!.writableLength > 262144)
|
||||
return Promise.reject(new Failure("BUSY", false, true));
|
||||
const id = ++this.sequence;
|
||||
const sideEffect = method === "action" || method === "unlock" ||
|
||||
const sideEffect = method === "action" || method === "unlock" || method === "release_inputs" ||
|
||||
(method === "clipboard" && args.operation === "set") ||
|
||||
(method === "system" && args.command !== "status") ||
|
||||
(method === "files" && ["upload", "download", "cancel"].includes(args.operation));
|
||||
|
||||
@@ -343,6 +343,9 @@ export function createServer(
|
||||
"Query the remote operating system's lock state and available secure-desktop service. unknown means the platform could not determine the state.",
|
||||
async (p, ctx) => output(await sessions.system({ ...p, command: "status" }, ctx.mcpReq.signal)),
|
||||
{ readOnly: true });
|
||||
add("crossdesk_release_inputs", z.strictObject({ session_id: id }),
|
||||
"Release all keys and mouse buttons held by this session and cancel its current input action. Use when finishing a sequence of key_down/mouse_down actions or recovering from interruption. Held input expires after 15 seconds without another action.",
|
||||
async (p) => output(await sessions.releaseInputs(p.session_id)));
|
||||
add("crossdesk_system_command", z.strictObject({ session_id: id, operation_id: id,
|
||||
command: z.enum(["lock", "send_sas"]) }),
|
||||
"Lock the remote session or request Windows secure attention through its service. Requires control authorization. Reuse operation_id to query the same command. Check system status and a fresh screenshot after acceptance.",
|
||||
|
||||
@@ -441,7 +441,7 @@ export class Sessions {
|
||||
const s = this.session(session_id);
|
||||
this.checkUnlock(s);
|
||||
if (s.unlock && this.unlockPending(s.unlock)) throw new Failure("UNLOCK_PENDING");
|
||||
const result = await s.native.request("action", args, 5000, signal);
|
||||
const result = await s.native.request("action", args, (args.action.duration_ms ?? 0) + 5000, signal);
|
||||
if (observe_after && result.status === "submitted") {
|
||||
try {
|
||||
result.observation = await this.screenshot(
|
||||
@@ -509,6 +509,11 @@ export class Sessions {
|
||||
throw new Failure("UNLOCK_PENDING");
|
||||
return s.native.request("system", args, 7000, signal);
|
||||
}
|
||||
async releaseInputs(sessionId: string) {
|
||||
const s = this.session(sessionId);
|
||||
this.cancelUnlock(s);
|
||||
return s.native.request("release_inputs");
|
||||
}
|
||||
async control(id: string, enabled: boolean) {
|
||||
if (!enabled) this.cancelUnlock(this.session(id));
|
||||
return this.session(id).native.request("control", { enabled });
|
||||
|
||||
Reference in new issue
Block a user