Skip to content

koffi 获取指定 hwnd 句柄 id 的窗口坐标

使用 GetWindowRect 获取

GetWindowRect:https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-getwindowrect

getAllRectangles.js

js
const koffi = require("koffi");

// 加载 user32.dll
const user32 = koffi.load("user32.dll");

const LPRECT = koffi.struct("LPRECT", {
  left: "long",
  top: "long",
  right: "long",
  bottom: "long",
});

// https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-getwindowrect
const GetWindowRect = user32.func(
  "bool __stdcall GetWindowRect(int32, LPRECT* lpRect)"
  // 也可以这么写:
  // "__stdcall", "GetWindowRect", "bool", [
  // "int32",
  // koffi.pointer(LPRECT),
  // ]
);

const getRectangleOfWindow = (hwnd) => {
  const rectBuffer = Buffer.alloc(16);

  console.log("hwnd", hwnd);

  if (GetWindowRect(hwnd, rectBuffer)) {
    console.log("koffi.decode(LPRECT)", koffi.decode(rectBuffer, LPRECT));
    const left = rectBuffer.readInt32LE(0);
    const top = rectBuffer.readInt32LE(4);
    const right = rectBuffer.readInt32LE(8);
    const bottom = rectBuffer.readInt32LE(12);
    return { left, top, right, bottom };
  }
  throw new Error(`Failed to get rectangle for hwnd: ${hwnd}`);
};

function convertToXYWH({ rect, className }) {
  const newItem = {
    x: rect.left,
    y: rect.top,
    width: rect.right - rect.left,
    height: rect.bottom - rect.top,
    className,
  };
  if (!className) {
    delete newItem.className;
  }
  return newItem;
}

function getAllRectangles(hwndArray) {
  const rectangles = [];

  hwndArray.forEach((hwnd) => {
    try {
      const rect = getRectangleOfWindow(hwnd);
      rectangles.push(convertToXYWH({ rect }));
    } catch (err) {
      console.error(err.message);
    }
  });

  return rectangles;
}

const hwndArray = ["655872"].map(Number);

const result = getAllRectangles(hwndArray);
console.log("result", result);

代码提示

提示

hwndArray 句柄 ID 可以通过 electron desktopCapturer.getSources API 获取

提示

bool __stdcall GetWindowRect(int32, LPRECT* lpRect) 这里的 LPRECT* 是一个指针类型,必须要带*

下面两种写法都是正确的,表示的同一个效果,Node.js 为了表示引用 LPRECT,可以使用写法 2

写法 1:

js
const GetWindowRect = user32.func(
  "bool __stdcall GetWindowRect(int32, LPRECT* lpRect)"
);

写法 2:

JS
const GetWindowRect = user32.func(
  "__stdcall", "GetWindowRect", "bool", [
  "int32",
  koffi.pointer(LPRECT),
  ]
);

提示

在这段代码中,koffi.decoderectBuffer.readInt32LE 可以达到相同的效果

js
const getRectangleOfWindow = (hwnd) => {
  const rectBuffer = Buffer.alloc(16);

  console.log("hwnd", hwnd);

  if (GetWindowRect(hwnd, rectBuffer)) {
    console.log("koffi.decode(LPRECT)", koffi.decode(rectBuffer, LPRECT));
    const left = rectBuffer.readInt32LE(0);
    const top = rectBuffer.readInt32LE(4);
    const right = rectBuffer.readInt32LE(8);
    const bottom = rectBuffer.readInt32LE(12);
    return { left, top, right, bottom };
  }
  throw new Error(`Failed to get rectangle for hwnd: ${hwnd}`);
};

下面代码 1 和代码 2 效果一样

区别:代码 2 更通用,适用于 Node.js 所有的库,代码 1 在 koffi 中适用

代码 1:

js
console.log("koffi.decode(LPRECT)", koffi.decode(rectBuffer, LPRECT));

代码 2:

