Skip to content

feat: add macOS PlayCover/MaaTools support - #1720

Open
WenSilvia wants to merge 2 commits into
runhey:devfrom
WenSilvia:agent/add-macos-playcover-maatools-support
Open

feat: add macOS PlayCover/MaaTools support#1720
WenSilvia wants to merge 2 commits into
runhey:devfrom
WenSilvia:agent/add-macos-playcover-maatools-support

Conversation

@WenSilvia

@WenSilvia WenSilvia commented Aug 5, 2026

Copy link
Copy Markdown

中文

本 PR 为 OAS 添加 macOS PlayCover/MaaTools 支持,接入思路参考了 MAA 明日方舟 macOS 使用方案

主要改动

  • 新增 PlayCover MaaTools 通信实现。
  • 在 OASX 控制方式中新增 MacPlayTools
  • 支持 MacBGRRGBAMacSCK 截图方式。
  • PlayCover、ADB 和 minitouch 作为并列控制方式,可在 OASX 中直接选择。
  • 保留原有 Android 模拟器连接方式,不影响 ADB 和 minitouch。
  • 新增 macOS 手动依赖清单 requirements-macos-playcover.txt

使用方式

  1. 安装带有 MaaTools 的 PlayCover 版本,并按照 MAA macOS 手册完成设置。
  2. 在 PlayCover 中将游戏分辨率设置为 1280×720。OAS 要求使用该分辨率,否则无法正常识别和点击。
  3. 在 OASX 中选择 PlayCover(control=MacPlayTools)。
  4. 截图方式选择 MacBGRRGBAMacSCK
  5. 将 PlayCover 标题栏显示的地址填入 serial,例如 localhost:1718
  6. 如需使用 Android 模拟器,选择 adbminitouch 即可。

已使用 OASX Flutter 源码编译并验证 macOS 本地 app。本 PR 不上传该 app,也不内置 Python、OCR 或 OAS 服务。Python 及相关依赖需要按照 requirements-macos-playcover.txt 手动安装。

测试结果:PlayCover 协议、截图、触控及控制方式切换测试共 13/13 通过。


English

This PR adds macOS PlayCover/MaaTools support to OAS, following the approach described in the MAA macOS guide.

Changes

  • Add PlayCover MaaTools communication support.
  • Add MacPlayTools as a selectable control method in OASX.
  • Add MacBGR, RGBA, and MacSCK screenshot methods.
  • Keep PlayCover, ADB, and minitouch as parallel control options.
  • Preserve the existing Android emulator connection methods.
  • Add requirements-macos-playcover.txt for manual macOS dependency installation.

Usage

  1. Install a PlayCover build that includes MaaTools and follow the MAA macOS guide.
  2. Set the game resolution in PlayCover to 1280×720. OAS requires this resolution for recognition and tapping.
  3. Select PlayCover (control=MacPlayTools) in OASX.
  4. Select MacBGR, RGBA, or MacSCK as the screenshot method.
  5. Enter the address shown in the PlayCover title bar as serial, for example localhost:1718.
  6. Select adb or minitouch when using an Android emulator.

A local macOS app compiled from the OASX Flutter source was used for validation. The app is not included in this PR and does not bundle Python, OCR, or the OAS service. Python and the required packages must be installed manually using requirements-macos-playcover.txt.

Validation result: all 13/13 PlayCover protocol, screenshot, touch, and control-selection tests passed.

Summary by Sourcery

添加实验性的 macOS PlayCover/MaaTools 集成为现有基于 Android 的方法之外的备用设备控制路径。

新功能:

  • 引入 PlayCover 客户端实现,用于在 macOS 上处理 MaaTools 协议通信,实现截图和触控输入。
  • 在设备配置中暴露 MacPlayTools 作为可选控制方法,以及 MacBGR/RGBA/MacSCK 作为可选截图方法。
  • 允许 PlayCover、ADB 和 minitouch 作为并行控制选项使用,而无需更改现有的 Android 模拟器工作流。

文档:

  • 在 README 中记录实验性的 macOS PlayCover/MaaTools 使用方法、设置步骤和限制,包括 Python 和依赖环境的配置说明。

测试:

  • 添加针对 PlayCover 客户端的协议级测试,包括握手、截图解码、触控范围限制(clamping)以及错误处理。
  • 添加集成测试,以验证 PlayCover 选择线路、截图透传以及与 minitouch/ADB 默认配置共存的情况。
Original summary in English

Summary by Sourcery

Add experimental macOS PlayCover/MaaTools integration as an alternative device control path alongside existing Android-based methods.

New Features:

  • Introduce a PlayCover client implementation to handle MaaTools protocol communication for screenshots and touch input on macOS.
  • Expose MacPlayTools as a selectable control method and MacBGR/RGBA/MacSCK as selectable screenshot methods in device configuration.
  • Allow PlayCover, ADB, and minitouch to be used as parallel control options without changing existing Android emulator workflows.

Documentation:

  • Document experimental macOS PlayCover/MaaTools usage, setup steps, and limitations in the README, including Python and dependency setup instructions.

Tests:

  • Add protocol-level tests for the PlayCover client, including handshake, screenshot decoding, touch clamping, and error handling.
  • Add integration tests to verify PlayCover selection wiring, screenshot passthrough, and coexistence with minitouch/ADB configuration defaults.

Summary by CodeRabbit

  • 新功能

    • 新增 macOS PlayCover/MaaTools 实验性支持。
    • 支持 PlayCover 设备的连接、截图、点击、长按和滑动操作。
    • 新增 MacBGR、RGBA、MacSCK 截图模式及 MacPlayTools 控制方式。
    • 支持配置 OASX 参数,并要求使用 1280×720 分辨率。
  • 文档

    • 新增 macOS 环境配置、依赖安装和使用说明。
  • 测试

    • 新增 PlayCover 连接、协议、截图及触控操作测试覆盖。

