Skip to content

feat(linux): Wayland(kwin)下的截图和控制#1357

Open
lava114514 wants to merge 10 commits into
MaaXYZ:mainfrom
lava114514:feat-kwin-wayland
Open

feat(linux): Wayland(kwin)下的截图和控制#1357
lava114514 wants to merge 10 commits into
MaaXYZ:mainfrom
lava114514:feat-kwin-wayland

Conversation

@lava114514
Copy link
Copy Markdown

@lava114514 lava114514 commented Jun 5, 2026

添加对Wayland(KWin)的支持

具体实现:
控制器:使用/dev/uinput 创建虚拟鼠标设备进行输入
屏幕捕获 (PipeWireScreencap): 基于 xdg-desktop-portal 的 CreateSession -> SelectSources -> Start -> OpenPipeWireRemote 协议

测试环境 :

操作系统: Arch Linux
桌面环境: KDE Plasma (Wayland): 6.6.5
KDE 程序框架版本: 6.26.0
Qt 版本: 6.11.1
内核版本: 7.0.11-1-cachyos-bore (64 位)
图形平台: Wayland
pipewire 1.6.6-1 +xdg-desktop-portal-kde 6.6.5-1

测试结果:
测试截图和简单点击和滑动事件通过

要求:
1.运行前需确保用户对 /dev/uinput 具有读写权限(建议添加 udev 规则:KERNEL=="uinput", MODE="0660", GROUP="input")
2.调用截图功能时会弹出 KDE 原生授权窗口,需要手动授权

由 Sourcery 总结

添加一个基于 KWin 的 Linux 控制器,支持 PipeWire 屏幕捕获和 uinput 输入,将其集成到现有框架和构建系统中,并扩展 CI 以提供 PipeWire/D-Bus 依赖(包括 ARM64 交叉编译支持)。

新功能:

  • 引入 KWin 控制单元 API 和管理器,通过虚拟 uinput 设备和基于 PipeWire 的屏幕捕获来控制 KDE Plasma Wayland 会话。
  • 在 MaaFramework 中暴露新的 MaaKWinControllerCreate API,用于通过指定的 uinput 设备节点和屏幕尺寸来构造 KWin 控制器。
  • 提供用于 Linux 上 KWin 控制器连接、屏幕捕获和键盘输入行为的集成测试可执行文件。

增强:

  • 扩展控制单元库持有者和框架,以便在启用时动态加载 KWin 控制单元。
  • 通过将 Wayland 套接字名称解析为无符号整数,改进 Linux Wayland 桌面窗口发现机制。
  • 通过 WITH_KWIN_CONTROLLER CMake 标志使 KWin 控制器的构建变为可选,并将其库接入整体构建和依赖图中。

构建:

  • 更新 CMake,添加 MaaKWinControlUnit 子项目、其共享库以及测试可执行文件,并由 WITH_KWIN_CONTROLLER 选项进行控制。

CI:

  • 在构建/测试工作流中安装 PipeWire 和 D-Bus 开发包,并新增 ARM64 交叉编译步骤,将其头文件和库注入到 x-tools 的 sysroot 中。
Original summary in English

Summary by Sourcery

Add a new KWin-based Linux controller with PipeWire screencapture and uinput input support, integrate it into the framework and build system, and extend CI to provide PipeWire/D-Bus dependencies including ARM64 cross-compilation.

New Features:

  • Introduce a KWin control unit API and manager to control KDE Plasma Wayland sessions via a virtual uinput device and PipeWire-based screencapture.
  • Expose a new MaaKWinControllerCreate API in MaaFramework to construct KWin controllers using a specified uinput device node and screen dimensions.
  • Provide integration test binaries for KWin controller connection, screencapture, and keyboard input behavior on Linux.

Enhancements:

  • Extend the control unit library holder and framework to dynamically load the KWin control unit when enabled.
  • Improve Linux Wayland desktop window discovery by parsing wayland socket names as unsigned integers.
  • Make the KWin controller build optional via a WITH_KWIN_CONTROLLER CMake flag and wire its library into the overall build and dependency graph.

Build:

  • Update CMake to add the MaaKWinControlUnit subproject, its shared library, and test executables, guarded by the WITH_KWIN_CONTROLLER option.

CI:

  • Install PipeWire and D-Bus development packages in build/test workflows and add ARM64 cross-compilation steps to inject their headers and libraries into the x-tools sysroot.