js
const left = rectBuffer.readInt32LE(0);
const top = rectBuffer.readInt32LE(4);
const right = rectBuffer.readInt32LE(8);
const bottom = rectBuffer.readInt32LE(12);

代码运行结果

下面的结果有 6 像素的误差,原理可以看微软的官方文档 https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-getwindowrect#remarks

powershell
PS C:\Users\Administrator\Documents\mycode\electron-screenshot> node .\main\koffi\getAllRectangles.js
hwnd 655872
koffi.decode(LPRECT) { left: -6, top: -6, right: 2055, bottom: 1111 }
result [ { x: -6, y: -6, width: 2061, height: 1117 } ]
PS C:\Users\Administrator\Documents\mycode\electron-screenshot>

这个“6”的误差可能是由窗口的边框和阴影造成的。Windows 系统中的窗口通常带有外部边框和阴影区域,这些区域属于窗口的一部分,因此在使用 GetWindowRect 函数获取窗口的矩形坐标时,返回的坐标会包含窗口的边框、阴影等外部装饰部分。

具体的原因如下:

  1. 窗口边框:默认情况下,Windows 的窗口有边框,边框的厚度会导致坐标有一定的偏移。
  2. 窗口阴影:现代 Windows 系统(如 Windows 10 和 11)在窗口外部增加了阴影效果,阴影部分也是窗口区域的一部分,因此也会影响坐标计算。

如何解决

如果你只想获取窗口的客户端区域(不包括边框和阴影),可以使用 GetClientRect 函数而不是 GetWindowRectGetClientRect 函数只返回窗口内容区域的坐标,而不包含窗口装饰(如标题栏、边框、阴影)。

你可以在现有代码基础上增加对 GetClientRect 的调用,来获取更精确的窗口坐标。

如果你依然需要 GetWindowRect 的结果,但想去掉边框和阴影的影响,可以使用 AdjustWindowRectEx 函数,它可以根据窗口的样式调整矩形区域,从而排除边框和阴影的影响。

使用 GetWindowRect 获取,修复 6 像素误差

修改后的代码

js
const koffi = require("koffi");

// 加载 user32.dll
const user32 = koffi.load("user32.dll");

const LPRECT = koffi.struct("LPRECT", {
  left: "long",
  top: "long",
  right: "long",
  bottom: "long",
});

// 加载 GetWindowRect 函数
const GetWindowRect = user32.func(
  "bool __stdcall GetWindowRect(int32, LPRECT* lpRect)"
);

// 加载 GetClientRect 函数
const GetClientRect = user32.func( 
  "bool __stdcall GetClientRect(int32, LPRECT* lpRect)"
); 

// 获取窗口矩形函数
const getRectangleOfWindow = (hwnd) => {
  const rectBuffer = Buffer.alloc(16);

  console.log("hwnd", hwnd);

  // 优先获取窗口的客户区矩形
  if (GetClientRect(hwnd, rectBuffer)) { 
    console.log("koffi.decode(LPRECT)", koffi.decode(rectBuffer, LPRECT)); 
    const left = rectBuffer.readInt32LE(0); 
    const top = rectBuffer.readInt32LE(4); 
    const right = rectBuffer.readInt32LE(8); 
    const bottom = rectBuffer.readInt32LE(12); 
    return { left, top, right, bottom }; 
  }

  // 如果 GetClientRect 失败,则尝试获取完整窗口区域
  if (GetWindowRect(hwnd, rectBuffer)) {
    console.log("koffi.decode(LPRECT)", koffi.decode(rectBuffer, LPRECT));
    const left = rectBuffer.readInt32LE(0);
    const top = rectBuffer.readInt32LE(4);
    const right = rectBuffer.readInt32LE(8);
    const bottom = rectBuffer.readInt32LE(12);
    return { left, top, right, bottom };
  }

  throw new Error(`Failed to get rectangle for hwnd: ${hwnd}`);
};

