Skip to content

feat: add screenshot subcommand - #630

Closed
wangl-cc wants to merge 5 commits into
mainfrom
codex/screenshot-command
Closed

wangl-cc wants to merge 5 commits into
mainfrom
codex/screenshot-command

Conversation

@wangl-cc

@wangl-cc wangl-cc commented Oct 24, 2026 •

Copy link
Copy Markdown
Member

动机 / Motivation

增加 maa screenshot [output],通过 MaaCore 获取设备画面,方便排查连接和画面问题。指定路径时保存 PNG;省略路径时将 PNG 原始字节写入 stdout,便于重定向或管道处理。

方案 / Solution

复用任务的初始化和连接流程,支持 --profile、--addr 和 --user-resource。调用现有 get_fresh_image() 等待截图执行后读取缓存,不增加回调等待设施或改变 Assistant 的回调行为。连接成功检查统一放在 with_connection;文件输出使用原子替换,stdout 输出二进制 PNG,日志默认走 stderr。截图不触发资源更新,也不启停游戏或执行任务队列。

影响与验证 / Impact and testing

  • 新增截图子命令和可选输出路径,已有文件会被覆盖;已更新五种语言的使用文档。
  • 截图刷新可能失败并返回旧缓存,成功退出不保证取得新帧;API 文档、README、命令帮助和用户文档均明确说明此限制。连接失败、空图和输出写入失败仍会报错。
  • 格式检查、Clippy、文档检查、maa-core 无默认 feature 编译检查,以及 cargo x test --no-core-tests --no-ignored-tests 通过。
  • macOS arm64 + MaaCore v6.20.1、本机 Android 15 arm64 AVD:文件与 stdout 输出均生成 1280×720 PNG,stdout 无日志混入;两种 Assistant 构造方式的注入失败测试确认了文档所述的旧缓存行为。未验证实体设备或其他平台。

AI 辅助 / AI assistance

使用 Codex 编写实现、测试和文档。

  • 我已审阅全部改动,能解释每一处修改 / I have reviewed all changes and can explain each of them

Sourcery 摘要

新增截图子命令,通过 MaaCore 获取设备图像,并将其输出为 PNG 文件或标准输出。

新功能:

  • 新增 maa screenshot [output] 命令,可捕获已连接设备的图像,并将 PNG 数据写入文件或标准输出。
  • 支持截图所需的现有连接选项,包括配置文件、地址和用户资源。

增强:

  • 重构共享配置、助手创建和连接处理逻辑,以便在任务执行和截图功能之间复用。
  • 明确 MaaCore 的截图行为,并说明成功的捕获请求可能会返回先前缓存的帧。

文档:

  • 在所有受支持的用户指南中,记录截图用法、输出模式、连接选项、限制和示例。
  • 更新 MaaCore API 文档,说明截图请求和缓存图像语义。

测试:

  • 增加命令行解析覆盖,以及截图处理中的输出保留覆盖。
Original summary in English

Summary by Sourcery

Add a screenshot subcommand that retrieves device images through MaaCore and outputs them as PNG files or standard output.

New Features:

  • Add a maa screenshot [output] command that captures a connected device image and writes PNG data to a file or stdout.
  • Support existing connection options, including profiles, addresses, and user resources, for screenshot capture.

Enhancements:

  • Refactor shared configuration, assistant creation, and connection handling for reuse across task execution and screenshots.
  • Clarify MaaCore screenshot behavior and document that successful capture requests may return a previously cached frame.

Documentation:

  • Document screenshot usage, output modes, connection options, limitations, and examples in all supported user guides.
  • Update MaaCore API documentation for screenshot request and cached-image semantics.

Tests:

  • Add command-line parsing coverage and output-preservation coverage for screenshot handling.

@codecov

codecov Bot commented Oct 24, 2026 •

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 2.91262% with 100 lines in your changes missing coverage. Please review.
✅ Project coverage is 76.33%. Comparing base (4695c10) to head (20bf504).
⚠️ Report is 1 commits behind head on main.

Files with missing lines Patch % Lines
crates/maa-cli/src/run/mod.rs 0.00% 80 Missing ⚠️
crates/maa-cli/src/run/screenshot.rs 13.63% 19 Missing ⚠️
crates/maa-cli/src/main.rs 0.00% 1 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main     #630      +/-   ##
==========================================
- Coverage   76.81%   76.33%   -0.49%     
==========================================
  Files          74       75       +1     
  Lines        7763     7816      +53     
  Branches     7763     7816      +53     
==========================================
+ Hits         5963     5966       +3     
- Misses       1472     1522      +50     
  Partials      328      328              

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@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.

嘿——我发现了 4 个问题