新功能:

  • 引入 KWin 控制单元管理器和 API,用于控制 KDE Plasma(Wayland)会话。
  • 添加基于 PipeWire 的屏幕投射实现,使用 xdg-desktop-portal 在 Wayland 上进行全屏捕获。
  • 添加基于 uinput 的虚拟指针控制器,在 Linux 上支持绝对/相对鼠标输入和滚动。
  • 在 MaaFramework 中暴露新的 MaaKWinControllerCreate API,用于根据设备节点和屏幕尺寸创建 KWin 控制器。
  • 提供一个集成测试可执行文件,用于验证 KWin 控制器的连接和屏幕捕获流程。

增强:

  • 扩展 LibraryHolderMaaFramework,在启用时动态加载 KWin 控制单元库。
  • 更新 Linux 桌面窗口发现逻辑,更健壮地将 Wayland 套接字名称解析为无符号整数。

构建:

  • MaaKWinControlUnit 库和测试接入 CMake 构建系统,并由 WITH_KWIN_CONTROLLER 选项进行控制。
Original summary in English

由 Sourcery 总结

添加一个基于 KWin 的 Linux 控制器,支持 PipeWire 屏幕捕获和 uinput 输入,将其集成到现有框架和构建系统中,并扩展 CI 以提供 PipeWire/D-Bus 依赖(包括 ARM64 交叉编译支持)。

新功能:

  • 引入 KWin 控制单元 API 和管理器,通过虚拟 uinput 设备和基于 PipeWire 的屏幕捕获来控制 KDE Plasma Wayland 会话。
  • 在 MaaFramework 中暴露新的 MaaKWinControllerCreate API,用于通过指定的 uinput 设备节点和屏幕尺寸来构造 KWin 控制器。
  • 提供用于 Linux 上 KWin 控制器连接、屏幕捕获和键盘输入行为的集成测试可执行文件。

增强:

  • 扩展控制单元库持有者和框架,以便在启用时动态加载 KWin 控制单元。
  • 通过将 Wayland 套接字名称解析为无符号整数,改进 Linux Wayland 桌面窗口发现机制。
  • 通过 WITH_KWIN_CONTROLLER CMake 标志使 KWin 控制器的构建变为可选,并将其库接入整体构建和依赖图中。

构建:

  • 更新 CMake,添加 MaaKWinControlUnit 子项目、其共享库以及测试可执行文件,并由 WITH_KWIN_CONTROLLER 选项进行控制。

CI:

  • 在构建/测试工作流中安装 PipeWire 和 D-Bus 开发包,并新增 ARM64 交叉编译步骤,将其头文件和库注入到 x-tools 的 sysroot 中。
Original summary in English

Summary by Sourcery

Add a new KWin-based Linux controller with PipeWire screencapture and uinput input support, integrate it into the framework and build system, and extend CI to provide PipeWire/D-Bus dependencies including ARM64 cross-compilation.

New Features:

  • Introduce a KWin control unit API and manager to control KDE Plasma Wayland sessions via a virtual uinput device and PipeWire-based screencapture.
  • Expose a new MaaKWinControllerCreate API in MaaFramework to construct KWin controllers using a specified uinput device node and screen dimensions.
  • Provide integration test binaries for KWin controller connection, screencapture, and keyboard input behavior on Linux.

Enhancements:

  • Extend the control unit library holder and framework to dynamically load the KWin control unit when enabled.
  • Improve Linux Wayland desktop window discovery by parsing wayland socket names as unsigned integers.
  • Make the KWin controller build optional via a WITH_KWIN_CONTROLLER CMake flag and wire its library into the overall build and dependency graph.

Build:

  • Update CMake to add the MaaKWinControlUnit subproject, its shared library, and test executables, guarded by the WITH_KWIN_CONTROLLER option.

CI:

  • Install PipeWire and D-Bus development packages in build/test workflows and add ARM64 cross-compilation steps to inject their headers and libraries into the x-tools sysroot.

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.

