mirror of
https://github.com/hanzoai/jsautogui.git
synced 2026-10-30 18:33:10 +00:00
347 lines
13 KiB
TypeScript
347 lines
13 KiB
TypeScript
// @ts-ignore
|
|
import __native from "./build/Release/jsautogui.node";
|
|
import * as tween from "jstweening";
|
|
import {linear} from "jstweening";
|
|
|
|
const native = <NATIVE_API>__native;
|
|
|
|
type int32 = number;
|
|
|
|
type NATIVE_API = {
|
|
set_pause(pause: int32): void;
|
|
get_screen_size(): { width: int32, height: int32 };
|
|
set_mouse_pos_rel(dx: int32, dy: int32): boolean;
|
|
set_mouse_pos(x: int32, y: int32): boolean;
|
|
set_mouse_x(x: int32): boolean;
|
|
set_mouse_y(y: int32): boolean;
|
|
get_mouse_pos(): { x: int32, y: int32 };
|
|
mouse_is_left_down(): boolean;
|
|
mouse_is_right_down(): boolean;
|
|
mouse_is_middle_down(): boolean;
|
|
click_left(): boolean;
|
|
click_right(): boolean;
|
|
click_middle(): boolean;
|
|
mouse_left_down(): boolean;
|
|
mouse_right_down(): boolean;
|
|
mouse_middle_down(): boolean;
|
|
mouse_left_up(): boolean;
|
|
mouse_right_up(): boolean;
|
|
mouse_middle_up(): boolean;
|
|
is_mouse_swapped(): boolean;
|
|
mouse_scroll(x: int32, y: int32): boolean;
|
|
keys_press(key_data: Uint32Array): boolean;
|
|
};
|
|
|
|
const modifiers: string[] = [
|
|
"accept", "add", "alt", "altleft", "altright", "apps", "backspace",
|
|
"browserback", "browserfavorites", "browserforward", "browserhome",
|
|
"browserrefresh", "browsersearch", "browserstop", "capslock", "clear",
|
|
"convert", "ctrl", "ctrlleft", "ctrlright", "decimal", "del", "delete",
|
|
"divide", "down", "end", "enter", "esc", "escape", "execute", "f1", "f10",
|
|
"f11", "f12", "f13", "f14", "f15", "f16", "f17", "f18", "f19", "f2", "f20",
|
|
"f21", "f22", "f23", "f24", "f3", "f4", "f5", "f6", "f7", "f8", "f9",
|
|
"final", "hanguel", "hangul", "hanja", "help", "home", "insert", "junja",
|
|
"kana", "kanji", "launchapp1", "launchapp2", "launchmail",
|
|
"launchmediaselect", "left", "modechange", "multiply", "nexttrack",
|
|
"nonconvert", "num0", "num1", "num2", "num3", "num4", "num5", "num6",
|
|
"num7", "num8", "num9", "numlock", "pagedown", "pageup", "pause", "pgdn",
|
|
"pgup", "playpause", "prevtrack", "print", "printscreen", "prntscrn",
|
|
"prtsc", "prtscr", "return", "right", "scrolllock", "select", "separator",
|
|
"shift", "shiftleft", "shiftright", "sleep", "space", "stop", "subtract", "tab",
|
|
"up", "volumedown", "volumemute", "volumeup", "win", "winleft", "winright", "yen",
|
|
"command", "option", "optionleft", "optionright"
|
|
] as const;
|
|
|
|
type TweenFunction = (t: number) => number;
|
|
|
|
type AllMouseButtons = "primary" | "secondary" | "left" | "right" | "middle";
|
|
|
|
const mouseButtons = ["left", "right", "middle"] as const;
|
|
|
|
const INT32_MIN = -2147483648;
|
|
const INT32_MAX = 2147483647;
|
|
|
|
function eType(a: any, t: string, n: string): void {
|
|
if (typeof a !== t) throw new Error(`Expected ${n} to be a ${t}, instead got: ${typeof a}`);
|
|
}
|
|
|
|
function eInt32(a: number, n: string): void {
|
|
eSize(a, INT32_MIN, INT32_MAX, n);
|
|
if (a !== Math.floor(a)) throw new Error(`Expected ${n} to be an int32, instead got a float/double.`);
|
|
}
|
|
|
|
function eSize(a: number, min: number, max: number, n: string): void {
|
|
eType(a, "number", n);
|
|
if (a < min || a > max) throw new Error(`Expected ${n} to be between (${min}, ${max}).`);
|
|
}
|
|
|
|
function perm(s: string): never {
|
|
throw new Error(`Couldn't ${s}, is the program lacking some permissions?`);
|
|
}
|
|
|
|
type Interval = ReturnType<typeof setInterval>;
|
|
|
|
function applyMovement(x1: number, y1: number, x2: number, y2: number, duration: number, tween: TweenFunction) {
|
|
let interval = <Interval>null!;
|
|
const prom = new Promise<void>((resolve) => {
|
|
let t = 0;
|
|
let last = performance.now();
|
|
interval = setInterval(() => {
|
|
const now = performance.now();
|
|
t += now - last;
|
|
last = now;
|
|
if (t >= duration) {
|
|
native.set_mouse_pos(x2, y2);
|
|
clearInterval(interval);
|
|
resolve();
|
|
} else {
|
|
const rt = t / duration;
|
|
const tw = tween(rt);
|
|
API.mouse.setPosition(x1 + (x2 - x1) * tw, y1 + (y2 - y1) * tw);
|
|
}
|
|
});
|
|
});
|
|
return {
|
|
wait: () => prom,
|
|
cancel: () => interval && clearInterval(interval)
|
|
};
|
|
}
|
|
|
|
enum KeyPressMode {
|
|
ASCII = 0,
|
|
UNICODE = 1,
|
|
SPECIAL = 2
|
|
}
|
|
|
|
function encodeKeyPress(key: number, mode: KeyPressMode, down: boolean, up: boolean): number {
|
|
return key | ((mode & 0x3) << 16) | (down ? (1 << 19) : 0) | (up ? (1 << 20) : 0);
|
|
}
|
|
|
|
function encodeKeyPresses(keys: string[], down: boolean, up: boolean): number[] {
|
|
return keys.map(k => {
|
|
if (typeof k !== "string") throw new Error(`Expected key to be a string, instead got: ${typeof k}`);
|
|
if (modifiers.includes(k)) {
|
|
return encodeKeyPress(k as unknown as number, KeyPressMode.SPECIAL, down, up);
|
|
} else if (k.length === 1) {
|
|
return encodeKeyPress(k.charCodeAt(0), KeyPressMode.ASCII, down, up);
|
|
} else if (k.length <= 4) {
|
|
const c1 = k.charCodeAt(0) || 0;
|
|
const c2 = k.charCodeAt(1) || 0;
|
|
const c3 = k.charCodeAt(2) || 0;
|
|
const c4 = k.charCodeAt(3) || 0;
|
|
const encoded32 = (c1 << 24) | (c2 << 16) | (c3 << 8) | c4;
|
|
return encodeKeyPress(encoded32, KeyPressMode.UNICODE, down, up);
|
|
}
|
|
throw new Error(`Unsupported key format: ${k}`);
|
|
});
|
|
}
|
|
|
|
function keysPress(keys: string[], down: boolean, up: boolean): void {
|
|
native.keys_press(new Uint32Array(encodeKeyPresses(keys, down, up)));
|
|
}
|
|
|
|
function hotkeyPress(keys: string[]): void {
|
|
native.keys_press(new Uint32Array([
|
|
...encodeKeyPresses(keys, true, false),
|
|
...encodeKeyPresses(keys, false, true)
|
|
]));
|
|
}
|
|
|
|
function xyFix(x: number, y: number, min = 0) {
|
|
x = Math.min(INT32_MAX, Math.max(min, Math.round(x)));
|
|
y = Math.min(INT32_MAX, Math.max(min, Math.round(y)));
|
|
return [x, y];
|
|
}
|
|
|
|
let callPause = 0;
|
|
let failSafe = false;
|
|
let failSafeInterval = <Interval>null!;
|
|
|
|
const API = {
|
|
__native_do_not_use_this_it_might_crash_natively_if_misused: native,
|
|
get FAIL_SAFE() {
|
|
return failSafe;
|
|
},
|
|
set FAIL_SAFE(v) {
|
|
eType(v, "boolean", "FAIL_SAFE");
|
|
if (v === failSafe) return;
|
|
clearInterval(failSafeInterval);
|
|
if (v) {
|
|
failSafeInterval = setInterval(() => {
|
|
const pos = native.get_mouse_pos();
|
|
if (!pos) return;
|
|
if (pos.x < 3 && pos.y < 3) throw new Error("Failed safely. This behavior can be disabled by setting jsautogui.FAIL_SAFE to false.");
|
|
}, 100);
|
|
failSafe = true;
|
|
} else {
|
|
failSafe = false;
|
|
failSafeInterval = <Interval>null!;
|
|
}
|
|
},
|
|
get PAUSE() {
|
|
return callPause;
|
|
},
|
|
set PAUSE(v) {
|
|
eInt32(v, "PAUSE");
|
|
eSize(v, 0, INT32_MAX, "PAUSE");
|
|
callPause = v;
|
|
native.set_pause(v);
|
|
},
|
|
mouse: {
|
|
LinearTween: linear,
|
|
get position() {
|
|
return this.getPosition();
|
|
},
|
|
set position(v) {
|
|
this.setPosition(v.x, v.y);
|
|
},
|
|
get x() {
|
|
return this.position.x;
|
|
},
|
|
set x(v) {
|
|
eInt32(v, "mouse.position.x");
|
|
eSize(v, 0, INT32_MAX, "mouse.position.x");
|
|
if (!native.set_mouse_x(v)) perm("set the x position of the mouse");
|
|
},
|
|
get y() {
|
|
return this.position.y;
|
|
},
|
|
set y(v) {
|
|
eInt32(v, "mouse.position.y");
|
|
eSize(v, 0, INT32_MAX, "mouse.position.y");
|
|
if (!native.set_mouse_y(v)) perm("set the y position of the mouse");
|
|
},
|
|
getPosition() {
|
|
const pos = native.get_mouse_pos();
|
|
if (!pos) perm("find the position of the mouse");
|
|
const obj = {};
|
|
Object.defineProperties(obj, {
|
|
x: {
|
|
get: () => pos.x,
|
|
set: v => this.x = pos.x = v
|
|
},
|
|
y: {
|
|
get: () => pos.y,
|
|
set: v => this.y = pos.y = v
|
|
}
|
|
});
|
|
return <{ x: number, y: number }>obj;
|
|
},
|
|
setPosition(x: number, y: number) {
|
|
[x, y] = xyFix(x, y);
|
|
if (!native.set_mouse_pos(x, y)) return perm("set the position of the mouse");
|
|
},
|
|
moveTo(x: number, y: number, duration = 0, tween: TweenFunction = linear) {
|
|
[x, y] = xyFix(x, y);
|
|
eInt32(duration, "duration");
|
|
if (duration === 0) {
|
|
this.setPosition(x, y);
|
|
return {wait: () => null, cancel: () => null};
|
|
}
|
|
eType(tween, "function", "mouse.moveTo.tween");
|
|
const start = native.get_mouse_pos();
|
|
return applyMovement(start.x, start.y, x, y, duration, tween);
|
|
},
|
|
moveRel(dx: number, dy: number, duration = 0, tween: TweenFunction = linear) {
|
|
[dx, dy] = xyFix(dx, dy, 0);
|
|
eInt32(duration, "duration");
|
|
if (duration === 0) {
|
|
native.set_mouse_pos_rel(dx, dy) || perm("set the position of the mouse");
|
|
return {wait: () => null, cancel: () => null};
|
|
}
|
|
const start = native.get_mouse_pos();
|
|
const x0 = start.x, y0 = start.y;
|
|
return applyMovement(x0, y0, x0 + dx, y0 + dx, duration, tween);
|
|
},
|
|
dragTo(x: number, y: number, button: AllMouseButtons = "primary", duration = 0, tween: TweenFunction = linear) {
|
|
[x, y] = xyFix(x, y);
|
|
this.down(button);
|
|
const r = this.moveTo(x, y, duration, tween);
|
|
if (r instanceof Promise) return r.then(() => this.up(button));
|
|
this.up(button);
|
|
},
|
|
dragRel(dx: number, dy: number, button: AllMouseButtons = "primary", duration = 0, tween = linear) {
|
|
[dx, dy] = xyFix(dx, dy, 0);
|
|
this.down(button);
|
|
const r = this.moveRel(dx, dy, duration, tween);
|
|
if (r instanceof Promise) return r.then(() => this.up(button));
|
|
this.up(button);
|
|
},
|
|
get isSwapped() {
|
|
return native.is_mouse_swapped();
|
|
},
|
|
down(button: AllMouseButtons = "primary") {
|
|
if (button === "primary") button = this.isSwapped ? "right" : "left";
|
|
else if (button === "secondary") button = this.isSwapped ? "left" : "right";
|
|
else if (mouseButtons.includes(button)) return (native as any)["mouse_" + button + "_down"]() || perm("press a button in mouse");
|
|
throw new Error("Expected the button argument to be 'right', 'left', 'middle', 'primary', 'secondary'. Got: " + button);
|
|
},
|
|
up(button: AllMouseButtons = "primary") {
|
|
if (button === "primary") button = this.isSwapped ? "right" : "left";
|
|
if (button === "secondary") button = this.isSwapped ? "left" : "right";
|
|
if (mouseButtons.includes(button)) return (native as any)["mouse_" + button + "_up"]() || perm("release a button in mouse");
|
|
throw new Error("Expected the button argument to be 'right', 'left', 'middle', 'primary', 'secondary'. Got: " + button);
|
|
},
|
|
click(button: AllMouseButtons = "primary") {
|
|
if (button === "primary") button = this.isSwapped ? "right" : "left";
|
|
if (button === "secondary") button = this.isSwapped ? "left" : "right";
|
|
if (mouseButtons.includes(button)) return (native as any)["click_" + button]() || perm("click a button in mouse");
|
|
throw new Error("Expected the button argument to be 'right', 'left', 'middle', 'primary', 'secondary'. Got: " + button);
|
|
},
|
|
scroll(y = 0, x = 0) {
|
|
[x, y] = xyFix(x, y, INT32_MIN);
|
|
if (!native.mouse_scroll(x, y)) perm("scroll the wheel of mouse");
|
|
},
|
|
hscroll(x = 0, y = 0) {
|
|
[x, y] = xyFix(x, y, INT32_MIN);
|
|
if (!native.mouse_scroll(x, y)) perm("scroll the wheel of mouse");
|
|
}
|
|
},
|
|
keyboard: {
|
|
press(...keys: string[]) {
|
|
keysPress(keys, true, true);
|
|
},
|
|
down(...keys: string[]) {
|
|
keysPress(keys, true, false);
|
|
},
|
|
up(...keys: string[]) {
|
|
keysPress(keys, false, true);
|
|
},
|
|
write(...texts: string[]) {
|
|
for (const text of texts) {
|
|
for (let i = 0; i < text.length; i++) this.press(text[i]);
|
|
}
|
|
},
|
|
hotkey(...keys: string[]) {
|
|
hotkeyPress(keys);
|
|
}
|
|
},
|
|
screen: {
|
|
get size() {
|
|
return native.get_screen_size();
|
|
},
|
|
get width() {
|
|
return this.size.width;
|
|
},
|
|
get height() {
|
|
return this.size.height;
|
|
},
|
|
get center() {
|
|
const s = this.size;
|
|
return {x: Math.floor(s.width / 2), y: Math.floor(s.height / 2)};
|
|
},
|
|
includes(x: number, y: number) {
|
|
eInt32(x, "x");
|
|
eInt32(y, "y");
|
|
const size = native.get_screen_size();
|
|
return x >= 0 && y >= 0 && x < size.width && y < size.height;
|
|
},
|
|
contains(x: number, y: number) {
|
|
return this.includes(x, y);
|
|
}
|
|
}
|
|
};
|
|
|
|
Object.assign(API, tween);
|
|
|
|
export = API as typeof API & typeof tween; |