UNPKG

@energypatrikhu/keysender

Version:

Node.js keyboard and mouse inputs emulator, global hotkey register for Windows

527 lines (406 loc) 15.1 kB
import { _Worker, _Hardware, _Virtual } from "./addon"; import { bindPick, lazyGetters, makeCancelable, normalizeWindowInfo, random, sleep, stringsToBuffers, } from "./utils"; import { Keyboard, KeyboardButton, Mouse, RGB, Workwindow } from "./types"; import { DEFAULT_DELAY, MICRO_DELAY } from "./constants"; import { SetWorkwindow } from "./types/utils"; declare class Worker { /** Provides methods to synthesize keystrokes */ readonly keyboard: Keyboard; /** Provides methods to synthesize mouse motions, and button clicks */ readonly mouse: Mouse; /** Provides methods to work with workwindow */ readonly workwindow: Workwindow; /** Use entire desktop as workwindow */ constructor(); /** Use the first window with {@link handle} */ constructor(handle: number); /** Use the first window with {@link title} and/or {@link className} and sets it as current workwindow */ constructor(title: string | null, className?: string | null); /** Use the first child window with {@link childClassName} and/or {@link childTitle} of window with {@link parentHandle} and sets it as current workwindow */ constructor( parentHandle: number, childClassName: string | null, childTitle?: string | null ); /** Use the first child window with {@link childClassName} and/or {@link childTitle} of the first found window with {@link parentTitle} and/or {@link parentClassName} and sets it as current workwindow */ constructor( parentTitle: string | null, parentClassName: string | null, childClassName: string | null, childTitle?: string | null ); } const handleSetWorkwindow = (worker: _Worker): SetWorkwindow => (...args: any[]) => { worker.setWorkwindow(...stringsToBuffers(args)); }; const handleWorker = (WorkerClass: typeof _Worker): typeof Worker => class { declare readonly keyboard: Keyboard; declare readonly mouse: Mouse; declare readonly workwindow: Workwindow; constructor(...args: any[]) { const worker = new WorkerClass(); handleSetWorkwindow(worker).apply(null, args); lazyGetters(this, { keyboard() { const _toggleKey = (key: KeyboardButton, state: boolean) => { worker.toggleKey(key, state); return sleep(MICRO_DELAY); }; const _toggleKeys = async ( keys: KeyboardButton[], state: boolean ) => { const l = keys.length - 1; let i: number; if (state) { for (i = 0; i < l; i++) { await _toggleKey(keys[i], true); } } else { for (i = l; i > 0; i--) { await _toggleKey(keys[i], false); } } worker.toggleKey(keys[i], state); }; const sendKey: Keyboard["sendKey"] = async ( key, delayAfterPress = DEFAULT_DELAY, delayAfterRelease = 0 ) => { if (Array.isArray(key)) { await _toggleKeys(key, true); await sleep(delayAfterPress); await _toggleKeys(key, false); } else { worker.toggleKey(key, true); await sleep(delayAfterPress); worker.toggleKey(key, false); } return sleep(delayAfterRelease); }; return { printText: makeCancelable<Keyboard["printText"]>(async function ( text, delayAfterCharTyping = 0, delay = 0 ) { if (text) { if (delayAfterCharTyping) { this.isCancelable = true; const it = new Intl.Segmenter() .segment(text) [Symbol.iterator](); let curr = it.next(); for (let next = it.next(); !next.done; next = it.next()) { if (this.isCanceled()) { return; } worker.printChar(Buffer.from(curr.value.segment, "ucs2")); await sleep(delayAfterCharTyping); curr = next; } if (this.isCanceled()) { return; } delete this.isCancelable; worker.printChar(Buffer.from(curr.value.segment, "ucs2")); } else { worker.printChar(Buffer.from(text, "ucs2")); } } return sleep(delay); }), async toggleKey(key, state, delay = 0) { if (Array.isArray(key)) { await _toggleKeys(key, state); } else { worker.toggleKey(key, state); } return sleep(delay); }, sendKey, sendKeys: makeCancelable<Keyboard["sendKeys"]>(async function ( keys, delayAfterPress = DEFAULT_DELAY, delayAfterRelease = DEFAULT_DELAY, delay = 0 ) { this.isCancelable = true; const l = keys.length - 1; for (let i = 0; i < l; i++) { if (this.isCanceled()) { return; } await sendKey(keys[i], delayAfterPress, delayAfterRelease); } if (this.isCanceled()) { return; } delete this.isCancelable; return sendKey(keys[l], delayAfterPress, delay); }), }; }, mouse() { const _getSign = () => (Math.random() > 0.5 ? 1 : -1); const _tremor = (probability: number) => Math.random() <= probability ? _getSign() : 0; const _curveMaker = ( t: number, start: number, curveDot1: number, curveDot2: number, end: number ) => { const invertT = 1 - t; const invertT2 = invertT * invertT; const t2 = t * t; return Math.floor( invertT2 * invertT * start + 3 * invertT2 * t * curveDot1 + 3 * invertT * t2 * curveDot2 + t2 * t * end ); }; const _curveDotMaker = ( start: number, end: number, deviation: number ) => Math.round(start + (end - start) * (0.5 + _getSign() * deviation)); const _firstCurveDotMaker = ( start: number, end: number, deviation: number, sign: 1 | -1 ) => Math.round(start + sign * (end - start) * deviation); const moveTo: Mouse["moveTo"] = (x, y, delay = 0) => { worker.move(x, y, true); return sleep(delay); }; const toggle: Mouse["toggle"] = (button, state, delay = 0) => { worker.toggleMb(button, state); return sleep(delay); }; return { ...bindPick(worker, ["getPos"]), toggle, async click( button = "left", delayAfterPress = DEFAULT_DELAY, delayAfterRelease = 0 ) { await toggle(button, true, delayAfterPress); return toggle(button, false, delayAfterRelease); }, moveTo, humanMoveTo: makeCancelable<Mouse["humanMoveTo"]>(async function ( xE, yE, speed = 5, deviation = 30, delay = 0 ) { deviation /= 100; const sleepTime = speed >= 1 ? 1 : Math.round(1 / speed); const { x, y } = worker.lastCoords; if (x === xE && y === yE) { return; } this.isCancelable = true; const partLength = random(50, 200) / 2; const partsTotal = Math.ceil( Math.pow(Math.pow(xE - x, 2) + Math.pow(yE - y, 2), 0.5) / partLength ); const xPartLength = (xE - x) / partsTotal; const yPartLength = (yE - y) / partsTotal; const speedMultiplier = (speed > 1 ? speed + 2 : 3) / partLength; let partsLeft = partsTotal; let isLastOne = partsTotal === 1; let parts: number; let xPartEnd: number; let yPartEnd: number; let xPartStart = x; let yPartStart = y; if (!isLastOne) { parts = random(1, Math.ceil(partsTotal / 2)); xPartEnd = x + xPartLength * parts; yPartEnd = y + yPartLength * parts; } else { parts = 1; xPartEnd = xE; yPartEnd = yE; } let getCurveDots = () => { getCurveDots = () => ({ curveDotX1: _curveDotMaker( xPartStart, xPartEnd, random(deviation / 3, deviation) ), curveDotY1: _curveDotMaker( yPartStart, yPartEnd, random(deviation / 3, deviation / 2) ), curveDotX2: _curveDotMaker( xPartStart, xPartEnd, random(0, deviation) ), curveDotY2: _curveDotMaker( yPartStart, yPartEnd, random(0, deviation / 2) ), }); return { curveDotX1: _firstCurveDotMaker( xPartStart, xPartEnd, random(deviation / 2, deviation), 1 ), curveDotY1: _firstCurveDotMaker( yPartStart, yPartEnd, random(deviation / 4, deviation / 3), 1 ), curveDotX2: _firstCurveDotMaker( xPartStart, xPartEnd, random(deviation / 2, deviation), _getSign() ), curveDotY2: _firstCurveDotMaker( yPartStart, yPartEnd, random(deviation / 2, deviation), _getSign() ), }; }; const tremorProbability = speed / 15; while (true) { const { curveDotX1, curveDotX2, curveDotY1, curveDotY2 } = getCurveDots(); const dotIterator = speedMultiplier / parts; const count = 1 / dotIterator; for (let i = 1; i < count; i++) { if (this.isCanceled()) { return; } const t = i * dotIterator; await moveTo( _curveMaker( t, xPartStart, curveDotX1, curveDotX2, xPartEnd ), _curveMaker( t, yPartStart, curveDotY1, curveDotY2, yPartEnd ) + _tremor(tremorProbability), sleepTime ); } if (this.isCanceled()) { return; } if (isLastOne) { delete this.isCancelable; await moveTo(xE, yE, delay); return; } await moveTo( xPartEnd, yPartEnd + _tremor(tremorProbability), sleepTime ); partsLeft -= parts; xPartStart = xPartEnd; yPartStart = yPartEnd; if (partsLeft > 2) { parts = random(1, partsLeft - 1); xPartEnd += xPartLength * parts; yPartEnd += yPartLength * parts; } else { parts = partsLeft; xPartEnd = xE; yPartEnd = yE; isLastOne = true; } } }), move(x, y, delay = 0) { worker.move(x, y, false); return sleep(delay); }, scrollWheel(amount, delay = 0) { worker.scrollWheel(amount); return sleep(delay); }, }; }, workwindow() { const _add0 = (item: string) => (item.length > 1 ? item : "0" + item); const _hex = (...rgb: RGB) => rgb.reduce((hex, color) => hex + _add0(color.toString(16)), ""); return { ...bindPick(worker, [ "refresh", "setForeground", "isForeground", "isOpen", "capture", "kill", "close", "getView", "setView", ]), set: handleSetWorkwindow(worker), get: () => normalizeWindowInfo(worker.getWorkwindow()), colorAt(x, y, format = "string"): any { const bgr = worker.getColor(x, y); const r = bgr & 0xff; const g = (bgr >> 8) & 0xff; const b = (bgr >> 16) & 0xff; switch (format) { case "array": return [r, g, b]; case "number": return (r << 16) | (g << 8) | b; case "string": return _hex(r, g, b); default: throw new Error("wrong format"); } }, }; }, }); } }; /** Provides methods implementations on hardware level. */ export const Hardware = handleWorker(_Hardware); /** Provides methods implementations on virtual level. */ export const Virtual = handleWorker(_Virtual);