给 AI Agent 的提示
请处理这次代码审查中的评论:

## 单条评论

### 评论 1
<location path="crates/maa-cli/src/run/screenshot.rs" line_range="65" />
<code_context>
+    // The blocking API can return before its completion callback is delivered.
+    loop {
+        let result = results
+            .recv()
+            .context("Screenshot callback disconnected")??;
+        if result.async_call_id == id {
</code_context>
<issue_to_address>
**截图命令会永久挂起**

当 maaCore 接受截图请求但始终不发送完成回调时,`wait_for_screencap` 会无限期阻塞在 `results.recv()` 上,因此截图命令始终不会返回,调用方也收不到错误。

添加有时限的等待或取消机制,以便在缺少完成回调时返回错误。
</issue_to_address>

### 评论 2
<location path="crates/maa-cli/src/run/screenshot.rs" line_range="76" />
<code_context>
+
+fn save_image(output: &Path, image: Option<&[u8]>) -> Result<()> {
+    let image = image.context("No screenshot returned by MaaCore")?;
+    maa_atomic_fs::write(output, image)
+        .with_context(|| format!("Failed to save screenshot to {}", output.display()))
+}
</code_context>
<issue_to_address>
**较早完成的截图会覆盖较新的截图**

当两个截图命令写入同一个输出路径,且较早发起的截图比之后发起的截图更晚完成时,`save_image` 会以原子方式替换目标文件,却没有协调并发调用。因此,较早发起的截图可能最后完成并覆盖较新的截图;此时文件中保存的是过时的屏幕内容。

按目标路径串行化写入并保持调用顺序,或者要求调用方在并发截图时使用不同的输出路径。
</issue_to_address>

### 评论 3
<location path="crates/maa-cli/src/run/screenshot.rs" line_range="92" />
<code_context>
+        let (callback, results) = screenshot_callback();
+        callback.on_message(
+            MessageKind::AsyncCallInfo,
+            Some(r#"{"what":"Connect","async_call_id":1,"details":{"ret":false}}"#),
+        );
+        std::thread::scope(|scope| {
</code_context>
<issue_to_address>
**无关的成功事件会结束截图等待**

当截图回调到达之前,先成功收到一个无关的 `AsyncCallInfo` 时,`crates/maa-cli/src/run/screenshot.rs:92` 中的 `waits_for_matching_result` 会将不匹配事件的 `ret` 设为 `false`。因此,如果等待逻辑接受任何成功回调而不检查其 ID,测试仍会通过,并在截图完成前返回。

在测试样例中使用一个成功但不匹配的事件,并断言只有在匹配的回调到达后等待逻辑才会完成。
</issue_to_address>

### 评论 4
<location path="crates/maa-cli/src/run/screenshot.rs" line_range="101" />
<code_context>
+                Some(r#"{"what":"Screencap","async_call_id":2,"details":{"ret":true}}"#),
+            );
+            // The callback and receiver must finish without panicking.
+            completion.join().expect("screenshot receiver panicked")
+        })
+    }
</code_context>
<issue_to_address>
**测试中的错误处理缺乏合理说明**

新增测试调用了 `expect` 和 `unwrap_err`,但没有附上简短的理由说明,因此新增代码违反了仓库的错误处理规则。

请将这些调用替换为显式的结果处理,或分别为每个调用添加简短的理由说明。

`crates/maa-cli/src/run/screenshot.rs:159` 处也有此问题。
</issue_to_address>

Sourcery 对开源项目免费——如果您喜欢我们的审查,请考虑分享 ✨
Original comment in English

Hey - I've found 4 issues

Prompt for AI Agents
Please address the comments from this code review:

## Individual Comments

### Comment 1
<location path="crates/maa-cli/src/run/screenshot.rs" line_range="65" />
<code_context>
+    // The blocking API can return before its completion callback is delivered.
+    loop {
+        let result = results
+            .recv()
+            .context("Screenshot callback disconnected")??;
+        if result.async_call_id == id {
</code_context>
<issue_to_address>
**Screenshot commands hang forever**

When maaCore accepts a screenshot request but never delivers its completion callback, `wait_for_screencap` blocks on `results.recv()` indefinitely, so the screenshot command never returns and the caller receives no error.

Add a bounded wait or cancellation path so a missing completion callback returns an error.
</issue_to_address>

### Comment 2
<location path="crates/maa-cli/src/run/screenshot.rs" line_range="76" />
<code_context>
+
+fn save_image(output: &Path, image: Option<&[u8]>) -> Result<()> {
+    let image = image.context("No screenshot returned by MaaCore")?;
+    maa_atomic_fs::write(output, image)
+        .with_context(|| format!("Failed to save screenshot to {}", output.display()))
+}
</code_context>
<issue_to_address>
**Older capture overwrites newer screenshot**

When two screenshot commands write to the same output path and the older capture finishes after the newer one, `save_image` atomically replaces the destination without coordinating concurrent invocations, so the older capture can finish last and replace a newer screenshot; the file then contains stale screen content.

Serialize writes per destination and preserve invocation order, or require callers to use distinct output paths for concurrent captures.
</issue_to_address>

### Comment 3
<location path="crates/maa-cli/src/run/screenshot.rs" line_range="92" />
<code_context>
+        let (callback, results) = screenshot_callback();
+        callback.on_message(
+            MessageKind::AsyncCallInfo,
+            Some(r#"{"what":"Connect","async_call_id":1,"details":{"ret":false}}"#),
+        );
+        std::thread::scope(|scope| {
</code_context>
<issue_to_address>
**Unrelated success completes screenshot wait**

When an unrelated `AsyncCallInfo` arrives successfully before the screencap callback, `waits_for_matching_result` at `crates/maa-cli/src/run/screenshot.rs:92` makes its mismatched event `ret:false`, so a waiter that accepts any successful callback without checking its ID still passes and returns before capture completes.

Use a successful nonmatching event in the fixture and assert the waiter completes only after the matching callback arrives.
</issue_to_address>

### Comment 4
<location path="crates/maa-cli/src/run/screenshot.rs" line_range="101" />
<code_context>
+                Some(r#"{"what":"Screencap","async_call_id":2,"details":{"ret":true}}"#),
+            );
+            // The callback and receiver must finish without panicking.
+            completion.join().expect("screenshot receiver panicked")
+        })
+    }
</code_context>
<issue_to_address>
**Unjustified test error handling**

The new tests call `expect` and `unwrap_err` without brief justification comments, so the added code violates the repository’s error-handling rule.

Replace these calls with explicit result handling, or add a brief justification comment for each.

Also at `crates/maa-cli/src/run/screenshot.rs:159`.
</issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨

Comment thread crates/maa-cli/src/run/screenshot.rs Outdated
Comment thread crates/maa-cli/src/run/screenshot.rs Outdated

fn save_image(output: &Path, image: Option<&[u8]>) -> Result<()> {
let image = image.context("No screenshot returned by MaaCore")?;
maa_atomic_fs::write(output, image)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🟡 中 · 较早完成的截图会覆盖较新的截图

当两个截图命令写入同一个输出路径,且较早发起的截图比之后发起的截图更晚完成时,save_image 会以原子方式替换目标文件,却没有协调并发调用。因此,较早发起的截图可能最后完成并覆盖较新的截图;此时文件中保存的是过时的屏幕内容。

按目标路径串行化写入并保持调用顺序,或者要求调用方在并发截图时使用不同的输出路径。

给 AI Agent 的提示
在 `crates/maa-cli/src/run/screenshot.rs` 第 76 行:

**较早完成的截图会覆盖较新的截图**

当两个截图命令写入同一个输出路径,且较早发起的截图比之后发起的截图更晚完成时,`save_image` 会以原子方式替换目标文件,却没有协调并发调用。因此,较早发起的截图可能最后完成并覆盖较新的截图;此时文件中保存的是过时的屏幕内容。

按目标路径串行化写入并保持调用顺序,或者要求调用方在并发截图时使用不同的输出路径。
Original comment in English

🟡 Medium · Older capture overwrites newer screenshot

When two screenshot commands write to the same output path and the older capture finishes after the newer one, save_image atomically replaces the destination without coordinating concurrent invocations, so the older capture can finish last and replace a newer screenshot; the file then contains stale screen content.

Serialize writes per destination and preserve invocation order, or require callers to use distinct output paths for concurrent captures.

Prompt for AI agents
In `crates/maa-cli/src/run/screenshot.rs` at line 76:

**Older capture overwrites newer screenshot**

When two screenshot commands write to the same output path and the older capture finishes after the newer one, `save_image` atomically replaces the destination without coordinating concurrent invocations, so the older capture can finish last and replace a newer screenshot; the file then contains stale screen content.

Serialize writes per destination and preserve invocation order, or require callers to use distinct output paths for concurrent captures.

Comment thread crates/maa-cli/src/run/screenshot.rs Outdated
Comment thread crates/maa-cli/src/run/screenshot.rs Outdated
@wangl-cc
wangl-cc marked this pull request as draft October 24, 2026 15:17
Restore existing core capture behavior and document that failed refreshes can return cached frames.
@wangl-cc wangl-cc closed this Oct 24, 2026
@wangl-cc
wangl-cc deleted the codex/screenshot-command branch October 24, 2026 15:52
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.

1 participant