production-taskbar-client / src / main / ipc / inputChannel.js
inputChannel.js
Raw
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",
          }
        );
      }
    }
  });
}