Skip to content

feat: 重构MaaToolkitDesktopWindow相关API #1228

Draft
YJBeetle wants to merge 3 commits into
MaaXYZ:mainfrom
YJBeetle:NewStructDesktopWindow
Draft

feat: 重构MaaToolkitDesktopWindow相关API #1228
YJBeetle wants to merge 3 commits into
MaaXYZ:mainfrom
YJBeetle:NewStructDesktopWindow

Conversation

@YJBeetle
Copy link
Copy Markdown
Contributor

@YJBeetle YJBeetle commented Mar 25, 2026

继续这里的讨论 #1115

这里开PR主要是想作为一个可能的提案来讨论下

本提交将会在API层面发生一些变动
本提交旨在提升相关的API的通用性和可扩展性,同时更语意化。

以下为具体变更:
将DesktopWindow划分为通用字段和平台专有用段。
原有的class_name是win专有,现API层已冠以平台前缀。
GetHandle返回值类型从指针换成uint64整数以适应多种平台,同时保证了不同语言binding的一致性。 添加了MacOS的专有字段
Linux的专有字段进行重命名

Summary by Sourcery

重构跨平台的 DesktopWindow API,引入与平台无关的句柄和窗口名称访问器以及平台特定字段,并更新绑定、CLI 工具、示例和测试以使用新的接口。

新特性:

  • 通过 DesktopWindow API 和各语言绑定,暴露 macOS 特定的桌面窗口元数据,包括 PID、bundle ID 和应用名称。
  • 通过 DesktopWindow API 和各语言绑定,暴露 Linux Wayland 桌面窗口的 socket 路径。

改进:

  • 在各平台上统一桌面窗口句柄为 uint64 表示,以改进跨语言绑定的一致性。
  • 将 DesktopWindow 的平台无关字段和方法与平台特定字段和方法分离,以实现更清晰且更易扩展的 API。
  • 调整 Python 和 NodeJS 绑定,以及 CLI 工具和示例,以使用新的 DesktopWindow getter 和平台特定元数据。
Original summary in English

Summary by Sourcery

Refactor the cross-platform DesktopWindow API to introduce platform-agnostic handle and window name accessors plus platform-specific fields, and update bindings, CLI tools, samples, and tests to use the new interface.

New Features:

  • Expose macOS-specific desktop window metadata including PID, bundle ID, and application name through the DesktopWindow API and language bindings.
  • Expose Linux Wayland socket path for desktop windows through the DesktopWindow API and language bindings.

Enhancements:

  • Unify desktop window handle representation as a uint64 across platforms to improve cross-language binding consistency.
  • Separate platform-independent and platform-specific DesktopWindow fields and methods for clearer, more extensible APIs.
  • Adjust Python and NodeJS bindings, as well as CLI utilities and samples, to consume the new DesktopWindow getters and platform-specific metadata.

本提交将会在API层面发生一些变动
本提交旨在提升相关的API的通用性和可扩展性,同时更语意化。

以下为具体变更:
将DesktopWindow划分为通用字段和平台专有用段。
原有的class_name是win专有,现API层已冠以平台前缀。
GetHandle返回值类型从指针换成uint64整数以适应多种平台,同时保证了不同语言binding的一致性。
添加了MacOS的专有字段
Linux的专有字段进行重命名
Copy link
Copy Markdown
Contributor

@sourcery-ai sourcery-ai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

你好——我发现了 1 个问题,并给出了一些整体层面的反馈:

  • 类似 win32_class_name(), macos_pid()linux_socket_path() 这样的特定平台访问器,在其他平台上被调用时会记录 "Only available on ..." 日志,但更高层的代码(Python 绑定、NodeJS 控制器)现在会无条件调用所有这些函数;建议要么在不支持的平台上移除这些日志,要么在绑定层按平台做调用控制,以避免运行时出现大量噪声错误输出。
  • 在将 MaaToolkitDesktopWindowGetHandle 的返回值改为 uint64_t 之后,若干调用点现在使用 reinterpret_cast<void*>(MaaToolkitDesktopWindowGetHandle(...));为了更好的可移植性和可读性,建议统一通过 uintptr_t 做中间转换(例如 reinterpret_cast<void*>(static_cast<uintptr_t>(...))),并且/或者增加一个静态断言 sizeof(void*) <= sizeof(uint64_t),把依赖的前提条件显式表达出来。