Hey - 我发现了 3 个问题,并给出了一些高层次的反馈:

  • KWinControlUnitMgr::get_features 对外声明了 MaaControllerFeature_UseKeyboardDownAndUpInsteadOfClick,但 key_down/key_up 目前尚未实现并且总是返回 false,因此要么实现键盘 uinput 处理,要么移除该特性标志,以避免调用方误以为键盘事件可用。
  • PipeWireScreencap 中,有若干未使用或只部分接线的成员(core_events_stream_mutex_/stream_cv_/stream_params_received_pw_on_core_error 从未被挂接),这增加了复杂度,也可能让未来的维护者困惑;建议要么完整地接好 core 事件处理,要么移除这些未使用的状态和回调。
  • D-Bus 辅助方法(dbus_create_sessiondbus_select_sourcesdbus_start_stream)各自单独调用 dbus_bus_get 并打开新的连接;建议复用一个单一的 DBusConnection,或清晰地文档化/集中管理连接,以避免重复的初始化和潜在的资源抖动。
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- `KWinControlUnitMgr::get_features` 对外声明了 `MaaControllerFeature_UseKeyboardDownAndUpInsteadOfClick`,但 `key_down`/`key_up` 目前尚未实现并且总是返回 `false`,因此要么实现键盘 uinput 处理,要么移除该特性标志,以避免调用方误以为键盘事件可用。
-`PipeWireScreencap` 中,有若干未使用或只部分接线的成员(`core_events_``stream_mutex_`/`stream_cv_`/`stream_params_received_``pw_on_core_error` 从未被挂接),这增加了复杂度,也可能让未来的维护者困惑;建议要么完整地接好 core 事件处理,要么移除这些未使用的状态和回调。
- D-Bus 辅助方法(`dbus_create_session``dbus_select_sources``dbus_start_stream`)各自单独调用 `dbus_bus_get` 并打开新的连接;建议复用一个单一的 `DBusConnection`,或清晰地文档化/集中管理连接,以避免重复的初始化和潜在的资源抖动。

## Individual Comments

### Comment 1
<location path="source/MaaKWinControlUnit/Input/UInputController.cpp" line_range="71-73" />
<code_context>
+    LogInfo << "UInput device closed";
+}
+
+bool UInputController::connected() const
+{
+    return connected_;
+}
+
</code_context>
<issue_to_address>
**issue (bug_risk):** `connected()` 在未加锁的情况下读取共享状态,这可能与 `open()`/`close()` 产生竞争。

`connected_``mutex_` 保护下被写入,但在这里读取时没有加锁。如果 `connected()` 可以与 `open()`/`close()` 并发运行,这就是一个数据竞争。可以将 `connected_` 改为 `std::atomic<bool>`,或者在 `connected()` 中获取 `mutex_`,以保持同步方式一致。
</issue_to_address>

### Comment 2
<location path="source/MaaKWinControlUnit/Screencap/PipeWireScreencap.h" line_range="112-125" />
<code_context>
+    struct spa_hook stream_hook_;
+
+    /* Heap-allocated event vtables (must outlive the listener) */
+    struct pw_core_events* core_events_ = nullptr;
+    struct pw_stream_events* stream_events_ = nullptr;
+
+    /* Negotiated frame dimensions (set by param_changed callback) */
+    int frame_width_ = 0;
+    int frame_height_ = 0;
+
+    /* Synchronisation: stream parameters received */
+    std::mutex stream_mutex_;
+    std::condition_variable stream_cv_;
+    bool stream_params_received_ = false;
+
+    /* Latest frame data (protected by frame_mutex_) */
</code_context>
<issue_to_address>
**suggestion:** 若干成员(`core_events_``stream_mutex_`/`stream_cv_``stream_params_received_`)从未使用。

这些成员被声明但从未使用,会增加噪音,并暗示有未完成的行为。请考虑移除它们,或者在本次变更中真正接入(例如用于 core 错误监听或同步参数协商),以免在类中留下无用的状态。

```suggestion
    /* Heap-allocated event vtables (must outlive the listener) */
    struct pw_stream_events* stream_events_ = nullptr;

    /* Negotiated frame dimensions (set by param_changed callback) */
    int frame_width_ = 0;
    int frame_height_ = 0;

    /* Latest frame data (protected by frame_mutex_) */
```
</issue_to_address>

