import os from "os"; import ffi from "@inigolabs/ffi-napi"; import ref from "@inigolabs/ref-napi"; import StructType from "ref-struct-di"; import UnionType from "ref-union-di"; import { spawn } from "child_process"; import { ipcMain } from "electron"; const StructFactory = StructType(ref); const UnionFactory = UnionType(ref); const kInputChannel = "input-channel"; const kPressSuper = "press-super-key"; const kPressOSK = "press-osk"; const isWin = process.platform === "win32"; const isWin10 = parseInt(os.release(), 10) >= 10; const user32 = new ffi.Library("user32.dll", { SendInput: ["uint32", ["int32", "pointer", "int32"]], // number of inputs, array of inputs and sizeof input FindWindowA: ["long", ["string", "string"]], }); const INPUT_KEYBOARD = 1; const KEYEVENTF_KEYUP = 0x0002; const VK_LWIN = 0x5b; const VK_CONTROL = 0x11; const VK_O = 0x4f; const MouseInput = StructFactory({ dx: "int32", // LONG dy: "int32", // LONG mouseData: "uint32", // DWORD dwFlags: "uint32", // DWORD time: "uint32", // DWORD dwExtraInfo: "pointer", // ULONG_PTR }); const KeyboardInput = StructFactory({ wVk: "uint16", // WORD wScan: "uint16", // WORD dwFlags: "uint32", // DWORD time: "uint32", // DWORD dwExtraInfo: "pointer", // ULONG_PTR }); const HardwareInput = StructFactory({ uMsg: "uint32", // DWORD wParamL: "uint16", // WORD wParamH: "uint16", // WORD }); const Union = UnionFactory({ mi: MouseInput, ki: KeyboardInput, hi: HardwareInput, }); const Input = StructFactory({ type: "uint32", // DWORD union: Union, }); const pressKey = (virtualKeyCode) => { const keyDownKeyboardInput = KeyboardInput({ wVk: virtualKeyCode }); const keyDownInput = Input({ type: INPUT_KEYBOARD, union: Union({ ki: keyDownKeyboardInput }), }); user32.SendInput(1, keyDownInput.ref(), Input.size); const keyUpKeyboardInput = KeyboardInput({ wVk: virtualKeyCode, dwFlags: KEYEVENTF_KEYUP, }); const keyUpInput = Input({ type: INPUT_KEYBOARD, union: Union({ ki: keyUpKeyboardInput }), }); user32.SendInput(1, keyUpInput.ref(), Input.size); }; // Press sequence of virtual key codes e.g. [0x4f, 0x50, 0x51, 0x53], for future // const pressKeys = (array) => { // const inputArray = Buffer.alloc(Input.size * array.length * 2); // let i = 0; // Object.values(array).forEach((vk) => { // const keyDownKeyboardInput = KeyboardInput({ wVk: vk }); // const keyDownInput = Input({ // type: INPUT_KEYBOARD, // union: Union({ ki: keyDownKeyboardInput }), // }); // keyDownInput.ref().copy(inputArray, Input.size * i); // i += 1; // const keyUpKeyboardInput = KeyboardInput({ // wVk: vk, // dwFlags: KEYEVENTF_KEYUP, // }); // const keyUpInput = Input({ // type: INPUT_KEYBOARD, // union: Union({ ki: keyUpKeyboardInput }), // }); // keyUpInput.ref().copy(inputArray, Input.size * i); // i += 1; // }); // user32.SendInput(i, inputArray, Input.size); // }; const pressHotkeys = (array) => { const inputArray = Buffer.alloc(Input.size * array.length * 2); let i = 0; // press all down Object.values(array).forEach((vk) => { const keyDownKeyboardInput = KeyboardInput({ wVk: vk }); const keyDownInput = Input({ type: INPUT_KEYBOARD, union: Union({ ki: keyDownKeyboardInput }), }); keyDownInput.ref().copy(inputArray, Input.size * i); i += 1; }); // press all up Object.values(array).forEach((vk) => { const keyUpKeyboardInput = KeyboardInput({ wVk: vk, dwFlags: KEYEVENTF_KEYUP, }); const keyUpInput = Input({ type: INPUT_KEYBOARD, union: Union({ ki: keyUpKeyboardInput }), }); keyUpInput.ref().copy(inputArray, Input.size * i); i += 1; }); user32.SendInput(i, inputArray, Input.size); }; const pressSuperKey = () => pressKey(VK_LWIN); export default function initInputChannel() { ipcMain.on(kInputChannel, (_e, { event }) => { if (event === kPressSuper) { if (isWin) { const tray = user32.FindWindowA("Shell_TrayWnd", null); if (!tray) { spawn("C:\\Windows\\explorer.exe", { detached: true, stdio: "ignore", }); } pressSuperKey(); } } if (event === kPressOSK) { if (isWin10) pressHotkeys([VK_LWIN, VK_CONTROL, VK_O]); else { spawn( "C:\\Program Files\\Common Files\\microsoft shared\\ink\\TabTip.exe", { detached: true, stdio: "ignore", } ); } } }); }