diff --git a/.claude/skills/changelog/SKILL.md b/.claude/skills/changelog/SKILL.md index 4330a81d72..9d50ef6ce3 100644 --- a/.claude/skills/changelog/SKILL.md +++ b/.claude/skills/changelog/SKILL.md @@ -63,37 +63,38 @@ description: 根据提交记录、PR、diff、现有 CHANGELOG 与历史 tag 内 - patch 版详细区块只能写“自上一发布版本之后新增的变化”,不能把更早正式版已经出现过的条目整段复制到当前 patch 版本下面。 - 例如生成 v6.10.4 时,v6.10.4 区块只能写 v6.10.3 之后的新变化;v6.10.0、v6.10.1、v6.10.2、v6.10.3 的既有内容应保留在历史区块,而不是重新抄进 v6.10.4。 - 若非 patch 版本不同,则直接根据现有内容组织该版本及其历史区块。 -### 5. patch 版本的 Highlights 复用规则 -- patch 版本(例如 v6.10.4 相对于 v6.10.3)如果没有用户可感知的重要新功能或重大变化,必须直接复用其正式版父版本(v6.10.3)的 Highlights 内容,不得自行重写或另起一套。 -- patch 版本复用 Highlights 时,只改顶部版本号标题(例如 `## v6.10.3` → `## v6.10.4`),Highlights 正文原样保留。 -- 当 patch 版本确实包含用户可感知的重要新变化时(例如新增了重大功能、改变了核心交互),可以为 Highlights 追加新段落,但必须保留原有 Highlights 内容,新段落追加在末尾。 +### 5. patch / 测试版的 Highlights 复用规则 -### 6. patch 版本编辑的完整结构 +- patch 版本(例如 v6.10.4 相对于 v6.10.3)和测试版(例如 v6.11.0-beta.2 相对于 v6.11.0-beta.1)如果没有用户可感知的重要新功能或重大变化,必须直接复用其父版本的 Highlights 内容,不得自行重写或另起一套。 +- 复用 Highlights 时,只改顶部版本号标题和发版日期(例如 `## v6.11.0-beta.1 (2026-05-27)` → `## v6.11.0-beta.2 (2026-05-29)`),Highlights 正文原样保留。 +- 当 patch 版本或测试版确实包含用户可感知的重要新变化时(例如新增了重大功能、改变了核心交互),可以为 Highlights 追加新段落,但必须保留原有 Highlights 内容,新段落追加在末尾。 -- 输出文件的结构必须严格遵循以下层次,不得把 patch 版本的详细内容插入到父版本的 Highlights 与详细内容之间: - 1. 顶部:`## vX.Y.Z`(patch 版本标题) +### 6. patch / 测试版编辑的完整结构 + +- 输出文件的结构必须严格遵循以下层次,不得把 patch 版本或测试版的详细内容插入到父版本的 Highlights 与详细内容之间: + 1. 顶部:`## vX.Y.Z (YYYY-MM-DD)`(patch / 测试版标题,含发版日期) 2. `### Highlights`(复用父版本内容,或在有必要时追加新段落) 3. 英文 Highlights 折叠块 4. `----` 5. `以下是详细内容:` - 6. `
vX.Y.Z`(当前版本详细内容,默认展开) - 7. `
vX.Y.Z-1`(上一 patch 版本,默认收起) + 6. `
vX.Y.Z (YYYY-MM-DD)`(当前版本详细内容,默认展开) + 7. `
vX.Y.Z-1 (YYYY-MM-DD)`(上一 patch 版本,默认收起) 8. 更早版本各自独立折叠块... - 9. `
vX.Y.0`(正式版,默认收起) -- 每个版本的详细内容各自放入独立的 `
` 折叠块,当前目标版本使用 `
` 默认展开,其余默认收起。 + 9. `
vX.Y.0 (YYYY-MM-DD)`(正式版,默认收起) +- 每个版本的详细内容各自放入独立的 `
` 折叠块,`` 内格式为 `vX.Y.Z (YYYY-MM-DD)`(版本号 + 发版日期),当前目标版本使用 `
` 默认展开,其余默认收起。 - 折叠块内只保留详细内容(改进、修复等),不重复 Highlights,不写 `## vX.Y.Z` 子标题(`` 已提供版本标识)。 -### 5. Highlights 必须中英双语且先中后英 +### 7. Highlights 必须中英双语且先中后英 -- 输出顶部必须包含当前目标版本,例如 ## vX.Y.Z。 +- 输出顶部必须包含当前目标版本和发版日期,例如 `## vX.Y.Z (2026-05-29)`。 - 必须包含 ### Highlights。 - 中文 Highlights 直接展示,不折叠。 - 英文 Highlights 放入折叠块:`
English` ... `
`。 - 中文与英文都应按主题分段,标题简洁明确,正文面向最终用户,不是 commit 列表翻译。 - Highlights 只总结本次版本中最值得强调的变化,不要把所有条目机械搬进去。 -### 6. 必须过滤的噪音项 +### 8. 必须过滤的噪音项 - 删除或忽略纯 bot 自动生成的 changelog、update、release 条目。 - 删除显式的 Release 发布记录,例如 Release vX.Y.Z。 @@ -120,9 +121,9 @@ description: 根据提交记录、PR、diff、现有 CHANGELOG 与历史 tag 内 - 把同一功能拆成多条重复表述,例如同一个生息演算功能拆成多个相近新增或改进条目。 - 保留玩梗、口语化、半成品标题,例如不会现在还有人选沙中遗火吧、特意删的 PNS 怎么又给加回来了。 - 机械沿用 commit type 导致分类错误,例如把用户能感知的修复放进其他,把兼容性提升放进新增。 -- patch 版本没有用户可感知的重要新变化,却自行重写了独立的 Highlights,而非复用父版本内容。 +- patch 版本或测试版没有用户可感知的重要新变化,却自行重写了独立的 Highlights,而非复用父版本内容。 - 把 patch 版本的详细内容插入到父版本的 Highlights 与详细内容之间,破坏了文件结构。 -- patch 版本的历史区块中重复保留了 Highlights 和"以下是详细内容:"引导语,这些应只在顶部出现一次。 +- patch / 测试版的历史区块中重复保留了 Highlights 和"以下是详细内容:"引导语,这些应只在顶部出现一次。 ## Output Requirements @@ -174,7 +175,7 @@ English summary paragraph. 以下是详细内容:
-vX.Y.Z +vX.Y.Z (YYYY-MM-DD) ### 改进 | Improved @@ -191,7 +192,7 @@ English summary paragraph.
-vX.Y.1 +vX.Y.1 (YYYY-MM-DD) ### 改进 | Improved @@ -204,7 +205,7 @@ English summary paragraph.
-vX.Y.0 +vX.Y.0 (YYYY-MM-DD) ### 新增 | New @@ -230,7 +231,7 @@ English summary paragraph. - 是否已经按模块正确分类、排序,并保持中文在前、英文在后? - 是否已经输出完整 Markdown,而不是说明文字或代码块? - 如果是 patch 版本且没有用户可感知的重要新变化,是否复用了父版本的 Highlights 而非自行重写? -- patch 版本的详细内容是否紧跟在"以下是详细内容:"之后,而非插入到父版本的 Highlights 下方? +- patch / 测试版的详细内容是否紧跟在"以下是详细内容:"之后,而非插入到父版本的 Highlights 下方? - 历史版本区块中是否只保留详细内容,没有重复 Highlights 和引导语? - 英文 Highlights 是否放入 `
` 折叠块(中文不折叠)? - 每个版本的详细内容是否各自放入独立的 `
` 折叠块? diff --git a/CHANGELOG.md b/CHANGELOG.md index bc8e6bb72c..d9be113d41 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,4 @@ -## v6.11.0-beta.1 +## v6.11.0-beta.2 ### Highlights @@ -36,7 +36,18 @@ The settings page now features a search bar with keyword highlighting and sectio 以下是详细内容:
-v6.11.0-beta.1 +v6.11.0-beta.2 (2026-05-29) + +### 修复 | Fix + +* 修复 adb-lite 在连接阶段未正确绑定当前设备,减少切换设备或并发执行命令时的连接异常 (#16853) @wangl-cc +* 修复快捷切换在开启基建退出提醒时无法退出基建的问题 @ABA2396 +* 修复修复快捷切换在遇到 LoadingText 时无法跳出循环的问题 @ABA2396 + +
+ +
+v6.11.0-beta.1 (2026-05-29) ### 新增 | New diff --git a/resource/tasks/UiTheme/QuickSwitch.json b/resource/tasks/UiTheme/QuickSwitch.json index c042b69646..bcc4f342c5 100644 --- a/resource/tasks/UiTheme/QuickSwitch.json +++ b/resource/tasks/UiTheme/QuickSwitch.json @@ -6,6 +6,13 @@ "maskRange": [150, 255], "postDelay": 500 }, + "QuickSwitch@Confirm": { + "Doc": "小房子切换后如果弹出返回确认框,由 QuickSwitch 自己处理并回到调用方", + "template": "PopupConfirm.png", + "action": "ClickSelf", + "roi": [630, 400, 650, 160], + "next": ["QuickSwitch@Confirm", "LoadingText", "LoadingIcon", "#back"] + }, "QuickSwitch@ToHome": { "Doc": "优先使用小房子快捷回到首页,成功后通过 #back 回到调用方", "algorithm": "JustReturn", @@ -16,13 +23,13 @@ "Doc": "小房子下拉框中的首页图标", "template": "QuickSwitch/Home.png", "roi": [11, 206, 164, 149], - "next": ["#self", "QuickSwitch@ToHome@Entry@LoadingText", "QuickSwitch@ToHome@Entry@LoadingIcon", "#back"] + "next": ["QuickSwitch@ToHome@Entry", "QuickSwitch@Confirm", "LoadingText", "LoadingIcon", "#back"] }, "QuickSwitch@ToHome@Open": { "baseTask": "QuickSwitch@Base", "template": "QuickSwitch/Menu.png", "roi": [197, 0, 144, 134], - "next": ["QuickSwitch@ToHome@Entry", "#self"] + "next": ["QuickSwitch@ToHome@Entry", "QuickSwitch@ToHome@Open"] }, "QuickSwitch@ToTerminal": { "Doc": "优先使用小房子快捷切到终端,成功后通过 #back 回到调用方", @@ -34,18 +41,13 @@ "Doc": "小房子下拉框中的终端图标", "template": "QuickSwitch/Terminal.png", "roi": [450, 0, 159, 148], - "next": [ - "#self", - "QuickSwitch@ToTerminal@Entry@LoadingText", - "QuickSwitch@ToTerminal@Entry@LoadingIcon", - "#back" - ] + "next": ["QuickSwitch@ToTerminal@Entry", "QuickSwitch@Confirm", "LoadingText", "LoadingIcon", "#back"] }, "QuickSwitch@ToTerminal@Open": { "baseTask": "QuickSwitch@Base", "template": "QuickSwitch/Menu.png", "roi": [197, 0, 144, 134], - "next": ["QuickSwitch@ToTerminal@Entry", "#self"] + "next": ["QuickSwitch@ToTerminal@Entry", "QuickSwitch@ToTerminal@Open"] }, "QuickSwitch@ToOperBox": { "Doc": "优先使用小房子快捷切到干员页面,成功后通过 #back 回到调用方", @@ -57,13 +59,13 @@ "Doc": "小房子下拉框中的干员图标", "template": "QuickSwitch/OperBox.png", "roi": [226, 204, 163, 154], - "next": ["#self", "QuickSwitch@ToOperBox@Entry@LoadingText", "QuickSwitch@ToOperBox@Entry@LoadingIcon", "#back"] + "next": ["QuickSwitch@ToOperBox@Entry", "QuickSwitch@Confirm", "LoadingText", "LoadingIcon", "#back"] }, "QuickSwitch@ToOperBox@Open": { "baseTask": "QuickSwitch@Base", "template": "QuickSwitch/Menu.png", "roi": [197, 0, 144, 134], - "next": ["QuickSwitch@ToOperBox@Entry", "#self"] + "next": ["QuickSwitch@ToOperBox@Entry", "QuickSwitch@ToOperBox@Open"] }, "QuickSwitch@ToInfrast": { "Doc": "优先使用小房子快捷切到基建页面,成功后通过 #back 回到调用方", @@ -75,13 +77,13 @@ "Doc": "小房子下拉框中的基建图标", "template": "QuickSwitch/Infrast.png", "roi": [610, 0, 150, 147], - "next": ["#self", "QuickSwitch@ToInfrast@Entry@LoadingText", "QuickSwitch@ToInfrast@Entry@LoadingIcon", "#back"] + "next": ["QuickSwitch@ToInfrast@Entry", "QuickSwitch@Confirm", "LoadingText", "LoadingIcon", "#back"] }, "QuickSwitch@ToInfrast@Open": { "baseTask": "QuickSwitch@Base", "template": "QuickSwitch/Menu.png", "roi": [197, 0, 144, 134], - "next": ["QuickSwitch@ToInfrast@Entry", "#self"] + "next": ["QuickSwitch@ToInfrast@Entry", "QuickSwitch@ToInfrast@Open"] }, "QuickSwitch@ToRecruit": { "Doc": "优先使用小房子快捷切到公开招募页面,成功后通过 #back 回到调用方", @@ -93,13 +95,13 @@ "Doc": "小房子下拉框中的公开招募图标", "template": "QuickSwitch/Recruit.png", "roi": [879, 207, 152, 152], - "next": ["#self", "QuickSwitch@ToRecruit@Entry@LoadingText", "QuickSwitch@ToRecruit@Entry@LoadingIcon", "#back"] + "next": ["QuickSwitch@ToRecruit@Entry", "QuickSwitch@Confirm", "LoadingText", "LoadingIcon", "#back"] }, "QuickSwitch@ToRecruit@Open": { "baseTask": "QuickSwitch@Base", "template": "QuickSwitch/Menu.png", "roi": [197, 0, 144, 134], - "next": ["QuickSwitch@ToRecruit@Entry", "#self"] + "next": ["QuickSwitch@ToRecruit@Entry", "QuickSwitch@ToRecruit@Open"] }, "QuickSwitch@ToGacha": { "Doc": "优先使用小房子快捷切到干员寻访页面,成功后通过 #back 回到调用方", @@ -111,13 +113,13 @@ "Doc": "小房子下拉框中的干员寻访图标", "template": "QuickSwitch/Gacha.png", "roi": [1003, 208, 155, 149], - "next": ["#self", "QuickSwitch@ToGacha@Entry@LoadingText", "QuickSwitch@ToGacha@Entry@LoadingIcon", "#back"] + "next": ["QuickSwitch@ToGacha@Entry", "QuickSwitch@Confirm", "LoadingText", "LoadingIcon", "#back"] }, "QuickSwitch@ToGacha@Open": { "baseTask": "QuickSwitch@Base", "template": "QuickSwitch/Menu.png", "roi": [197, 0, 144, 134], - "next": ["QuickSwitch@ToGacha@Entry", "#self"] + "next": ["QuickSwitch@ToGacha@Entry", "QuickSwitch@ToGacha@Open"] }, "QuickSwitch@ToMall": { "Doc": "优先使用小房子快捷切到采购中心页面,成功后通过 #back 回到调用方", @@ -129,12 +131,12 @@ "Doc": "小房子下拉框中的采购中心图标", "template": "QuickSwitch/Mall.png", "roi": [1117, 207, 163, 150], - "next": ["#self", "QuickSwitch@ToMall@Entry@LoadingText", "QuickSwitch@ToMall@Entry@LoadingIcon", "#back"] + "next": ["QuickSwitch@ToMall@Entry", "QuickSwitch@Confirm", "LoadingText", "LoadingIcon", "#back"] }, "QuickSwitch@ToMall@Open": { "baseTask": "QuickSwitch@Base", "template": "QuickSwitch/Menu.png", "roi": [197, 0, 144, 134], - "next": ["QuickSwitch@ToMall@Entry", "#self"] + "next": ["QuickSwitch@ToMall@Entry", "QuickSwitch@ToMall@Open"] } } diff --git a/resource/tasks/tasks.json b/resource/tasks/tasks.json index aad315ef71..a395479b3a 100644 --- a/resource/tasks/tasks.json +++ b/resource/tasks/tasks.json @@ -2192,7 +2192,13 @@ }, "MallBegin": { "algorithm": "JustReturn", - "next": ["Mall", "Mall#next", "MallBegin@QuickSwitch@ToMall#next", "Mall@ReturnButtons#next", "Mall@CloseAnnos#next"] + "next": [ + "Mall", + "Mall#next", + "MallBegin@QuickSwitch@ToMall#next", + "Mall@ReturnButtons#next", + "Mall@CloseAnnos#next" + ] }, "Mall": { "action": "DoNothing", diff --git a/src/MaaCore/Controller/AdbController.cpp b/src/MaaCore/Controller/AdbController.cpp index 6e3745ee14..dba7e5b259 100644 --- a/src/MaaCore/Controller/AdbController.cpp +++ b/src/MaaCore/Controller/AdbController.cpp @@ -915,6 +915,7 @@ bool asst::AdbController::connect(const std::string& adb_path, const std::string // 设置配置 connect、release 命令,即使这里不连接,后续也会需要用到 m_adb.connect = m_conn_ctx.replace_cmd(adb_cfg.connect); m_adb.release = m_conn_ctx.replace_cmd(adb_cfg.release); + m_platform_io->set_adb_serial(address); if (need_connect) { // 如果不包含 `:` 且需要连接,connect 命令也不会成功 if (address.find(':') == std::string::npos) { diff --git a/src/MaaCore/Controller/Platform/AdbLiteIO.cpp b/src/MaaCore/Controller/Platform/AdbLiteIO.cpp index fe7e89ca07..08eaa6959d 100644 --- a/src/MaaCore/Controller/Platform/AdbLiteIO.cpp +++ b/src/MaaCore/Controller/Platform/AdbLiteIO.cpp @@ -62,8 +62,15 @@ std::optional asst::AdbLiteIO::call_command( // adb connect if (boost::regex_match(cmd, match, connect_regex)) { + const std::string serial = match[1].str(); + auto lock = lock_adb_client(serial); + if (!lock) { + ret = std::nullopt; + goto ret_exit; + } + try { - pipe_data = get_adb_client(match[1].str())->connect(); + pipe_data = m_adb_client->connect(); ret = 0; goto ret_exit; } @@ -77,11 +84,18 @@ std::optional asst::AdbLiteIO::call_command( // adb shell if (boost::regex_match(cmd, match, shell_regex)) { + const std::string serial = match[1].str(); std::string command = match[2].str(); remove_quotes(command); + auto lock = lock_adb_client(serial); + if (!lock) { + ret = std::nullopt; + goto ret_exit; + } + try { - pipe_data = get_adb_client(match[1].str())->shell(command); + pipe_data = m_adb_client->shell(command); ret = 0; goto ret_exit; } @@ -94,11 +108,18 @@ std::optional asst::AdbLiteIO::call_command( // adb exec-out if (boost::regex_match(cmd, match, exec_regex)) { + const std::string serial = match[1].str(); std::string command = match[2].str(); remove_quotes(command); + auto lock = lock_adb_client(serial); + if (!lock) { + ret = std::nullopt; + goto ret_exit; + } + try { - pipe_data = get_adb_client(match[1].str())->exec(command); + pipe_data = m_adb_client->exec(command); ret = 0; goto ret_exit; } @@ -111,8 +132,15 @@ std::optional asst::AdbLiteIO::call_command( // adb push if (boost::regex_match(cmd, match, push_regex)) { + const std::string serial = match[1].str(); + auto lock = lock_adb_client(serial); + if (!lock) { + ret = std::nullopt; + goto ret_exit; + } + try { - get_adb_client(match[1].str())->push(match[2].str(), match[3].str(), 0644); + m_adb_client->push(match[2].str(), match[3].str(), 0644); ret = 0; goto ret_exit; } @@ -135,17 +163,35 @@ ret_exit: return ret; } -std::shared_ptr asst::AdbLiteIO::get_adb_client(std::string_view serial) +std::optional> asst::AdbLiteIO::lock_adb_client(std::string_view serial) +{ + std::unique_lock lock(m_adb_client_mutex); + if (!m_adb_client || m_adb_serial != serial) { + Log.error("adb client not initialized for serial:", std::string(serial), "current:", m_adb_serial); + return std::nullopt; + } + + return std::move(lock); +} + +void asst::AdbLiteIO::set_adb_serial(std::string_view serial) { std::lock_guard lock(m_adb_client_mutex); const std::string serial_str(serial); - if (!m_adb_client || m_adb_serial != serial_str) { + if (m_adb_client && m_adb_serial == serial_str) { + return; + } + + try { auto adb_client = adb::client::create(serial_str); m_adb_serial = serial_str; m_adb_client = std::move(adb_client); } - - return m_adb_client; + catch (const std::exception& e) { + Log.error("failed to create adb-lite client for serial:", serial_str, e.what()); + m_adb_serial.clear(); + m_adb_client.reset(); + } } std::shared_ptr asst::AdbLiteIO::interactive_shell(const std::string& cmd) @@ -154,11 +200,17 @@ std::shared_ptr asst::AdbLiteIO::interactive_shell(const std::s boost::smatch match; if (boost::regex_match(cmd, match, shell_regex)) { + const std::string serial = match[1].str(); std::string command = match[2].str(); remove_quotes(command); + auto lock = lock_adb_client(serial); + if (!lock) { + return nullptr; + } + try { - return std::make_shared(get_adb_client(match[1].str())->interactive_shell(command)); + return std::make_shared(m_adb_client->interactive_shell(command)); } catch (const std::exception& e) { Log.error("adb shell failed:", e.what()); @@ -173,18 +225,19 @@ std::shared_ptr asst::AdbLiteIO::interactive_shell(const std::s void asst::AdbLiteIO::release_adb(const std::string& adb_release, int64_t timeout) { - // 只在读取 m_adb_client 时持锁,避免 call_command 内部调用 get_adb_client 时死锁 + bool has_adb_client = false; { std::lock_guard lock(m_adb_client_mutex); - if (!m_adb_client) { - return; - } + has_adb_client = static_cast(m_adb_client); } - std::string pipe_data; - std::string sock_data; - auto start_time = std::chrono::steady_clock::now(); - call_command(adb_release, false, pipe_data, sock_data, timeout, start_time); + if (has_adb_client) { + std::string pipe_data; + std::string sock_data; + auto start_time = std::chrono::steady_clock::now(); + + call_command(adb_release, false, pipe_data, sock_data, timeout, start_time); + } } bool asst::AdbLiteIO::remove_quotes(std::string& data) diff --git a/src/MaaCore/Controller/Platform/AdbLiteIO.h b/src/MaaCore/Controller/Platform/AdbLiteIO.h index 1c6d2c3979..7da811d87a 100644 --- a/src/MaaCore/Controller/Platform/AdbLiteIO.h +++ b/src/MaaCore/Controller/Platform/AdbLiteIO.h @@ -13,6 +13,7 @@ #include "Utils/Logger.hpp" #include +#include #include #include @@ -42,15 +43,17 @@ public: virtual std::shared_ptr interactive_shell(const std::string& cmd) override; + virtual void set_adb_serial(std::string_view serial) override; + virtual void release_adb(const std::string& adb_release, int64_t timeout = 20000) override; private: - std::shared_ptr get_adb_client(std::string_view serial); + std::optional> lock_adb_client(std::string_view serial); static bool remove_quotes(std::string& data); // 保护 m_adb_client / m_adb_serial,防止 call_command 与 interactive_shell 并发访问 - mutable std::mutex m_adb_client_mutex; + std::mutex m_adb_client_mutex; std::shared_ptr m_adb_client = nullptr; std::string m_adb_serial; }; diff --git a/src/MaaCore/Controller/Platform/PlatformIO.h b/src/MaaCore/Controller/Platform/PlatformIO.h index f0f67c4476..112bfd18b5 100644 --- a/src/MaaCore/Controller/Platform/PlatformIO.h +++ b/src/MaaCore/Controller/Platform/PlatformIO.h @@ -4,6 +4,7 @@ #include #include #include +#include namespace asst { @@ -33,6 +34,8 @@ public: virtual std::shared_ptr interactive_shell(const std::string& cmd) = 0; + virtual void set_adb_serial(std::string_view) {} + virtual void release_adb(const std::string& adb_release, int64_t timeout = 20000) = 0; bool m_support_socket = false;