// 将矩形转为 x, y, width, height
function convertToXYWH({ rect, className }) {
  const newItem = {
    x: rect.left,
    y: rect.top,
    width: rect.right - rect.left,
    height: rect.bottom - rect.top,
    className,
  };
  if (!className) {
    delete newItem.className;
  }
  return newItem;
}

function getAllRectangles(hwndArray) {
  const rectangles = [];

  hwndArray.forEach((hwnd) => {
    try {
      const rect = getRectangleOfWindow(hwnd);
      rectangles.push(convertToXYWH({ rect }));
    } catch (err) {
      console.error(err.message);
    }
  });

  return rectangles;
}

const hwndArray = ["655872"].map(Number);

const result = getAllRectangles(hwndArray);
console.log("result", result);

// module.exports = { getAllRectangles };

代码运行结果如下:

powershell
PS C:\Users\Administrator\Documents\mycode\electron-screenshot> node .\main\koffi\getAllRectangles.js
hwnd 655872
koffi.decode(LPRECT) { left: 0, top: 0, right: 2048, bottom: 1104 }
result [ { x: 0, y: 0, width: 2048, height: 1104 } ]
PS C:\Users\Administrator\Documents\mycode\electron-screenshot>

已修复存在 6 像素误差的问题

增加任务栏图标坐标获取

js
const koffi = require("koffi");

// 加载 user32.dll
const user32 = koffi.load("user32.dll");

const LPRECT = koffi.struct("LPRECT", {
  left: "long",
  top: "long",
  right: "long",
  bottom: "long",
});
// https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-getwindowrect
// 加载 GetWindowRect 函数
const GetWindowRect = user32.func(
  "bool __stdcall GetWindowRect(int32, LPRECT* lpRect)"
);

// 加载 GetClientRect 函数
const GetClientRect = user32.func(
  "bool __stdcall GetClientRect(int32, LPRECT* lpRect)"
  // 也可以这么写:
  // "__stdcall", "GetWindowRect", "bool", [
  // "int32",
  // koffi.pointer(LPRECT),
  // ]
);
// FindWindowW: ["int32", ["string", "string"]],
const FindWindowW = user32.func("__stdcall", "FindWindowW", "int32", [
  "string",
  "string",
]);

// 定义 GetClassNameW 函数
const GetClassNameW = user32.func(
  "int __stdcall GetClassNameW(int32 hWnd, char* lpClassName, int nMaxCount)"
);

// 获取窗口矩形函数
const getRectangleOfWindow = (hwnd) => {
  const rectBuffer = Buffer.alloc(16);

  console.log("hwnd", hwnd);

  // 优先获取窗口的客户区矩形
  if (GetClientRect(hwnd, rectBuffer)) {
    console.log("koffi.decode(LPRECT)", koffi.decode(rectBuffer, LPRECT));
    const left = rectBuffer.readInt32LE(0);
    const top = rectBuffer.readInt32LE(4);
    const right = rectBuffer.readInt32LE(8);
    const bottom = rectBuffer.readInt32LE(12);
    return { left, top, right, bottom };
  }

  // 如果 GetClientRect 失败,则尝试获取完整窗口区域
  if (GetWindowRect(hwnd, rectBuffer)) {
    console.log("koffi.decode(LPRECT)", koffi.decode(rectBuffer, LPRECT));
    const left = rectBuffer.readInt32LE(0);
    const top = rectBuffer.readInt32LE(4);
    const right = rectBuffer.readInt32LE(8);
    const bottom = rectBuffer.readInt32LE(12);
    return { left, top, right, bottom };
  }

  throw new Error(`Failed to get rectangle for hwnd: ${hwnd}`);
};

const BUFFER_SIZE = 256; // 缓冲区大小,足够存储类名
const classNameBuffer = Buffer.alloc(BUFFER_SIZE * 2); // WCHAR 是 2 字节

// 定义 EnumWindowsProc 回调函数的签名
const EnumWindowsProc = koffi.proto(
  "bool __stdcall EnumWindowsProc(int32 hwnd, long lParam)"
);

