diff --git a/include/MaaFramework/MaaDef.h b/include/MaaFramework/MaaDef.h index 79855ee2d..b5e2d6564 100644 --- a/include/MaaFramework/MaaDef.h +++ b/include/MaaFramework/MaaDef.h @@ -80,6 +80,10 @@ enum MaaCtrlOptionEnum // For StopApp // value: string, eg: "com.hypergryph.arknights"; val_size: string length MaaCtrlOption_DefaultAppPackage = 4, + + // Dump all screenshots and actions + // value: bool, eg: true; val_size: sizeof(bool) + MaaCtrlOption_Recording = 5, }; typedef MaaOption MaaInstOption; diff --git a/source/MaaFramework/Controller/ControllerMgr.cpp b/source/MaaFramework/Controller/ControllerMgr.cpp index 03e137fe4..f03c62db3 100644 --- a/source/MaaFramework/Controller/ControllerMgr.cpp +++ b/source/MaaFramework/Controller/ControllerMgr.cpp @@ -1,7 +1,9 @@ #include "ControllerMgr.h" #include "MaaFramework/MaaMsg.h" +#include "Option/GlobalOptionMgr.h" #include "Resource/ResourceMgr.h" +#include "Utils/ImageIo.h" #include "Utils/NoWarningCV.hpp" #include @@ -43,6 +45,9 @@ bool ControllerMgr::set_option(MaaCtrlOption key, MaaOptionValue value, MaaOptio case MaaCtrlOption_DefaultAppPackage: return set_default_app_package(value, val_size); + case MaaCtrlOption_Recording: + return set_recording(value, val_size); + default: LogError << "Unknown key" << VAR(key) << VAR(value); return false; @@ -223,6 +228,227 @@ bool ControllerMgr::stop_app(const std::string& package) return status(id) == MaaStatus_Success; } +bool ControllerMgr::handle_connect() +{ + std::chrono::steady_clock::time_point start_time; + if (recording_) { + start_time = std::chrono::steady_clock::now(); + } + + connected_ = _connect(); + + if (recording_) { + json::value info { + { "type", "connect" }, + { "success", connected_ }, + }; + append_recording(std::move(info), start_time, connected_); + } + + return connected_; +} + +bool ControllerMgr::handle_click(const ClickParam& param) +{ + std::chrono::steady_clock::time_point start_time; + if (recording_) { + start_time = std::chrono::steady_clock::now(); + } + + bool ret = _click(param); + + if (recording_) { + json::value info = { + { "type", "click" }, + { "x", param.x }, + { "y", param.y }, + }; + append_recording(std::move(info), start_time, ret); + } + + return ret; +} + +bool ControllerMgr::handle_swipe(const SwipeParam& param) +{ + std::chrono::steady_clock::time_point start_time; + if (recording_) { + start_time = std::chrono::steady_clock::now(); + } + + bool ret = _swipe(param); + + if (recording_) { + json::value info = { + { "type", "swipe" }, { "x1", param.x1 }, { "y1", param.y1 }, + { "x2", param.x2 }, { "y2", param.y2 }, { "duration", param.duration }, + }; + append_recording(std::move(info), start_time, ret); + } + + return ret; +} + +bool ControllerMgr::handle_touch_down(const TouchParam& param) +{ + std::chrono::steady_clock::time_point start_time; + if (recording_) { + start_time = std::chrono::steady_clock::now(); + } + + bool ret = _touch_down(param); + + if (recording_) { + json::value info = { + { "type", "touch_down" }, { "contact", param.contact }, { "x", param.x }, + { "y", param.y }, { "pressure", param.pressure }, + }; + append_recording(std::move(info), start_time, ret); + } + + return ret; +} + +bool ControllerMgr::handle_touch_move(const TouchParam& param) +{ + std::chrono::steady_clock::time_point start_time; + if (recording_) { + start_time = std::chrono::steady_clock::now(); + } + + bool ret = _touch_move(param); + + if (recording_) { + json::value info = { + { "type", "touch_move" }, { "contact", param.contact }, { "x", param.x }, + { "y", param.y }, { "pressure", param.pressure }, + }; + append_recording(std::move(info), start_time, ret); + } + + return ret; +} + +bool ControllerMgr::handle_touch_up(const TouchParam& param) +{ + std::chrono::steady_clock::time_point start_time; + if (recording_) { + start_time = std::chrono::steady_clock::now(); + } + + bool ret = _touch_up(param); + + if (recording_) { + json::value info = { + { "type", "touch_up" }, + { "contact", param.contact }, + }; + append_recording(std::move(info), start_time, ret); + } + + return ret; +} + +bool ControllerMgr::handle_press_key(const PressKeyParam& param) +{ + std::chrono::steady_clock::time_point start_time; + if (recording_) { + start_time = std::chrono::steady_clock::now(); + } + + bool ret = _press_key(param); + + if (recording_) { + json::value info = { + { "type", "press_key" }, + { "keycode", param.keycode }, + }; + append_recording(std::move(info), start_time, ret); + } + + return ret; +} + +bool ControllerMgr::handle_screencap() +{ + std::chrono::steady_clock::time_point start_time; + if (recording_) { + start_time = std::chrono::steady_clock::now(); + } + + bool ret = postproc_screenshot(_screencap()); + + if (recording_) { + auto image_relative_path = path("Screenshot") / path(now_filestem() + ".png"); + auto image_path = recording_path_.parent_path() / image_relative_path; + MAA_NS::imwrite(image_path, image_); + + json::value info = { + { "type", "screencap" }, + { "path", path_to_utf8_string(image_relative_path) }, + }; + append_recording(std::move(info), start_time, ret); + } + + return ret; +} + +bool ControllerMgr::handle_start_app(const AppParam& param) +{ + std::chrono::steady_clock::time_point start_time; + if (recording_) { + start_time = std::chrono::steady_clock::now(); + } + + bool ret = _start_app(param); + clear_target_image_size(); + + if (recording_) { + json::value info = { + { "type", "start_app" }, + { "package", param.package }, + }; + append_recording(std::move(info), start_time, ret); + } + return ret; +} + +bool ControllerMgr::handle_stop_app(const AppParam& param) +{ + std::chrono::steady_clock::time_point start_time; + if (recording_) { + start_time = std::chrono::steady_clock::now(); + } + + bool ret = _stop_app(param); + clear_target_image_size(); + + if (recording_) { + json::value info = { + { "type", "stop_app" }, + { "package", param.package }, + }; + append_recording(std::move(info), start_time, ret); + } + return ret; +} + +void ControllerMgr::append_recording(json::value info, const std::chrono::steady_clock::time_point& start_time, + bool success) +{ + if (!recording_) { + return; + } + + info["time"] = start_time.time_since_epoch().count(); + info["cost"] = duration_since(start_time).count(); + info["success"] = success; + + std::ofstream ofs(recording_path_, std::ios::app); + ofs << info.to_string() << "\n"; + ofs.close(); +} + cv::Point ControllerMgr::rand_point(const cv::Rect& r) { int x = 0, y = 0; @@ -268,42 +494,39 @@ bool ControllerMgr::run_action(typename AsyncRunner::Id id, Action actio switch (action.type) { case Action::Type::connect: - ret = _connect(); - connected_ = ret; + ret = handle_connect(); break; case Action::Type::click: - ret = _click(std::get(action.param)); + ret = handle_click(std::get(action.param)); break; case Action::Type::swipe: - ret = _swipe(std::get(action.param)); + ret = handle_swipe(std::get(action.param)); break; case Action::Type::touch_down: - ret = _touch_down(std::get(action.param)); + ret = handle_touch_down(std::get(action.param)); break; case Action::Type::touch_move: - ret = _touch_move(std::get(action.param)); + ret = handle_touch_move(std::get(action.param)); break; case Action::Type::touch_up: - ret = _touch_up(std::get(action.param)); + ret = handle_touch_up(std::get(action.param)); break; case Action::Type::press_key: - ret = _press_key(std::get(action.param)); + ret = handle_press_key(std::get(action.param)); break; case Action::Type::screencap: - ret = postproc_screenshot(_screencap()); + ret = handle_screencap(); break; case Action::Type::start_app: - ret = _start_app(std::get(action.param)); - clear_target_image_size(); + ret = handle_start_app(std::get(action.param)); break; case Action::Type::stop_app: - ret = _stop_app(std::get(action.param)); - clear_target_image_size(); + ret = handle_stop_app(std::get(action.param)); break; default: @@ -452,6 +675,21 @@ bool ControllerMgr::set_default_app_package(MaaOptionValue value, MaaOptionValue return true; } +bool ControllerMgr::set_recording(MaaOptionValue value, MaaOptionValueSize val_size) +{ + if (val_size != sizeof(recording_)) { + LogError << "invalid value size: " << val_size; + return false; + } + recording_ = *reinterpret_cast(value); + + auto recording_dir = GlobalOptionMgr::get_instance().logging_path() / "Recording"; + std::filesystem::create_directories(recording_dir); + recording_path_ = recording_dir / MAA_FMT::format("MaaRecording_{}.txt", now_filestem()); + + return true; +} + std::ostream& operator<<(std::ostream& os, const Action& action) { switch (action.type) { diff --git a/source/MaaFramework/Controller/ControllerMgr.h b/source/MaaFramework/Controller/ControllerMgr.h index e9baec7d9..230f6e568 100644 --- a/source/MaaFramework/Controller/ControllerMgr.h +++ b/source/MaaFramework/Controller/ControllerMgr.h @@ -127,6 +127,20 @@ class ControllerMgr : public MaaControllerAPI protected: MessageNotifier notifier; +private: + bool handle_connect(); + bool handle_click(const ClickParam& param); + bool handle_swipe(const SwipeParam& param); + bool handle_touch_down(const TouchParam& param); + bool handle_touch_move(const TouchParam& param); + bool handle_touch_up(const TouchParam& param); + bool handle_press_key(const PressKeyParam& param); + bool handle_screencap(); + bool handle_start_app(const AppParam& param); + bool handle_stop_app(const AppParam& param); + + void append_recording(json::value info, const std::chrono::steady_clock::time_point& start_time, bool success); + private: static cv::Point rand_point(const cv::Rect& r); @@ -141,6 +155,7 @@ class ControllerMgr : public MaaControllerAPI bool set_image_target_short_side(MaaOptionValue value, MaaOptionValueSize val_size); bool set_default_app_package_entry(MaaOptionValue value, MaaOptionValueSize val_size); bool set_default_app_package(MaaOptionValue value, MaaOptionValueSize val_size); + bool set_recording(MaaOptionValue value, MaaOptionValueSize val_size); private: // InstanceInternalAPI* inst_ = nullptr; @@ -160,6 +175,9 @@ class ControllerMgr : public MaaControllerAPI std::string default_app_package_entry_; std::string default_app_package_; + bool recording_ = false; + std::filesystem::path recording_path_; + std::set::Id> post_ids_; std::mutex post_ids_mutex_; std::unique_ptr> action_runner_ = nullptr;