From ab75da5fbfe18d61c8e6c0a22cf7dbcc78de6bbd Mon Sep 17 00:00:00 2001 From: MistEO Date: Wed, 18 Dec 2024 16:20:54 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E6=96=B0=E5=A2=9E=E5=8A=A8=E4=BD=9C=20?= =?UTF-8?q?`Command`=20(#463)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit @MaaXYZ/binding-developers 来看看怎么样=。= --- docs/en_us/3.1-PipelineProtocol.md | 57 +++++++- ...64\347\272\277\345\215\217\350\256\256.md" | 57 +++++++- .../Base/ProcessArgvGenerator.cpp | 15 +- .../Resource/DefaultPipelineMgr.cpp | 2 + .../MaaFramework/Resource/PipelineResMgr.cpp | 36 ++++- source/MaaFramework/Resource/PipelineResMgr.h | 1 + source/MaaFramework/Resource/PipelineTypes.h | 10 +- source/MaaFramework/Resource/ResourceMgr.h | 2 + .../MaaFramework/Task/Component/Actuator.cpp | 32 +++- source/MaaFramework/Task/Component/Actuator.h | 6 +- .../Task/Component/CommandAction.cpp | 138 ++++++++++++++++++ .../Task/Component/CommandAction.h | 53 +++++++ source/MaaFramework/Task/TaskBase.cpp | 2 +- 13 files changed, 389 insertions(+), 22 deletions(-) create mode 100644 source/MaaFramework/Task/Component/CommandAction.cpp create mode 100644 source/MaaFramework/Task/Component/CommandAction.h diff --git a/docs/en_us/3.1-PipelineProtocol.md b/docs/en_us/3.1-PipelineProtocol.md index b62053317..5ff3bbce6 100644 --- a/docs/en_us/3.1-PipelineProtocol.md +++ b/docs/en_us/3.1-PipelineProtocol.md @@ -70,7 +70,7 @@ This loop continues until the "next" of a task is empty, which signifies that th - `action`: *string* Action to execute. Optional, default is `DoNothing`. - Possible values: `DoNothing` | `Click` | `Swipe` | `MultiSwipe` | `Key` | `InputText` | `StartApp` | `StopApp` | `StopTask` | `Custom`. + Possible values: `DoNothing` | `Click` | `Swipe` | `MultiSwipe` | `Key` | `InputText` | `StartApp` | `StopApp` | `StopTask` | `Command` | `Custom`. See [Action Types](#action-types) for details. - `next`: *string* | *list* @@ -553,6 +553,61 @@ Additional properties for this action: Stops the current task chain (the individual task chain passed to MaaTaskerPostPipeline). +### `Command` + +Execute a command. + +This action attribute requires additional fields: + +- `exec`: *string* + The path of the program to be executed. Required. + +- `args`: *list* + The arguments to be executed. Optional. + supports runtime parameters replacement: + + - `{ENTRY}`: Entry name. + - `{NODE}`: Node name. + - `{IMAGE}`: The path to the file where the screenshot is saved. The file is deleted before the process exits. Please copy it by yourself if you want to save it permanently. + - `{BOX}`: Identify the hit target, the format is `[x, y, w, h]`. + - `{RESOURCE_DIR}`: The path of the resource folder loaded last time. + - `{LIBRARY_DIR}`: The path of the folder where the MaaFW library is located. + +- `detach`: *bool* + Detach the child process, that is, do not wait for the child process to complete, and directly continue with the subsequent tasks. Optional, default false. + +Example: + +```jsonc +{ + "TaskA": { + "action": "Command", + "exec": "Python", + "args": [ + "{RESOURCE_DIR}/my_script/test.py" + "Haha", + "{IMAGE}", + "{NODE}", + "{BOX}" + ] + }, + "TaskB": { + "action": "Command", + "exec": "{RESOURCE_DIR}/my_exec/my_exec.exe" + } +} +``` + +The actual command is: + +```bash +# TaskA +Python C:/MaaXXX/resource/my_script/test.py Haha C:/temp/123.png TaskA [0,0,0,0] + +# TaskB +C:/MaaXXX/resource/my_exec/my_exec.exe +``` + ### `Custom` Execute the action handle passed in through the `MaaResourceRegisterCustomAction` interface diff --git "a/docs/zh_cn/3.1-\344\273\273\345\212\241\346\265\201\346\260\264\347\272\277\345\215\217\350\256\256.md" "b/docs/zh_cn/3.1-\344\273\273\345\212\241\346\265\201\346\260\264\347\272\277\345\215\217\350\256\256.md" index 7615019ad..c368acc2e 100644 --- "a/docs/zh_cn/3.1-\344\273\273\345\212\241\346\265\201\346\260\264\347\272\277\345\215\217\350\256\256.md" +++ "b/docs/zh_cn/3.1-\344\273\273\345\212\241\346\265\201\346\260\264\347\272\277\345\215\217\350\256\256.md" @@ -75,7 +75,7 @@ Orange 的 next 中, - `action`: *string* 执行的动作。可选,默认 `DoNothing` 。 - 可选的值:`DoNothing` | `Click` | `Swipe` | `MultiSwipe` | `Key` | `InputText` | `StartApp` | `StopApp` | `StopTask` | `Custom` + 可选的值:`DoNothing` | `Click` | `Swipe` | `MultiSwipe` | `Key` | `InputText` | `StartApp` | `StopApp` | `StopTask` | `Command` | `Custom` 详见 [动作类型](#动作类型)。 - `next` : *string* | *list* @@ -563,6 +563,61 @@ Orange 的 next 中, 停止当前任务链(MaaTaskerPostPipeline 传入的单个任务链)。 +### `Command` + +执行命令。 + +该动作属性需额外部分字段: + +- `exec`: *string* + 执行的程序路径。必选。 + +- `args`: *list* + 执行的参数。可选。 + 支持部分运行期参数替换: + + - `{ENTRY}`: 任务入口名。 + - `{NODE}`: 当前任务名。 + - `{IMAGE}`: 截图保存到文件的路径。该文件在进程退出前删除,若要持久保存请自行复制。 + - `{BOX}`: 识别命中的目标,格式为 `[x, y, w, h]`。 + - `{RESOURCE_DIR}`: 最后一次加载的资源文件夹路径。 + - `{LIBRARY_DIR}`: MaaFW 库所在的文件夹路径。 + +- `detach`: *bool* + 分离子进程,即不等待子进程执行完成,直接继续之后后面的任务。可选,默认 false。 + +举例: + +```jsonc +{ + "TaskA": { + "action": "Command", + "exec": "Python", + "args": [ + "{RESOURCE_DIR}/my_script/test.py" + "Haha", + "{IMAGE}", + "{NODE}", + "{BOX}" + ] + }, + "TaskB": { + "action": "Command", + "exec": "{RESOURCE_DIR}/my_exec/my_exec.exe" + } +} +``` + +实际将会执行命令 + +```bash +# TaskA +Python C:/MaaXXX/resource/my_script/test.py Haha C:/temp/123.png TaskA [0,0,0,0] + +# TaskB +C:/MaaXXX/resource/my_exec/my_exec.exe +``` + ### `Custom` 执行通过 `MaaResourceRegisterCustomAction` 接口传入的动作句柄。 diff --git a/source/MaaAdbControlUnit/Base/ProcessArgvGenerator.cpp b/source/MaaAdbControlUnit/Base/ProcessArgvGenerator.cpp index f7ce16587..51c26d45a 100644 --- a/source/MaaAdbControlUnit/Base/ProcessArgvGenerator.cpp +++ b/source/MaaAdbControlUnit/Base/ProcessArgvGenerator.cpp @@ -34,22 +34,15 @@ std::optional ProcessArgvGenerator::gen(const string_replace_all_(s, replacement); } - std::filesystem::path abs_path; - if (auto raw_path = MAA_NS::path(res.front()); raw_path.is_absolute()) { - abs_path = raw_path; - } - else { - abs_path = boost::process::search_path(raw_path); - } - - if (!std::filesystem::exists(abs_path)) { - LogError << "exec path not exists" << VAR(abs_path); + std::filesystem::path exec = boost::process::search_path(path(res.front())); + if (!std::filesystem::exists(exec)) { + LogError << "exec path not exists" << VAR(res.front()) << VAR(exec); return std::nullopt; } auto args = std::vector(std::make_move_iterator(res.begin() + 1), std::make_move_iterator(res.end())); - return ProcessArgv { .exec = std::move(abs_path), .args = std::move(args) }; + return ProcessArgv { .exec = std::move(exec), .args = std::move(args) }; } MAA_CTRL_UNIT_NS_END diff --git a/source/MaaFramework/Resource/DefaultPipelineMgr.cpp b/source/MaaFramework/Resource/DefaultPipelineMgr.cpp index 11d600b28..3d6fe5507 100644 --- a/source/MaaFramework/Resource/DefaultPipelineMgr.cpp +++ b/source/MaaFramework/Resource/DefaultPipelineMgr.cpp @@ -125,6 +125,8 @@ bool DefaultPipelineMgr::parse_action(const json::value& input) { "startapp", Type::StartApp }, { "StopApp", Type::StopApp }, { "stopapp", Type::StopApp }, + { "Command", Type::Command }, + { "command", Type::Command }, { "Custom", Type::Custom }, { "custom", Type::Custom }, { "StopTask", Type::StopTask }, diff --git a/source/MaaFramework/Resource/PipelineResMgr.cpp b/source/MaaFramework/Resource/PipelineResMgr.cpp index 416f1e5ff..49d9cb27a 100644 --- a/source/MaaFramework/Resource/PipelineResMgr.cpp +++ b/source/MaaFramework/Resource/PipelineResMgr.cpp @@ -1138,6 +1138,8 @@ bool PipelineResMgr::parse_action( { "startapp", Type::StartApp }, { "StopApp", Type::StopApp }, { "stopapp", Type::StopApp }, + { "Command", Type::Command }, + { "command", Type::Command }, { "Custom", Type::Custom }, { "custom", Type::Custom }, { "StopTask", Type::StopTask }, @@ -1176,7 +1178,8 @@ bool PipelineResMgr::parse_action( return parse_multi_swipe( input, std::get(out_param), - same_type ? std::get(parent_param) : default_multi, default_single); + same_type ? std::get(parent_param) : default_multi, + default_single); } break; case Type::Key: { @@ -1203,6 +1206,15 @@ bool PipelineResMgr::parse_action( return parse_app_info(input, std::get(out_param), same_type ? std::get(parent_param) : default_param); } break; + case Type::Command: { + auto default_param = default_mgr.get_action_param(Type::Command); + out_param = default_param; + return parse_command_param( + input, + std::get(out_param), + same_type ? std::get(parent_param) : default_param); + } break; + case Type::Custom: { auto default_param = default_mgr.get_action_param(Type::Custom); out_param = default_param; @@ -1210,7 +1222,7 @@ bool PipelineResMgr::parse_action( input, std::get(out_param), same_type ? std::get(parent_param) : default_param); - } + } break; case Type::StopTask: out_param = {}; @@ -1326,6 +1338,26 @@ bool PipelineResMgr::parse_app_info(const json::value& input, Action::AppParam& return true; } +bool PipelineResMgr::parse_command_param(const json::value& input, Action::CommandParam& output, const Action::CommandParam& default_value) +{ + if (!get_and_check_value(input, "exec", output.exec, default_value.exec)) { + LogError << "failed to get_and_check_value exec" << VAR(input); + return false; + } + + if (!get_and_check_value_or_array(input, "args", output.args, default_value.args)) { + LogError << "failed to get_and_check_value args" << VAR(input); + return false; + } + + if (!get_and_check_value(input, "detach", output.detach, default_value.detach)) { + LogError << "failed to get_and_check_value detach" << VAR(input); + return false; + } + + return true; +} + bool PipelineResMgr::parse_custom_action_param( const json::value& input, Action::CustomParam& output, diff --git a/source/MaaFramework/Resource/PipelineResMgr.h b/source/MaaFramework/Resource/PipelineResMgr.h index 09164017a..9e0eba2ac 100644 --- a/source/MaaFramework/Resource/PipelineResMgr.h +++ b/source/MaaFramework/Resource/PipelineResMgr.h @@ -99,6 +99,7 @@ class PipelineResMgr : public NonCopyable static bool parse_press_key(const json::value& input, Action::KeyParam& output, const Action::KeyParam& default_value); static bool parse_input_text(const json::value& input, Action::TextParam& output, const Action::TextParam& default_value); static bool parse_app_info(const json::value& input, Action::AppParam& output, const Action::AppParam& default_value); + static bool parse_command_param(const json::value& input, Action::CommandParam& output, const Action::CommandParam& default_value); static bool parse_custom_action_param(const json::value& input, Action::CustomParam& output, const Action::CustomParam& default_value); static bool parse_wait_freezes_param( diff --git a/source/MaaFramework/Resource/PipelineTypes.h b/source/MaaFramework/Resource/PipelineTypes.h index 50ddcb394..307c61ba3 100644 --- a/source/MaaFramework/Resource/PipelineTypes.h +++ b/source/MaaFramework/Resource/PipelineTypes.h @@ -55,6 +55,7 @@ enum class Type Text, StartApp, StopApp, + Command, Custom, StopTask, }; @@ -95,6 +96,13 @@ struct AppParam std::string package; }; +struct CommandParam +{ + std::string exec; + std::vector args; + bool detach = false; +}; + struct CustomParam { std::string name; @@ -102,7 +110,7 @@ struct CustomParam Target target; }; -using Param = std::variant; +using Param = std::variant; } // namespace Action struct WaitFreezesParam diff --git a/source/MaaFramework/Resource/ResourceMgr.h b/source/MaaFramework/Resource/ResourceMgr.h index a0f78e5ac..6cde4e308 100644 --- a/source/MaaFramework/Resource/ResourceMgr.h +++ b/source/MaaFramework/Resource/ResourceMgr.h @@ -75,6 +75,8 @@ class ResourceMgr : public MaaResource const auto& default_pipeline() const { return default_pipeline_; } + const std::vector& paths() const { return paths_; } + CustomRecognitionSession custom_recognition(const std::string& name) const; CustomActionSession custom_action(const std::string& name) const; diff --git a/source/MaaFramework/Task/Component/Actuator.cpp b/source/MaaFramework/Task/Component/Actuator.cpp index dd8dc6406..ab510832c 100644 --- a/source/MaaFramework/Task/Component/Actuator.cpp +++ b/source/MaaFramework/Task/Component/Actuator.cpp @@ -1,5 +1,6 @@ #include "Actuator.h" +#include "CommandAction.h" #include "Controller/ControllerAgent.h" #include "CustomAction.h" #include "Utils/Logger.h" @@ -13,7 +14,7 @@ Actuator::Actuator(Tasker* tasker, Context& context) { } -bool Actuator::run(const cv::Rect& reco_hit, MaaRecoId reco_id, const PipelineData& pipeline_data) +bool Actuator::run(const cv::Rect& reco_hit, MaaRecoId reco_id, const PipelineData& pipeline_data, const std::string& entry) { using namespace MAA_RES_NS::Action; LogFunc << VAR(pipeline_data.name); @@ -52,6 +53,9 @@ bool Actuator::run(const cv::Rect& reco_hit, MaaRecoId reco_id, const PipelineDa case Type::StopApp: ret = stop_app(std::get(pipeline_data.action_param)); break; + case Type::Command: + ret = command(std::get(pipeline_data.action_param), reco_hit, pipeline_data.name, entry); + break; case Type::Custom: ret = custom_action(std::get(pipeline_data.action_param), reco_hit, reco_id, pipeline_data.name); break; @@ -217,6 +221,32 @@ bool Actuator::stop_app(const MAA_RES_NS::Action::AppParam& param) return controller()->stop_app(param.package); } +bool Actuator::command( + const MAA_RES_NS::Action::CommandParam& param, + const cv::Rect& box, + const std::string& name, + const std::string& entry) +{ + if (!controller()) { + LogError << "Controller is null"; + return false; + } + auto* resource = tasker_ ? tasker_->resource() : nullptr; + if (!resource) { + LogError << "Resource is null"; + return false; + } + + CommandAction::Runtime rt { + .resource_paths = resource->paths(), + .entry = entry, + .node = name, + .image = controller()->cached_image(), + .box = box, + }; + return CommandAction().run(param, rt); +} + bool Actuator::custom_action(const MAA_RES_NS::Action::CustomParam& param, const cv::Rect& box, MaaRecoId reco_id, const std::string& name) { if (!tasker_) { diff --git a/source/MaaFramework/Task/Component/Actuator.h b/source/MaaFramework/Task/Component/Actuator.h index 398e4bd65..e10cb3a46 100644 --- a/source/MaaFramework/Task/Component/Actuator.h +++ b/source/MaaFramework/Task/Component/Actuator.h @@ -1,8 +1,5 @@ #pragma once -#include -#include - #include #include "API/MaaTypes.h" @@ -23,7 +20,7 @@ class Actuator public: Actuator(Tasker* tasker, Context& context); - bool run(const cv::Rect& reco_hit, MaaRecoId reco_id, const PipelineData& pipeline_data); + bool run(const cv::Rect& reco_hit, MaaRecoId reco_id, const PipelineData& pipeline_data, const std::string& entry); private: bool click(const MAA_RES_NS::Action::ClickParam& param, const cv::Rect& box); @@ -34,6 +31,7 @@ class Actuator bool start_app(const MAA_RES_NS::Action::AppParam& param); bool stop_app(const MAA_RES_NS::Action::AppParam& param); + bool command(const MAA_RES_NS::Action::CommandParam& param, const cv::Rect& box, const std::string& name, const std::string& entry); bool custom_action(const MAA_RES_NS::Action::CustomParam& param, const cv::Rect& box, MaaRecoId reco_id, const std::string& name); void wait_freezes(const MAA_RES_NS::WaitFreezesParam& param, const cv::Rect& box); diff --git a/source/MaaFramework/Task/Component/CommandAction.cpp b/source/MaaFramework/Task/Component/CommandAction.cpp new file mode 100644 index 000000000..62a34af2f --- /dev/null +++ b/source/MaaFramework/Task/Component/CommandAction.cpp @@ -0,0 +1,138 @@ +#include "CommandAction.h" + +#include + +#include "Utils/Codec.h" +#include "Utils/IOStream/BoostIO.hpp" +#include "Utils/ImageIo.h" +#include "Utils/Logger.h" +#include "Utils/Platform.h" +#include "Utils/Runtime.h" +#include "Utils/StringMisc.hpp" + +MAA_TASK_NS_BEGIN + +TempFileHolder::~TempFileHolder() +{ + LogFunc; + + for (const auto& p : files_) { + if (!std::filesystem::exists(p)) { + continue; + } + LogTrace << "remove" << VAR(p); + std::filesystem::remove(p); + } +} + +void TempFileHolder::emplace(const std::filesystem::path& p) +{ + LogTrace << p; + files_.emplace_back(p); +} + +bool CommandAction::run(const MAA_RES_NS::Action::CommandParam& command, const Runtime& runtime) +{ + LogFunc << VAR(command.exec) << VAR(command.args) << VAR(command.detach); + + auto gen_runtime = [&](const std::string& src) -> std::string { + static std::unordered_map> kArgvReplacement = { + { "{ENTRY}", std::bind(&CommandAction::get_entry_name, this, std::placeholders::_1) }, + { "{NODE}", std::bind(&CommandAction::get_node_name, this, std::placeholders::_1) }, + { "{IMAGE}", std::bind(&CommandAction::get_image_path, this, std::placeholders::_1) }, + { "{BOX}", std::bind(&CommandAction::get_box, this, std::placeholders::_1) }, + { "{LIBRARY_DIR}", std::bind(&CommandAction::get_library_dir, this, std::placeholders::_1) }, + { "{RESOURCE_DIR}", std::bind(&CommandAction::get_resource_dir, this, std::placeholders::_1) }, + }; + + std::string dst = src; + for (const auto& [key, func] : kArgvReplacement) { + if (src.find(key) == std::string::npos) { + continue; + } + dst = string_replace_all(dst, key, func(runtime)); + } + return dst; + }; + + std::string conv_exec = gen_runtime(command.exec); + std::filesystem::path exec = boost::process::search_path(path(conv_exec)); + if (!std::filesystem::exists(exec)) { + LogError << "exec not exists" << VAR(command.exec) << VAR(conv_exec) << VAR(exec); + return false; + } + + std::vector args; + for (const std::string& arg : command.args) { + std::string dst = gen_runtime(arg); + +#ifdef _WIN32 + args.emplace_back(to_u16(dst)); +#else + args.emplace_back(dst); +#endif + } + + LogInfo << VAR(exec) << VAR(args); + boost::process::child child(exec, args); + + if (!child.joinable()) { + LogError << "child is not joinable"; + return false; + } + + if (command.detach) { + child.detach(); + } + else { + child.join(); + } + return true; +} + +std::string CommandAction::get_entry_name(const Runtime& runtime) +{ + return runtime.entry; +} + +std::string CommandAction::get_node_name(const Runtime& runtime) +{ + return runtime.node; +} + +std::string CommandAction::get_image_path(const Runtime& runtime) +{ + if (!image_path_.empty()) { + return image_path_; + } + + auto dst_path = std::filesystem::temp_directory_path() / (format_now_for_filename() + ".png"); + TempFileHolder::get_instance().emplace(dst_path); + imwrite(dst_path, runtime.image); + image_path_ = path_to_utf8_string(dst_path); + return image_path_; +} + +std::string CommandAction::get_box(const Runtime& runtime) +{ + return std::format("[{},{},{},{}]", runtime.box.x, runtime.box.y, runtime.box.width, runtime.box.height); +} + +std::string CommandAction::get_library_dir(const Runtime& runtime) +{ + std::ignore = runtime; + + return path_to_utf8_string(library_dir()); +} + +std::string CommandAction::get_resource_dir(const Runtime& runtime) +{ + if (runtime.resource_paths.empty()) { + LogWarn << "no resource"; + return {}; + } + + return path_to_utf8_string(runtime.resource_paths.back()); +} + +MAA_TASK_NS_END diff --git a/source/MaaFramework/Task/Component/CommandAction.h b/source/MaaFramework/Task/Component/CommandAction.h new file mode 100644 index 000000000..0884d228f --- /dev/null +++ b/source/MaaFramework/Task/Component/CommandAction.h @@ -0,0 +1,53 @@ +#pragma once + +#include +#include +#include + +#include "Conf/Conf.h" +#include "Resource/PipelineTypes.h" +#include "Utils/Platform.h" +#include "Utils/SingletonHolder.hpp" + +MAA_TASK_NS_BEGIN + +class TempFileHolder : public SingletonHolder +{ +public: + virtual ~TempFileHolder() override; + + void emplace(const std::filesystem::path& p); + +private: + std::vector files_; +}; + +class CommandAction +{ +public: + struct Runtime + { + std::vector resource_paths; + + std::string entry; + std::string node; + cv::Mat image; + cv::Rect box {}; + }; + +public: + bool run(const MAA_RES_NS::Action::CommandParam& command, const Runtime& runtime); + +private: + std::string get_entry_name(const Runtime& runtime); + std::string get_node_name(const Runtime& runtime); + std::string get_image_path(const Runtime& runtime); + std::string get_box(const Runtime& runtime); + std::string get_library_dir(const Runtime& runtime); + std::string get_resource_dir(const Runtime& runtime); + +private: + std::string image_path_; +}; + +MAA_TASK_NS_END diff --git a/source/MaaFramework/Task/TaskBase.cpp b/source/MaaFramework/Task/TaskBase.cpp index fcd43b741..2041b7938 100644 --- a/source/MaaFramework/Task/TaskBase.cpp +++ b/source/MaaFramework/Task/TaskBase.cpp @@ -171,7 +171,7 @@ NodeDetail TaskBase::run_action(const RecoResult& reco) } Actuator actuator(tasker_, *context_); - bool ret = actuator.run(*reco.box, reco.reco_id, pipeline_data); + bool ret = actuator.run(*reco.box, reco.reco_id, pipeline_data, entry_); NodeDetail result { .node_id = generate_node_id(),