// 定义 EnumChildWindows 函数
const EnumChildWindows = user32.func(
  // "bool __stdcall EnumChildWindows(int32 hWndParent, void* lpEnumFunc, int32 lParam)"
  "__stdcall",
  "EnumChildWindows",
  "bool",
  ["int32", koffi.pointer(EnumWindowsProc), "int32"]
);

const IsWindowVisible = user32.func(
  "bool __stdcall IsWindowVisible(int32 hwnd)"
);

const getClassNameOfWindow = (hwnd) => {
  const length = GetClassNameW(hwnd, classNameBuffer, BUFFER_SIZE);
  if (length > 0) {
    return classNameBuffer.toString("ucs2", 0, length * 2); // 使用 ucs2 解码宽字符
  }
  throw new Error(`Failed to get class name for hwnd: ${hwnd}`);
};

// 将矩形转为 x, y, width, height
function convertToXYWH({ rect, className }) {
  const newItem = {
    x: rect.left,
    y: rect.top,
    width: rect.right - rect.left,
    height: rect.bottom - rect.top,
    className,
  };
  if (!className) {
    delete newItem.className;
  }
  return newItem;
}

function getTaskBarWindows(rectangles) {
  const hwnd = FindWindowW(Buffer.from(`Shell_TrayWnd\0`, "ucs2"), null);

  if (hwnd !== 0) {
    const cb1 = koffi.register((hwndChild, lParam) => {
      if (!IsWindowVisible(hwndChild)) {
        console.log("[hwndChild, lParam false]", hwndChild, lParam);
        return true;
      }

      console.log("[hwndChild, lParam true]", hwndChild, lParam);
      try {
        const rect = getRectangleOfWindow(hwndChild);
        const className = getClassNameOfWindow(hwndChild);
        if (rect && className && !["Static"].includes(className)) {
          rectangles.unshift(convertToXYWH({ rect, className }));
        }
      } catch (err) {
        console.error(err.message);
      }

      return true;
    }, koffi.pointer(EnumWindowsProc));

    EnumChildWindows(hwnd, cb1, 0);

    koffi.unregister(cb1);

    const parentRect = getRectangleOfWindow(hwnd);
    const parentClassName = getClassNameOfWindow(hwnd);
    if (parentRect && parentClassName) {
      rectangles.push(
        convertToXYWH({ rect: parentRect, className: parentClassName })
      );
    }
  } else {
    console.error("Window not found.");
  }
}
function getAllRectangles(hwndArray) {
  const rectangles = [];

  hwndArray.forEach((hwnd) => {
    try {
      const rect = getRectangleOfWindow(hwnd);
      rectangles.push(convertToXYWH({ rect }));
    } catch (err) {
      console.error(err.message);
    }
  });

  getTaskBarWindows(rectangles);

  return rectangles;
}

const hwndArray = ["655872"].map(Number);
const result = getAllRectangles(hwndArray);
console.log("result", result);

module.exports = { getAllRectangles };

代码执行结果