@sourcery-ai sourcery-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Hey - 我发现了两个问题,并留下了一些整体性的反馈:

  • ConnectionAttr.__init__Connection.__init__ 中,针对 PlayCover 的提前 return 分支会跳过常规的 ADB 相关初始化;请再次确认,当 is_playcover 为 true 时,这些类上其他代码所依赖的所有属性和不变式(例如 adb_clientis_over_httppackage_name 的处理)要么确实不会被使用,要么都被显式初始化为安全值,以避免属性缺失错误或者细微的行为差异。
  • 新增的截图模式中,在 PlayCover 专用的 MacBGRMacSCK 之外,还包含了一个通用的 RGBA 值;建议重命名或限定 RGBA 的作用范围,使其清晰地表明是 PlayCover 专用(或者仅在 is_playcover 为 true 时进行校验),以避免在使用非 PlayCover 控制方式时引起混淆或被误选。
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
-`ConnectionAttr.__init__``Connection.__init__` 中,针对 PlayCover 的提前 `return` 分支会跳过常规的 ADB 相关初始化;请再次确认,当 `is_playcover` 为 true 时,这些类上其他代码所依赖的所有属性和不变式(例如 `adb_client``is_over_http``package_name` 的处理)要么确实不会被使用,要么都被显式初始化为安全值,以避免属性缺失错误或者细微的行为差异。
- 新增的截图模式中,在 PlayCover 专用的 `MacBGR``MacSCK` 之外,还包含了一个通用的 `RGBA` 值;建议重命名或限定 `RGBA` 的作用范围,使其清晰地表明是 PlayCover 专用(或者仅在 `is_playcover` 为 true 时进行校验),以避免在使用非 PlayCover 控制方式时引起混淆或被误选。

## Individual Comments

### Comment 1
<location path="module/device/app_control.py" line_range="14-18" />
<code_context>
     _app_u2_family = ['uiautomator2', 'minitouch', 'scrcpy']

     def app_is_running(self) -> bool:
+        if self.is_playcover:
+            return True
         method = self.config.script.device.control_method
</code_context>
<issue_to_address>
**issue (bug_risk):** 对于 PlayCover,从 `dump_hierarchy` 返回 `None` 可能会破坏那些期望获得 XML 元素的调用方。

函数签名和文档字符串承诺返回 `etree._Element`,但现在 PlayCover 分支返回 `None`,这会在调用方将 `self.hierarchy` 当作元素(例如调用 `.xpath(...)`)时导致运行时错误。如果这是有意为之,要么更新所有调用点以显式处理 `None`,要么返回一个最小的空层级元素,以保持类型契约的一致性。
</issue_to_address>

### Comment 2
<location path="tests/test_playcover_integration.py" line_range="11" />
<code_context>
+ROOT = Path(__file__).resolve().parents[1]
+
+
+class PlayCoverIntegrationTests(unittest.TestCase):
+    def test_template_defaults(self):
+        template = json.loads((ROOT / "config" / "template.json").read_text(encoding="utf-8"))
</code_context>
<issue_to_address>
**suggestion (testing):** 为 AppControl 中 PlayCover 特定行为添加测试覆盖(app_is_running/app_start/app_stop/dump_hierarchy)。

目前有一条 PlayCover 专用路径尚未被覆盖,即当 `is_playcover``True``AppControl` 的行为:

- `app_is_running` 应在不调用 ADB/u2 的情况下直接返回 `True`- `app_start``app_stop` 应该是空操作(no-op)。
- `dump_hierarchy` 应返回 `None`。

请添加一个测试,在 `is_playcover=True` 的情况下(通过真实的 `Connection` 或一个简单的桩对象)调用这些方法,并断言预期的返回值,同时确保不会调用任何 ADB/u2 相关方法,类似现有对 `detect_device`/`adb_connect` 的检查。这样可以防止 PlayCover 的提前返回逻辑在未来出现回归。

建议实现:

```python
ROOT = Path(__file__).resolve().parents[1]


class PlayCoverAppControlTests(unittest.TestCase):
    def test_playcover_appcontrol_early_return_behavior(self):
        # Deferred import to avoid hard dependency if AppControl is not available in some environments
        try:
            from module.device.control import AppControl
        except ImportError:
            self.skipTest("AppControl is not available in this environment")

        class FakeConnection:
            def __init__(self):
                # PlayCover-specific flag
                self.is_playcover = True

                # ADB / u2-like interfaces that we can inspect for calls
                self.adb = Mock()
                self.u2 = Mock()

        conn = FakeConnection()

        # Construct AppControl with a PlayCover connection; adjust if your ctor signature differs
        app_control = AppControl(conn)

        # app_is_running: should return True and not hit ADB/u2
        self.assertTrue(app_control.app_is_running())

        # app_start/app_stop: should be no-ops and not hit ADB/u2
        self.assertIsNone(app_control.app_start())
        self.assertIsNone(app_control.app_stop())

        # dump_hierarchy: should return None and not hit ADB/u2
        self.assertIsNone(app_control.dump_hierarchy())

        # Ensure no ADB/u2 calls were made (protect PlayCover early-return paths)
        self.assertEqual(conn.adb.method_calls, [])
        self.assertEqual(conn.u2.method_calls, [])

```

1. 如果 `AppControl` 不在 `module.device.control` 中,请在 `test_playcover_appcontrol_early_return_behavior` 中更新导入路径,使其匹配 `AppControl` 实际所在的模块(例如 `from module.app_control import AppControl` 等)。
2. 如果 `AppControl` 的构造函数并非只接收一个 `connection` 参数(例如期望关键字参数或不同的参数名),请相应调整 `app_control = AppControl(conn)` 这一行(比如改为 `app_control = AppControl(connection=conn)`)。
3. 如果你的连接对象上 ADB/u2 属性名称不同(例如 `adb_client``u2_client``device`),请更新 `FakeConnection` 桩类以及最后的 `assertEqual(...method_calls, [])` 断言,使其引用正确的属性。
</issue_to_address>

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

Hey - I've found 2 issues, and left some high level feedback:

  • The early return paths for PlayCover in ConnectionAttr.__init__ and Connection.__init__ skip the usual ADB-related initialization; please double-check that all attributes and invariants other parts of the code expect on these classes (e.g., adb_client, is_over_http, package_name handling) are either not used when is_playcover is true or are explicitly initialized to safe values to avoid attribute errors or subtle behavior differences.
  • The new screenshot modes include a generic RGBA value alongside the PlayCover-specific MacBGR and MacSCK; consider renaming or scoping RGBA to be clearly PlayCover-specific (or validating it only when is_playcover is true) to avoid confusion or accidental selection when using non-PlayCover control methods.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- The early `return` paths for PlayCover in `ConnectionAttr.__init__` and `Connection.__init__` skip the usual ADB-related initialization; please double-check that all attributes and invariants other parts of the code expect on these classes (e.g., `adb_client`, `is_over_http`, `package_name` handling) are either not used when `is_playcover` is true or are explicitly initialized to safe values to avoid attribute errors or subtle behavior differences.
- The new screenshot modes include a generic `RGBA` value alongside the PlayCover-specific `MacBGR` and `MacSCK`; consider renaming or scoping `RGBA` to be clearly PlayCover-specific (or validating it only when `is_playcover` is true) to avoid confusion or accidental selection when using non-PlayCover control methods.

## Individual Comments

### Comment 1
<location path="module/device/app_control.py" line_range="14-18" />
<code_context>
     _app_u2_family = ['uiautomator2', 'minitouch', 'scrcpy']

     def app_is_running(self) -> bool:
+        if self.is_playcover:
+            return True
         method = self.config.script.device.control_method
</code_context>
<issue_to_address>
**issue (bug_risk):** Returning `None` from `dump_hierarchy` for PlayCover may break callers expecting an XML element.

The signature and docstring promise an `etree._Element`, but the PlayCover path now returns `None`, which can cause runtime errors when callers use `self.hierarchy` as an element (e.g., `.xpath(...)`). If this is intentional, either update call sites to handle `None` or return a minimal empty hierarchy element to keep the type contract consistent.
</issue_to_address>

### Comment 2
<location path="tests/test_playcover_integration.py" line_range="11" />
<code_context>
+ROOT = Path(__file__).resolve().parents[1]
+
+
+class PlayCoverIntegrationTests(unittest.TestCase):
+    def test_template_defaults(self):
+        template = json.loads((ROOT / "config" / "template.json").read_text(encoding="utf-8"))
</code_context>
<issue_to_address>
**suggestion (testing):** Add coverage for PlayCover-specific behavior in AppControl (app_is_running/app_start/app_stop/dump_hierarchy).

One PlayCover-specific path that isn’t covered is `AppControl` behavior when `is_playcover` is `True`:

- `app_is_running` should return `True` without calling ADB/u2.
- `app_start` and `app_stop` should be no-ops.
- `dump_hierarchy` should return `None`.

Please add a test that exercises these methods with `is_playcover=True` (via a real `Connection` or a simple stub) and asserts both the expected return values and that ADB/u2-related methods are not invoked, similar to the `detect_device`/`adb_connect` checks. This will protect the PlayCover early-return logic from regressions.

Suggested implementation:

```python
ROOT = Path(__file__).resolve().parents[1]


class PlayCoverAppControlTests(unittest.TestCase):
    def test_playcover_appcontrol_early_return_behavior(self):
        # Deferred import to avoid hard dependency if AppControl is not available in some environments
        try:
            from module.device.control import AppControl
        except ImportError:
            self.skipTest("AppControl is not available in this environment")

        class FakeConnection:
            def __init__(self):
                # PlayCover-specific flag
                self.is_playcover = True

                # ADB / u2-like interfaces that we can inspect for calls
                self.adb = Mock()
                self.u2 = Mock()

        conn = FakeConnection()

        # Construct AppControl with a PlayCover connection; adjust if your ctor signature differs
        app_control = AppControl(conn)

        # app_is_running: should return True and not hit ADB/u2
        self.assertTrue(app_control.app_is_running())

        # app_start/app_stop: should be no-ops and not hit ADB/u2
        self.assertIsNone(app_control.app_start())
        self.assertIsNone(app_control.app_stop())

        # dump_hierarchy: should return None and not hit ADB/u2
        self.assertIsNone(app_control.dump_hierarchy())

        # Ensure no ADB/u2 calls were made (protect PlayCover early-return paths)
        self.assertEqual(conn.adb.method_calls, [])
        self.assertEqual(conn.u2.method_calls, [])

```

1. If `AppControl` is not located in `module.device.control`, update the import path in `test_playcover_appcontrol_early_return_behavior` to match the actual module where `AppControl` is defined (e.g. `from module.app_control import AppControl` or similar).
2. If `AppControl`'s constructor does not take a single `connection` argument (e.g. it expects keyword arguments or a different parameter name), adjust the `app_control = AppControl(conn)` line accordingly (for example, `app_control = AppControl(connection=conn)`).
3. If your ADB/u2 attributes are named differently on the connection (for example `adb_client`, `u2_client`, or `device`), update the `FakeConnection` stub and the final `assertEqual(...method_calls, [])` checks so they refer to the correct attributes.
</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 +14 to 18
if self.is_playcover:
return True
method = self.config.script.device.control_method
# if self.is_wsa:
# package = self.app_current_wsa()

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

issue (bug_risk): 对于 PlayCover,从 dump_hierarchy 返回 None 可能会破坏那些期望获得 XML 元素的调用方。

函数签名和文档字符串承诺返回 etree._Element,但现在 PlayCover 分支返回 None,这会在调用方将 self.hierarchy 当作元素(例如调用 .xpath(...))时导致运行时错误。如果这是有意为之,要么更新所有调用点以显式处理 None,要么返回一个最小的空层级元素,以保持类型契约的一致性。

Original comment in English

issue (bug_risk): Returning None from dump_hierarchy for PlayCover may break callers expecting an XML element.

The signature and docstring promise an etree._Element, but the PlayCover path now returns None, which can cause runtime errors when callers use self.hierarchy as an element (e.g., .xpath(...)). If this is intentional, either update call sites to handle None or return a minimal empty hierarchy element to keep the type contract consistent.

ROOT = Path(__file__).resolve().parents[1]


class PlayCoverIntegrationTests(unittest.TestCase):

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

suggestion (testing): 为 AppControl 中 PlayCover 特定行为添加测试覆盖(app_is_running/app_start/app_stop/dump_hierarchy)。

目前有一条 PlayCover 专用路径尚未被覆盖,即当 is_playcoverTrueAppControl 的行为:

  • app_is_running 应在不调用 ADB/u2 的情况下直接返回 True
  • app_startapp_stop 应该是空操作(no-op)。
  • dump_hierarchy 应返回 None

请添加一个测试,在 is_playcover=True 的情况下(通过真实的 Connection 或一个简单的桩对象)调用这些方法,并断言预期的返回值,同时确保不会调用任何 ADB/u2 相关方法,类似现有对 detect_device/adb_connect 的检查。这样可以防止 PlayCover 的提前返回逻辑在未来出现回归。

建议实现:

ROOT = Path(__file__).resolve().parents[1]


class PlayCoverAppControlTests(unittest.TestCase):
    def test_playcover_appcontrol_early_return_behavior(self):
        # Deferred import to avoid hard dependency if AppControl is not available in some environments
        try:
            from module.device.control import AppControl
        except ImportError:
            self.skipTest("AppControl is not available in this environment")

        class FakeConnection:
            def __init__(self):
                # PlayCover-specific flag
                self.is_playcover = True

                # ADB / u2-like interfaces that we can inspect for calls
                self.adb = Mock()
                self.u2 = Mock()

        conn = FakeConnection()

        # Construct AppControl with a PlayCover connection; adjust if your ctor signature differs
        app_control = AppControl(conn)

        # app_is_running: should return True and not hit ADB/u2
        self.assertTrue(app_control.app_is_running())

        # app_start/app_stop: should be no-ops and not hit ADB/u2
        self.assertIsNone(app_control.app_start())
        self.assertIsNone(app_control.app_stop())

        # dump_hierarchy: should return None and not hit ADB/u2
        self.assertIsNone(app_control.dump_hierarchy())

        # Ensure no ADB/u2 calls were made (protect PlayCover early-return paths)
        self.assertEqual(conn.adb.method_calls, [])
        self.assertEqual(conn.u2.method_calls, [])
  1. 如果 AppControl 不在 module.device.control 中,请在 test_playcover_appcontrol_early_return_behavior 中更新导入路径,使其匹配 AppControl 实际所在的模块(例如 from module.app_control import AppControl 等)。
  2. 如果 AppControl 的构造函数并非只接收一个 connection 参数(例如期望关键字参数或不同的参数名),请相应调整 app_control = AppControl(conn) 这一行(比如改为 app_control = AppControl(connection=conn))。
  3. 如果你的连接对象上 ADB/u2 属性名称不同(例如 adb_clientu2_clientdevice),请更新 FakeConnection 桩类以及最后的 assertEqual(...method_calls, []) 断言,使其引用正确的属性。