供 AI 代理使用的提示
Please address the comments from this code review:

## Overall Comments
- Platform-specific accessors like `win32_class_name()`, `macos_pid()`, and `linux_socket_path()` log `"Only available on ..."` when called on other platforms, but higher-level code (Python bindings, NodeJS controllers) now calls all of them unconditionally; consider either removing these logs on unsupported platforms or gating the binding calls by platform to avoid noisy error output at runtime.
- After changing `MaaToolkitDesktopWindowGetHandle` to return `uint64_t`, several call sites now do `reinterpret_cast<void*>(MaaToolkitDesktopWindowGetHandle(...))`; for better portability and clarity, consider consistently casting through `uintptr_t` (e.g., `reinterpret_cast<void*>(static_cast<uintptr_t>(...))`) and/or adding a static assertion that `sizeof(void*) <= sizeof(uint64_t)` to make the intended assumptions explicit.

## Individual Comments

### Comment 1
<location path="source/binding/Python/maa/toolkit.py" line_range="184-193" />
<code_context>
                 .MaaToolkitDesktopWindowGetWindowName(window_handle)
                 .decode()
             )
+            win32_class_name = (
+                Library.toolkit()
+                .MaaToolkitDesktopWindowGetWin32ClassName(window_handle)
+                .decode()
+            )
+            macos_pid = int(
+                Library.toolkit().MaaToolkitDesktopWindowGetMacOSPID(window_handle)
+            )
+            macos_bundle_id = (
+                Library.toolkit()
+                .MaaToolkitDesktopWindowGetMacOSBundleID(window_handle)
+                .decode()
+            )
+            macos_application_name = (
+                Library.toolkit()
+                .MaaToolkitDesktopWindowGetMacOSApplicationName(window_handle)
+                .decode()
+            )
+            linux_socket_path = (
+                Library.toolkit()
+                .MaaToolkitDesktopWindowGetLinuxSocketPath(window_handle)
+                .decode()
+            )
</code_context>
<issue_to_address>
**建议:** 无条件调用特定平台的 getter 可能会在其他平台上产生大量日志噪声。

这些访问器会针对每一个窗口被调用,即使当前平台并不支持它们。在这些平台上,C++ 层会记录一条错误日志并返回一个默认值,因此 `find_desktop_windows()` 会为每个窗口、针对这些本身与当前平台无关的字段生成一条错误日志。

建议要么使用 `sys.platform` 对这些调用做条件判断,只执行当前平台适用的 getter,要么调整 C++ 访问器,使其在不支持的平台上只返回默认值而不记录错误日志(或者仅在调试模式下记录)。这样可以避免在正常使用中出现误导性的错误噪声。

建议的实现方式:

```python
            handle = int(
                Library.toolkit().MaaToolkitDesktopWindowGetHandle(window_handle)
            )
            window_name = (
                Library.toolkit()
                .MaaToolkitDesktopWindowGetWindowName(window_handle)
                .decode()
            )
            if sys.platform.startswith("win"):
                win32_class_name = (
                    Library.toolkit()
                    .MaaToolkitDesktopWindowGetWin32ClassName(window_handle)
                    .decode()
                )
            else:
                win32_class_name = ""

            if sys.platform == "darwin":
                macos_pid = int(
                    Library.toolkit().MaaToolkitDesktopWindowGetMacOSPID(window_handle)
                )
                macos_bundle_id = (
                    Library.toolkit().MaaToolkitDesktopWindowGetMacOSBundleID(window_handle)
                    .decode()
                )
                macos_application_name = (
                    Library.toolkit()
                    .MaaToolkitDesktopWindowGetMacOSApplicationName(window_handle)
                    .decode()
                )
            else:
                macos_pid = 0
                macos_bundle_id = ""
                macos_application_name = ""

            if sys.platform.startswith("linux"):
                linux_socket_path = (
                    Library.toolkit()
                    .MaaToolkitDesktopWindowGetLinuxSocketPath(window_handle)
                    .decode()
                )
            else:
                linux_socket_path = ""

            handle = int(
                Library.toolkit().MaaToolkitDesktopWindowGetHandle(window_handle)
            )
            window_name = (
                Library.toolkit()

```

