Skip to content

feat(controller): add MaaControllerPostMouseMoveRelative#1189

Merged
MistEO merged 11 commits intomainfrom
feat/win32-relative-mouse-move
Mar 7, 2026
Merged

feat(controller): add MaaControllerPostMouseMoveRelative#1189
MistEO merged 11 commits intomainfrom
feat/win32-relative-mouse-move

Conversation

@Lemon-miaow
Copy link
Member

@Lemon-miaow Lemon-miaow commented Mar 6, 2026

本 PR 旨在为 controller 层补充一个新的基础输入能力:相对鼠标移动

背景

当前框架已经提供了较为完整的控制接口,例如:

  • post_touch_down
  • post_touch_move
  • post_touch_up
  • post_key_down
  • post_key_up
  • post_scroll

这些接口对于点击、拖拽、移动到某个位置、键盘操作等场景都很好用,尤其适合以 绝对坐标 为核心的交互方式。

不过在一些 3D 游戏或自由视角场景里,业务层需要表达的并不是“把指针移动到某个点”,而是:

  • 本次向右移动多少
  • 本次向左移动多少
  • 本次产生一个 dx / dy 增量

也就是一种 relative delta 语义,而不是 absolute position 语义。

关于为什么需要补充这个接口?

在现有接口下,如果业务层要实现“相对移动”的效果,理论上也可以通过“记录当前位置、再换算目标点”的方式,用 post_touch_move 去模拟。这种方式在一些简单场景下是可行的。

但对于 3D 镜头控制这类持续、增量式输入来说,业务层通常还需要额外处理一系列复杂问题:

  • 内部锚点 / 当前位置维护
  • 目标点边界与截断
  • 请求位移和实际位移不一致
  • 回中或重建锚点的策略
  • 长时间运行后的漂移修正

也就是说,在这类场景下,现有 absolute move 接口并不是单纯“写起来绕一点”,而是很难长期稳定地表达镜头控制所需的连续相对位移。

这些逻辑放在业务层并不是完全做不到,只是它们和“业务策略”本身关系不大,更像是在弥补 controller 层缺少的一种基础输入语义。

如果没有这一层原生能力,业务实现往往会不断堆积额外的状态维护和补偿逻辑,最终让问题从“控制业务”变成“维护输入模拟本身”。

换句话说,这里缺的不是一个“更方便的 helper”,而是一种更适合这类场景的原生功能:

  • TouchMove 更适合表达**“移动到哪里”**
  • MouseMoveRelative 更适合表达**“本次移动多少”**

本 PR 做了什么

本 PR 新增了一个核心接口:

MaaControllerPostMouseMoveRelative(MaaController* ctrl, int32_t dx, int32_t dy)

它的语义非常直接:

  • dx / dy 表示本次相对移动增量。
  • 不表示绝对目标点。
  • 不替代现有的 TouchMove。
  • 当前在 Win32 controller 中提供原生实现。

与现有接口的关系

这个接口不是为了替代 post_touch_move,而是补充另一种输入语义。可以简单对比理解为:

  • post_touch_move:absolute position
  • post_mouse_move_relative:relative delta

两者解决的问题不一样,各自有适合的场景:

  • 对于 UI 坐标操作,现有的 TouchMove 仍然是最合适的。
  • 对于 3D 镜头、自由视角、鼠标增量式控制,这个新接口会更自然、更精准。

期望效果

希望补上这个接口之后,controller 层能够同时覆盖两类常见输入方式:

  1. Absolute position:移动到某个绝对位置
  2. Relative delta:相对当前状态移动一个增量

这样业务层在面对不同输入模型时,都能选择更贴近实际语义的接口,而不再需要额外承担过多的状态补偿与模拟逻辑。

Summary by Sourcery

通过核心框架、Win32 控制单元、远程/代理层以及语言绑定新增并贯通用于相对鼠标移动的控制器 API。

新特性:

  • 暴露 MaaControllerPostMouseMoveRelative API,用于通过控制器抽象层投递相对鼠标移动动作。
  • 在 Win32 控制单元和 seize-input 实现中支持相对鼠标移动。
  • 扩展 Python 和 Node.js 绑定以及自定义控制器接口,以调用相对鼠标移动任务和回调。