### Comment 3
<location path="source/MaaKWinControlUnit/Screencap/PipeWireScreencap.cpp" line_range="1052-152" />
<code_context>
+                need_unmap = true;
+            }
+        }
+        else if (data.type == SPA_DATA_DmaBuf) {
+            LogError << "DMABUF not supported (need EGL import)";
+            return false;
+        }
+        else {
</code_context>
<issue_to_address>
**suggestion (bug_risk):** 对 DMABUF buffer 直接失败可能会在 compositor 切换 buffer 类型时导致捕获中断。

在 `process_frame` 中,当前对基于 DMABUF 的 buffer 是记录日志后返回 `false`。在那些只提供 DMABUF(现在或未来的 KWin/PipeWire 版本) 的系统上,一旦 buffer 类型切换,这会让屏幕捕获永久失败。如果 EGL import 还未实现,建议清楚地暴露这一限制(比如通过 `get_info()`/特性标志),或者增加回退方案(例如丢弃该帧但保留上一帧图像,这样 `screencap()` 仍能成功)。

建议实现方式:

```cpp
        else if (data.type == SPA_DATA_DmaBuf) {
            if (!m_loggedDmabufUnsupported) {
                LogWarning << "DMABUF buffers not yet supported; dropping frame (need EGL import)";
                m_loggedDmabufUnsupported = true;
            }

            // Mark this frame as DMABUF-backed so we can drop it gracefully later,
            // keeping the previously captured image available to callers.
            is_dmabuf_frame = true;
            continue;
        }

```

```cpp
    if (!mapped_ptr) {
        // If this was a DMABUF-backed frame, drop it but keep the previous image
        // so screencap() can still succeed instead of hard-failing.
        if (is_dmabuf_frame) {
            return true;
        }

        return false;
    }

```

1. 确保 `process_frame`(或其所在的函数)在 buffer 循环前定义并初始化一个本地标志:
   - 在函数顶部附近添加:
   ```cpp
   bool is_dmabuf_frame = false;
   ```
2. 在截屏类中添加一个成员,用于只记录一次 DMABUF 限制日志:
   - 在类声明(头文件)中添加:
   ```cpp
   bool m_loggedDmabufUnsupported = false;
   ```
   如果该代码库不使用默认成员初始化,则在构造函数中初始化。
3. 通过现有的能力/自省 API 暴露缺失的 DMABUF 支持:
   - 在报告截屏能力的 `get_info()` 或等价函数中,增加一个类似 `supportsDmabuf = false` 的标志(或遵循现有约定的名称),以便调用方可以检测并据此处理。
4. 如果调用方假定成功的 `process_frame()` 总是会更新底层图像,你可能需要调整这部分逻辑,使得返回 `true``is_dmabuf_frame == true` 表示“没有新帧;保留上一帧图像”,或者引入显式的状态枚举来替代简单的 `bool`。
</issue_to_address>

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

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

  • KWinControlUnitMgr::get_features advertises MaaControllerFeature_UseKeyboardDownAndUpInsteadOfClick, but key_down/key_up are currently unimplemented and always return false, so either implement keyboard uinput handling or drop that feature flag to avoid callers assuming working key events.
  • In PipeWireScreencap there are several unused or partially wired members (core_events_, stream_mutex_/stream_cv_/stream_params_received_, pw_on_core_error is never hooked), which adds complexity and may confuse future maintainers; consider either fully wiring core event handling or removing the unused state and callbacks.
  • The D-Bus helper methods (dbus_create_session, dbus_select_sources, dbus_start_stream) each call dbus_bus_get separately and open fresh connections; consider reusing a single DBusConnection or clearly documenting/centralising connection management to avoid repeated setup and potential resource churn.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- KWinControlUnitMgr::get_features advertises MaaControllerFeature_UseKeyboardDownAndUpInsteadOfClick, but key_down/key_up are currently unimplemented and always return false, so either implement keyboard uinput handling or drop that feature flag to avoid callers assuming working key events.
- In PipeWireScreencap there are several unused or partially wired members (core_events_, stream_mutex_/stream_cv_/stream_params_received_, pw_on_core_error is never hooked), which adds complexity and may confuse future maintainers; consider either fully wiring core event handling or removing the unused state and callbacks.
- The D-Bus helper methods (dbus_create_session, dbus_select_sources, dbus_start_stream) each call dbus_bus_get separately and open fresh connections; consider reusing a single DBusConnection or clearly documenting/centralising connection management to avoid repeated setup and potential resource churn.

## Individual Comments

### Comment 1
<location path="source/MaaKWinControlUnit/Input/UInputController.cpp" line_range="71-73" />
<code_context>
+    LogInfo << "UInput device closed";
+}
+
+bool UInputController::connected() const
+{
+    return connected_;
+}
+
</code_context>
<issue_to_address>
**issue (bug_risk):** connected() reads shared state without locking, which can race with open()/close().

`connected_` is written under `mutex_`, but read here without locking. If `connected()` can run concurrently with `open()`/`close()`, this is a data race. Either make `connected_` an `std::atomic<bool>` or take `mutex_` in `connected()` to keep synchronization consistent.
</issue_to_address>

### Comment 2
<location path="source/MaaKWinControlUnit/Screencap/PipeWireScreencap.h" line_range="112-125" />
<code_context>
+    struct spa_hook stream_hook_;
+
+    /* Heap-allocated event vtables (must outlive the listener) */
+    struct pw_core_events* core_events_ = nullptr;
+    struct pw_stream_events* stream_events_ = nullptr;
+
+    /* Negotiated frame dimensions (set by param_changed callback) */
+    int frame_width_ = 0;
+    int frame_height_ = 0;
+
+    /* Synchronisation: stream parameters received */
+    std::mutex stream_mutex_;
+    std::condition_variable stream_cv_;
+    bool stream_params_received_ = false;
+
+    /* Latest frame data (protected by frame_mutex_) */
</code_context>
<issue_to_address>
**suggestion:** Several members (core_events_, stream_mutex_/stream_cv_, stream_params_received_) are never used.

