Files
MaaAssistantArknights/src/MaaCore/Controller/MinitouchController.cpp
2023-10-18 15:46:29 +08:00

376 lines
12 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#include "MinitouchController.h"
#include <future>
#include "Common/AsstTypes.h"
#include "Config/GeneralConfig.h"
#include "Utils/Logger.hpp"
#include "Utils/StringMisc.hpp"
asst::MinitouchController::~MinitouchController()
{
LogTraceFunction;
release_minitouch();
}
void asst::MinitouchController::release_minitouch([[maybe_unused]] bool force)
{
m_minitoucher.reset();
m_minitouch_handler.reset();
}
bool asst::MinitouchController::call_and_hup_minitouch()
{
LogTraceFunction;
release_minitouch(true);
std::string cmd = m_use_maa_touch ? m_adb.call_maatouch : m_adb.call_minitouch;
Log.info(cmd);
std::string pipe_str;
m_minitouch_handler = m_platform_io->interactive_shell(cmd);
if (!m_minitouch_handler) {
Log.error("unable to start minitouch");
return false;
}
auto check_timeout = [&](const auto& start_time) -> bool {
using namespace std::chrono_literals;
return std::chrono::steady_clock::now() - start_time < 3s;
};
const auto start_time = std::chrono::steady_clock::now();
while (true) {
if (need_exit()) {
release_minitouch(true);
return false;
}
pipe_str += m_minitouch_handler->read(3);
if (!check_timeout(start_time)) {
Log.info("unable to find $ from pipe_str:", Logger::separator::newline, pipe_str);
release_minitouch(true);
return false;
}
if (pipe_str.find('$') != std::string::npos) {
break;
}
}
Log.info("pipe str", Logger::separator::newline, pipe_str);
convert_lf(pipe_str);
size_t s_pos = pipe_str.find('^');
size_t e_pos = pipe_str.find('\n', s_pos);
if (s_pos == std::string::npos || e_pos == std::string::npos) {
Log.error("Failed to find ^ in minitouch pipe");
release_minitouch(true);
return false;
}
std::string key_info = pipe_str.substr(s_pos + 1, e_pos - s_pos - 1);
Log.info("minitouch key props", key_info);
int size_1 = 0, size_2 = 0;
std::stringstream ss;
ss << key_info;
ss >> m_minitouch_props.max_contacts;
ss >> size_1;
ss >> size_2;
ss >> m_minitouch_props.max_pressure;
// 有些模拟器在竖屏分辨率时,这里的输出是反过来的
// 考虑到应该没人竖屏玩明日方舟,所以取较大值为 x较小值为 y
m_minitouch_props.max_x = std::max(size_1, size_2);
m_minitouch_props.max_y = std::min(size_1, size_2);
m_minitouch_props.x_scaling = static_cast<double>(m_minitouch_props.max_x) / m_width;
m_minitouch_props.y_scaling = static_cast<double>(m_minitouch_props.max_y) / m_height;
m_minitoucher = std::make_unique<Minitoucher>(
std::bind(&MinitouchController::input_to_minitouch, this, std::placeholders::_1), m_minitouch_props);
return true;
}
std::optional<std::string> asst::MinitouchController::reconnect(const std::string& cmd, int64_t timeout,
bool recv_by_socket)
{
LogTraceFunction;
auto ret = AdbController::reconnect(cmd, timeout, recv_by_socket);
if (!ret) {
return std::nullopt;
}
call_and_hup_minitouch();
return ret;
}
bool asst::MinitouchController::input_to_minitouch(const std::string& cmd)
{
if (!m_minitouch_handler->write(cmd)) {
Log.error("Failed to write to minitouch, try restart minitouch and re-write");
call_and_hup_minitouch();
return m_minitouch_handler->write(cmd);
}
return true;
}
void asst::MinitouchController::set_swipe_with_pause(bool enable) noexcept
{
m_swipe_with_pause_enabled = enable;
}
bool asst::MinitouchController::use_swipe_with_pause() const noexcept
{
return m_swipe_with_pause_enabled && (!m_adb.press_esc.empty() || m_use_maa_touch);
}
bool asst::MinitouchController::click(const Point& p)
{
if (p.x < 0 || p.x >= m_width || p.y < 0 || p.y >= m_height) {
Log.error("click point out of range");
}
Log.trace(m_use_maa_touch ? "maatouch" : "minitouch", "click:", p);
bool ret = m_minitoucher->down(p.x, p.y) && m_minitoucher->up();
return ret;
}
bool asst::MinitouchController::swipe(const Point& p1, const Point& p2, int duration, bool extra_swipe, double slope_in,
double slope_out, bool with_pause)
{
int x1 = p1.x, y1 = p1.y;
int x2 = p2.x, y2 = p2.y;
// 起点不能在屏幕外,但是终点可以
if (x1 < 0 || x1 >= m_width || y1 < 0 || y1 >= m_height) {
Log.warn("swipe point1 is out of range", x1, y1);
x1 = std::clamp(x1, 0, m_width - 1);
y1 = std::clamp(y1, 0, m_height - 1);
}
Log.trace(m_use_maa_touch ? "maatouch" : "minitouch", "swipe", p1, p2, duration, extra_swipe, slope_in, slope_out);
m_minitoucher->down(x1, y1);
constexpr int TimeInterval = Minitoucher::DefaultSwipeDelay;
auto cubic_spline = [](double slope_0, double slope_1, double t) {
const double a = slope_0;
const double b = -(2 * slope_0 + slope_1 - 3);
const double c = -(-slope_0 - slope_1 + 2);
return a * t + b * std::pow(t, 2) + c * std::pow(t, 3);
}; // TODO: move this to math.hpp
bool need_pause = with_pause && use_swipe_with_pause();
const auto& opt = Config.get_options();
std::future<void> pause_future;
auto minitouch_move = [&](int _x1, int _y1, int _x2, int _y2, int _duration) {
for (int cur_time = TimeInterval; cur_time < _duration; cur_time += TimeInterval) {
double progress = cubic_spline(slope_in, slope_out, static_cast<double>(cur_time) / duration);
int cur_x = static_cast<int>(std::lerp(_x1, _x2, progress));
int cur_y = static_cast<int>(std::lerp(_y1, _y2, progress));
if (need_pause && std::sqrt(std::pow(cur_x - _x1, 2) + std::pow(cur_y - _y1, 2)) >
opt.swipe_with_pause_required_distance) {
need_pause = false;
if (m_use_maa_touch) {
constexpr int EscKeyCode = 111;
m_minitoucher->key_down(EscKeyCode);
m_minitoucher->key_up(EscKeyCode, 0);
}
else {
pause_future = std::async(std::launch::async, [&]() { press_esc(); });
}
}
if (cur_x < 0 || cur_x > m_minitouch_props.max_x || cur_y < 0 || cur_y > m_minitouch_props.max_y) {
continue;
}
m_minitoucher->move(cur_x, cur_y);
}
if (_x2 >= 0 && _x2 <= m_minitouch_props.max_x && _y2 >= 0 && _y2 <= m_minitouch_props.max_y) {
m_minitoucher->move(_x2, _y2);
}
};
minitouch_move(x1, y1, x2, y2, duration ? duration : opt.minitouch_swipe_default_duration);
if (extra_swipe && opt.minitouch_extra_swipe_duration > 0) {
m_minitoucher->sleep(opt.minitouch_swipe_extra_end_delay); // 停留终点
minitouch_move(x2, y2, x2, y2 - opt.minitouch_extra_swipe_dist, opt.minitouch_extra_swipe_duration);
}
bool ret = m_minitoucher->up();
return ret;
}
bool asst::MinitouchController::inject_input_event(const InputEvent& event)
{
LogTraceFunction;
if (!m_minitoucher) {
Log.error("minitoucher is not initialized");
return false;
}
switch (event.type) {
case InputEvent::Type::KEY_DOWN:
return m_minitoucher->key_down(event.keycode, 0, false);
case InputEvent::Type::KEY_UP:
return m_minitoucher->key_up(event.keycode, 0, false);
case InputEvent::Type::TOUCH_DOWN:
return m_minitoucher->down(event.point.x, event.point.y, 0, false, event.pointerId);
case InputEvent::Type::TOUCH_UP:
return m_minitoucher->up(0, false, event.pointerId);
case InputEvent::Type::TOUCH_MOVE:
return m_minitoucher->move(event.point.x, event.point.y, 0, false, event.pointerId);
case InputEvent::Type::TOUCH_RESET:
return m_minitoucher->reset();
case InputEvent::Type::WAIT_MS:
return m_minitoucher->wait(event.milisec);
case InputEvent::Type::COMMIT:
return m_minitoucher->commit();
case InputEvent::Type::UNKNOWN:
default:
Log.error("unknown input event type");
return false;
}
}
asst::ControlFeat::Feat asst::MinitouchController::support_features() const noexcept
{
auto feat = ControlFeat::PRECISE_SWIPE;
if (use_swipe_with_pause()) {
feat |= ControlFeat::SWIPE_WITH_PAUSE;
}
return feat;
}
void asst::MinitouchController::clear_info() noexcept
{
AdbController::clear_info();
m_minitouch_available = false;
m_minitouch_props = decltype(m_minitouch_props)();
}
bool asst::MinitouchController::probe_minitouch(const AdbCfg& adb_cfg,
std::function<std::string(const std::string&)> cmd_replace)
{
LogTraceFunction;
std::string_view touch_program;
if (m_use_maa_touch) {
touch_program = "maatouch";
m_minitouch_props.orientation = 0;
}
else {
std::string abilist = call_command(cmd_replace(adb_cfg.abilist)).value_or(std::string());
for (const auto& abi : Config.get_options().minitouch_programs_order) {
if (abilist.find(abi) != std::string::npos) {
touch_program = abi;
break;
}
}
std::string orientation_str = call_command(cmd_replace(adb_cfg.orientation)).value_or("0");
if (!orientation_str.empty()) {
char first = orientation_str.front();
if (first == '0' || first == '1' || first == '2' || first == '3') {
m_minitouch_props.orientation = static_cast<int>(first - '0');
}
}
}
Log.info("touch_program", touch_program, "orientation", m_minitouch_props.orientation);
if (touch_program.empty()) return false;
auto minitouch_cmd_rep = [&](const std::string& cfg_cmd) -> std::string {
using namespace asst::utils::path_literals;
return utils::string_replace_all(
cmd_replace(cfg_cmd),
{
{ "[minitouchLocalPath]",
utils::path_to_utf8_string(ResDir.get() / "minitouch"_p / touch_program / "minitouch"_p) },
{ "[minitouchWorkingFile]", m_uuid },
});
};
if (!call_command(minitouch_cmd_rep(adb_cfg.push_minitouch))) return false;
if (!call_command(minitouch_cmd_rep(adb_cfg.chmod_minitouch))) return false;
m_adb.call_minitouch = minitouch_cmd_rep(adb_cfg.call_minitouch);
m_adb.call_maatouch = minitouch_cmd_rep(adb_cfg.call_maatouch);
if (!call_and_hup_minitouch()) return false;
return true;
}
bool asst::MinitouchController::connect(const std::string& adb_path, const std::string& address,
const std::string& config)
{
LogTraceFunction;
release_minitouch();
#ifdef ASST_DEBUG
if (config == "DEBUG") {
m_inited = true;
return true;
}
#endif
auto ret = AdbController::connect(adb_path, address, config);
if (!ret) {
return false;
}
auto get_info_json = [&]() -> json::value {
return json::object {
{ "uuid", m_uuid },
{ "details",
json::object {
{ "adb", adb_path },
{ "address", address },
{ "config", config },
} },
};
};
std::string display_id;
std::string nc_address = "10.0.2.2";
uint16_t nc_port = 0;
auto cmd_replace = [&](const std::string& cfg_cmd) -> std::string {
return utils::string_replace_all(cfg_cmd, {
{ "[Adb]", adb_path },
{ "[AdbSerial]", address },
{ "[DisplayId]", display_id },
{ "[NcPort]", std::to_string(nc_port) },
{ "[NcAddress]", nc_address },
});
};
auto adb_ret = Config.get_adb_cfg(config);
if (!adb_ret) {
return false;
}
const auto& adb_cfg = adb_ret.value();
m_minitouch_available = probe_minitouch(adb_cfg, cmd_replace);
if (!m_minitouch_available) {
json::value info = get_info_json() | json::object {
{ "what", "TouchModeNotAvailable" },
{ "why", "" },
};
callback(AsstMsg::ConnectionInfo, info);
return false;
}
return true;
}