增强:

  • 扩展控制器动作类型、参数和控制单元 API,以在本地和远程控制器之间端到端处理 mouse_move_relative 动作。

文档:

  • 在英文和中文的一体化接口概览文档中记录新的 MaaControllerPostMouseMoveRelative API。

测试:

  • 扩展 Python、Node.js 和 agent 测试,以覆盖新的相对鼠标移动控制器 API。
Original summary in English

Summary by Sourcery

Add a new controller API for relative mouse movement and wire it through the core framework, Win32 control unit, remote/agent layers, and language bindings.

New Features:

  • Expose a MaaControllerPostMouseMoveRelative API to post relative mouse movement actions through the controller abstraction.
  • Support relative mouse movement in the Win32 control unit and seize-input implementation.
  • Extend Python and Node.js bindings and custom controller interfaces to invoke relative mouse movement jobs and callbacks.

Enhancements:

  • Extend controller action types, parameters, and control unit APIs to handle mouse_move_relative actions end-to-end across local and remote controllers.

Documentation:

  • Document the new MaaControllerPostMouseMoveRelative API in the English and Chinese integrated interface overview docs.

Tests:

  • Expand Python, Node.js, and agent tests to cover the new relative mouse movement controller API.

Copy link
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.

Hey - 我发现了 1 个问题,并留下了一些高层次的反馈:

  • 在 Python 绑定中,CustomController.mouse_move_relative 是一个新的抽象方法(必需),而在 NodeJS 的 CustomControllerActor 中它是可选的(mouse_move_relative?);建议统一这两边的语义,以便不同语言的自定义控制器作者在要求和失败模式上保持一致。
  • 对于 ControlUnitAPI::mouse_move_relativeInputBase::mouse_move_relative,你目前默认返回 false;如果不希望静默地“不支持但不报错”,可以考虑打印一个警告日志,或者让调用方显式检查特性支持情况,从而更容易暴露误用问题。
面向 AI Agent 的提示
Please address the comments from this code review:

## Overall Comments
- In the Python binding, `CustomController.mouse_move_relative` is a new abstract method (mandatory), while in the NodeJS `CustomControllerActor` it is optional (`mouse_move_relative?`); consider aligning these semantics so that custom-controller authors in different languages have consistent requirements and failure modes.
- For `ControlUnitAPI::mouse_move_relative` and `InputBase::mouse_move_relative`, you currently return `false` by default; if silent unsupported behavior is undesirable, consider logging a warning or making callers explicitly check feature support so that misuse is more easily surfaced.

## Individual Comments

### Comment 1
<location path="source/binding/Python/maa/controller.py" line_range="1383-1398" />
<code_context>

         return int(self.touch_up(int(c_contact)))

+    @staticmethod
+    @MaaCustomControllerCallbacks.MouseMoveRelativeFunc
+    def _c_mouse_move_relative_agent(
+        c_dx: ctypes.c_int32,
+        c_dy: ctypes.c_int32,
+        trans_arg: ctypes.c_void_p,
+    ) -> int:
+        if not trans_arg:
+            return int(False)
+
+        self: CustomController = ctypes.cast(
+            trans_arg,
+            ctypes.py_object,
+        ).value
+
+        return int(self.mouse_move_relative(int(c_dx), int(c_dy)))
+
     @staticmethod
</code_context>
<issue_to_address>
**suggestion (bug_risk):** 在跨越 C 边界之前,考虑对 mouse_move_relative 回调添加针对 Python 异常的保护

如果 `mouse_move_relative` 抛出异常,该异常会通过 C 回调向下传播,并可能导致原生崩溃或未定义行为。请用 try/except 包裹这次调用,记录 traceback,并在失败时返回 `False`,以保证 FFI 边界的安全性,并与健壮的 C API 实践保持一致。