powershell
PS C:\Users\Administrator\Documents\mycode\electron-screenshot> node .\main\koffi\getAllRectangles.js
hwnd 655872
koffi.decode(LPRECT) { left: 0, top: 0, right: 2048, bottom: 1104 }
[hwndChild, lParam false] 66176 0
[hwndChild, lParam false] 196688 0
[hwndChild, lParam true] 196690 0
hwnd 196690
koffi.decode(LPRECT) { left: 0, top: 0, right: 0, bottom: 0 }
[hwndChild, lParam true] 196692 0
hwnd 196692
koffi.decode(LPRECT) { left: 0, top: 0, right: 316, bottom: 48 }
[hwndChild, lParam true] 196694 0
hwnd 196694
koffi.decode(LPRECT) { left: 0, top: 0, right: 0, bottom: 48 }
[hwndChild, lParam false] 196696 0
[hwndChild, lParam true] 196666 0
hwnd 196666
koffi.decode(LPRECT) { left: 0, top: 0, right: 0, bottom: 0 }
[hwndChild, lParam true] 393350 0
hwnd 393350
koffi.decode(LPRECT) { left: 0, top: 0, right: 0, bottom: 0 }
[hwndChild, lParam false] 131252 0
[hwndChild, lParam false] 131248 0
[hwndChild, lParam false] 131246 0
[hwndChild, lParam false] 131242 0
[hwndChild, lParam true] 131238 0
hwnd 131238
koffi.decode(LPRECT) { left: 0, top: 0, right: 0, bottom: 48 }
[hwndChild, lParam true] 131236 0
hwnd 131236
koffi.decode(LPRECT) { left: 0, top: 0, right: 356, bottom: 48 }
[hwndChild, lParam true] 131228 0
hwnd 131228
koffi.decode(LPRECT) { left: 0, top: 0, right: 356, bottom: 48 }
[hwndChild, lParam false] 196642 0
[hwndChild, lParam true] 66214 0
hwnd 66214
koffi.decode(LPRECT) { left: 0, top: 0, right: 2048, bottom: 48 }
[hwndChild, lParam true] 66216 0
hwnd 66216
koffi.decode(LPRECT) { left: 0, top: 0, right: 0, bottom: 0 }
hwnd 196684
koffi.decode(LPRECT) { left: 0, top: 0, right: 2048, bottom: 48 }
result [
  {
    x: 0,
    y: 0,
    width: 0,
    height: 0,
    className: 'Windows.UI.Input.InputSite.WindowClass'
  },
  {
    x: 0,
    y: 0,
    width: 2048,
    height: 48,
    className: 'Windows.UI.Composition.DesktopWindowContentBridge'
  },
  { x: 0, y: 0, width: 356, height: 48, className: 'MSTaskSwWClass' },
  { x: 0, y: 0, width: 356, height: 48, className: 'ReBarWindow32' },
  {
    x: 0,
    y: 0,
    width: 0,
    height: 48,
    className: 'TrayShowDesktopButtonWClass'
  },
  { x: 0, y: 0, width: 0, height: 0, className: 'SysPager' },
  { x: 0, y: 0, width: 0, height: 0, className: 'ToolbarWindow32' },
  { x: 0, y: 0, width: 0, height: 48, className: 'Button' },
  { x: 0, y: 0, width: 316, height: 48, className: 'TrayNotifyWnd' },
  {
    x: 0,
    y: 0,
    width: 0,
    height: 0,
    className: 'TrayDummySearchControl'
  },
  { x: 0, y: 0, width: 2048, height: 1104 },
  { x: 0, y: 0, width: 2048, height: 48, className: 'Shell_TrayWnd' }
]
PS C:\Users\Administrator\Documents\mycode\electron-screenshot>

上面的 left,top 是错误的,但是 right,bottom 是对的。

DANGER

根据官方的文档得知:GetClientRect 的 left,top 都为零,所以需要用 ClientToScreenz 转换成屏幕坐标,具体如何做继续往下看

https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-getclientrect#parameters

修复 GetClientRect left,right 为 0 的问题

ClientToScreen https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-clienttoscreen

js
const koffi = require("koffi");

// 加载 user32.dll
const user32 = koffi.load("user32.dll");

const LPRECT = koffi.struct("LPRECT", {
  left: "long",
  top: "long",
  right: "long",
  bottom: "long",
});
// https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-getwindowrect
// 加载 GetWindowRect 函数
const GetWindowRect = user32.func(
  "bool __stdcall GetWindowRect(int32, LPRECT* lpRect)"
);

// 加载 GetClientRect 函数
const GetClientRect = user32.func(
  "bool __stdcall GetClientRect(int32, LPRECT* lpRect)"
  // 也可以这么写:
  // "__stdcall", "GetWindowRect", "bool", [
  // "int32",
  // koffi.pointer(LPRECT),
  // ]
);
// FindWindowW: ["int32", ["string", "string"]],
const FindWindowW = user32.func("__stdcall", "FindWindowW", "int32", [
  "string",
  "string",
]);