These members are declared but never used, which adds noise and implies unfinished behavior. Please either remove them or wire them into the current change (e.g., for core error listening or synchronous param negotiation) so we don’t leave dead state in the class.

```suggestion
    /* Heap-allocated event vtables (must outlive the listener) */
    struct pw_stream_events* stream_events_ = nullptr;

    /* Negotiated frame dimensions (set by param_changed callback) */
    int frame_width_ = 0;
    int frame_height_ = 0;

    /* Latest frame data (protected by frame_mutex_) */
```
</issue_to_address>

### Comment 3
<location path="source/MaaKWinControlUnit/Screencap/PipeWireScreencap.cpp" line_range="1052-152" />
<code_context>
+                need_unmap = true;
+            }
+        }
+        else if (data.type == SPA_DATA_DmaBuf) {
+            LogError << "DMABUF not supported (need EGL import)";
+            return false;
+        }
+        else {
</code_context>
<issue_to_address>
**suggestion (bug_risk):** Hard-failing on DMABUF buffers may break capture on compositors that switch buffer types.

In `process_frame`, DMABUF-backed buffers currently log and return `false`. On systems that only offer DMABUF (now or in future KWin/PipeWire versions), this would make screencap fail permanently once the buffer type switches. If EGL import isn’t implemented yet, consider clearly exposing this limitation (e.g., via `get_info()`/feature flag) or adding a fallback (e.g., drop the frame but keep the previous image so `screencap()` still succeeds).

Suggested implementation:

```cpp
        else if (data.type == SPA_DATA_DmaBuf) {
            if (!m_loggedDmabufUnsupported) {
                LogWarning << "DMABUF buffers not yet supported; dropping frame (need EGL import)";
                m_loggedDmabufUnsupported = true;
            }

            // Mark this frame as DMABUF-backed so we can drop it gracefully later,
            // keeping the previously captured image available to callers.
            is_dmabuf_frame = true;
            continue;
        }

```

```cpp
    if (!mapped_ptr) {
        // If this was a DMABUF-backed frame, drop it but keep the previous image
        // so screencap() can still succeed instead of hard-failing.
        if (is_dmabuf_frame) {
            return true;
        }

        return false;
    }

```

1. Ensure `process_frame` (or the containing function) defines and initializes a local flag before the buffer loop:
   - Add near the top of the function:
   ```cpp
   bool is_dmabuf_frame = false;
   ```
2. Add a member to the screencap class to log the DMABUF limitation only once:
   - In the class declaration (header), add:
   ```cpp
   bool m_loggedDmabufUnsupported = false;
   ```
   or, if default member initializers are not used in this codebase, initialize it in the constructor.
3. Expose the missing-DMABUF support via the existing capability/introspection API:
   - In the `get_info()` or equivalent function that reports screencap capabilities, add a flag such as `supportsDmabuf = false` (or similar, matching existing conventions), and ensure callers can detect this and handle it appropriately.
4. If the caller assumes that a successful `process_frame()` always updates the backing image, you may need to adjust that logic so that a `true` return combined with `is_dmabuf_frame == true` means "no new frame; keep previous image", or alternatively introduce an explicit status enum instead of a bare `bool`.
</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 thread source/MaaKWinControlUnit/Input/UInputController.cpp
Comment thread source/MaaKWinControlUnit/Screencap/PipeWireScreencap.h
Comment thread source/MaaKWinControlUnit/Screencap/PipeWireScreencap.cpp
@lava114514
Copy link
Copy Markdown
Author

@sourcery-ai review

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.

Hey - 我发现了 4 个问题,并给出了一些整体反馈:

  • UInputController::maa_to_linux_keycode 中,ASCII 数字映射把 '0''1'..'9' 一样处理,导致 '0' 被映射到错误的 Linux 键码(应该像测试用例中那样是 KEY_0);请添加一个显式的 '0' 分支以保持行为一致。
  • MaaController.hMaaKWinControllerCreate 的注释仍然写着截图功能尚未实现并且需要 PipeWire,但现在你已经提供了基于 PipeWire 的实现,建议更新这段注释,让它反映当前的实际行为。
  • PipeWire/DBus 依赖的 ARM64 交叉编译配置在 build.ymltest.yml 中完全重复了同一段内容;可以考虑把它抽取到一个共享脚本或可复用的 action 中,以减少维护成本,并把逻辑集中到一个地方。
给 AI Agent 的提示
Please address the comments from this code review:

## Overall Comments
- In `UInputController::maa_to_linux_keycode`, ASCII digit mapping treats `'0'` the same as `'1'..'9'`, causing `'0'` to map to the wrong Linux keycode (should be `KEY_0` like in the test reference); add an explicit `'0'` case to keep behavior consistent.
- The comment for `MaaKWinControllerCreate` in `MaaController.h` still states that screencap is not implemented and requires PipeWire, but you now provide a PipeWire-based implementation, so it would be good to update that note to reflect the current behavior.
- The ARM64 cross-compilation setup for PipeWire/DBus dependencies is duplicated verbatim in both `build.yml` and `test.yml`; consider extracting this into a shared script or reusable action to reduce maintenance overhead and keep the logic in one place.

## Individual Comments

### Comment 1
<location path="source/MaaKWinControlUnit/Screencap/PipeWireScreencap.cpp" line_range="1036-1045" />
<code_context>
+    bool need_unmap = false;
+    size_t map_size = 0;
+
+    if (!mapped_ptr) {
+        if (data.type == SPA_DATA_MemFd && data.fd >= 0) {
+            map_size = data.maxsize > 0 ? data.maxsize : data.chunk->size;
+            if (map_size > 0) {
+                mapped_ptr = mmap(nullptr, map_size, PROT_READ, MAP_PRIVATE, data.fd, 0);
+                if (mapped_ptr == MAP_FAILED) {
+                    LogError << "mmap of PipeWire buffer failed: " << strerror(errno);
+                    return false;
+                }
+                need_unmap = true;
+            }
+        }
+        else if (data.type == SPA_DATA_DmaBuf) {
+            // DMABuf cannot be directly mapped via mmap; silently drop the frame
+            // and return true to indicate "no error, but skip this frame".
+            LogWarn << "DMABUF buffer type received, dropping frame (EGL import not supported)";
</code_context>
<issue_to_address>
**issue (bug_risk):** DMABUF 分支在没有生成图像的情况下报告成功,会向调用方发出误导性的成功信号。

这里你记录了一条 warning 日志并 `return true`,但从未写入 `out_image`。由于对 `pw_on_stream_process` 来说 `true` 表示“帧已就绪”,它会存储一个空的 `Mat` 并唤醒条件变量,从而让 `screencap()` 在没有可用帧的情况下退出。可以考虑在这里返回 `false` 以忽略该帧,或者仅在确保 `out_image` 非空时才返回 `true`。
</issue_to_address>

### Comment 2
<location path="source/MaaKWinControlUnit/Input/UInputController.cpp" line_range="335-344" />
<code_context>
+    return { screen_width_, screen_height_ };
+}
+
+int UInputController::maa_to_linux_keycode(int key_code)
+{
+    // ASCII letter mapping
+    if (key_code >= 'A' && key_code <= 'Z') {
+        return KEY_A + (key_code - 'A');
+    }
+    if (key_code >= 'a' && key_code <= 'z') {
+        return KEY_A + (key_code - 'a');
+    }
+    // ASCII digit mapping
+    if (key_code >= '0' && key_code <= '9') {
+        return KEY_1 + (key_code - '1');
+    }
</code_context>
<issue_to_address>
**issue (bug_risk):** 字符 '0' 的数字键映射不正确,会产生无效的 Linux 键码。

这种算术映射只对 '1'–'9' 有效;对于 '0',结果是 `KEY_1 - 1`,这是无效值,会导致 '0' 的按键事件错误或被忽略。请对 '0' 做特殊处理映射到 `KEY_0`,或使用一个显式的查找表来映射 '0'–'9'。
</issue_to_address>

### Comment 3
<location path="source/MaaKWinControlUnit/API/KWinControlUnitAPI.cpp" line_range="21-22" />
<code_context>
+
+#else
+
+    if (!device_node) {
+        LogError << "device_node is null";
+        return nullptr;
</code_context>
<issue_to_address>
**nitpick:** 日志消息声称 device_node 可能是空字符串,但实际上只检查了指针是否为 null。

条件只检查了空指针,但日志信息暗示也处理了空字符串情况。可以考虑增加一个显式的空字符串检查(例如 `device_node[0] == '\0'`),或者修改日志信息,只提到指针为 null 的情况。
</issue_to_address>

### Comment 4
<location path="source/MaaKWinControlUnit/test_kwin_keyboard.cpp" line_range="29-38" />
<code_context>
+// ─── Keycode mapping reference (mirrors UInputController::maa_to_linux_keycode) ───
</code_context>
<issue_to_address>
**issue (testing):** 当前的映射测试只验证了一个重复实现的参考版本,而不是实际线上使用的 maa_to_linux_keycode 逻辑。

由于 Phase 1 中使用的 `reference_keycode` 是手动“镜像” `UInputController::maa_to_linux_keycode` 的实现,即便真实实现退化,只要两边以同样错误的方式被修改,测试仍然可能通过。为了增强这些测试,可以考虑:

- 添加一个仅用于测试的小钩子或辅助函数(例如在测试头文件中使用 `friend` 或包装函数)来直接调用 `maa_to_linux_keycode`,并在测试中使用它;或者
-`maa_to_linux_keycode` 抽取到一个共享模块,让库代码和测试都使用同一份实现。

这样可以确保 Phase 1 实际执行的是线上使用的映射逻辑,而不是一个重复的参考实现。
</issue_to_address>

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

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

  • In UInputController::maa_to_linux_keycode, ASCII digit mapping treats '0' the same as '1'..'9', causing '0' to map to the wrong Linux keycode (should be KEY_0 like in the test reference); add an explicit '0' case to keep behavior consistent.
  • The comment for MaaKWinControllerCreate in MaaController.h still states that screencap is not implemented and requires PipeWire, but you now provide a PipeWire-based implementation, so it would be good to update that note to reflect the current behavior.
  • The ARM64 cross-compilation setup for PipeWire/DBus dependencies is duplicated verbatim in both build.yml and test.yml; consider extracting this into a shared script or reusable action to reduce maintenance overhead and keep the logic in one place.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- In `UInputController::maa_to_linux_keycode`, ASCII digit mapping treats `'0'` the same as `'1'..'9'`, causing `'0'` to map to the wrong Linux keycode (should be `KEY_0` like in the test reference); add an explicit `'0'` case to keep behavior consistent.
- The comment for `MaaKWinControllerCreate` in `MaaController.h` still states that screencap is not implemented and requires PipeWire, but you now provide a PipeWire-based implementation, so it would be good to update that note to reflect the current behavior.
- The ARM64 cross-compilation setup for PipeWire/DBus dependencies is duplicated verbatim in both `build.yml` and `test.yml`; consider extracting this into a shared script or reusable action to reduce maintenance overhead and keep the logic in one place.

## Individual Comments

### Comment 1
<location path="source/MaaKWinControlUnit/Screencap/PipeWireScreencap.cpp" line_range="1036-1045" />
<code_context>
+    bool need_unmap = false;
+    size_t map_size = 0;
+
+    if (!mapped_ptr) {
+        if (data.type == SPA_DATA_MemFd && data.fd >= 0) {
+            map_size = data.maxsize > 0 ? data.maxsize : data.chunk->size;
+            if (map_size > 0) {
+                mapped_ptr = mmap(nullptr, map_size, PROT_READ, MAP_PRIVATE, data.fd, 0);
+                if (mapped_ptr == MAP_FAILED) {
+                    LogError << "mmap of PipeWire buffer failed: " << strerror(errno);
+                    return false;
+                }
+                need_unmap = true;
+            }
+        }
+        else if (data.type == SPA_DATA_DmaBuf) {
+            // DMABuf cannot be directly mapped via mmap; silently drop the frame
+            // and return true to indicate "no error, but skip this frame".
+            LogWarn << "DMABUF buffer type received, dropping frame (EGL import not supported)";
</code_context>
<issue_to_address>
**issue (bug_risk):** DMABUF branch reports success without producing a frame, which can signal a false-positive to callers.

Here you log a warning and `return true`, but never write `out_image`. Since `true` means "frame ready" to `pw_on_stream_process`, it will store an empty `Mat` and signal the condition variable, letting `screencap()` exit without a usable frame. Consider returning `false` here so the frame is ignored, or only returning `true` after ensuring `out_image` is non-empty.
</issue_to_address>

### Comment 2
<location path="source/MaaKWinControlUnit/Input/UInputController.cpp" line_range="335-344" />
<code_context>
+    return { screen_width_, screen_height_ };
+}
+
+int UInputController::maa_to_linux_keycode(int key_code)
+{
+    // ASCII letter mapping
+    if (key_code >= 'A' && key_code <= 'Z') {
+        return KEY_A + (key_code - 'A');
+    }
+    if (key_code >= 'a' && key_code <= 'z') {
+        return KEY_A + (key_code - 'a');
+    }
+    // ASCII digit mapping
+    if (key_code >= '0' && key_code <= '9') {
+        return KEY_1 + (key_code - '1');
+    }
</code_context>
<issue_to_address>
**issue (bug_risk):** Digit key mapping for '0' is incorrect and produces an invalid Linux keycode.

This arithmetic mapping only works for '1'–'9'; for '0' it yields `KEY_1 - 1`, which is invalid and will cause incorrect or ignored key events for '0'. Please either special-case '0' to `KEY_0` or replace this arithmetic with an explicit lookup table for '0'–'9'.
</issue_to_address>

### Comment 3
<location path="source/MaaKWinControlUnit/API/KWinControlUnitAPI.cpp" line_range="21-22" />
<code_context>
+
+#else
+
+    if (!device_node) {
+        LogError << "device_node is null";
+        return nullptr;
</code_context>
<issue_to_address>
**nitpick:** Log message claims device_node can be empty even though only null is checked.

The condition only checks for a null pointer, but the message suggests empty strings are also handled. Either add an explicit empty-string check (e.g. `device_node[0] == '\0'`) or update the log message to mention only the null case.
</issue_to_address>

### Comment 4
<location path="source/MaaKWinControlUnit/test_kwin_keyboard.cpp" line_range="29-38" />
<code_context>
+// ─── Keycode mapping reference (mirrors UInputController::maa_to_linux_keycode) ───
</code_context>
<issue_to_address>
**issue (testing):** The mapping tests currently only validate a duplicated reference implementation, not the actual production maa_to_linux_keycode logic.

Because Phase 1 uses a `reference_keycode` that manually mirrors `UInputController::maa_to_linux_keycode`, the tests can still pass even if the real implementation regresses (as long as both are changed in the same wrong way). To strengthen these tests, either:

- Add a small test-only hook or helper (e.g., a `friend` or wrapper in a test header) that calls `maa_to_linux_keycode` directly, and use that in the tests; or
- Extract `maa_to_linux_keycode` into a shared module used by both the library and tests so they execute the same code.

This ensures Phase 1 exercises the real mapping logic rather than a duplicated reference implementation.
</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 thread source/MaaKWinControlUnit/Screencap/PipeWireScreencap.cpp
Comment thread source/MaaKWinControlUnit/Input/UInputController.cpp
Comment thread source/MaaKWinControlUnit/API/KWinControlUnitAPI.cpp Outdated
Comment on lines +29 to +38
// ─── Keycode mapping reference (mirrors UInputController::maa_to_linux_keycode) ───

static int reference_keycode(int c)
{
// ASCII letter mapping
if (c >= 'A' && c <= 'Z') {
return KEY_A + (c - 'A');
}
if (c >= 'a' && c <= 'z') {
return KEY_A + (c - 'a');
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.

issue (testing): 当前的映射测试只验证了一个重复实现的参考版本,而不是实际线上使用的 maa_to_linux_keycode 逻辑。

由于 Phase 1 中使用的 reference_keycode 是手动“镜像” UInputController::maa_to_linux_keycode 的实现,即便真实实现退化,只要两边以同样错误的方式被修改,测试仍然可能通过。为了增强这些测试,可以考虑:

  • 添加一个仅用于测试的小钩子或辅助函数(例如在测试头文件中使用 friend 或包装函数)来直接调用 maa_to_linux_keycode,并在测试中使用它;或者
  • maa_to_linux_keycode 抽取到一个共享模块,让库代码和测试都使用同一份实现。

这样可以确保 Phase 1 实际执行的是线上使用的映射逻辑,而不是一个重复的参考实现。

Original comment in English

issue (testing): The mapping tests currently only validate a duplicated reference implementation, not the actual production maa_to_linux_keycode logic.

Because Phase 1 uses a reference_keycode that manually mirrors UInputController::maa_to_linux_keycode, the tests can still pass even if the real implementation regresses (as long as both are changed in the same wrong way). To strengthen these tests, either:

  • Add a small test-only hook or helper (e.g., a friend or wrapper in a test header) that calls maa_to_linux_keycode directly, and use that in the tests; or
  • Extract maa_to_linux_keycode into a shared module used by both the library and tests so they execute the same code.

This ensures Phase 1 exercises the real mapping logic rather than a duplicated reference implementation.

@horror-proton
Copy link
Copy Markdown

这个方案组合感觉有点怪,pipewire 录屏截图是比较强大,但是输入用 uinput 又感觉有点暴力了? 如果操作的时候窗口还是必须在前台,是不是直接用偷懒一点的截图方法就行了, 比如 XDG Screenshot 之类的

@lava114514
Copy link
Copy Markdown
Author

这个方案组合感觉有点怪,pipewire 录屏截图是比较强大,但是输入用 uinput 又感觉有点暴力了? 如果操作的时候窗口还是必须在前台,是不是直接用偷懒一点的截图方法就行了, 比如 XDG Screenshot 之类的

XDG Screenshot好像要每次截图授权

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.

3 participants