mirror of
https://github.com/MaaAssistantArknights/MaaAssistantArknights.git
synced 2026-07-15 17:30:27 +08:00
## 概述 - 在 `AdbController::connect` 确定目标 address/serial 后,通过 `PlatformIO::set_adb_serial` 显式声明当前 adb 设备。 - NativeIO 默认忽略该调用;AdbLiteIO 使用该 serial 初始化或切换内部 adb-lite client。 - adb-lite 的 `shell`、`exec-out`、`push` 和交互式 shell 只使用已声明的 client,不再从命令路径里隐式初始化 client。 - AdbLiteIO 内部集中校验 client/serial,并用 mutex 覆盖 `m_adb_client` / `m_adb_serial` 以及对 adb-lite client 的调用,避免 `call_command` 与 `interactive_shell` 并发访问同一状态。 ## 背景 #16850 已修复 `adb connect` 被跳过后 adb-lite client 未初始化的问题,并补充了内部锁保护。这个 PR 是后续调整:把 client 初始化入口从命令执行路径挪到连接生命周期中,让状态声明更明确。 ## 修复方式 连接阶段已经知道当前 address/serial,所以在设置 connect/release 命令后调用: ```cpp m_platform_io->set_adb_serial(address); ``` 对 NativeIO 来说这是 no-op;对 AdbLiteIO 来说,这是初始化或切换 `adb::client` 的入口。 后续 adb-lite 命令路径通过 `lock_adb_client(serial)` 统一校验命令中的 `-s <serial>` 与当前 client 对应的 serial 一致,并在同一把锁仍然持有时调用 adb-lite client。这样避免在 `interactive_shell` 中隐式写入 client/serial 状态,也避免把 `m_adb_client` 暴露给无锁调用。 `set_adb_serial` 也改为先创建临时 `adb::client`,创建成功后再提交 `m_adb_serial` / `m_adb_client` 状态;如果创建失败则记录错误并清空 adb-lite client 状态,避免 serial 已切换但 client 仍指向旧对象的不一致状态。 ## 验证 - `git diff --check --cached` - `cmake --build /private/tmp/maa-build-8599-core --parallel 8` - `cmake --install /private/tmp/maa-build-8599-core --config RelWithDebInfo` - 本机 `maa` CLI + Android 35 arm64 AVD: - `maa run connect-only -p adb-lite-test --no-summary -vvv` - 日志显示 `Loading MaaCore from: /private/tmp/maa-test-data-8599/lib` - 日志显示 `Setting adb lite enabled to true` - 日志到达 `Connected` - 日志到达 `FastestWayToScreencap Encode` - 未出现 `adb client not initialized` - 未出现 `failed to create adb-lite client` - 未出现 `fallback to NativeIO`
1212 lines
41 KiB
C++
1212 lines
41 KiB
C++
#include "AdbController.h"
|
||
|
||
#include "Assistant.h"
|
||
#include "Controller.h"
|
||
#include "MaaUtils/NoWarningCV.hpp"
|
||
#include <cstdint>
|
||
#include <numeric>
|
||
|
||
#ifdef _MSC_VER
|
||
#pragma warning(push)
|
||
#pragma warning(disable: 4068)
|
||
#endif
|
||
#include <zlib/decompress.hpp>
|
||
#ifdef _MSC_VER
|
||
#pragma warning(pop)
|
||
#endif
|
||
|
||
#include "Common/AsstTypes.h"
|
||
#include "Config/GeneralConfig.h"
|
||
#include "Utils/Logger.hpp"
|
||
#include "Utils/Platform.hpp"
|
||
#include "Utils/StringMisc.hpp"
|
||
|
||
#include <boost/regex.hpp>
|
||
|
||
asst::AdbController::AdbController(const AsstCallback& callback, Assistant* inst, PlatformType type) :
|
||
InstHelper(inst),
|
||
m_callback(callback)
|
||
{
|
||
LogTraceFunction;
|
||
|
||
m_platform_io = PlatformFactory::create_platform(inst, type);
|
||
|
||
if (!m_platform_io) {
|
||
Log.error("platform not supported");
|
||
throw std::runtime_error("platform not supported");
|
||
}
|
||
|
||
m_support_socket = m_platform_io->m_support_socket;
|
||
|
||
if (!m_support_socket) {
|
||
Log.error("socket not supported");
|
||
}
|
||
}
|
||
|
||
asst::AdbController::~AdbController()
|
||
{
|
||
LogTraceFunction;
|
||
|
||
m_inited = false;
|
||
release();
|
||
}
|
||
|
||
std::optional<std::string> asst::AdbController::reconnect(const std::string& cmd, int64_t timeout, bool recv_by_socket)
|
||
{
|
||
LogTraceFunction;
|
||
|
||
json::value reconnect_info = json::object {
|
||
{ "uuid", m_uuid },
|
||
{ "what", "Reconnecting" },
|
||
{ "why", "" },
|
||
{ "details",
|
||
json::object {
|
||
{ "reconnect", m_adb.connect },
|
||
{ "cmd", cmd },
|
||
} },
|
||
};
|
||
static constexpr int ReconnectTimes = 5;
|
||
for (int i = 0; i < ReconnectTimes; ++i) {
|
||
if (need_exit()) {
|
||
break;
|
||
}
|
||
reconnect_info["details"]["times"] = i;
|
||
callback(AsstMsg::ConnectionInfo, reconnect_info);
|
||
|
||
sleep(10 * 1000);
|
||
if (need_exit()) {
|
||
break;
|
||
}
|
||
auto reconnect_ret = call_command(m_adb.connect, 60LL * 1000, false /* 禁止重连避免无限递归 */);
|
||
if (need_exit()) {
|
||
break;
|
||
}
|
||
bool is_reconnect_success = false;
|
||
if (reconnect_ret) {
|
||
auto& reconnect_str = reconnect_ret.value();
|
||
is_reconnect_success = reconnect_str.find("error") == std::string::npos;
|
||
}
|
||
if (is_reconnect_success) {
|
||
auto recall_ret = call_command(cmd, timeout, false /* 禁止重连避免无限递归 */, recv_by_socket);
|
||
if (recall_ret) {
|
||
// 重连并成功执行了
|
||
reconnect_info["what"] = "Reconnected";
|
||
callback(AsstMsg::ConnectionInfo, reconnect_info);
|
||
return recall_ret;
|
||
}
|
||
}
|
||
}
|
||
json::value info = json::object {
|
||
{ "uuid", m_uuid },
|
||
{ "what", "Disconnect" },
|
||
{ "why", "Reconnect failed" },
|
||
{ "details",
|
||
json::object {
|
||
{ "cmd", m_adb.connect },
|
||
} },
|
||
};
|
||
m_inited = false; // 重连失败,释放
|
||
release();
|
||
callback(AsstMsg::ConnectionInfo, info);
|
||
|
||
return std::nullopt;
|
||
}
|
||
|
||
std::optional<std::string> asst::AdbController::call_command(
|
||
const std::string& cmd,
|
||
int64_t timeout,
|
||
bool allow_reconnect,
|
||
bool recv_by_socket)
|
||
{
|
||
using namespace std::chrono_literals;
|
||
using namespace std::chrono;
|
||
// LogTraceScope(std::string(__FUNCTION__) + " | `" + cmd + "`");
|
||
|
||
std::string pipe_data;
|
||
std::string sock_data;
|
||
asst::platform::single_page_buffer<char> pipe_buffer;
|
||
asst::platform::single_page_buffer<char> sock_buffer;
|
||
|
||
auto start_time = steady_clock::now();
|
||
std::unique_lock<std::mutex> callcmd_lock(m_callcmd_mutex);
|
||
|
||
std::optional<int> exit_res;
|
||
|
||
exit_res = m_platform_io->call_command(cmd, recv_by_socket, pipe_data, sock_data, timeout, start_time);
|
||
|
||
if (!exit_res) {
|
||
Log.warn("Call `", cmd, "` failed");
|
||
return std::nullopt;
|
||
}
|
||
const int exit_ret = exit_res.value();
|
||
|
||
callcmd_lock.unlock();
|
||
|
||
m_last_command_duration = duration_cast<milliseconds>(steady_clock::now() - start_time).count();
|
||
Log.info(
|
||
"Call `",
|
||
cmd,
|
||
"` ret",
|
||
exit_ret,
|
||
", cost",
|
||
m_last_command_duration,
|
||
"ms , stdout size:",
|
||
pipe_data.size(),
|
||
", socket size:",
|
||
sock_data.size());
|
||
if (!pipe_data.empty() && pipe_data.size() < 4096) {
|
||
m_pipe_data_size = pipe_data.size();
|
||
Log.trace("stdout output:", Logger::separator::newline, pipe_data);
|
||
}
|
||
if (recv_by_socket && !sock_data.empty() && sock_data.size() < 4096) {
|
||
Log.trace("socket output:", Logger::separator::newline, sock_data);
|
||
}
|
||
// 直接 return,避免走到下面的 else if 里的 m_inited = false) 关闭 adb 连接,
|
||
// 导致停止后再开始任务还需要重连一次
|
||
if (need_exit()) {
|
||
return std::nullopt;
|
||
}
|
||
|
||
if (!exit_ret) {
|
||
return recv_by_socket ? sock_data : pipe_data;
|
||
}
|
||
else if (inited() && allow_reconnect) {
|
||
// 之前可以运行,突然运行不了了,这种情况多半是 adb 炸了。所以重新连接一下
|
||
return reconnect(cmd, timeout, recv_by_socket);
|
||
}
|
||
|
||
return std::nullopt;
|
||
}
|
||
|
||
size_t asst::AdbController::get_pipe_data_size() const noexcept
|
||
{
|
||
return m_pipe_data_size;
|
||
}
|
||
|
||
size_t asst::AdbController::get_version() const noexcept
|
||
{
|
||
return m_version;
|
||
}
|
||
|
||
void asst::AdbController::callback(AsstMsg msg, const json::value& details)
|
||
{
|
||
if (m_callback) {
|
||
m_callback(msg, details, m_inst);
|
||
}
|
||
}
|
||
|
||
int asst::AdbController::get_mumu_index(const std::string& address)
|
||
{
|
||
LogTrace << VAR(address);
|
||
|
||
auto pos = address.find(":");
|
||
if (pos == std::string::npos) {
|
||
Log.error("address is invalid", address);
|
||
return 0;
|
||
}
|
||
|
||
std::string port_str = address.substr(pos + 1);
|
||
if (port_str.empty() || !std::ranges::all_of(port_str, [](const char& c) -> bool { return std::isdigit(c); })) {
|
||
Log.error("port is invalid", port_str);
|
||
return 0;
|
||
}
|
||
int port = std::stoi(port_str);
|
||
int mumu_index = 0;
|
||
if (port >= 16384) {
|
||
mumu_index = (port - 16384) / 32;
|
||
}
|
||
else if (port == 7555) {
|
||
mumu_index = 0;
|
||
LogWarn << "Port 7555 is deprecated for MuMu6, please use 16384 or above.";
|
||
}
|
||
else if (port >= 5555) {
|
||
mumu_index = (port - 5555) / 2;
|
||
}
|
||
LogInfo << VAR(port_str) << VAR(port) << VAR(mumu_index);
|
||
return mumu_index;
|
||
}
|
||
|
||
void asst::AdbController::init_mumu_extras(const AdbCfg& adb_cfg, const std::string& address)
|
||
{
|
||
#if !ASST_WITH_EMULATOR_EXTRAS
|
||
std::ignore = adb_cfg;
|
||
std::ignore = address;
|
||
Log.error("MaaCore is not compiled with ASST_WITH_EMULATOR_EXTRAS");
|
||
#else
|
||
if (adb_cfg.extras.empty()) {
|
||
LogWarn << "adb_cfg.extras is empty";
|
||
return;
|
||
}
|
||
|
||
set_mumu_package(adb_cfg.extras.get("client_type", ""));
|
||
|
||
std::filesystem::path mumu_path = utils::path(adb_cfg.extras.get("path", ""));
|
||
|
||
if (adb_cfg.extras.contains("index")) { // MuMu index is provided directly
|
||
m_mumu_extras.init(mumu_path, adb_cfg.extras.get("index", 0));
|
||
}
|
||
else {
|
||
m_mumu_extras.init(mumu_path, get_mumu_index(address));
|
||
}
|
||
#endif
|
||
}
|
||
|
||
void asst::AdbController::set_mumu_package(const std::string& client_type)
|
||
{
|
||
#if !ASST_WITH_EMULATOR_EXTRAS
|
||
std::ignore = client_type;
|
||
Log.error("MaaCore is not compiled with ASST_WITH_EMULATOR_EXTRAS");
|
||
#else
|
||
std::string package_name = Config.get_package_name(client_type).value_or("");
|
||
m_mumu_extras.set_package_name(package_name);
|
||
#endif
|
||
}
|
||
|
||
int asst::AdbController::get_ld_index(const std::string& address)
|
||
{
|
||
LogTrace << VAR(address);
|
||
|
||
// emulator-5554
|
||
if (address.starts_with("emulator-")) {
|
||
constexpr int base_emulator_port = 5554;
|
||
std::string port_str = address.substr(9); // after "emulator-"
|
||
if (port_str.empty() || !std::ranges::all_of(port_str, [](char c) { return std::isdigit(c); })) {
|
||
Log.error("emulator port is invalid", port_str);
|
||
return 0;
|
||
}
|
||
int port = std::stoi(port_str);
|
||
int index = (port - base_emulator_port) / 2;
|
||
LogInfo << VAR(port_str) << VAR(port) << VAR(index);
|
||
return index;
|
||
}
|
||
|
||
// 127.0.0.1:5555
|
||
auto pos = address.find(':');
|
||
if (pos != std::string::npos && address.substr(0, pos) == "127.0.0.1") {
|
||
constexpr int base_adb_port = 5555;
|
||
std::string port_str = address.substr(pos + 1);
|
||
if (port_str.empty() || !std::ranges::all_of(port_str, [](char c) { return std::isdigit(c); })) {
|
||
Log.error("adb port is invalid", port_str);
|
||
return 0;
|
||
}
|
||
int port = std::stoi(port_str);
|
||
int index = (port - base_adb_port) / 2;
|
||
LogInfo << VAR(port_str) << VAR(port) << VAR(index);
|
||
return index;
|
||
}
|
||
|
||
Log.error("address is invalid or unsupported", address);
|
||
return 0;
|
||
}
|
||
|
||
void asst::AdbController::init_ld_extras(const AdbCfg& adb_cfg, const std::string& address)
|
||
{
|
||
#if !ASST_WITH_EMULATOR_EXTRAS
|
||
std::ignore = adb_cfg;
|
||
std::ignore = address;
|
||
Log.error("MaaCore is not compiled with ASST_WITH_EMULATOR_EXTRAS");
|
||
#else
|
||
if (adb_cfg.extras.empty()) {
|
||
LogWarn << "adb_cfg.extras is empty";
|
||
return;
|
||
}
|
||
|
||
std::filesystem::path ld_path = utils::path(adb_cfg.extras.get("path", ""));
|
||
int ld_index;
|
||
if (adb_cfg.extras.contains("index")) {
|
||
ld_index = adb_cfg.extras.get("index", 0);
|
||
}
|
||
else {
|
||
ld_index = get_ld_index(address);
|
||
}
|
||
int ld_pid = adb_cfg.extras.get("pid", 0);
|
||
m_ld_extras.init(ld_path, ld_index, ld_pid, m_width, m_height);
|
||
#endif
|
||
}
|
||
|
||
void asst::AdbController::close_socket() noexcept
|
||
{
|
||
m_platform_io->close_socket();
|
||
}
|
||
|
||
std::optional<unsigned short> asst::AdbController::init_socket(const std::string& local_address)
|
||
{
|
||
return m_platform_io->init_socket(local_address);
|
||
}
|
||
|
||
void asst::AdbController::clear_info() noexcept
|
||
{
|
||
m_inited = false;
|
||
m_adb = decltype(m_adb)();
|
||
m_uuid.clear();
|
||
m_width = 0;
|
||
m_height = 0;
|
||
m_screen_size = { 0, 0 };
|
||
}
|
||
|
||
bool asst::AdbController::inited() const noexcept
|
||
{
|
||
return m_inited;
|
||
}
|
||
|
||
bool asst::AdbController::start_game(const std::string& client_type)
|
||
{
|
||
if (client_type.empty()) {
|
||
return false;
|
||
}
|
||
auto package_name = Config.get_package_name(client_type);
|
||
if (!package_name) {
|
||
return false;
|
||
}
|
||
|
||
std::string cur_cmd = utils::string_replace_all(m_adb.start, "[PackageName]", package_name.value());
|
||
bool ret = call_command(cur_cmd).has_value();
|
||
|
||
return ret;
|
||
}
|
||
|
||
bool asst::AdbController::stop_game(const std::string& client_type)
|
||
{
|
||
if (client_type.empty()) {
|
||
return false;
|
||
}
|
||
auto package_name = Config.get_package_name(client_type);
|
||
if (!package_name) {
|
||
return false;
|
||
}
|
||
std::string cur_cmd = utils::string_replace_all(m_adb.stop, "[PackageName]", package_name.value());
|
||
bool ret = call_command(cur_cmd).has_value();
|
||
|
||
return ret;
|
||
}
|
||
|
||
bool asst::AdbController::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");
|
||
}
|
||
|
||
std::string cur_cmd =
|
||
utils::string_replace_all(m_adb.click, { { "[x]", std::to_string(p.x) }, { "[y]", std::to_string(p.y) } });
|
||
return call_command(cur_cmd).has_value();
|
||
}
|
||
|
||
bool asst::AdbController::input(const std::string& text)
|
||
{
|
||
if (text == "") {
|
||
Log.error("empty text");
|
||
}
|
||
|
||
std::string cur_cmd = utils::string_replace_all(m_adb.input, { { "[text]", text } });
|
||
return call_command(cur_cmd).has_value();
|
||
}
|
||
|
||
bool asst::AdbController::swipe(
|
||
const Point& p1,
|
||
const Point& p2,
|
||
int duration,
|
||
bool extra_swipe,
|
||
[[maybe_unused]] double slope_in,
|
||
[[maybe_unused]] double slope_out,
|
||
[[maybe_unused]] 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);
|
||
}
|
||
|
||
const auto& opt = Config.get_options();
|
||
|
||
std::string duration_str =
|
||
duration <= 0 ? "" : std::to_string(static_cast<int>(duration * opt.adb_swipe_duration_multiplier));
|
||
std::string cur_cmd = utils::string_replace_all(
|
||
m_adb.swipe,
|
||
{
|
||
{ "[x1]", std::to_string(x1) },
|
||
{ "[y1]", std::to_string(y1) },
|
||
{ "[x2]", std::to_string(x2) },
|
||
{ "[y2]", std::to_string(y2) },
|
||
{ "[duration]", duration_str },
|
||
});
|
||
bool ret = call_command(cur_cmd).has_value();
|
||
|
||
// 额外的滑动:adb有bug,同样的参数,偶尔会划得非常远。额外做一个短程滑动,把之前的停下来
|
||
if (extra_swipe && opt.adb_extra_swipe_duration > 0) {
|
||
std::string extra_cmd = utils::string_replace_all(
|
||
m_adb.swipe,
|
||
{
|
||
{ "[x1]", std::to_string(x2) },
|
||
{ "[y1]", std::to_string(y2) },
|
||
{ "[x2]", std::to_string(x2) },
|
||
{ "[y2]", std::to_string(y2 - opt.adb_extra_swipe_dist /* * m_control_scale*/) },
|
||
{ "[duration]", std::to_string(opt.adb_extra_swipe_duration) },
|
||
});
|
||
ret &= call_command(extra_cmd).has_value();
|
||
}
|
||
return ret;
|
||
}
|
||
|
||
bool asst::AdbController::press_esc()
|
||
{
|
||
LogTraceFunction;
|
||
|
||
return call_command(m_adb.press_esc).has_value();
|
||
}
|
||
|
||
std::pair<int, int> asst::AdbController::get_screen_res() const noexcept
|
||
{
|
||
return m_screen_size;
|
||
}
|
||
|
||
void asst::AdbController::release()
|
||
{
|
||
close_socket();
|
||
|
||
if (m_kill_adb_on_exit && !m_adb.release.empty()) {
|
||
m_platform_io->release_adb(m_adb.release, 20000);
|
||
}
|
||
}
|
||
|
||
const std::string& asst::AdbController::get_uuid() const
|
||
{
|
||
return m_uuid;
|
||
}
|
||
|
||
bool asst::AdbController::convert_lf(std::string& data)
|
||
{
|
||
if (data.empty() || data.size() < 2) {
|
||
return false;
|
||
}
|
||
auto pred = [](const std::string::iterator& cur) -> bool {
|
||
return *cur == '\r' && *(cur + 1) == '\n';
|
||
};
|
||
// find the first of "\r\n"
|
||
auto first_iter = data.end();
|
||
for (auto iter = data.begin(); iter != data.end() - 1; ++iter) {
|
||
if (pred(iter)) {
|
||
first_iter = iter;
|
||
break;
|
||
}
|
||
}
|
||
if (first_iter == data.end()) {
|
||
return false;
|
||
}
|
||
// move forward all non-crlf elements
|
||
auto end_r1_iter = data.end() - 1;
|
||
auto next_iter = first_iter;
|
||
while (++first_iter != end_r1_iter) {
|
||
if (!pred(first_iter)) {
|
||
*next_iter = *first_iter;
|
||
++next_iter;
|
||
}
|
||
}
|
||
*next_iter = *end_r1_iter;
|
||
++next_iter;
|
||
data.erase(next_iter, data.end());
|
||
return true;
|
||
}
|
||
|
||
bool asst::AdbController::screencap(cv::Mat& image_payload, bool allow_reconnect)
|
||
{
|
||
using namespace std::chrono;
|
||
DecodeFunc decode_raw = [&](const std::string& data) -> bool {
|
||
if (data.size() < 8) {
|
||
return false;
|
||
}
|
||
// assuming little endian
|
||
uint32_t w = static_cast<uint32_t>(static_cast<unsigned char>(data[0])) << 0 |
|
||
static_cast<uint32_t>(static_cast<unsigned char>(data[1])) << 8 |
|
||
static_cast<uint32_t>(static_cast<unsigned char>(data[2])) << 16 |
|
||
static_cast<uint32_t>(static_cast<unsigned char>(data[3])) << 24;
|
||
uint32_t h = static_cast<uint32_t>(static_cast<unsigned char>(data[4])) << 0 |
|
||
static_cast<uint32_t>(static_cast<unsigned char>(data[5])) << 8 |
|
||
static_cast<uint32_t>(static_cast<unsigned char>(data[6])) << 16 |
|
||
static_cast<uint32_t>(static_cast<unsigned char>(data[7])) << 24;
|
||
if (int(w) != m_width || int(h) != m_height) {
|
||
Log.error("Size from image header", w, h, "does not match the size of screen", m_width, m_height);
|
||
return false;
|
||
}
|
||
size_t std_size = 4ULL * m_width * m_height;
|
||
if (data.size() < std_size) {
|
||
return false;
|
||
}
|
||
const size_t header_size = data.size() - std_size; // 12 or 16. ref:
|
||
// https://android.googlesource.com/platform/frameworks/base/+/26a2b97dbe48ee45e9ae70110714048f2f360f97%5E%21/cmds/screencap/screencap.cpp
|
||
auto img_data_beg = data.cbegin() + header_size;
|
||
cv::Mat temp(m_height, m_width, CV_8UC4, const_cast<char*>(&*img_data_beg));
|
||
if (temp.empty()) {
|
||
return false;
|
||
}
|
||
const auto& br = *(temp.end<cv::Vec4b>() - 1);
|
||
if (br[3] != 255) { // only check alpha
|
||
return false;
|
||
}
|
||
cv::cvtColor(temp, temp, cv::COLOR_RGBA2BGR);
|
||
image_payload = temp;
|
||
return true;
|
||
};
|
||
|
||
DecodeFunc decode_raw_with_gzip = [&](const std::string& data) -> bool {
|
||
const std::string raw_data = gzip::decompress(data.data(), data.size());
|
||
return decode_raw(raw_data);
|
||
};
|
||
|
||
DecodeFunc decode_encode = [&](const std::string& data) -> bool {
|
||
cv::Mat temp = cv::imdecode({ data.data(), int(data.size()) }, cv::IMREAD_COLOR);
|
||
if (temp.empty()) {
|
||
return false;
|
||
}
|
||
image_payload = temp;
|
||
return true;
|
||
};
|
||
|
||
image_payload = cv::Mat(); // 清空缓存
|
||
if (m_adb.screencap_method == AdbProperty::ScreencapMethod::UnknownYet) {
|
||
std::vector<std::pair<AdbProperty::ScreencapMethod, std::string>> all_methods_cost;
|
||
|
||
Log.info("Try to find the fastest way to screencap");
|
||
auto min_cost = milliseconds(LLONG_MAX);
|
||
clear_lf_info();
|
||
|
||
auto start_time = steady_clock::now();
|
||
if (m_support_socket && m_server_started &&
|
||
screencap(m_adb.screencap_raw_by_nc, decode_raw, allow_reconnect, true, 5000)) {
|
||
auto duration = duration_cast<milliseconds>(steady_clock::now() - start_time);
|
||
if (duration < min_cost) {
|
||
m_adb.screencap_method = AdbProperty::ScreencapMethod::RawByNc;
|
||
m_inited = true;
|
||
min_cost = duration;
|
||
}
|
||
Log.info("RawByNc cost", duration.count(), "ms");
|
||
all_methods_cost.emplace_back(AdbProperty::ScreencapMethod::RawByNc, std::to_string(duration.count()));
|
||
}
|
||
else {
|
||
Log.info("RawByNc is not supported");
|
||
all_methods_cost.emplace_back(AdbProperty::ScreencapMethod::RawByNc, "???");
|
||
}
|
||
clear_lf_info();
|
||
|
||
start_time = steady_clock::now();
|
||
if (screencap(m_adb.screencap_raw_with_gzip, decode_raw_with_gzip, allow_reconnect)) {
|
||
auto duration = duration_cast<milliseconds>(steady_clock::now() - start_time);
|
||
if (duration < min_cost) {
|
||
m_adb.screencap_method = AdbProperty::ScreencapMethod::RawWithGzip;
|
||
m_inited = true;
|
||
min_cost = duration;
|
||
}
|
||
Log.info("RawWithGzip cost", duration.count(), "ms");
|
||
all_methods_cost.emplace_back(AdbProperty::ScreencapMethod::RawWithGzip, std::to_string(duration.count()));
|
||
}
|
||
else {
|
||
Log.info("RawWithGzip is not supported");
|
||
all_methods_cost.emplace_back(AdbProperty::ScreencapMethod::RawWithGzip, "???");
|
||
}
|
||
clear_lf_info();
|
||
|
||
start_time = steady_clock::now();
|
||
if (screencap(m_adb.screencap_encode, decode_encode, allow_reconnect)) {
|
||
auto duration = duration_cast<milliseconds>(steady_clock::now() - start_time);
|
||
if (duration < min_cost) {
|
||
m_adb.screencap_method = AdbProperty::ScreencapMethod::Encode;
|
||
m_inited = true;
|
||
min_cost = duration;
|
||
}
|
||
Log.info("Encode cost", duration.count(), "ms");
|
||
all_methods_cost.emplace_back(AdbProperty::ScreencapMethod::Encode, std::to_string(duration.count()));
|
||
}
|
||
else {
|
||
Log.info("Encode is not supported");
|
||
all_methods_cost.emplace_back(AdbProperty::ScreencapMethod::Encode, "???");
|
||
}
|
||
|
||
#if ASST_WITH_EMULATOR_EXTRAS
|
||
if (m_mumu_extras.inited()) {
|
||
start_time = steady_clock::now();
|
||
if (m_mumu_extras.screencap()) {
|
||
auto duration = duration_cast<milliseconds>(steady_clock::now() - start_time);
|
||
if (duration < min_cost) {
|
||
m_adb.screencap_method = AdbProperty::ScreencapMethod::MumuExtras;
|
||
m_inited = true;
|
||
min_cost = duration;
|
||
}
|
||
Log.info("MumuExtras cost", duration.count(), "ms");
|
||
all_methods_cost.emplace_back(
|
||
AdbProperty::ScreencapMethod::MumuExtras,
|
||
std::to_string(duration.count()));
|
||
}
|
||
else {
|
||
Log.info("MumuExtras is not supported");
|
||
all_methods_cost.emplace_back(AdbProperty::ScreencapMethod::MumuExtras, "???");
|
||
}
|
||
}
|
||
if (m_ld_extras.inited()) {
|
||
start_time = steady_clock::now();
|
||
if (m_ld_extras.screencap()) {
|
||
auto duration = duration_cast<milliseconds>(steady_clock::now() - start_time);
|
||
if (duration < min_cost) {
|
||
m_adb.screencap_method = AdbProperty::ScreencapMethod::LDExtras;
|
||
m_inited = true;
|
||
min_cost = duration;
|
||
}
|
||
Log.info("LDExtras cost", duration.count(), "ms");
|
||
all_methods_cost.emplace_back(AdbProperty::ScreencapMethod::LDExtras, std::to_string(duration.count()));
|
||
}
|
||
else {
|
||
Log.info("LDExtras is not supported");
|
||
all_methods_cost.emplace_back(AdbProperty::ScreencapMethod::LDExtras, "???");
|
||
}
|
||
}
|
||
#endif
|
||
|
||
static const std::unordered_map<AdbProperty::ScreencapMethod, std::string> MethodName = {
|
||
{ AdbProperty::ScreencapMethod::UnknownYet, "UnknownYet" },
|
||
{ AdbProperty::ScreencapMethod::RawByNc, "RawByNc" },
|
||
{ AdbProperty::ScreencapMethod::RawWithGzip, "RawWithGzip" },
|
||
{ AdbProperty::ScreencapMethod::Encode, "Encode" },
|
||
#if ASST_WITH_EMULATOR_EXTRAS
|
||
{ AdbProperty::ScreencapMethod::MumuExtras, "MumuExtras" },
|
||
{ AdbProperty::ScreencapMethod::LDExtras, "LDExtras" },
|
||
#endif
|
||
};
|
||
Log.info("The fastest way is", MethodName.at(m_adb.screencap_method), ", cost:", min_cost.count(), "ms");
|
||
if (m_adb.screencap_method != AdbProperty::ScreencapMethod::UnknownYet) {
|
||
json::value info = json::object {
|
||
{ "uuid", m_uuid },
|
||
{ "what", "FastestWayToScreencap" },
|
||
{ "details",
|
||
json::object {
|
||
{ "method", MethodName.at(m_adb.screencap_method) },
|
||
{ "cost", min_cost.count() },
|
||
} },
|
||
};
|
||
json::array alt;
|
||
for (auto& [method, cost] : all_methods_cost) {
|
||
alt.push_back(json::object { { "method", MethodName.at(method) }, { "cost", cost } });
|
||
}
|
||
auto details_obj = info.at("details").as_object();
|
||
details_obj["alternatives"] = std::move(alt);
|
||
info.as_object()["details"] = std::move(details_obj);
|
||
callback(AsstMsg::ConnectionInfo, info);
|
||
}
|
||
clear_lf_info();
|
||
return m_adb.screencap_method != AdbProperty::ScreencapMethod::UnknownYet;
|
||
}
|
||
else {
|
||
auto start_time = high_resolution_clock::now();
|
||
bool screencap_ret = false;
|
||
switch (m_adb.screencap_method) {
|
||
case AdbProperty::ScreencapMethod::RawByNc:
|
||
screencap_ret = screencap(m_adb.screencap_raw_by_nc, decode_raw, allow_reconnect, true);
|
||
break;
|
||
case AdbProperty::ScreencapMethod::RawWithGzip:
|
||
screencap_ret = screencap(m_adb.screencap_raw_with_gzip, decode_raw_with_gzip, allow_reconnect);
|
||
break;
|
||
case AdbProperty::ScreencapMethod::Encode:
|
||
screencap_ret = screencap(m_adb.screencap_encode, decode_encode, allow_reconnect);
|
||
break;
|
||
#if ASST_WITH_EMULATOR_EXTRAS
|
||
case AdbProperty::ScreencapMethod::MumuExtras: {
|
||
auto img_opt = m_mumu_extras.screencap();
|
||
screencap_ret = img_opt.has_value();
|
||
|
||
if (!screencap_ret && allow_reconnect) {
|
||
m_mumu_extras.reload();
|
||
img_opt = m_mumu_extras.screencap();
|
||
screencap_ret = img_opt.has_value();
|
||
}
|
||
|
||
if (screencap_ret) {
|
||
image_payload = img_opt.value();
|
||
}
|
||
} break;
|
||
case AdbProperty::ScreencapMethod::LDExtras: {
|
||
auto img_opt = m_ld_extras.screencap();
|
||
screencap_ret = img_opt.has_value();
|
||
|
||
if (!screencap_ret && allow_reconnect) {
|
||
m_ld_extras.reload();
|
||
img_opt = m_ld_extras.screencap();
|
||
screencap_ret = img_opt.has_value();
|
||
}
|
||
|
||
if (screencap_ret) {
|
||
image_payload = img_opt.value();
|
||
}
|
||
} break;
|
||
#endif
|
||
default:
|
||
break;
|
||
}
|
||
auto duration = duration_cast<milliseconds>(high_resolution_clock::now() - start_time);
|
||
// 记录截图耗时,每10次截图回传一次最值+平均值
|
||
m_screencap_cost.emplace_back(screencap_ret ? duration.count() : -1); // 记录截图耗时
|
||
++m_screencap_times;
|
||
|
||
if (m_screencap_cost.size() > 30) {
|
||
m_screencap_cost.pop_front();
|
||
}
|
||
if (m_screencap_times > 9) { // 每 10 次截图计算一次平均耗时
|
||
m_screencap_times = 0;
|
||
auto filtered_cost = m_screencap_cost | std::views::filter([](auto num) { return num > 0; });
|
||
if (filtered_cost.empty()) {
|
||
return screencap_ret;
|
||
}
|
||
// 过滤后的有效截图用时次数
|
||
auto filtered_count = m_screencap_cost.size() - std::ranges::count(m_screencap_cost, -1);
|
||
auto [screencap_cost_min, screencap_cost_max] = std::ranges::minmax(filtered_cost);
|
||
json::value info = json::object {
|
||
{ "uuid", m_uuid },
|
||
{ "what", "ScreencapCost" },
|
||
{ "details",
|
||
json::object {
|
||
{ "min", screencap_cost_min },
|
||
{ "max", screencap_cost_max },
|
||
{ "avg",
|
||
filtered_count > 0
|
||
? std::accumulate(filtered_cost.begin(), filtered_cost.end(), 0ll) / filtered_count
|
||
: -1 },
|
||
} },
|
||
};
|
||
if (m_screencap_cost.size() > filtered_count) {
|
||
info["details"]["fault_times"] = m_screencap_cost.size() - filtered_count;
|
||
}
|
||
callback(AsstMsg::ConnectionInfo, info);
|
||
}
|
||
return screencap_ret;
|
||
}
|
||
}
|
||
|
||
bool asst::AdbController::screencap(
|
||
const std::string& cmd,
|
||
const DecodeFunc& decode_func,
|
||
bool allow_reconnect,
|
||
bool by_socket,
|
||
int timeout)
|
||
{
|
||
if ((!m_support_socket || !m_server_started) && by_socket) [[unlikely]] {
|
||
return false;
|
||
}
|
||
auto ret = call_command(cmd, timeout, allow_reconnect, by_socket);
|
||
|
||
if (!ret || ret.value().empty()) [[unlikely]] {
|
||
Log.warn("data is empty!");
|
||
return false;
|
||
}
|
||
auto& data = ret.value();
|
||
|
||
bool tried_conversion = false;
|
||
if (m_adb.screencap_end_of_line == AdbProperty::ScreencapEndOfLine::CRLF) {
|
||
tried_conversion = true;
|
||
if (!convert_lf(data)) [[unlikely]] { // 没找到 "\r\n"
|
||
Log.info("screencap_end_of_line is set to CRLF but no `\\r\\n` found, set it to LF");
|
||
m_adb.screencap_end_of_line = AdbProperty::ScreencapEndOfLine::LF;
|
||
}
|
||
}
|
||
|
||
if (decode_func(data)) [[likely]] {
|
||
if (m_adb.screencap_end_of_line == AdbProperty::ScreencapEndOfLine::UnknownYet) [[unlikely]] {
|
||
Log.info("screencap_end_of_line is LF");
|
||
m_adb.screencap_end_of_line = AdbProperty::ScreencapEndOfLine::LF;
|
||
}
|
||
}
|
||
else {
|
||
Log.info("data is not empty, but image is empty");
|
||
|
||
if (tried_conversion) { // 已经转换过行尾,再次转换 data 不会变化,不必重试
|
||
Log.error("skip retry decoding and decode failed!");
|
||
return false;
|
||
}
|
||
|
||
Log.info("try to cvt lf");
|
||
if (!convert_lf(data)) { // 没找到 "\r\n",data 没有变化,不必重试
|
||
Log.error("no `\\r\\n` found, skip retry decode");
|
||
return false;
|
||
}
|
||
if (!decode_func(data)) {
|
||
Log.error("convert lf and retry decode failed!");
|
||
return false;
|
||
}
|
||
|
||
if (m_adb.screencap_end_of_line == AdbProperty::ScreencapEndOfLine::UnknownYet) {
|
||
Log.info("screencap_end_of_line is CRLF");
|
||
}
|
||
else {
|
||
Log.info("screencap_end_of_line is changed to CRLF");
|
||
}
|
||
m_adb.screencap_end_of_line = AdbProperty::ScreencapEndOfLine::CRLF;
|
||
}
|
||
return true;
|
||
}
|
||
|
||
bool asst::AdbController::connect(const std::string& adb_path, const std::string& address, const std::string& config)
|
||
{
|
||
LogTraceFunction;
|
||
|
||
clear_info();
|
||
|
||
#ifdef ASST_DEBUG
|
||
if (config == "DEBUG") {
|
||
m_inited = true;
|
||
return true;
|
||
}
|
||
#endif
|
||
|
||
auto get_info_json = [&]() -> json::value {
|
||
return json::object {
|
||
{ "uuid", m_uuid },
|
||
{ "details",
|
||
json::object {
|
||
{ "adb", adb_path },
|
||
{ "address", address },
|
||
{ "config", config },
|
||
} },
|
||
};
|
||
};
|
||
|
||
std::string client_type = ctrler() ? ctrler()->get_client_type() : "";
|
||
m_conn_ctx.reset(adb_path, address, client_type);
|
||
|
||
auto adb_ret = Config.get_adb_cfg(config);
|
||
if (!adb_ret) {
|
||
json::value info = get_info_json() | json::object {
|
||
{ "what", "ConnectFailed" },
|
||
{ "why", "ConfigNotFound" },
|
||
};
|
||
callback(AsstMsg::ConnectionInfo, info);
|
||
#ifdef ASST_DEBUG
|
||
return false;
|
||
#else
|
||
Log.error("config ", config, "not found");
|
||
adb_ret = Config.get_adb_cfg("General");
|
||
#endif
|
||
}
|
||
|
||
m_conn_ctx.adb_cfg = adb_ret.value();
|
||
const auto& adb_cfg = m_conn_ctx.adb_cfg;
|
||
|
||
if (need_exit()) {
|
||
return false;
|
||
}
|
||
|
||
/* connect */
|
||
{
|
||
// 先用 devices 读取输出
|
||
m_adb.devices = m_conn_ctx.replace_cmd(adb_cfg.devices);
|
||
m_adb.address_regex = m_conn_ctx.replace_cmd(adb_cfg.address_regex);
|
||
auto devices_ret = call_command(m_adb.devices, 60LL * 1000, false);
|
||
bool need_connect = true;
|
||
if (devices_ret) {
|
||
const auto& devices_str = devices_ret.value();
|
||
const boost::regex address_regex(m_adb.address_regex);
|
||
for (boost::sregex_iterator iter(devices_str.begin(), devices_str.end(), address_regex), end; iter != end;
|
||
++iter) {
|
||
if (iter->size() > 1 && iter->str(1) == address) {
|
||
need_connect = false;
|
||
break;
|
||
}
|
||
}
|
||
}
|
||
|
||
// 设置配置 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) {
|
||
json::value info = get_info_json() | json::object {
|
||
{ "what", "ConnectFailed" },
|
||
{ "why", "Cannot connect: address appears to be serial number but device not found" },
|
||
};
|
||
callback(AsstMsg::ConnectionInfo, info);
|
||
return false;
|
||
}
|
||
|
||
auto connect_ret = call_command(m_adb.connect, 60LL * 1000, false /* adb 连接时不允许重试 */);
|
||
if (connect_ret) {
|
||
auto& connect_str = connect_ret.value();
|
||
// 检查连接字符串是否包含 "connected"
|
||
if (connect_str.find("connected") == std::string::npos) {
|
||
json::value info = get_info_json() | json::object {
|
||
{ "what", "ConnectFailed" },
|
||
{ "why", "Connection command did not report \"connected\"" },
|
||
};
|
||
callback(AsstMsg::ConnectionInfo, info);
|
||
return false;
|
||
}
|
||
}
|
||
else {
|
||
json::value info = get_info_json() | json::object {
|
||
{ "what", "ConnectFailed" },
|
||
{ "why", "Connection command failed to exec" },
|
||
};
|
||
callback(AsstMsg::ConnectionInfo, info);
|
||
return false;
|
||
}
|
||
}
|
||
}
|
||
|
||
if (need_exit()) {
|
||
return false;
|
||
}
|
||
|
||
/* get uuid (imei) */
|
||
{
|
||
auto uuid_ret = call_command(m_conn_ctx.replace_cmd(adb_cfg.uuid), 20000, false /* adb 连接时不允许重试 */);
|
||
if (!uuid_ret) {
|
||
json::value info = get_info_json() | json::object {
|
||
{ "what", "ConnectFailed" },
|
||
{ "why", "Uuid command failed to exec" },
|
||
};
|
||
callback(AsstMsg::ConnectionInfo, info);
|
||
return false;
|
||
}
|
||
|
||
std::string raw_output = std::move(uuid_ret.value());
|
||
std::string uuid_str = raw_output;
|
||
std::erase_if(uuid_str, [](char c) { return !std::isdigit(c) && !std::isalpha(c); });
|
||
|
||
boost::regex hex_regex("^[0-9a-fA-F]{8,}$");
|
||
if (uuid_str.empty() || !boost::regex_match(uuid_str, hex_regex)) {
|
||
json::value info = get_info_json() | json::object {
|
||
{ "what", "ConnectFailed" },
|
||
{ "why", "Uuid invalid or shell command not available" },
|
||
};
|
||
info["details"]["raw_output"] = std::move(raw_output);
|
||
callback(AsstMsg::ConnectionInfo, info);
|
||
return false;
|
||
}
|
||
|
||
m_uuid = std::move(uuid_str);
|
||
|
||
json::value info = get_info_json() | json::object {
|
||
{ "what", "UuidGot" },
|
||
{ "why", "" },
|
||
};
|
||
info["details"]["uuid"] = m_uuid;
|
||
callback(AsstMsg::ConnectionInfo, info);
|
||
}
|
||
/* get android version */
|
||
{
|
||
auto version_ret = call_command(m_conn_ctx.replace_cmd(adb_cfg.version));
|
||
if (!version_ret) {
|
||
json::value info = get_info_json() | json::object {
|
||
{ "what", "ConnectFailed" },
|
||
{ "why", "Android version command failed to exec" },
|
||
};
|
||
callback(AsstMsg::ConnectionInfo, info);
|
||
return false;
|
||
}
|
||
|
||
std::string raw_output = version_ret.value();
|
||
convert_lf(raw_output);
|
||
|
||
std::erase_if(raw_output, [](char c) { return !std::isalnum(static_cast<unsigned char>(c)); });
|
||
bool all_digit = !raw_output.empty() && std::ranges::all_of(raw_output, [](char c) {
|
||
return std::isdigit(static_cast<unsigned char>(c));
|
||
});
|
||
|
||
if (!all_digit) {
|
||
json::value info = get_info_json() | json::object {
|
||
{ "what", "ConnectFailed" },
|
||
{ "why", "Android version invalid or shell command not available" },
|
||
};
|
||
info["details"]["raw_output"] = std::move(version_ret.value());
|
||
callback(AsstMsg::ConnectionInfo, info);
|
||
return false;
|
||
}
|
||
|
||
m_version = std::stoul(raw_output);
|
||
}
|
||
|
||
if (need_exit()) {
|
||
return false;
|
||
}
|
||
|
||
// 按需获取display ID 信息
|
||
if (!adb_cfg.display_id.empty()) {
|
||
// [PackageName] 不参与 replace_cmd 的统一替换(保留为运行时参数)。
|
||
// 只有 connect 阶段命令确实依赖包名的配置,才需要在连接前调用
|
||
// AsstSetInstanceOption(ClientType, ...)。当前内置配置仅 Androws / WSA 的
|
||
// displayId 查询走这里,因此其他连接配置不应被迫传入 client type。
|
||
auto display_id_cmd = utils::string_replace_all(
|
||
m_conn_ctx.replace_cmd(adb_cfg.display_id),
|
||
"[PackageName]",
|
||
m_conn_ctx.package_name);
|
||
auto display_id_ret = call_command(display_id_cmd);
|
||
if (!display_id_ret) {
|
||
return false;
|
||
}
|
||
|
||
auto& display_id_pipe_str = display_id_ret.value();
|
||
convert_lf(display_id_pipe_str);
|
||
auto last = display_id_pipe_str.rfind(':');
|
||
if (last == std::string::npos) {
|
||
return false;
|
||
}
|
||
|
||
m_conn_ctx.display_id = display_id_pipe_str.substr(last + 1);
|
||
// 去掉换行
|
||
m_conn_ctx.display_id.pop_back();
|
||
}
|
||
|
||
if (need_exit()) {
|
||
return false;
|
||
}
|
||
|
||
// 按需获取 event ID 信息(用于 minitouch 的 /dev/input/eventN)
|
||
if (!adb_cfg.event_id.empty()) {
|
||
auto event_id_ret = call_command(m_conn_ctx.replace_cmd(adb_cfg.event_id));
|
||
if (!event_id_ret) {
|
||
Log.warn("Failed to get event_id, skip");
|
||
}
|
||
else {
|
||
auto& event_id_pipe_str = event_id_ret.value();
|
||
convert_lf(event_id_pipe_str);
|
||
// 去掉空白字符
|
||
std::erase_if(event_id_pipe_str, [](char c) { return !std::isdigit(c); });
|
||
|
||
if (event_id_pipe_str.empty()) {
|
||
Log.warn("event_id is empty, skip");
|
||
}
|
||
else {
|
||
m_conn_ctx.event_id = event_id_pipe_str;
|
||
Log.info("event_id:", m_conn_ctx.event_id);
|
||
}
|
||
}
|
||
}
|
||
|
||
if (need_exit()) {
|
||
return false;
|
||
}
|
||
|
||
/* display */
|
||
{
|
||
auto display_ret = call_command(m_conn_ctx.replace_cmd(adb_cfg.display));
|
||
if (!display_ret) {
|
||
json::value info = get_info_json() | json::object {
|
||
{ "what", "ConnectFailed" },
|
||
{ "why", "Display command failed to exec" },
|
||
};
|
||
callback(AsstMsg::ConnectionInfo, info);
|
||
return false;
|
||
}
|
||
std::stringstream display_ss(display_ret.value());
|
||
int size_value1 = 0;
|
||
int size_value2 = 0;
|
||
display_ss >> size_value1 >> size_value2;
|
||
|
||
m_width = (std::max)(size_value1, size_value2);
|
||
m_height = (std::min)(size_value1, size_value2);
|
||
|
||
json::value info = get_info_json() | json::object {
|
||
{ "what", "ResolutionGot" },
|
||
{ "why", "" },
|
||
};
|
||
|
||
info["details"] |= json::object {
|
||
{ "width", m_width },
|
||
{ "height", m_height },
|
||
};
|
||
|
||
callback(AsstMsg::ConnectionInfo, info);
|
||
|
||
if (m_width == 0 || m_height == 0) {
|
||
info["what"] = "ResolutionError";
|
||
info["why"] = "Get resolution failed";
|
||
callback(AsstMsg::ConnectionInfo, info);
|
||
return false;
|
||
}
|
||
m_screen_size = { m_width, m_height };
|
||
}
|
||
|
||
if (need_exit()) {
|
||
return false;
|
||
}
|
||
|
||
{
|
||
json::value info = get_info_json() | json::object {
|
||
{ "what", "Connected" },
|
||
{ "why", "" },
|
||
};
|
||
callback(AsstMsg::ConnectionInfo, info);
|
||
}
|
||
|
||
m_adb.click = m_conn_ctx.replace_cmd(adb_cfg.click);
|
||
m_adb.input = m_conn_ctx.replace_cmd(adb_cfg.input);
|
||
m_adb.swipe = m_conn_ctx.replace_cmd(adb_cfg.swipe);
|
||
m_adb.press_esc = m_conn_ctx.replace_cmd(adb_cfg.press_esc);
|
||
m_adb.screencap_raw_with_gzip = m_conn_ctx.replace_cmd(adb_cfg.screencap_raw_with_gzip);
|
||
m_adb.screencap_encode = m_conn_ctx.replace_cmd(adb_cfg.screencap_encode);
|
||
m_adb.start = m_conn_ctx.replace_cmd(adb_cfg.start);
|
||
m_adb.stop = m_conn_ctx.replace_cmd(adb_cfg.stop);
|
||
m_adb.back_to_home = m_conn_ctx.replace_cmd(adb_cfg.back_to_home);
|
||
|
||
if (m_support_socket && !m_server_started) {
|
||
std::string bind_address;
|
||
if (size_t pos = address.rfind(':'); pos != std::string::npos) {
|
||
bind_address = address.substr(0, pos);
|
||
}
|
||
else {
|
||
bind_address = "127.0.0.1";
|
||
}
|
||
|
||
// reference from
|
||
// https://github.com/ArknightsAutoHelper/ArknightsAutoHelper/blob/master/automator/connector/ADBConnector.py#L436
|
||
auto nc_address_ret = call_command(m_conn_ctx.replace_cmd(adb_cfg.nc_address));
|
||
if (nc_address_ret && !m_server_started) {
|
||
auto& nc_result_str = nc_address_ret.value();
|
||
if (auto pos = nc_result_str.find(' '); pos != std::string::npos) {
|
||
m_conn_ctx.nc_address = nc_result_str.substr(0, pos);
|
||
}
|
||
}
|
||
|
||
auto socket_opt = init_socket(bind_address);
|
||
if (socket_opt) {
|
||
m_conn_ctx.nc_port = socket_opt.value();
|
||
m_adb.screencap_raw_by_nc = m_conn_ctx.replace_cmd(adb_cfg.screencap_raw_by_nc);
|
||
m_server_started = true;
|
||
}
|
||
else {
|
||
m_server_started = false;
|
||
}
|
||
}
|
||
|
||
if (need_exit()) {
|
||
return false;
|
||
}
|
||
|
||
if (config == "MuMuEmulator12") {
|
||
init_mumu_extras(adb_cfg, address);
|
||
}
|
||
else if (config == "LDPlayer") {
|
||
init_ld_extras(adb_cfg, address);
|
||
}
|
||
if (need_exit()) {
|
||
return false;
|
||
}
|
||
|
||
return true;
|
||
}
|
||
|
||
void asst::AdbController::set_kill_adb_on_exit(bool enable) noexcept
|
||
{
|
||
m_kill_adb_on_exit = enable;
|
||
}
|
||
|
||
void asst::AdbController::clear_lf_info()
|
||
{
|
||
m_adb.screencap_end_of_line = AdbProperty::ScreencapEndOfLine::UnknownYet;
|
||
}
|
||
|
||
void asst::AdbController::back_to_home() noexcept
|
||
{
|
||
call_command(m_adb.back_to_home);
|
||
return;
|
||
}
|