// 定义 GetClassNameW 函数
const GetClassNameW = user32.func(
  "int __stdcall GetClassNameW(int32 hWnd, char* lpClassName, int nMaxCount)"
);

const ClientToScreen = user32.func( 
  "bool __stdcall ClientToScreen(int32, LPRECT* lpRect)"
); 

// 获取窗口矩形函数
const getRectangleOfWindow = (hwnd, type) => {
  const rectBuffer = Buffer.alloc(16);

  // console.log("hwnd", hwnd);
  // 优先获取窗口的客户区矩形
  // 根据官方的文档得知:GetClientRect 的 left,top都为零,所以需要转换成屏幕坐标
  // https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-getclientrect#parameters
  if (GetClientRect(hwnd, rectBuffer)) {
    // 获取客户区的矩形
    const rect = koffi.decode(rectBuffer, LPRECT);

    // 将客户区坐标转换为屏幕坐标
    if (ClientToScreen(hwnd, rectBuffer)) { 
      const left = rectBuffer.readInt32LE(0); 
      const top = rectBuffer.readInt32LE(4); 

      // 计算客户区的宽度和高度
      const width = rect.right - rect.left; 
      const height = rect.bottom - rect.top; 

      // 将左上角更新为屏幕坐标
      rect.left = left; 
      rect.top = top; 

      // 右下角坐标转换为屏幕坐标
      rect.right = rect.left + width; 
      rect.bottom = rect.top + height; 
    } 

    return rect;
    // return koffi.decode(rectBuffer, LPRECT);
    // console.log("koffi.decode(LPRECT)", koffi.decode(rectBuffer, LPRECT));
    // const left = rectBuffer.readInt32LE(0);
    // const top = rectBuffer.readInt32LE(4);
    // const right = rectBuffer.readInt32LE(8);
    // const bottom = rectBuffer.readInt32LE(12);

    // console.log("{ left, top, right, bottom }", { left, top, right, bottom });
    // return { left, top, right, bottom };
  } 

  // 如果 GetClientRect 失败,则尝试获取完整窗口区域
  if (GetWindowRect(hwnd, rectBuffer)) {
    return koffi.decode(rectBuffer, LPRECT);
    // console.log("koffi.decode(LPRECT)", koffi.decode(rectBuffer, LPRECT));

    // const left = rectBuffer.readInt32LE(0);
    // const top = rectBuffer.readInt32LE(4);
    // const right = rectBuffer.readInt32LE(8);
    // const bottom = rectBuffer.readInt32LE(12);

    // return { left, top, right, bottom };
  }

  throw new Error(`Failed to get rectangle for hwnd: ${hwnd}`);
};

const BUFFER_SIZE = 256; // 缓冲区大小,足够存储类名
const classNameBuffer = Buffer.alloc(BUFFER_SIZE * 2); // WCHAR 是 2 字节

// 定义 EnumWindowsProc 回调函数的签名
const EnumWindowsProc = koffi.proto(
  "bool __stdcall EnumWindowsProc(int32 hwnd, long lParam)"
);

// 定义 EnumChildWindows 函数
const EnumChildWindows = user32.func(
  // "bool __stdcall EnumChildWindows(int32 hWndParent, void* lpEnumFunc, int32 lParam)"
  "__stdcall",
  "EnumChildWindows",
  "bool",
  ["int32", koffi.pointer(EnumWindowsProc), "int32"]
);

const IsWindowVisible = user32.func(
  "bool __stdcall IsWindowVisible(int32 hwnd)"
);

const getClassNameOfWindow = (hwnd) => {
  const length = GetClassNameW(hwnd, classNameBuffer, BUFFER_SIZE);
  if (length > 0) {
    return classNameBuffer.toString("ucs2", 0, length * 2); // 使用 ucs2 解码宽字符
  }
  throw new Error(`Failed to get class name for hwnd: ${hwnd}`);
};

