production-taskbar-client / src / main / utils / winapi / getWindowData.js
getWindowData.js
Raw
import ffi from "@inigolabs/ffi-napi";
import ref from "@inigolabs/ref-napi";
import StructType from "ref-struct-di";

import { LONG, HWND, INT, BOOL, POINTER, LPARAM } from "./types";

const Struct = StructType(ref);

const PROCESS_QUERY_LIMITED_INFORMATION = 0x1000;
const lpdwordPtr = ref.refType(ref.types.ulong);
const lpwstrPtr = ref.refType(ref.types.CString);

const RECT = Struct({
  left: LONG,
  top: LONG,
  right: LONG,
  bottom: LONG,
});

const user32 = ffi.Library("user32", {
  GetWindowThreadProcessId: [INT, [HWND, lpdwordPtr]],
  GetWindowTextW: [INT, [HWND, POINTER, INT]],
  FindWindowW: [INT, [lpwstrPtr, lpwstrPtr]],
  EnumWindows: [BOOL, [POINTER, LPARAM]],
  IsWindowVisible: [BOOL, [HWND]],
  GetWindowRect: [BOOL, [HWND, POINTER]],
});

const kernel32 = new ffi.Library("Kernel32", {
  OpenProcess: [INT, [INT, BOOL, INT]],
  CloseHandle: [BOOL, [INT]],
  QueryFullProcessImageNameW: [BOOL, [LONG, "uint32", lpwstrPtr, POINTER]],
});

function decodeUnicodeBuffer(buffer) {
  return buffer
    .toString("ucs2")
    .replace(/[\u0000-\u0008,\u000A-\u001F,\u007F-\u00A0]+/g, ""); // NOSONAR
}

function getWindowData(hwnd) {
  const pidPtr = ref.alloc(lpdwordPtr);
  const threadPid = user32.GetWindowThreadProcessId(hwnd, pidPtr);
  const pid = pidPtr.readInt32LE();

  const hProcess = kernel32.OpenProcess(
    PROCESS_QUERY_LIMITED_INFORMATION,
    false,
    pid
  );

  const maxLength = 0x400; // 1024
  const pPath = Buffer.alloc(maxLength);
  const pSize = ref.alloc(ref.types.uint32, maxLength);
  kernel32.QueryFullProcessImageNameW(hProcess, 0, pPath, pSize);
  const path = decodeUnicodeBuffer(pPath);
  kernel32.CloseHandle(hProcess);

  const pTitle = Buffer.alloc(512);
  user32.GetWindowTextW(hwnd, pTitle, 512);
  const title = decodeUnicodeBuffer(pTitle);

  return { title, hwnd, path, pid, threadPid };
}

function findExactWindow(className, windowName) {
  const namePtr = windowName ? ref.allocCString(windowName, "ucs2") : null;
  const classNamePtr = className ? ref.allocCString(className, "ucs2") : null;
  return user32.FindWindowW(classNamePtr, namePtr);
}

function findWindow({ name, exeName }, callback) {
  let findedData; // use coz callback doesnt stop enumerating on return false (why?)
  let isIncludes;
  const windowsProc = ffi.Callback(INT, [HWND, LPARAM], (hwnd) => {
    if (user32.IsWindowVisible(hwnd)) {
      const data = getWindowData(hwnd);

      if (data.title && name)
        isIncludes = data.title.toLowerCase().includes(name.toLowerCase());
      if (data.path && exeName)
        isIncludes = data.path.toLowerCase().includes(exeName.toLowerCase());

      if (!findedData && isIncludes) {
        if (callback) callback(data);
        findedData = data;
        return 0; // must stop enumerate
      }
    }
    return 1; // continue enumerate
  });

  user32.EnumWindows(windowsProc, 0);
  return findedData;
}

function getWindowRect(hwnd) {
  const rect = new RECT();
  const result = user32.GetWindowRect(hwnd, rect.ref());
  return result ? rect : false;
}

export { getWindowData, findWindow, findExactWindow, getWindowRect };