3

I wanted to use the SendInput function from the windows Api in nodejs, using the FFI package.

My knowledge of C is limited so I can't really figure out what problem I have, I'm basically trying to Virtually press a key on the keyboard.

That's the code I have:

var ffi = require('ffi');
var ref = require ('ref');
var struct = require ('ref-struct');

var keyboardInput = struct({
    'type': 'int',
    'wVK': 'int',
    'wScan': 'int',
    'dwFlags': 'int',
    'time': 'int',
    'dwExtraInfo': 'int64'
});

var keyboardInputPtr = ref.refType(keyboardInput);
var keyboard = new keyboardInput();
keyboard.type = 1;
keyboard.wVK = 0x41;
keyboard.wScan = 0;
keyboard.dwFlags = 2;
keyboard.time = 0;
keyboard.dwExtraInfo = 0;

var user32 = ffi.Library('user32', {
    'SendInput': [ 'int', [ 'uint', keyboardInputPtr, 'int' ] ]
});

setInterval(function(){
    var r = user32.SendInput(1, keyboard.ref(), 40);
    console.log(r);
}, 500);

It logs me a "1" in the console, shouldn't that mean it works? Because I don't get a key pressed when I open notepad.

simon-p-r
  • 3,623
  • 2
  • 20
  • 35
  • `SendInput` places input in the hardware input queue. Whichever window (or thread, really) is in the foreground at the time this input event is picked up receives the input. So when you launch your application, naturally Notepad isn't the foreground window. At any rate, what you have described is your proposed solution. Now what **problem** are you really trying to solve? – IInspectable Dec 27 '16 at 19:39
  • Nobody ever has faking input to notepad as the final goal. What are you really trying to do. Quite possibly, even if you can fake it I to notepad your true goal won't be solved the same way. – David Heffernan Dec 27 '16 at 19:43
  • @IInspectable I'm trying to write a program that clicks a button (on the keyboard) into the current active window. With the interval it's basically like an auto clicker. –  Dec 27 '16 at 21:26
  • *"clicks a button (on the keyboard)"* - That doesn't make sense. Do you want to click a button, or do you want to generate keyboard input. – IInspectable Dec 27 '16 at 21:49
  • @IInspectable i'm sorry if I wasn't clear, but I want to generate a keyboard input, just like pressing the button "A" on the keyboard. –  Dec 27 '16 at 21:52
  • If you truly want to generate keyboard input (like an on-screen keyboard), `SendInput` is the right tool. You still have to make sure, that the right control is in the foreground, when you generated input. – IInspectable Dec 27 '16 at 22:00

3 Answers3