```suggestion
    @staticmethod
    @MaaCustomControllerCallbacks.MouseMoveRelativeFunc
    def _c_mouse_move_relative_agent(
        c_dx: ctypes.c_int32,
        c_dy: ctypes.c_int32,
        trans_arg: ctypes.c_void_p,
    ) -> int:
        if not trans_arg:
            return int(False)

        self: CustomController = ctypes.cast(
            trans_arg,
            ctypes.py_object,
        ).value

        import logging

        try:
            return int(self.mouse_move_relative(int(c_dx), int(c_dy)))
        except Exception:
            logging.exception("Unhandled exception in CustomController.mouse_move_relative callback")
            return int(False)
```
</issue_to_address>

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

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

  • In the Python binding, CustomController.mouse_move_relative is a new abstract method (mandatory), while in the NodeJS CustomControllerActor it is optional (mouse_move_relative?); consider aligning these semantics so that custom-controller authors in different languages have consistent requirements and failure modes.
  • For ControlUnitAPI::mouse_move_relative and InputBase::mouse_move_relative, you currently return false by default; if silent unsupported behavior is undesirable, consider logging a warning or making callers explicitly check feature support so that misuse is more easily surfaced.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- In the Python binding, `CustomController.mouse_move_relative` is a new abstract method (mandatory), while in the NodeJS `CustomControllerActor` it is optional (`mouse_move_relative?`); consider aligning these semantics so that custom-controller authors in different languages have consistent requirements and failure modes.
- For `ControlUnitAPI::mouse_move_relative` and `InputBase::mouse_move_relative`, you currently return `false` by default; if silent unsupported behavior is undesirable, consider logging a warning or making callers explicitly check feature support so that misuse is more easily surfaced.

## Individual Comments

### Comment 1
<location path="source/binding/Python/maa/controller.py" line_range="1383-1398" />
<code_context>

         return int(self.touch_up(int(c_contact)))

+    @staticmethod
+    @MaaCustomControllerCallbacks.MouseMoveRelativeFunc
+    def _c_mouse_move_relative_agent(
+        c_dx: ctypes.c_int32,
+        c_dy: ctypes.c_int32,
+        trans_arg: ctypes.c_void_p,
+    ) -> int:
+        if not trans_arg:
+            return int(False)
+
+        self: CustomController = ctypes.cast(
+            trans_arg,
+            ctypes.py_object,
+        ).value
+
+        return int(self.mouse_move_relative(int(c_dx), int(c_dy)))
+
     @staticmethod
</code_context>
<issue_to_address>
**suggestion (bug_risk):** Consider guarding mouse_move_relative callback against Python exceptions before crossing the C boundary

If `mouse_move_relative` raises, the exception will propagate through the C callback and can cause native crashes or undefined behavior. Please wrap this call in a try/except, log the traceback, and return `False` on failure to keep the FFI boundary safe and consistent with robust C API practices.

```suggestion
    @staticmethod
    @MaaCustomControllerCallbacks.MouseMoveRelativeFunc
    def _c_mouse_move_relative_agent(
        c_dx: ctypes.c_int32,
        c_dy: ctypes.c_int32,
        trans_arg: ctypes.c_void_p,
    ) -> int:
        if not trans_arg:
            return int(False)

        self: CustomController = ctypes.cast(
            trans_arg,
            ctypes.py_object,
        ).value

        import logging

        try:
            return int(self.mouse_move_relative(int(c_dx), int(c_dy)))
        except Exception:
            logging.exception("Unhandled exception in CustomController.mouse_move_relative callback")
            return int(False)
```
</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 +1383 to +1398
@staticmethod
@MaaCustomControllerCallbacks.MouseMoveRelativeFunc
def _c_mouse_move_relative_agent(
c_dx: ctypes.c_int32,
c_dy: ctypes.c_int32,
trans_arg: ctypes.c_void_p,
) -> int:
if not trans_arg:
return int(False)

self: CustomController = ctypes.cast(
trans_arg,
ctypes.py_object,
).value

return int(self.mouse_move_relative(int(c_dx), int(c_dy)))
Copy link
Contributor

Choose a reason for hiding this comment

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