// 将矩形转为 x, y, width, height
function convertToXYWH({ rect, className }) {
  const newItem = {
    x: rect.left,
    y: rect.top,
    width: rect.right - rect.left,
    height: rect.bottom - rect.top,
    className,
  };
  if (!className) {
    delete newItem.className;
  }
  return newItem;
}

function getTaskBarWindows(rectangles = []) {
  const hwnd = FindWindowW(Buffer.from(`Shell_TrayWnd\0`, "ucs2"), null);

  if (hwnd !== 0) {
    const cb1 = koffi.register((hwndChild, lParam) => {
      if (!IsWindowVisible(hwndChild)) {
        // console.log("[hwndChild, lParam false]", hwndChild, lParam);
        return true;
      }

      // console.log("[hwndChild, lParam true]", hwndChild, lParam);
      try {
        const rect = getRectangleOfWindow(hwndChild, "TaskBarWindows");
        const className = getClassNameOfWindow(hwndChild);
        if (rect && className && !["Static"].includes(className)) {
          rectangles.unshift(convertToXYWH({ rect, className }));
        }
      } catch (err) {
        console.error(err.message);
      }

      return true;
    }, koffi.pointer(EnumWindowsProc));

    EnumChildWindows(hwnd, cb1, 0);

    koffi.unregister(cb1);

    const parentRect = getRectangleOfWindow(hwnd);
    const parentClassName = getClassNameOfWindow(hwnd);
    if (parentRect && parentClassName) {
      rectangles.push(
        convertToXYWH({ rect: parentRect, className: parentClassName })
      );
    }
  } else {
    console.error("Window not found.");
  }
  return rectangles;
}
function getAllRectangles(hwndArray) {
  const rectangles = [];
  hwndArray = hwndArray.map((item) => Number(item));

  console.log("hwndArray", hwndArray);

  hwndArray.forEach((hwnd) => {
    try {
      const rect = getRectangleOfWindow(hwnd);
      rectangles.push(convertToXYWH({ rect }));
    } catch (err) {
      console.error(err.message);
    }
  });

  getTaskBarWindows(rectangles);

  console.log("rectangles", rectangles);

  return rectangles;
}

// const hwndArray = ["655872"].map(Number);
// const result = getAllRectangles(hwndArray);
// console.log("result", result);

module.exports = { getAllRectangles };

最终输出了正确的坐标

powershell
hwndArray [ 655872, 1705290 ]
rectangles [
  {
    x: 0,
    y: 2760,
    width: 0,
    height: 0,
    className: 'Windows.UI.Input.InputSite.WindowClass'
  },
  {
    x: 0,
    y: 2760,
    width: 5120,
    height: 120,
    className: 'Windows.UI.Composition.DesktopWindowContentBridge'
  },
  {
    x: 2005,
    y: 2760,
    width: 890,
    height: 120,
    className: 'MSTaskSwWClass'
  },
  {
    x: 2005,
    y: 2760,
    width: 890,
    height: 120,
    className: 'ReBarWindow32'
  },
  {
    x: 4330,
    y: 2760,
    width: 0,
    height: 120,
    className: 'TrayShowDesktopButtonWClass'
  },
  { x: 4330, y: 2760, width: 0, height: 0, className: 'SysPager' },
  {
    x: 4330,
    y: 2760,
    width: 0,
    height: 0,
    className: 'ToolbarWindow32'
  },
  { x: 4330, y: 2760, width: 0, height: 120, className: 'Button' },
  {
    x: 4330,
    y: 2760,
    width: 790,
    height: 120,
    className: 'TrayNotifyWnd'
  },
  {
    x: 0,
    y: 2760,
    width: 0,
    height: 0,
    className: 'TrayDummySearchControl'
  },
  { x: 514, y: 162, width: 3930, height: 2085 },
  { x: 5120, y: 0, width: 3840, height: 2064 },
  {
    x: 0,
    y: 2760,
    width: 5120,
    height: 120,
    className: 'Shell_TrayWnd'
  }
]