Наконец-то я нашел способ использовать node-ffi
/node-ffi-napi
для ввода нажатий клавиш с помощью функции SendInput
! (текущий код ниже использует node-ffi-napi
, так как node-ffi
не поддерживается/сломан; см. историю изменений для версии node-ffi
, API почти точно такой же)
Однако обратите внимание, что существует два способа вызова функции SendInput, как показано здесь: https://autohotkey.com/boards/viewtopic.php?p=213617#p213617
В моем случае мне пришлось использовать второй подход (код сканирования), потому что первый подход (виртуальный ключ) не работал в программах, для которых мне нужна была имитация ключа.
Без лишних слов, вот полное решение:
import keycode from "keycode";
import ffi from "ffi-napi";
import ref from "ref-napi";
import os from "os";
import import_Struct from "ref-struct-di";
var arch = os.arch();
const Struct = import_Struct(ref);
var Input = Struct({
"type": "int",
// For some reason, the wScan value is only recognized as the wScan value when we add this filler slot.
// It might be because it's expecting the values after this to be inside a "wrapper" substructure, as seen here:
// https://msdn.microsoft.com/en-us/library/windows/desktop/ms646270(v=vs.85).aspx
"???": "int",
"wVK": "short",
"wScan": "short",
"dwFlags": "int",
"time": "int",
"dwExtraInfo": "int64"
});
var user32 = ffi.Library("user32", {
SendInput: ["int", ["int", Input, "int"]],
//MapVirtualKeyEx: ["uint", ["uint", "uint", intPtr]],
});
const extendedKeyPrefix = 0xe000;
const INPUT_KEYBOARD = 1;
const KEYEVENTF_EXTENDEDKEY = 0x0001;
const KEYEVENTF_KEYUP = 0x0002;
const KEYEVENTF_UNICODE = 0x0004;
const KEYEVENTF_SCANCODE = 0x0008;
//const MAPVK_VK_TO_VSC = 0;
export class KeyToggle_Options {
asScanCode = true;
keyCodeIsScanCode = false;
flags?: number;
async = false; // async can reduce stutter in your app, if frequently sending key-events
}
let entry = new Input(); // having one persistent native object, and just changing its fields, is apparently faster (from testing)
entry.type = INPUT_KEYBOARD;
entry.time = 0;
entry.dwExtraInfo = 0;
export function KeyToggle(keyCode: number, type = "down" as "down" | "up", options?: Partial<KeyToggle_Options>) {
const opt = Object.assign({}, new KeyToggle_Options(), options);
// scan-code approach (default)
if (opt.asScanCode) {
//let scanCode = user32.MapVirtualKeyEx(keyCode, MAPVK_VK_TO_VSC); // this should work, but it had a Win32 error (code 127) for me
let scanCode = opt.keyCodeIsScanCode ? keyCode : ConvertKeyCodeToScanCode(keyCode);
let isExtendedKey = (scanCode & extendedKeyPrefix) == extendedKeyPrefix;
entry.dwFlags = KEYEVENTF_SCANCODE;
if (isExtendedKey) {
entry.dwFlags |= KEYEVENTF_EXTENDEDKEY;
}
entry.wVK = 0;
entry.wScan = isExtendedKey ? scanCode - extendedKeyPrefix : scanCode;
}
// (virtual) key-code approach
else {
entry.dwFlags = 0;
entry.wVK = keyCode;
//info.wScan = 0x0200;
entry.wScan = 0;
}
if (opt.flags != null) {
entry.dwFlags = opt.flags;
}
if (type == "up") {
entry.dwFlags |= KEYEVENTF_KEYUP;
}
if (opt.async) {
return new Promise((resolve, reject)=> {
user32.SendInput.async(1, entry, arch === "x64" ? 40 : 28, (error, result)=> {
if (error) reject(error);
resolve(result);
});
});
}
return user32.SendInput(1, entry, arch === "x64" ? 40 : 28);
}
export function KeyTap(keyCode: number, opt?: Partial<KeyToggle_Options>) {
KeyToggle(keyCode, "down", opt);
KeyToggle(keyCode, "up", opt);
}
// Scan-code for a char equals its index in this list. List based on: https://qb64.org/wiki/Scancodes, https://www.qbasic.net/en/reference/general/scan-codes.htm
// Not all keys are in this list, of course. You can add a custom mapping for other keys to the function below it, as needed.
let keys = "**1234567890-=**qwertyuiop[]**asdfghjkl;'`*\\zxcvbnm,./".split("");
export function ConvertKeyCodeToScanCode(keyCode: number) {
let keyChar = String.fromCharCode(keyCode).toLowerCase();
let result = keys.indexOf(keyChar);
console.assert(result != -1, `Could not find scan-code for key ${keyCode} (${keycode.names[keyCode]}).`)
return result;
}
Чтобы использовать его, позвоните:
KeyTap(65); // press the A key
Или, если вы используете пакет keycode npm:
import keycode from "keycode";
KeyTap(keycode.codes.a);
person
Venryx
schedule
18.05.2018
SendInput
помещает ввод в аппаратную очередь ввода. Любое окно (или поток, на самом деле) находится на переднем плане в момент захвата этого входного события, получает входные данные. Поэтому, когда вы запускаете свое приложение, Блокнот, естественно, не является окном переднего плана. Во всяком случае, то, что вы описали, является предлагаемым вами решением. Какую проблему вы на самом деле пытаетесь решить? - person IInspectable   schedule 27.12.2016SendInput
— правильный инструмент. Вы все еще должны убедиться, что правильный элемент управления находится на переднем плане, когда вы генерируете ввод. - person IInspectable   schedule 28.12.2016