suggestion (bug_risk): 在跨越 C 边界之前,考虑对 mouse_move_relative 回调添加针对 Python 异常的保护

如果 mouse_move_relative 抛出异常,该异常会通过 C 回调向下传播,并可能导致原生崩溃或未定义行为。请用 try/except 包裹这次调用,记录 traceback,并在失败时返回 False,以保证 FFI 边界的安全性,并与健壮的 C API 实践保持一致。

Suggested change
@staticmethod
@MaaCustomControllerCallbacks.MouseMoveRelativeFunc
def _c_mouse_move_relative_agent(
c_dx: ctypes.c_int32,
c_dy: ctypes.c_int32,
trans_arg: ctypes.c_void_p,
) -> int:
if not trans_arg:
return int(False)
self: CustomController = ctypes.cast(
trans_arg,
ctypes.py_object,
).value
return int(self.mouse_move_relative(int(c_dx), int(c_dy)))
@staticmethod
@MaaCustomControllerCallbacks.MouseMoveRelativeFunc
def _c_mouse_move_relative_agent(
c_dx: ctypes.c_int32,
c_dy: ctypes.c_int32,
trans_arg: ctypes.c_void_p,
) -> int:
if not trans_arg:
return int(False)
self: CustomController = ctypes.cast(
trans_arg,
ctypes.py_object,
).value
import logging
try:
return int(self.mouse_move_relative(int(c_dx), int(c_dy)))
except Exception:
logging.exception("Unhandled exception in CustomController.mouse_move_relative callback")
return int(False)
Original comment in English

suggestion (bug_risk): Consider guarding mouse_move_relative callback against Python exceptions before crossing the C boundary

If mouse_move_relative raises, the exception will propagate through the C callback and can cause native crashes or undefined behavior. Please wrap this call in a try/except, log the traceback, and return False on failure to keep the FFI boundary safe and consistent with robust C API practices.

Suggested change
@staticmethod
@MaaCustomControllerCallbacks.MouseMoveRelativeFunc
def _c_mouse_move_relative_agent(
c_dx: ctypes.c_int32,
c_dy: ctypes.c_int32,
trans_arg: ctypes.c_void_p,
) -> int:
if not trans_arg:
return int(False)
self: CustomController = ctypes.cast(
trans_arg,
ctypes.py_object,
).value
return int(self.mouse_move_relative(int(c_dx), int(c_dy)))
@staticmethod
@MaaCustomControllerCallbacks.MouseMoveRelativeFunc
def _c_mouse_move_relative_agent(
c_dx: ctypes.c_int32,
c_dy: ctypes.c_int32,
trans_arg: ctypes.c_void_p,
) -> int:
if not trans_arg:
return int(False)
self: CustomController = ctypes.cast(
trans_arg,
ctypes.py_object,
).value
import logging
try:
return int(self.mouse_move_relative(int(c_dx), int(c_dy)))
except Exception:
logging.exception("Unhandled exception in CustomController.mouse_move_relative callback")
return int(False)

@MistEO
Copy link
Member

MistEO commented Mar 6, 2026

MaaControllerPostRelativeMove 吧,不要 mouse 了,毕竟以后其他控制器可能要支持,不一定是 mouse

目前可以参考 post shell (adb 的),如果不是 win32 控制器就报错

class Win32ControlUnitAPI : public ControlUnitAPI
{
public:
virtual ~Win32ControlUnitAPI() = default;
Copy link
Member

@MistEO MistEO Mar 7, 2026

Choose a reason for hiding this comment

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

relative_move 放到 Win32ControlUnitAPI 这里,参考 adb 的 shell 的这样

Image

所以 ControllerAgent 里才要判断 control unit 类型

Copy link
Member

@MistEO MistEO Mar 7, 2026

Choose a reason for hiding this comment

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

这样的话,其他 control unit 可以直接不用定义这个函数,ControlUnitAPI 里的空实现可以删掉,以保持纯虚

@MistEO MistEO merged commit 03d060b into main Mar 7, 2026
14 checks passed
@MistEO MistEO deleted the feat/win32-relative-mouse-move branch March 7, 2026 11:39
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