Original comment in English

suggestion (testing): Add coverage for PlayCover-specific behavior in AppControl (app_is_running/app_start/app_stop/dump_hierarchy).

One PlayCover-specific path that isn’t covered is AppControl behavior when is_playcover is True:

  • app_is_running should return True without calling ADB/u2.
  • app_start and app_stop should be no-ops.
  • dump_hierarchy should return None.

Please add a test that exercises these methods with is_playcover=True (via a real Connection or a simple stub) and asserts both the expected return values and that ADB/u2-related methods are not invoked, similar to the detect_device/adb_connect checks. This will protect the PlayCover early-return logic from regressions.

Suggested implementation:

ROOT = Path(__file__).resolve().parents[1]


class PlayCoverAppControlTests(unittest.TestCase):
    def test_playcover_appcontrol_early_return_behavior(self):
        # Deferred import to avoid hard dependency if AppControl is not available in some environments
        try:
            from module.device.control import AppControl
        except ImportError:
            self.skipTest("AppControl is not available in this environment")

        class FakeConnection:
            def __init__(self):
                # PlayCover-specific flag
                self.is_playcover = True

                # ADB / u2-like interfaces that we can inspect for calls
                self.adb = Mock()
                self.u2 = Mock()

        conn = FakeConnection()

        # Construct AppControl with a PlayCover connection; adjust if your ctor signature differs
        app_control = AppControl(conn)

        # app_is_running: should return True and not hit ADB/u2
        self.assertTrue(app_control.app_is_running())

        # app_start/app_stop: should be no-ops and not hit ADB/u2
        self.assertIsNone(app_control.app_start())
        self.assertIsNone(app_control.app_stop())

        # dump_hierarchy: should return None and not hit ADB/u2
        self.assertIsNone(app_control.dump_hierarchy())

        # Ensure no ADB/u2 calls were made (protect PlayCover early-return paths)
        self.assertEqual(conn.adb.method_calls, [])
        self.assertEqual(conn.u2.method_calls, [])
  1. If AppControl is not located in module.device.control, update the import path in test_playcover_appcontrol_early_return_behavior to match the actual module where AppControl is defined (e.g. from module.app_control import AppControl or similar).
  2. If AppControl's constructor does not take a single connection argument (e.g. it expects keyword arguments or a different parameter name), adjust the app_control = AppControl(conn) line accordingly (for example, app_control = AppControl(connection=conn)).
  3. If your ADB/u2 attributes are named differently on the connection (for example adb_client, u2_client, or device), update the FakeConnection stub and the final assertEqual(...method_calls, []) checks so they refer to the correct attributes.

@runhey

runhey commented Aug 6, 2026

Copy link
Copy Markdown
Owner

有空研究一下,哦对了提到dev分支

@WenSilvia

Copy link
Copy Markdown
Author

有空研究一下,哦对了提到dev分支

嗯呢 ,等会改。顺带我申请了加开发群,有空通过一下啦~

@WenSilvia
WenSilvia marked this pull request as ready for review August 8, 2026 06:43
@WenSilvia
WenSilvia changed the base branch from master to dev August 8, 2026 06:44
@coderabbitai

coderabbitai Bot commented Aug 8, 2026

Copy link
Copy Markdown

Review Change Stack

Walkthrough

新增 PlayCoverClient,实现 MaaTools 握手、截图和触控协议。
新增 PlayCover 设备配置、截图方式和控制方式。
PlayCover 设备连接会跳过常规 ADB 流程。
新增集成测试和协议测试。
README 与依赖文件新增 macOS 配置说明。

Poem