1. 确保在 `source/binding/Python/maa/toolkit.py` 文件顶部(与其他标准库导入放在一起)添加了 `import sys`2. 如果这些新变量(`win32_class_name`, `macos_pid`, `macos_bundle_id`, `macos_application_name`, `linux_socket_path`)稍后会被用于构建字典或对象,请确认默认值(`""``0`)与该结构期望的 schema/类型一致,并根据需要进行调整(例如,如果其他代码期望可空字段,可以改用 `None`)。
</issue_to_address>

Sourcery 对开源项目是免费的——如果你觉得我们的评审有帮助,欢迎分享 ✨
帮我变得更有用!请在每条评论上点击 👍 或 👎,我会根据反馈改进后续的评审。
Original comment in English

Hey - I've found 1 issue, and left some high level feedback:

  • Platform-specific accessors like win32_class_name(), macos_pid(), and linux_socket_path() log "Only available on ..." when called on other platforms, but higher-level code (Python bindings, NodeJS controllers) now calls all of them unconditionally; consider either removing these logs on unsupported platforms or gating the binding calls by platform to avoid noisy error output at runtime.
  • After changing MaaToolkitDesktopWindowGetHandle to return uint64_t, several call sites now do reinterpret_cast<void*>(MaaToolkitDesktopWindowGetHandle(...)); for better portability and clarity, consider consistently casting through uintptr_t (e.g., reinterpret_cast<void*>(static_cast<uintptr_t>(...))) and/or adding a static assertion that sizeof(void*) <= sizeof(uint64_t) to make the intended assumptions explicit.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- Platform-specific accessors like `win32_class_name()`, `macos_pid()`, and `linux_socket_path()` log `"Only available on ..."` when called on other platforms, but higher-level code (Python bindings, NodeJS controllers) now calls all of them unconditionally; consider either removing these logs on unsupported platforms or gating the binding calls by platform to avoid noisy error output at runtime.
- After changing `MaaToolkitDesktopWindowGetHandle` to return `uint64_t`, several call sites now do `reinterpret_cast<void*>(MaaToolkitDesktopWindowGetHandle(...))`; for better portability and clarity, consider consistently casting through `uintptr_t` (e.g., `reinterpret_cast<void*>(static_cast<uintptr_t>(...))`) and/or adding a static assertion that `sizeof(void*) <= sizeof(uint64_t)` to make the intended assumptions explicit.

## Individual Comments

### Comment 1
<location path="source/binding/Python/maa/toolkit.py" line_range="184-193" />
<code_context>
                 .MaaToolkitDesktopWindowGetWindowName(window_handle)
                 .decode()
             )
+            win32_class_name = (
+                Library.toolkit()
+                .MaaToolkitDesktopWindowGetWin32ClassName(window_handle)
+                .decode()
+            )
+            macos_pid = int(
+                Library.toolkit().MaaToolkitDesktopWindowGetMacOSPID(window_handle)
+            )
+            macos_bundle_id = (
+                Library.toolkit()
+                .MaaToolkitDesktopWindowGetMacOSBundleID(window_handle)
+                .decode()
+            )
+            macos_application_name = (
+                Library.toolkit()
+                .MaaToolkitDesktopWindowGetMacOSApplicationName(window_handle)
+                .decode()
+            )
+            linux_socket_path = (
+                Library.toolkit()
+                .MaaToolkitDesktopWindowGetLinuxSocketPath(window_handle)
+                .decode()
+            )
</code_context>
<issue_to_address>
**suggestion:** Unconditional calls to platform-specific getters may cause noisy logging on other platforms.

These accessors are invoked for every window, even when the current platform doesn’t support them. On those platforms the C++ layer logs an error and returns a default, so `find_desktop_windows()` will produce an error log per window for fields that are inherently irrelevant.

Consider either guarding these calls with `sys.platform` so only platform-appropriate getters run, or adjusting the C++ accessors so unsupported-platform calls return defaults without error logging (or only log in debug). This would avoid misleading error noise in normal usage.

Suggested implementation:

```python
            handle = int(
                Library.toolkit().MaaToolkitDesktopWindowGetHandle(window_handle)
            )
            window_name = (
                Library.toolkit()
                .MaaToolkitDesktopWindowGetWindowName(window_handle)
                .decode()
            )
            if sys.platform.startswith("win"):
                win32_class_name = (
                    Library.toolkit()
                    .MaaToolkitDesktopWindowGetWin32ClassName(window_handle)
                    .decode()
                )
            else:
                win32_class_name = ""

            if sys.platform == "darwin":
                macos_pid = int(
                    Library.toolkit().MaaToolkitDesktopWindowGetMacOSPID(window_handle)
                )
                macos_bundle_id = (
                    Library.toolkit()
                    .MaaToolkitDesktopWindowGetMacOSBundleID(window_handle)
                    .decode()
                )
                macos_application_name = (
                    Library.toolkit()
                    .MaaToolkitDesktopWindowGetMacOSApplicationName(window_handle)
                    .decode()
                )
            else:
                macos_pid = 0
                macos_bundle_id = ""
                macos_application_name = ""

            if sys.platform.startswith("linux"):
                linux_socket_path = (
                    Library.toolkit()
                    .MaaToolkitDesktopWindowGetLinuxSocketPath(window_handle)
                    .decode()
                )
            else:
                linux_socket_path = ""

            handle = int(
                Library.toolkit().MaaToolkitDesktopWindowGetHandle(window_handle)
            )
            window_name = (
                Library.toolkit()

```

1. Ensure `import sys` is present near the top of `source/binding/Python/maa/toolkit.py` (alongside the other standard-library imports).
2. If these new variables (`win32_class_name`, `macos_pid`, `macos_bundle_id`, `macos_application_name`, `linux_socket_path`) are later used to build a dict or object, confirm that the default values (`""` or `0`) align with the expected schema/typing for that structure, and adjust as needed (e.g., use `None` instead if the rest of the code expects nullable fields).
</issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

