production-taskbar-client / src / main / ipc / winEventChannel.js
winEventChannel.js
Raw
import ffi from "@inigolabs/ffi-napi";
import { app, ipcMain } from "electron";
import { Worker } from "worker_threads";

import { LPARAM, WPARAM, BOOL, DWORD, UINT } from "../utils/winapi/types";

const isDev = process.env.NODE_ENV === "development";
const WM_QUIT = 0x0012;
let enumWindows = [];
let threadId;

const user32 = ffi.Library("user32", {
  PostThreadMessageA: [BOOL, [DWORD, UINT, WPARAM, LPARAM]],
});

export default function initWinEventChannel(mainWindow) {
  // Worker threads cant access files is app.asar, access unpacked worker js files in app.asar.unpacked
  const wineventWorker = new Worker(
    `${app
      .getAppPath()
      .replace("app.asar", "app.asar.unpacked")}/dist/workers/winEventHook.js`
  );

  wineventWorker.on("message", ({ event, data }) => {
    if (event === "enum-foreground") {
      enumWindows.push(data);
      return;
    }
    if (event === "set-threadId") {
      threadId = data;
      return;
    }
    mainWindow.webContents.send("winevent", { event, data });
  });

  // Because worker listen os event loop - its impossible get reply from listener it, so used main cached reply in [enumWindows]
  ipcMain.on("winevent", (_event, arg) => {
    if (arg === "enum-foreground") {
      enumWindows.forEach((data) =>
        mainWindow.webContents.send("winevent", {
          event: "foreground",
          data,
        })
      );
      if (!isDev) enumWindows = [];
    }
  });

  // Terminate worker on app exit, prevent long awaiting on app.relaunch
  ipcMain.on("main", ({ event }) => {
    if (event === "app-exit" && threadId) {
      // send QUIT message to winEventHook event loop. This prevents it from hanging on relaunch
      user32.PostThreadMessageA(threadId, WM_QUIT, 0, 0);
      wineventWorker.terminate();
    }
  });
}