兔子敲下连接键,
PlayCover 传来清晰画面。
小爪点击,长按,滑行,
MaaTools 稳稳回应。
1280×720 月光铺开,
新路径今日启程。 🐇


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 5

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@module/device/method/playcover.py`:
- Around line 125-129: 调整 swipe 方法中的事件顺序:在发送 TOUCH_BEGAN 后等待 duration,再发送
TOUCH_MOVED,最后发送 TOUCH_ENDED;不要在 TOUCH_MOVED
之后再等待。必要时在等待期间按现有输入通道约定发送多个移动事件,以确保不同截图通道和输入方式保持一致的滑动行为。

In `@README.md`:
- Around line 84-92: 完善 README 中 macOS PlayCover 实验性集成的中英文说明:补充经过验证的 OASX app
获取或构建入口,并说明如何启动本地 OAS 服务及确认其配置,使用户能完成选择 MacPlayTools
前的环境准备。若流程仅支持开发者验证,请在两种语言版本中明确标注其范围;保留现有不分发二进制和 Python 依赖安装说明。

In `@requirements-macos-playcover.txt`:
- Line 34: 将 requirements-macos-playcover.txt 中的 requests 依赖升级至
requests>=2.32.0,并在升级前为现有 requests.Session 使用路径补充覆盖 verify=False
后同一主机请求不会复用未验证连接的测试。
- Line 25: Update the macOS dependency requirements around ppocr-onnx==0.0.3.9
to explicitly pin a compatible onnxruntime version for Python 3.10 on both Intel
and Apple Silicon with macOS 11/12 wheel support, then verify the selected
version resolves correctly for those environments.

In `@tasks/Script/config_device.py`:
- Around line 28-37: 在 Device 配置验证中新增截图方法与控制方法的成对校验:ControlMethod.MacPlayTools
仅允许与 MacBGR、RGBA、MacSCK 搭配,且这三种截图方法仅允许与 MacPlayTools
搭配;拒绝其余组合并保持现有配置校验行为不变。为这两类无效组合补充测试,覆盖 PlayCover 截图方法配合非 MacPlayTools,以及
MacPlayTools 配合非 PlayCover 截图方法。
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 8bffd8b3-ed06-4076-9fe6-0b7a911e80a3

📥 Commits

Reviewing files that changed from the base of the PR and between 7000dfc and 199ad20.

📒 Files selected for processing (11)
  • README.md
  • module/device/app_control.py
  • module/device/connection.py
  • module/device/connection_attr.py
  • module/device/control.py
  • module/device/method/playcover.py
  • module/device/screenshot.py
  • requirements-macos-playcover.txt
  • tasks/Script/config_device.py
  • tests/test_playcover_integration.py
  • tests/test_playcover_protocol.py

Comment on lines +125 to +129
def swipe(self, p1, p2, duration=0.2):
self.touch(self.TOUCH_BEGAN, p1[0], p1[1])
self.touch(self.TOUCH_MOVED, p2[0], p2[1])
time.sleep(max(0, float(duration)))
self.touch(self.TOUCH_ENDED, p2[0], p2[1])

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

修正 swipe 的持续时间顺序。

第 127 行在等待前发送 TOUCH_MOVED。因此,坐标位移立即发生,而 duration 只延迟 TOUCH_ENDED。这不能表示从起点到终点的滑动时间,并会降低真实设备上的滑动识别稳定性。

将等待放在开始触控和移动触控之间。必要时发送多个 TOUCH_MOVED 事件。

建议修改
     def swipe(self, p1, p2, duration=0.2):
         self.touch(self.TOUCH_BEGAN, p1[0], p1[1])
-        self.touch(self.TOUCH_MOVED, p2[0], p2[1])
         time.sleep(max(0, float(duration)))
+        self.touch(self.TOUCH_MOVED, p2[0], p2[1])
         self.touch(self.TOUCH_ENDED, p2[0], p2[1])

As per path instructions: “不同截图通道、不同输入方式之间的行为是否仍然一致”。

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@module/device/method/playcover.py` around lines 125 - 129, 调整 swipe
方法中的事件顺序:在发送 TOUCH_BEGAN 后等待 duration,再发送 TOUCH_MOVED,最后发送 TOUCH_ENDED;不要在
TOUCH_MOVED 之后再等待。必要时在等待期间按现有输入通道约定发送多个移动事件,以确保不同截图通道和输入方式保持一致的滑动行为。

Source: Path instructions

