I finally found a way to use node-ffi
/node-ffi-napi
to input key-presses using the SendInput
function! (current code below uses node-ffi-napi
, since node-ffi
has been unmaintained/broken; see edit history for node-ffi
version, the api is almost exactly the same)
However, note that there are two ways you can call the SendInput function, as seen here: https://autohotkey.com/boards/viewtopic.php?p=213617#p213617
In my case, I had to use the second (scan code) approach, because the first (virtual key) approach didn't work in the programs I needed the key simulation for.
Without further ado, here is the complete solution:
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"]],
MapVirtualKeyExA: ["uint", ["uint", "uint", "int"]],
});
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 = 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);
}
export function ConvertKeyCodeToScanCode(keyCode: number) {
//return user32.MapVirtualKeyExA(keyCode, MAPVK_VK_TO_VSC, 0);
return user32.MapVirtualKeyExA(keyCode, 0, 0);
}
To use it, call:
KeyTap(65); // press the A key
Or, if you're using the keycode npm package:
import keycode from "keycode";
KeyTap(keycode.codes.a);