7

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);
Venryx
  • 15,624
  • 10
  • 70
  • 96
  • @truefusion It looks like you're right, though I can't seem to find proper documentation for the field. The page I linked in the code comment mentions the field but doesn't describe it at all or provide the possible values. (it just gives the plain-text reference "DUMMYUNIONNAME.mi") – Venryx Jan 08 '19 at 03:24
  • 1
    to fixed the `MapVirtualKeyEx` issue, i used `MapVirtualKeyExA` instead and it worked : `user32.MapVirtualKeyExA(keyCode, 0, 0);` and `MapVirtualKeyExA: ["uint", ["uint", "uint", "int"]],` – yaya Oct 05 '20 at 15:29
  • 1
    @yaya Awesome! Thanks for the info. (I will update my answer once I've confirmed it on my computer.) – Venryx Oct 06 '20 at 17:23
  • Sure. Here is my full code, so if you face any error you can check it also : https://pastebin.pl/view/f527be0e – yaya Oct 06 '20 at 21:00
  • @Venryx I too ran into the need for the `"???": "int"` "padding" member while running in the`x64` architecture. I believe that this is due to pointers within structs needing to be aligned to the size of pointers (8 bytes in the `x64` architecture). By adding that 4-byte spacing, you effectively aligned the pointer (`ULONG_PTR dwExtraInfo`). I found another way to fix that: mark that field with type `pointer`, and `ref-struct-napi` will take care of the alignment for you. See my answer for more details. – Jason Fry Oct 16 '20 at 08:11
6

Here's a working example that presses the a key. It employs ref-struct-napi and ref-union-napi to accurately represent the INPUT structure.

const FFI = require('ffi-napi')
const StructType = require('ref-struct-napi')
const UnionType = require('ref-union-napi')
const ref = require('ref-napi')


const user32 = new FFI.Library('user32.dll', {
  // UINT SendInput(
  //   _In_ UINT cInputs,                     // number of input in the array
  //   _In_reads_(cInputs) LPINPUT pInputs,  // array of inputs
  //   _In_ int cbSize);                      // sizeof(INPUT)
  'SendInput': ['uint32', ['int32', 'pointer', 'int32']],
})

// typedef struct tagMOUSEINPUT {
//   LONG    dx;
//   LONG    dy;
//   DWORD   mouseData;
//   DWORD   dwFlags;
//   DWORD   time;
//   ULONG_PTR dwExtraInfo;
// } MOUSEINPUT;
const MOUSEINPUT = StructType({
  dx: 'int32',
  dy: 'int32',
  mouseData: 'uint32',
  flags: 'uint32',
  time: 'uint32',
  extraInfo: 'pointer',
})

// typedef struct tagKEYBDINPUT {
//   WORD    wVk;
//   WORD    wScan;
//   DWORD   dwFlags;
//   DWORD   time;
//   ULONG_PTR dwExtraInfo;
// } KEYBDINPUT;
const KEYBDINPUT = StructType({
  vk: 'uint16',
  scan: 'uint16',
  flags: 'uint32',
  time: 'uint32',
  extraInfo: 'pointer',
})

// typedef struct tagHARDWAREINPUT {
//   DWORD   uMsg;
//   WORD    wParamL;
//   WORD    wParamH;
// } HARDWAREINPUT;
const HARDWAREINPUT = StructType({
  msg: 'uint32',
  paramL: 'uint16',
  paramH: 'uint16',
})

// typedef struct tagINPUT {
//   DWORD   type;
//   union
//   {
//     MOUSEINPUT      mi;
//     KEYBDINPUT      ki;
//     HARDWAREINPUT   hi;
//   } DUMMYUNIONNAME;
// } INPUT;
const INPUT_UNION = UnionType({
  mi: MOUSEINPUT,
  ki: KEYBDINPUT,
  hi: HARDWAREINPUT,
})
const INPUT = StructType({
  type: 'uint32',
  union: INPUT_UNION,
})

const pressKey = (scanCode) => {
  const keyDownKeyboardInput = KEYBDINPUT({vk: 0, extraInfo: ref.NULL_POINTER, time: 0, scan: scanCode, flags: 0x0008})
  const keyDownInput = INPUT({type: 1, union: INPUT_UNION({ki: keyDownKeyboardInput})})
  user32.SendInput(1, keyDownInput.ref(), INPUT.size)

  const keyUpKeyboardInput = KEYBDINPUT({vk: 0, extraInfo: ref.NULL_POINTER, time: 0, scan: scanCode, flags: 0x0008 | 0x0002})
  const keyUpInput = INPUT({type: 1, union: INPUT_UNION({ki: keyUpKeyboardInput})})
  user32.SendInput(1, keyUpInput.ref(), INPUT.size)
}

pressKey(0x1E)

If you want to perform a single call to SendInput that includes multiple key presses, construct an array of INPUT structs:

const pressKey = (scanCode) => {
  const inputCount = 2
  const inputArray = Buffer.alloc(INPUT.size * inputCount)
  const keyDownKeyboardInput = KEYBDINPUT({vk: 0, extraInfo: ref.NULL_POINTER, time: 0, scan: scanCode, flags: 0x0008})
  const keyDownInput = INPUT({type: 1, union: INPUT_UNION({ki: keyDownKeyboardInput})})
  keyDownInput.ref().copy(inputArray, 0)
  const keyUpKeyboardInput = KEYBDINPUT({vk: 0, extraInfo: ref.NULL_POINTER, time: 0, scan: scanCode, flags: 0x0008 | 0x0002})
  const keyUpInput = INPUT({type: 1, union: INPUT_UNION({ki: keyUpKeyboardInput})})
  keyUpInput.ref().copy(inputArray, INPUT.size)
  user32.SendInput(inputCount, inputArray, INPUT.size)
}
Jason Fry
  • 1,204
  • 12
  • 24
  • I've read that SendInput is able to be sent multiple key-events within one function call (https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-sendinput#parameters). Do you know how to accomplish this using your code/structures above? (that's something I was not able to figure out how to do) – Venryx Oct 18 '20 at 18:33
  • 1
    @Venryx Check out my answer, I've updated it to include this. – Jason Fry Oct 19 '20 at 07:57
  • Awesome! Thanks so much, will come on handy. – Venryx Oct 19 '20 at 12:26
0

The "1" tells you that 1 event was inserted, not what the event actually is. I don't know about FFI but it seems to me that keyboardInput has some invalid type definitions. wVK and wScan must be 16-bit integers (hence the 'w' for WORD). Since they are typed the same as dwFlags (an 'int') that's cause invalid input values.

Ton Plooij
  • 2,583
  • 12
  • 15