Comment thread README.md
Comment on lines +84 to +92
本实验性接入借鉴了 MAA(明日方舟小助手)的 [macOS PlayCover / MaaTools 接入思路](https://docs.maa.plus/zh-cn/manual/device/macos.html),并使用 [OASX](https://github.com/runhey/OASX) 作为界面。我们已从 OASX Flutter 源码编译出一个可在 macOS 本地运行的 app,用于本集成验证;本 PR 不上传或分发该二进制。此功能仍处于试验性阶段,不代表官方 release 已经包含 macOS app。

Flutter app 仅是界面,不内置 Python 解释器、OCR、本地 OAS 服务或其依赖。请在 macOS 主机上安装 Python 3.10,创建 venv,并手动安装 [requirements-macos-playcover.txt](requirements-macos-playcover.txt) 中的 pip 包:

```bash
python3.10 -m venv .venv
source .venv/bin/activate
python -m pip install --upgrade pip
python -m pip install -r requirements-macos-playcover.txt

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟠 Major | 🏗️ Heavy lift

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "README size and relevant sections"
wc -l README.md
sed -n '70,125p' README.md

echo
echo "Search macOS/OASX/playcover/OAS service mentions"
rg -n "OASX|PlayCover|MaaTools|requirements-macos-playcover|MacPlayTools|OAS|OASX|playcover|本地 OAS|macOS" -S . | head -200

echo
echo "Files named requirements-macos-playcover.txt and docs"
fd -a 'requirements-macos-playcover\.txt|README|.*mac.*|.*OASX.*' . | sed 's#^\./##' | head -200

Repository: runhey/OnmyojiAutoScript

Length of output: 21602


补齐 OASX app 和本地 OAS 服务的可执行使用说明。

README 现在仅说明 PR 不分发 OASX 二进制,并给出 Python 依赖安装与 PlayCover 配置。但没有经过验证的 OASX 构建/获取入口,也没有启动本地 OAS 服务的命令和配置确认;当前说明无法让用户实际到达选择 MacPlayTools 前的环境准备状态。请补充构建或获取步骤,以及 OAS 服务的启动与配置方式;若仅为开发者验证流程,请在中英文说明中明确标注范围。

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@README.md` around lines 84 - 92, 完善 README 中 macOS PlayCover
实验性集成的中英文说明:补充经过验证的 OASX app 获取或构建入口,并说明如何启动本地 OAS 服务及确认其配置,使用户能完成选择
MacPlayTools 前的环境准备。若流程仅支持开发者验证,请在两种语言版本中明确标注其范围;保留现有不分发二进制和 Python 依赖安装说明。

numpy==1.24.3
opencv-python==4.7.0.72
Pillow==10.2.0
ppocr-onnx==0.0.3.9

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/usr/bin/env bash
set -euo pipefail

python3.10 -m pip install --dry-run --only-binary=:all: \
  -r requirements-macos-playcover.txt

Repository: runhey/OnmyojiAutoScript

Length of output: 210


🏁 Script executed:

#!/usr/bin/env bash
set -euo pipefail

printf '--- candidate file ---\n'
wc -l requirements-macos-playcover.txt
sed -n '1,80p' requirements-macos-playcover.txt

printf '\n--- pyproject/setup-related dependency metadata ---\n'
python3 - <<'PY'
import os, re, json
for fn in ['pyproject.toml', 'setup.py', 'setup.cfg']:
    if os.path.exists(fn):
        print(f'\n## {fn}')
        with open(fn) as f:
            data=f.read()
        print(data[:4000])
PY

printf '\n--- onnxruntime release metadata via pypi JSON API ---\n'
python3 - <<'PY'
import json, urllib.request, re, sys
url='https://pypi.org/pypi/onnxruntime/json'
with urllib.request.urlopen(url, timeout=20) as r:
    data=json.load(r)['releases']
samples={}
for v,'info' in data.items():
    ok=True
    req='python_version <= 3.10'
    for x in info.get('requires_python',''):
        if '<' in req or '>' in req:
            ok=False
    if ok:
        samples.setdefault(req,0)
        samples[req]+=1
for req, count in list(samples.items())[:8]:
    print(req, count)

target='0.0.3.9'
proj='https://pypi.org/pypi/ppocr-onnx/json'
raw=urllib.request.urlopen(proj, timeout=20).read()
meta=json.loads(raw)
if target in meta['releases']:
    print('\nppocr-onnx target dependencies:')
    for rel in meta['releases'][target]:
        if rel.get('packagetype'):
            print(rel['packagetype'], rel.get('requires_dist'))
else:
    print('target not found', target, sorted(meta['releases'].keys())[-10:])

# Print relevant onnxruntime file tags for selected versions
for v in ['1.14.0','1.23.0','1.23.1','1.24.0']:
    if v not in data:
        print('\nONNXRuntime', v, 'not found')
        continue
    print('\nONNXRuntime', v)
    for f in data[v]:
        fn=f['filename']
        mac=re.search(r'(macosx_\d+_\d+_\d+)', fn)
        py=re.search(r'-cp(\d{2})(\w+)\d*-|^cp(\d{2})(\w+)\d*-|^py', fn)
        if mac and fn.endswith('.whl'):
            print(fn, 'macos_tag=', mac.group(1), py.group() if py else '')
PY

Repository: runhey/OnmyojiAutoScript

Length of output: 253


🏁 Script executed:

#!/usr/bin/env bash
set -euo pipefail

printf '%s\n' '--- candidate file ---'
wc -l requirements-macos-playcover.txt
sed -n '1,90p' requirements-macos-playcover.txt

printf '%s\n' ''
printf '%s\n' '--- pyproject/setup-related dependency metadata ---'

python3 - <<'PY'
import os
for fn in ['pyproject.toml', 'setup.py', 'setup.cfg']:
    if os.path.exists(fn):
        print(f'\n## {fn}')
        with open(fn, encoding='utf-8') as f:
            print(f.read()[:6000])
PY

printf '%s\n' ''
printf '%s\n' '--- onnxruntime/ppocr-onnx pypi release metadata ---'
python3 - <<'PY'
import json, urllib.request, re, sys
try:
    with urllib.request.urlopen('https://pypi.org/pypi/onnxruntime/json', timeout=20) as r:
        meta=json.loads(r.read())
    data=meta['releases']
except Exception as e:
    print('ERROR onnxruntime release metadata:', repr(e), file=sys.stderr)
    sys.exit(0)

for label, versions in [('-- required_python samples for 3.10 target --',
                        ['python_version <= 3.10', 'python_version < 3.11', 'python_version >= 3.11', '']
                       )]:
    print(label)
    samples={}
    for v, files in data.items():
        req=next((f['requires_python'] for f in files if f['packagetype']=='sdist'), '')
        if '3.10' in req or '3.11' in req or not req:
            samples[req] = samples.get(req, 0) + 1
    for req, count in sorted(samples.items()):
        print(f'{req or "(none)"} {count}')

target='0.0.3.9'
try:
    raw=urllib.request.urlopen('https://pypi.org/pypi/ppocr-onnx/json', timeout=20).read()
    pmeta=json.loads(raw)
except Exception as e:
    print('ERROR ppocr-onnx metadata:', repr(e), file=sys.stderr)
    sys.exit(0)

print(f'\nppocr-onnx target {target} presence:', target in pmeta['releases'])
if target in pmeta['releases']:
    for rel in pmeta['releases'][target]:
        if rel.get('packagetype'):
            print(rel['packagetype'], rel.get('requires_dist'))

for v in ['1.14.0','1.23.0','1.23.1','1.24.0']:
    print(f'\nONNXRuntime {v}')
    if v not in data:
        print('version not found')
        continue
    version_files=[f for f in data[v] if f['packagetype']=='bdist_wheel' and 'macosx' in f['filename']]
    print(f'macOS wheel count={len(version_files)}')
    for f in version_files[:50]:
        fn=f['filename']
        mac=re.search(r'(\d+_\d+_\d+)', fn)
        py_re=re.search(r'-(cp(310|311|312).*|-py\d\.)', fn)
        print(fn, 'mac_os=', mac.group(1) if mac else '', 'py=', py_re.group(1) if py_re else '')

print(f'\nTarget onnxruntime range from ppocr-onnx {target}:')
for rel in pmeta.get('releases',{}).get(target,[]):
    reqs=rel.get('requires_dist') or []
    for req in reqs:
        if 'onnxruntime' in req:
            print(req)
            break
PY

printf '%s\n' ''
printf '%s\n' '--- local requirement references ---'
rg -n "onnxruntime|python_requires|python-version|macos|playcover|python3\.10|ppocr-onnx" -S requirements-macos-playcover.txt README.md pyproject.toml setup.py setup.cfg .github 2>/dev/null || true

Repository: runhey/OnmyojiAutoScript

Length of output: 6458


🌐 Web query:

PyPI pppocr-onnx 0.0.3.9 requires_dist onnxruntime

💡 Result:

The Python package ppocr-onnx version 0.0.3.9 requires the onnxruntime package as a dependency [1][2]. Official PyPI project metadata explicitly lists onnxruntime as a required package for this version, alongside others such as numpy, opencv-python, shapely, pyclipper, pillow, and requests [1][2]. This package is designed to perform text detection and recognition by utilizing models provided by PaddleOCR and the onnxruntime inference engine [1][2]. Users installing this package will typically have onnxruntime automatically resolved and installed by their Python package manager [1][2].

Citations:


🌐 Web query:

PyPI onnxruntime 1.14.0 Python 3.10 macOS wheel requirespython macosx

💡 Result:

The onnxruntime 1.14.0 release encountered known issues with its macOS wheel packaging, specifically involving architecture incompatibility (mismatch between arm64 and x86_64) [1]. These packaging problems were addressed and resolved in the subsequent patch release, onnxruntime 1.14.1 [2][1]. If you are experiencing installation errors or "no matching distribution" messages when attempting to install onnxruntime on macOS, it is recommended to use version 1.14.1 or later [2]. In some environments, if pip still fails to resolve the correct wheel due to older macOS SDK configurations, you may need to upgrade pip or use the following command to assist in correctly identifying compatible wheels [3]: SYSTEM_VERSION_COMPAT=0 pip install --no-cache-dir "onnxruntime>=1.14.1" For users on newer macOS versions or Apple Silicon (arm64), ensuring that you are using a Python environment that matches your hardware architecture is essential to avoid the architecture mismatch errors reported in the v1.14.0 release [1].

Citations:


🌐 Web query:

PyPI onnxruntime 1.23.0 Python 3.10 macOS wheel requirespython macosx

💡 Result:

ONNX Runtime version 1.23.0 was released on September 25, 2025 [1][2]. For this specific version, macOS wheels for Python 3.10 were provided, as the explicit removal of Python 3.10 support occurred in subsequent releases [3][4]. The release notes for version 1.23.0 explicitly stated that upcoming changes would include the discontinuation of Python 3.10 wheels [4]. Consequently, while version 1.23.0 and 1.23.1 included these wheels, version 1.23.2 and later releases dropped support for Python 3.10 macOS wheels [3]. Regarding Python version requirements, current versions of the onnxruntime package on PyPI typically specify a minimum Python version of 3.11 [5], though older versions like 1.23.2 indicated a requirement of 3.10 [6]. Users requiring Python 3.10 on macOS should utilize version 1.23.1 or earlier to ensure wheel availability [3].

Citations:


🌐 Web query:

site:pypi.org/project/onnxruntime/1.23.0/ "macosx" "cp310"

💡 Result:

In Python packaging and wheel filenames, the tag cp310 designates compatibility with CPython version 3.10. Wheels labeled with cp310 are intended for environments running Python 3.10. When combined with macosx in a filename, it indicates a pre-built binary package compiled specifically for macOS systems running CPython 3.10 [1].

Citations:


🌐 Web query:

site:pypi.org/project/onnxruntime/1.23.0/ "cp310" "macosx_*_12_0"

💡 Result:

The identifier cp310 macosx_12_0 refers to a Python wheel file naming convention defined by PEP 425, used to specify the compatibility of a pre-built binary package [1]. In this specific tag: cp310 indicates the wheel is compatible with CPython version 3.10. macosx_12_0 indicates the wheel is built for macOS version 12.0 or newer. These tags are used by pip and the Python packaging ecosystem to ensure that a downloaded package is compatible with the user's operating system and Python interpreter [1]. While specific details about the wheel tag format appear in technical distribution standards, package repositories like PyPI list these tags to confirm whether a user's system meets the requirements for a particular version of a library [1].

Citations:


固定并验证 onnxruntime macOS wheel 选择。

ppocr-onnx==0.0.3.9 传递依赖未固定在清单中,会由 pip 解析到 onnxruntime。如果解析结果不满足 Python 3.10 + Intel/Apple Silicon + macOS 11/12 的 wheel 标签要求,安装可能失败。固定一个已覆盖该环境的 onnxruntime 版本,或者在文档中明确最低 macOS 版本和架构支持范围。

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@requirements-macos-playcover.txt` at line 25, Update the macOS dependency
requirements around ppocr-onnx==0.0.3.9 to explicitly pin a compatible
onnxruntime version for Python 3.10 on both Intel and Apple Silicon with macOS
11/12 wheel support, then verify the selected version resolves correctly for
those environments.

Source: MCP tools

websockets==13.1
zerorpc==0.6.3
paho-mqtt==1.6.1
requests==2.31.0

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/usr/bin/env bash
set -euo pipefail

python3.10 -m pip_audit -r requirements-macos-playcover.txt
python3.10 -m pip install --dry-run 'requests>=2.32.2,<3'

Repository: runhey/OnmyojiAutoScript

Length of output: 210


🌐 Web query:

CVE-2024-35195 requests 2.31.0 vulnerability fix version 2.32.2

💡 Result:

The vulnerability identified as CVE-2024-35195 affects the Python requests library [1][2]. It involves a flaw where, when using a requests.Session object, if the first request is made with verify=False, certificate verification may remain disabled for all subsequent requests to the same host, regardless of the verify parameter's value in later requests [1][3]. This vulnerability is fixed in version 2.32.0 of the requests library [1][3]. While you mentioned version 2.32.2 as a potential target, the official fix was implemented in version 2.32.0 [1][4]. Any version prior to 2.32.0 is considered affected [5][6]. Users are encouraged to upgrade to 2.32.0 or any later stable version to remediate this issue [1][4].

Citations:


🌐 Web query:

requests 2.32.0 CVE-2024-35195 fix

💡 Result:

CVE-2024-35195 is a security vulnerability in the requests library that was resolved in version 2.32.0 [1][2][3]. The vulnerability occurred when using a requests.Session object [3]. If the first request made with a session included verify=False to disable SSL/TLS certificate verification, all subsequent requests to the same host via that session would also ignore certificate verification, even if verify=True was explicitly set in those later requests [2][4][3]. This persistence of the initial verification setting resulted from the reuse of connections within the session's connection pool [3]. To fix this issue, users should upgrade to requests version 2.32.0 or later [1][3][5]. For environments where an immediate upgrade is not possible, suggested mitigations include avoiding the use of verify=False for the initial request to a host within a session, or explicitly closing the session object to clear the connection pool if verify=False must be used [5].

Citations:


🏁 Script executed:

#!/usr/bin/env bash
set -euo pipefail

echo "== requirements line =="
sed -n '30,38p' requirements-macos-playcover.txt >/tmp/requirements-macos-playcover-requests.txt
cat /tmp/requirements-macos-playcover-requests.txt

echo
echo "== occurrences of Session/verify/CustomAdapter in repo =="
rg -n "requests\.Session|Session\(|verify\s*=\s*False|verify\s*=\s*True|[vV]erify\s*=" -S . \
  -g '!build/**' -g '!dist/**' -g '!.venv/**' -g '!venv/**' -g '!*cache*' \
  | head -n 200

echo
echo "== python availability and pip audit if present =="
python3 --version
python3 -m pip --version
python3 -m pip_audit --version >/tmp/pip_audit_version.txt 2>/tmp/pip_audit_error.txt || true
cat /tmp/pip_audit_version.txt
cat /tmp/pip_audit_error.txt

echo
echo "== dry-run upgrade check via pip resolver api =="
python3 - <<'PY'
from urllib.request import urlopen
import json
urls = [
    "https://pypi.org/pypi/requests/json",
    "https://osv.dev/v1/vulns/CVE-2024-35195",
]
for url in urls:
    try:
        with urlopen(url, timeout=20) as r:
            data = json.load(r)
        print(f"--- {url} ---")
        if "releases" in data:
            print("latest_requests_version", sorted(data["releases"].keys())[-1][:50])
        if "versions" in data:
            fixed = [v for v, effects in data["versions"].items() if any("requests" in str(v) and "fixed" in str(e.get("fixed_versions") or "") for e in [effects]) or "fixed" in effects]
            print("osv_versions", data.get("versions", []))
            print("data_keys", list(data.keys()))
            print("schema_version", data.get("schema_version"))
            print("severity", data.get("severity"))
            print("affected", data.get("affected", []))
        else:
            print(data)
    except Exception as e:
        print(url, "ERROR", repr(e))
PY

Repository: runhey/OnmyojiAutoScript

Length of output: 1674


升级 requests 到修复版本。

requests==2.31.0 存在 CVE-2024-35195:当 requests.Session 先使用 verify=False,后续同一主机的请求可能继续复用未验证证书的连接。升级到 requests>=2.32.0,并在升级前覆盖测试代码中已有的 Session 路径。

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@requirements-macos-playcover.txt` at line 34, 将
requirements-macos-playcover.txt 中的 requests 依赖升级至 requests>=2.32.0,并在升级前为现有
requests.Session 使用路径补充覆盖 verify=False 后同一主机请求不会复用未验证连接的测试。

Source: MCP tools

Comment on lines +28 to +37
MacBGR = 'MacBGR'
RGBA = 'RGBA'
MacSCK = 'MacSCK'

class ControlMethod(str, Enum):
ADB = 'adb'
UIAUTOMATOR2 = 'uiautomator2'
MINITOUCH = 'minitouch'
WINDOW_MESSAGE = 'window_message'
MacPlayTools = 'MacPlayTools'

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

验证 PlayCover 截图方法和控制方法的组合。

当前枚举允许 MacBGRRGBAMacSCKminitouch 等控制方法组合。此时 module/device/connection_attr.py 不会初始化 playcover_client,但 module/device/screenshot.py 会调用 self.playcover_client.screenshot(),从而触发 AttributeError

同样,MacPlayTools 配合 auto 或 ADB 截图方法会创建 PlayCover 连接,但截图会走 ADB 路径。请在 Device 配置验证中要求这两项成对使用:MacPlayTools 只能配合三个 PlayCover 截图方法,三个 PlayCover 截图方法也只能配合 MacPlayTools。同时添加这两个无效组合的测试。

As per path instructions: “不同截图通道、不同输入方式之间的行为是否仍然一致”。

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tasks/Script/config_device.py` around lines 28 - 37, 在 Device
配置验证中新增截图方法与控制方法的成对校验:ControlMethod.MacPlayTools 仅允许与 MacBGR、RGBA、MacSCK
搭配,且这三种截图方法仅允许与 MacPlayTools 搭配;拒绝其余组合并保持现有配置校验行为不变。为这两类无效组合补充测试,覆盖 PlayCover
截图方法配合非 MacPlayTools,以及 MacPlayTools 配合非 PlayCover 截图方法。

Source: Path instructions

@runhey

runhey commented Aug 10, 2026

Copy link
Copy Markdown
Owner

这玩意居然是裸socket,没有这些接口吗app_start,app_stop,app_is_running。
PlayCover 官方那套是基于http的json rpc对吧。

第二点,MacBGR,RGBA,MacSCK这三个太多了,知道是专门给mac处理的就好了,太多用户有心智负担,然后最好可以删到一个,然后我没看懂RGBA是干啥的,好像和MacSCk没区别,我看到MacBGR会多一些校验,这边是有什么性能上的考虑吗,这边速度有测试过吗,还有最好可以在redme上写一下,或者assets/i18n/zh-CN.json这里可以覆盖对应的说明文字。
RGBA 表示数据来源是"App 内渲染捕获"(Metal/OpenGL 渲染目标),MacSCK表示"macOS ScreenCaptureKit 桌面级捕获",评估一下性能/权限/兼容性

这种最好打个日志,表示不同的case, 感觉直接return后面会留坑
def app_is_running(self) -> bool:
if self.is_playcover:
return True

协议帧校验和坐标值做得扎实
噢对了考虑一下socket重连

@WenSilvia

Copy link
Copy Markdown
Author

嗯呢,这个逻辑先是 PlayCover 官方的 PlayTools,然后 MAA 使用的 PlayCover Fork 又接入了 MaaTools。MaaTools 实际是在 PlayTools 里增加了一套本地 TCP 服务,把截图和触控能力提供给 MAA,并不是 HTTP JSON-RPC。
app_xx maa这边也不是完全没有。MaaTools 有TERM,所以可以shutdown,但是没有START和RUNNING MAA 自己的start_game()实际也是直接返回,stop_xxxx 才会发送term。
这一大堆maa的逻辑跟上面那个你说的那串都在maaswift里,https://github.com/hguandl/PlayTools/blob/MaaTools/PlayTools/Utils/MaaTools.swift
很难翻!!!晦涩难懂是
因为停止以后没有对应的自动启动能力,我现在都只能手动启动手动关停每天报错,奶奶嘟。OAS 应该是不太能直接照搬maa明日方舟那个启停逻辑,maa是能自动退出的我记得,还给明日方舟做了一个启动命令,点一下就行,忘了是啥了改天看看。maayys那边这个我搬过去试试倒是可以

RGBA 的话,请看~

RGBA 是每像素4字节;BGR 是每像素3字节,传输量少25%,而且每帧会附带宽、高和数据长度,校验更完整,不过服务端也会多一次vImage通道转换。所以BGR理论上传输更轻,但实际速度和稳定性还需要测试,不能只看数据量下结论。 (这段是我让codex爬给我的)

现在的SCK 和 RGBA 一样我copy下来的还没改。rgba你也看到啦是metal,metal的话阴阳师在macos上刚好也就是metal渲染的,我就先用了这个,至少现在图像识别没什么问题
没改的话是因为playcover阴阳师今年1.8.4.4版本更新以后加了一个逻辑,以前是./data路径调用的应用包资源,后来改了,为了跟上果子的大一统计划,统一变成ios的。/app路径了,所以要加一个映射。这个小脚本我不一起传的话大部分用户应该还用布鲁埃playcover阴阳师,所以就先留着sck了,毕竟就我一个人用。

这点我倒是很骄傲,因为我翻了大半个互联网是没找到任何跟这个问题有关的解决方案,纯翻日志翻出来的,而且翻这个日志还得我下了两个不同版本的阴阳师然后花几十个g流量把他们都更新完了然后一点一点看对比出来,codex立大功。指令的话我翻了playcover中文网,他们有个算一丢丢类似的issue吧,洛克王国初始化资源失败,我看了眼觉得很像,虽然逻辑八竿子打不着,我看他映射我就试了试,跑通了。

MAA他们那个MacSCK非常好使,是直接找到窗口,然后ScreenCaptureKit 捕获。 理论上不需要转果子的vimage所以应该效率最高,现在这个bgr老是会掉色变成黑白,饱受其害。
后面改吧!

然后的话return确实,这部分最初确实是按照 MAA 的接过来的,当时主要先保证 PlayCover 能正常截图和触控。不过稳定运行的话这些个坑找机会要填了。

其他的就咳咳,前人种树后人乘凉,不敢当!

@runhey

runhey commented Aug 15, 2026

Copy link
Copy Markdown
Owner

RGBA 换一个名字,可以和别的一样在前面加上Mac,然后让AI 在这里加上module/daemon/benchmark.py测试。
如果MacBGR速度没有很快删掉就好了

@runhey

runhey commented Aug 15, 2026

Copy link
Copy Markdown
Owner

你是提这个功能的,既然你的最新阴阳师版本能跑,那就按照你的为准,最多标注上 阴阳师版本大于 1.8.4.4。多留一个选项背后所声明维护量是会变多的

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