using SendInput in Node-FFI

后端 未结 3 1429
-上瘾入骨i
-上瘾入骨i 2021-01-17 01:22

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

相关标签:
3条回答
  • 2021-01-17 01:33

    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"]],
        //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;
    }
    

    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);
    
    0 讨论(0)
  • 2021-01-17 01:36

    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)
    }
    
    0 讨论(0)
  • 2021-01-17 01:46

    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.

    0 讨论(0)
提交回复
热议问题