Comment on lines +184 to +193
win32_class_name = (
Library.toolkit()
.MaaToolkitDesktopWindowGetWin32ClassName(window_handle)
.decode()
)
macos_pid = int(
Library.toolkit().MaaToolkitDesktopWindowGetMacOSPID(window_handle)
)
macos_bundle_id = (
Library.toolkit()
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

建议: 无条件调用特定平台的 getter 可能会在其他平台上产生大量日志噪声。

这些访问器会针对每一个窗口被调用,即使当前平台并不支持它们。在这些平台上,C++ 层会记录一条错误日志并返回一个默认值,因此 find_desktop_windows() 会为每个窗口、针对这些本身与当前平台无关的字段生成一条错误日志。

建议要么使用 sys.platform 对这些调用做条件判断,只执行当前平台适用的 getter,要么调整 C++ 访问器,使其在不支持的平台上只返回默认值而不记录错误日志(或者仅在调试模式下记录)。这样可以避免在正常使用中出现误导性的错误噪声。

建议的实现方式:

            handle = int(
                Library.toolkit().MaaToolkitDesktopWindowGetHandle(window_handle)
            )
            window_name = (
                Library.toolkit()
                .MaaToolkitDesktopWindowGetWindowName(window_handle)
                .decode()
            )
            if sys.platform.startswith("win"):
                win32_class_name = (
                    Library.toolkit()
                    .MaaToolkitDesktopWindowGetWin32ClassName(window_handle)
                    .decode()
                )
            else:
                win32_class_name = ""

            if sys.platform == "darwin":
                macos_pid = int(
                    Library.toolkit().MaaToolkitDesktopWindowGetMacOSPID(window_handle)
                )
                macos_bundle_id = (
                    Library.toolkit().MaaToolkitDesktopWindowGetMacOSBundleID(window_handle)
                    .decode()
                )
                macos_application_name = (
                    Library.toolkit()
                    .MaaToolkitDesktopWindowGetMacOSApplicationName(window_handle)
                    .decode()
                )
            else:
                macos_pid = 0
                macos_bundle_id = ""
                macos_application_name = ""

            if sys.platform.startswith("linux"):
                linux_socket_path = (
                    Library.toolkit()
                    .MaaToolkitDesktopWindowGetLinuxSocketPath(window_handle)
                    .decode()
                )
            else:
                linux_socket_path = ""

            handle = int(
                Library.toolkit().MaaToolkitDesktopWindowGetHandle(window_handle)
            )
            window_name = (
                Library.toolkit()
  1. 确保在 source/binding/Python/maa/toolkit.py 文件顶部(与其他标准库导入放在一起)添加了 import sys
  2. 如果这些新变量(win32_class_name, macos_pid, macos_bundle_id, macos_application_name, linux_socket_path)稍后会被用于构建字典或对象,请确认默认值(""0)与该结构期望的 schema/类型一致,并根据需要进行调整(例如,如果其他代码期望可空字段,可以改用 None)。
Original comment in English

suggestion: Unconditional calls to platform-specific getters may cause noisy logging on other platforms.

These accessors are invoked for every window, even when the current platform doesn’t support them. On those platforms the C++ layer logs an error and returns a default, so find_desktop_windows() will produce an error log per window for fields that are inherently irrelevant.

Consider either guarding these calls with sys.platform so only platform-appropriate getters run, or adjusting the C++ accessors so unsupported-platform calls return defaults without error logging (or only log in debug). This would avoid misleading error noise in normal usage.

Suggested implementation:

            handle = int(
                Library.toolkit().MaaToolkitDesktopWindowGetHandle(window_handle)
            )
            window_name = (
                Library.toolkit()
                .MaaToolkitDesktopWindowGetWindowName(window_handle)
                .decode()
            )
            if sys.platform.startswith("win"):
                win32_class_name = (
                    Library.toolkit()
                    .MaaToolkitDesktopWindowGetWin32ClassName(window_handle)
                    .decode()
                )
            else:
                win32_class_name = ""

            if sys.platform == "darwin":
                macos_pid = int(
                    Library.toolkit().MaaToolkitDesktopWindowGetMacOSPID(window_handle)
                )
                macos_bundle_id = (
                    Library.toolkit()
                    .MaaToolkitDesktopWindowGetMacOSBundleID(window_handle)
                    .decode()
                )
                macos_application_name = (
                    Library.toolkit()
                    .MaaToolkitDesktopWindowGetMacOSApplicationName(window_handle)
                    .decode()
                )
            else:
                macos_pid = 0
                macos_bundle_id = ""
                macos_application_name = ""

            if sys.platform.startswith("linux"):
                linux_socket_path = (
                    Library.toolkit()
                    .MaaToolkitDesktopWindowGetLinuxSocketPath(window_handle)
                    .decode()
                )
            else:
                linux_socket_path = ""

            handle = int(
                Library.toolkit().MaaToolkitDesktopWindowGetHandle(window_handle)
            )
            window_name = (
                Library.toolkit()
  1. Ensure import sys is present near the top of source/binding/Python/maa/toolkit.py (alongside the other standard-library imports).
  2. If these new variables (win32_class_name, macos_pid, macos_bundle_id, macos_application_name, linux_socket_path) are later used to build a dict or object, confirm that the default values ("" or 0) align with the expected schema/typing for that structure, and adjust as needed (e.g., use None instead if the rest of the code expects nullable fields).

@MistEO
Copy link
Copy Markdown
Member

MistEO commented Mar 25, 2026

有 breaking changes,不太行,下游要遭殃了(

我觉得直接用同一个接口凑合凑合得了。也可以降低集成方心智,不用给各平台分别写一套代码

@YJBeetle
Copy link
Copy Markdown
Contributor Author

有 breaking changes,不太行,下游要遭殃了(

我觉得直接用同一个接口凑合凑合得了。也可以降低集成方心智,不用给各平台分别写一套代码

确实 所以我开pr主要是想作为一个提案来讨论下
那或者还有一个方案就是 现有的 GetHandle GetWindowName GetClassName 都不去动它, 就仅仅是去增加一些平台特有字段 ,刚刚写东西 苹果上 classname 已经被 BundleID 坑位占了 , 然后想拿application name拿不到就很蛋疼

@YJBeetle
Copy link
Copy Markdown
Contributor Author

YJBeetle commented Mar 25, 2026

OBS 上一般是长这样:
image
[ApplicationName]WindowName 就很直观。

以及苹果上有一堆windowLayer不为0的窗口,直接没有WindowName

@MistEO
Copy link
Copy Markdown
Member

MistEO commented Mar 25, 2026

那或者还有一个方案就是 现有的 GetHandle GetWindowName GetClassName 都不去动它, 就仅仅是去增加一些平台特有字段

这样感觉可以

@YJBeetle
Copy link
Copy Markdown
Contributor Author

那或者还有一个方案就是 现有的 GetHandle GetWindowName GetClassName 都不去动它, 就仅仅是去增加一些平台特有字段

这样感觉可以

行 那我去revert一部分然后再考虑下一些细节

@MistEO MistEO marked this pull request as draft April 2, 2026 11:06
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants