production-taskbar-client / src / main / workers / autoswitchWorker.js
autoswitchWorker.js
Raw
import { parentPort } from "worker_threads";
import ffi from "@inigolabs/ffi-napi";

import { createBarcodeHandler } from "../utils/barcodeHandler";
import {
  getWindowData,
  findWindow,
  getWindowRect,
} from "../utils/winapi/getWindowData";
import getPixelColor from "../utils/winapi/getPixelColor";
import { BOOL, DWORD, UINT } from "../utils/winapi/types";

const isDev = process.env.NODE_ENV === "development";

const kChangeDelayEvent = "change-delay";

const user32 = ffi.Library("user32", {
  GetForegroundWindow: ["int", []],
  AllowSetForegroundWindow: [BOOL, [DWORD]],
  LockSetForegroundWindow: [BOOL, [UINT]],
});

let isHandler;
let delay;
let intervalKhm;
let timeoutMao;
let dpi = 1;

const maoWin = "MAO_Client.exe";
const khmWinTitle = "WinKHM ©2010 OPTOSOFT";

const colorGreen = 10_747_542; // in hex a3fe96;
const colorGreen2 = 9_895_830; // in hex 0x96ff96;
const colorGray = 9_868_950; // in hex 0x969696
const colorRed = 255; // in hex 0xff, for testing in dev

// ============================

const activateMao = () => {
  parentPort.postMessage({
    event: "winactivate",
    data: "MAO_Client.exe",
  });
};

const activateKhm = () => {
  if (!intervalKhm) {
    let isKhmOk = false;

    findWindow({ name: khmWinTitle }, (data) => {
      const { hwnd } = data;
      // winActivate(hwnd);
      parentPort.postMessage({
        event: "winactivate",
        data: "WinKHM.exe",
      });

      intervalKhm = setInterval(() => {
        const rect = getWindowRect(hwnd);
        const x = Math.floor((rect.right - rect.left) / 2 / dpi);
        const y = Math.floor(((rect.bottom - rect.top) * 0.82) / dpi);
        const color = getPixelColor(hwnd, x, y);

        if (
          (color === colorGreen ||
            color === colorGreen2 ||
            (isDev && color === colorRed)) &&
          !isKhmOk
        ) {
          isKhmOk = true;
        }

        if (color === colorGray && isKhmOk) {
          isKhmOk = false;
          clearInterval(intervalKhm);
          intervalKhm = null;
          setTimeout(() => {
            activateMao();
          }, 1000);
        }
      }, 1000);
    });
  }
};

const isBoardBarcode = (barcode) => {
  const pattern = /^\w\d{3,4}$/; // Board num patter: B123 or B1234
  const re = new RegExp(pattern, "i");

  return !!barcode.match(re);
};

const barcodeProcessor = (barcode) => {
  const foregroundHwnd = user32.GetForegroundWindow();
  const { path } = getWindowData(foregroundHwnd);
  const isMao = path.includes(maoWin);

  if (isMao && isBoardBarcode(barcode)) {
    parentPort.postMessage({
      event: "activateKhm",
      data: delay,
    });

    timeoutMao = setTimeout(
      () => {
        clearInterval(intervalKhm);
        intervalKhm = null;
        activateKhm();
      },
      delay ? delay * 1000 : 200
    );
  }
};

const barcodeHandler = createBarcodeHandler(barcodeProcessor, 100);

parentPort.on("message", ({ event, payload }) => {
  switch (event) {
    case kChangeDelayEvent:
      delay = parseInt(payload, 10);

      if (Number.isNaN(delay)) {
        parentPort.postMessage({
          event: "remove-listener",
        });
        isHandler = false;
      } else if (!isHandler) {
        parentPort.postMessage({
          event: "add-listener",
        });
        isHandler = true;
      }

      clearTimeout(timeoutMao);
      clearInterval(intervalKhm);
      intervalKhm = null;
      break;
    case "set-dpi":
      dpi = payload;
      break;
    case "key-down": {
      const { char } = payload.data;
      barcodeHandler(char);
      break;
    }
    default:
      break;
  }
});