mirror of
https://github.com/MaaAssistantArknights/MaaAssistantArknights.git
synced 2026-07-20 10:57:45 +08:00
feat(PC): 支持 PC 端明日方舟 (#15407)
* feat(controller): 集成 MaaWin32ControlUnit 支持 Win32 窗口截图与控制 - 提取公共滑动插值逻辑到 SwipeHelper.hpp,重构 Minitouch/PlayTools 控制器 - 新增 Win32ControlUnitLoader 动态加载 MaaWin32ControlUnit.dll - 新增 Win32Controller 实现 ControllerAPI,适配 touch_down/move/up 接口 - 添加 AsstAsyncAttachWindow 公开 API 用于绑定 Win32 窗口 - 添加 Win32 截图/输入方式的类型定义和枚举常量 * feat(api): 增加同步版本的 AsstAttachWindow 接口 模仿 AsstConnect 实现同步绑定 Win32 窗口的接口,方便简单场景下直接调用。 该接口标记为 deprecated,建议使用异步版本 AsstAsyncAttachWindow。 * fix: can build MaaCore * feat: 完成整体适配 * feat(ci): 添加 MaaFramework Win32ControlUnit 下载步骤 使用 robinraju/release-downloader 从 MaaFramework 最新 release 下载 win-x64 版本并提取 MaaWin32ControlUnit 到构建产物中 * docs: 添加 Win32Controller 调试所需 MaaWin32ControlUnit 的说明 在开发文档的环境配置步骤中说明调试 Win32Controller 需要手动下载 MaaWin32ControlUnit.dll,并欢迎社区贡献自动下载脚本 * fix(i18n): 修改 UseAttachWindowWarning 措辞为"仅供尝鲜" * fix: 小细节修复 * ci: fix maafw filename * feat: PC 移至连接配置 * feat: 调整描述 * chore: 调整 PC 在连接配置中的顺序 * feat: 设置指引和开始唤醒界面 PC 选项绑定 --------- Co-authored-by: uye <99072975+ABA2396@users.noreply.github.com>
This commit is contained in:
@@ -23,6 +23,9 @@
|
||||
#include "MaatouchController.h"
|
||||
#include "MinitouchController.h"
|
||||
#include "PlayToolsController.h"
|
||||
#ifdef _WIN32
|
||||
#include "Win32Controller.h"
|
||||
#endif
|
||||
|
||||
#include "Common/AsstTypes.h"
|
||||
#include "Utils/Logger.hpp"
|
||||
@@ -284,6 +287,66 @@ bool asst::Controller::connect(const std::string& adb_path, const std::string& a
|
||||
return true;
|
||||
}
|
||||
|
||||
#ifdef _WIN32
|
||||
bool asst::Controller::attach_window(
|
||||
void* hwnd,
|
||||
Win32ScreencapMethod screencap_method,
|
||||
Win32InputMethod mouse_method,
|
||||
Win32InputMethod keyboard_method)
|
||||
{
|
||||
LogTraceFunction;
|
||||
|
||||
clear_info();
|
||||
|
||||
auto win32_controller = std::make_shared<Win32Controller>(m_callback, m_inst);
|
||||
if (!win32_controller->attach(hwnd, screencap_method, mouse_method, keyboard_method)) {
|
||||
Log.error("attach_window failed");
|
||||
return false;
|
||||
}
|
||||
|
||||
m_controller = win32_controller;
|
||||
m_controller_type = ControllerType::Win32;
|
||||
m_uuid = m_controller->get_uuid();
|
||||
|
||||
// 尝试截图
|
||||
if (!screencap()) {
|
||||
Log.error("Cannot screencap!");
|
||||
return false;
|
||||
}
|
||||
|
||||
auto proxy_callback = [&](const json::object& details) {
|
||||
json::value connection_info = json::object {
|
||||
{ "uuid", m_uuid },
|
||||
{ "details",
|
||||
json::object {
|
||||
{ "hwnd", reinterpret_cast<uintptr_t>(hwnd) },
|
||||
{ "screencap_method", screencap_method },
|
||||
{ "mouse_method", mouse_method },
|
||||
{ "keyboard_method", keyboard_method },
|
||||
} },
|
||||
} | details;
|
||||
callback(AsstMsg::ConnectionInfo, connection_info);
|
||||
};
|
||||
|
||||
try {
|
||||
m_scale_proxy = std::make_shared<ControlScaleProxy>(m_controller, m_controller_type, proxy_callback);
|
||||
}
|
||||
catch (const std::exception& e) {
|
||||
Log.error("Cannot create controller proxy: {}", e.what());
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!m_scale_proxy) {
|
||||
Log.error("Cannot create controller proxy!");
|
||||
return false;
|
||||
}
|
||||
|
||||
m_scale_size = m_scale_proxy->get_scale_size();
|
||||
|
||||
return true;
|
||||
}
|
||||
#endif
|
||||
|
||||
bool asst::Controller::inited() noexcept
|
||||
{
|
||||
CHECK_EXIST(m_controller, false);
|
||||
|
||||
@@ -45,6 +45,9 @@ public:
|
||||
PlatformType platform_type) const;
|
||||
|
||||
bool connect(const std::string& adb_path, const std::string& address, const std::string& config);
|
||||
#ifdef _WIN32
|
||||
bool attach_window(void* hwnd, Win32ScreencapMethod screencap_method, Win32InputMethod mouse_method, Win32InputMethod keyboard_method);
|
||||
#endif
|
||||
bool inited() noexcept;
|
||||
void set_touch_mode(const TouchMode& mode) noexcept;
|
||||
void set_swipe_with_pause(bool enable) noexcept;
|
||||
|
||||
@@ -15,6 +15,9 @@ enum class ControllerType
|
||||
Minitouch,
|
||||
Maatouch,
|
||||
MacPlayTools,
|
||||
#ifdef _WIN32
|
||||
Win32,
|
||||
#endif
|
||||
};
|
||||
|
||||
class ControllerAPI
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
|
||||
#include "Common/AsstTypes.h"
|
||||
#include "Config/GeneralConfig.h"
|
||||
#include "SwipeHelper.hpp"
|
||||
#include "Utils/Logger.hpp"
|
||||
#include "Utils/StringMisc.hpp"
|
||||
|
||||
@@ -183,50 +184,55 @@ bool asst::MinitouchController::swipe(
|
||||
|
||||
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 bounds_check = [this](int x, int y) {
|
||||
return x >= 0 && x <= m_minitouch_props.max_x && y >= 0 && y <= m_minitouch_props.max_y;
|
||||
};
|
||||
|
||||
auto move_func = [this](int x, int y) { return m_minitoucher->move(x, y); };
|
||||
|
||||
auto pause_check = [&opt](int cur_x, int cur_y, int start_x, int start_y) {
|
||||
return std::sqrt(std::pow(cur_x - start_x, 2) + std::pow(cur_y - start_y, 2)) >
|
||||
opt.swipe_with_pause_required_distance;
|
||||
};
|
||||
|
||||
auto pause_action = [this, &pause_future]() {
|
||||
if (m_use_maa_touch) {
|
||||
constexpr int EscKeyCode = 111;
|
||||
(void)m_minitoucher->key_down(EscKeyCode);
|
||||
(void)m_minitoucher->key_up(EscKeyCode, 0);
|
||||
}
|
||||
else {
|
||||
pause_future = std::async(std::launch::async, [this]() { press_esc(); });
|
||||
}
|
||||
};
|
||||
|
||||
auto minitouch_move = [&](int _x1, int _y1, int _x2, int _y2, int _duration) -> bool {
|
||||
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;
|
||||
if (!m_minitoucher->key_down(EscKeyCode)) {
|
||||
return false;
|
||||
}
|
||||
if (!m_minitoucher->key_up(EscKeyCode, 0)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
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;
|
||||
}
|
||||
if (!m_minitoucher->move(cur_x, cur_y)) {
|
||||
return false;
|
||||
}
|
||||
if (need_pause) {
|
||||
bool result = interpolate_swipe_with_pause(
|
||||
_x1,
|
||||
_y1,
|
||||
_x2,
|
||||
_y2,
|
||||
_duration,
|
||||
TimeInterval,
|
||||
slope_in,
|
||||
slope_out,
|
||||
move_func,
|
||||
bounds_check,
|
||||
pause_check,
|
||||
[&]() {
|
||||
need_pause = false;
|
||||
pause_action();
|
||||
});
|
||||
return result;
|
||||
}
|
||||
if (_x2 >= 0 && _x2 <= m_minitouch_props.max_x && _y2 >= 0 && _y2 <= m_minitouch_props.max_y) {
|
||||
if (!m_minitoucher->move(_x2, _y2)) {
|
||||
return false;
|
||||
}
|
||||
else {
|
||||
return interpolate_swipe(_x1, _y1, _x2, _y2, _duration, TimeInterval, slope_in, slope_out, move_func, bounds_check);
|
||||
}
|
||||
return true;
|
||||
};
|
||||
|
||||
if (!minitouch_move(x1, y1, x2, y2, duration ? duration : opt.minitouch_swipe_default_duration)) {
|
||||
@@ -235,7 +241,7 @@ bool asst::MinitouchController::swipe(
|
||||
|
||||
if (extra_swipe && opt.minitouch_extra_swipe_duration > 0) {
|
||||
if (!m_minitoucher->wait(opt.minitouch_swipe_extra_end_delay)) {
|
||||
return false; // 停留终点
|
||||
return false;
|
||||
}
|
||||
if (!minitouch_move(x2, y2, x2, y2 - opt.minitouch_extra_swipe_dist, opt.minitouch_extra_swipe_duration)) {
|
||||
return false;
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
|
||||
#include "Config/GeneralConfig.h"
|
||||
#include "MaaUtils/NoWarningCV.hpp"
|
||||
#include "SwipeHelper.hpp"
|
||||
|
||||
using boost::asio::ip::tcp;
|
||||
namespace socket_ops = boost::asio::detail::socket_ops;
|
||||
@@ -153,26 +154,15 @@ bool asst::PlayToolsController::swipe(
|
||||
|
||||
toucher_down(p1);
|
||||
|
||||
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
|
||||
auto bounds_check = [width, height](int x, int y) { return x >= 0 && x <= width && y >= 0 && y <= height; };
|
||||
|
||||
const auto progressive_move = [&](int _x1, int _y1, int _x2, int _y2, int _duration) {
|
||||
for (int cur_time = DefaultSwipeDelay; cur_time < _duration; cur_time += DefaultSwipeDelay) {
|
||||
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 (cur_x < 0 || cur_x > width || cur_y < 0 || cur_y > height) {
|
||||
continue;
|
||||
}
|
||||
toucher_move({ cur_x, cur_y });
|
||||
}
|
||||
if (_x2 >= 0 && _x2 <= width && _y2 >= 0 && _y2 <= height) {
|
||||
toucher_move({ _x2, _y2 });
|
||||
}
|
||||
auto move_func = [this](int x, int y) {
|
||||
toucher_move({ x, y });
|
||||
return true;
|
||||
};
|
||||
|
||||
auto progressive_move = [&](int _x1, int _y1, int _x2, int _y2, int _duration) {
|
||||
interpolate_swipe(_x1, _y1, _x2, _y2, _duration, DefaultSwipeDelay, slope_in, slope_out, move_func, bounds_check);
|
||||
};
|
||||
|
||||
const auto& opt = Config.get_options();
|
||||
@@ -180,7 +170,7 @@ bool asst::PlayToolsController::swipe(
|
||||
progressive_move(x1, y1, x2, y2, duration ? duration : opt.minitouch_swipe_default_duration);
|
||||
|
||||
if (extra_swipe && opt.minitouch_extra_swipe_duration > 0) {
|
||||
toucher_wait(opt.minitouch_swipe_extra_end_delay); // 停留终点
|
||||
toucher_wait(opt.minitouch_swipe_extra_end_delay);
|
||||
progressive_move(x2, y2, x2, y2 - opt.minitouch_extra_swipe_dist, opt.minitouch_extra_swipe_duration);
|
||||
}
|
||||
|
||||
|
||||
137
src/MaaCore/Controller/SwipeHelper.hpp
Normal file
137
src/MaaCore/Controller/SwipeHelper.hpp
Normal file
@@ -0,0 +1,137 @@
|
||||
#pragma once
|
||||
|
||||
#include <cmath>
|
||||
#include <functional>
|
||||
|
||||
#include "Common/AsstTypes.h"
|
||||
|
||||
namespace asst
|
||||
{
|
||||
|
||||
// 三次样条插值函数,用于生成平滑的滑动曲线
|
||||
// slope_0: 起点斜率,slope_1: 终点斜率,t: 插值进度 [0, 1]
|
||||
inline double 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);
|
||||
}
|
||||
|
||||
// 滑动插值执行器
|
||||
// MoveFunc: bool(int x, int y) - 移动到指定位置的回调,返回 false 表示失败
|
||||
// BoundsCheckFunc: bool(int x, int y) - 边界检查回调,返回 true 表示在边界内
|
||||
template <typename MoveFunc, typename BoundsCheckFunc>
|
||||
bool interpolate_swipe(
|
||||
int x1,
|
||||
int y1,
|
||||
int x2,
|
||||
int y2,
|
||||
int duration,
|
||||
int interval,
|
||||
double slope_in,
|
||||
double slope_out,
|
||||
MoveFunc&& move_func,
|
||||
BoundsCheckFunc&& bounds_check)
|
||||
{
|
||||
for (int cur_time = interval; cur_time < duration; cur_time += interval) {
|
||||
double progress = cubic_spline(slope_in, slope_out, static_cast<double>(cur_time) / duration);
|
||||
int cur_x = static_cast<int>(std::lerp(static_cast<double>(x1), static_cast<double>(x2), progress));
|
||||
int cur_y = static_cast<int>(std::lerp(static_cast<double>(y1), static_cast<double>(y2), progress));
|
||||
|
||||
if (!bounds_check(cur_x, cur_y)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!move_func(cur_x, cur_y)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// 确保到达终点(如果在边界内)
|
||||
if (bounds_check(x2, y2)) {
|
||||
if (!move_func(x2, y2)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// 简化版本:不需要边界检查
|
||||
template <typename MoveFunc>
|
||||
bool interpolate_swipe(
|
||||
int x1,
|
||||
int y1,
|
||||
int x2,
|
||||
int y2,
|
||||
int duration,
|
||||
int interval,
|
||||
double slope_in,
|
||||
double slope_out,
|
||||
MoveFunc&& move_func)
|
||||
{
|
||||
return interpolate_swipe(
|
||||
x1,
|
||||
y1,
|
||||
x2,
|
||||
y2,
|
||||
duration,
|
||||
interval,
|
||||
slope_in,
|
||||
slope_out,
|
||||
std::forward<MoveFunc>(move_func),
|
||||
[](int, int) { return true; });
|
||||
}
|
||||
|
||||
// 带暂停检测的滑动插值执行器(用于 MinitouchController 的 swipe_with_pause 功能)
|
||||
// PauseCheckFunc: bool(int cur_x, int cur_y, int start_x, int start_y) - 检查是否需要暂停
|
||||
// PauseFunc: void() - 执行暂停操作
|
||||
template <typename MoveFunc, typename BoundsCheckFunc, typename PauseCheckFunc, typename PauseFunc>
|
||||
bool interpolate_swipe_with_pause(
|
||||
int x1,
|
||||
int y1,
|
||||
int x2,
|
||||
int y2,
|
||||
int duration,
|
||||
int interval,
|
||||
double slope_in,
|
||||
double slope_out,
|
||||
MoveFunc&& move_func,
|
||||
BoundsCheckFunc&& bounds_check,
|
||||
PauseCheckFunc&& pause_check,
|
||||
PauseFunc&& pause_func)
|
||||
{
|
||||
bool pause_triggered = false;
|
||||
|
||||
for (int cur_time = interval; cur_time < duration; cur_time += interval) {
|
||||
double progress = cubic_spline(slope_in, slope_out, static_cast<double>(cur_time) / duration);
|
||||
int cur_x = static_cast<int>(std::lerp(static_cast<double>(x1), static_cast<double>(x2), progress));
|
||||
int cur_y = static_cast<int>(std::lerp(static_cast<double>(y1), static_cast<double>(y2), progress));
|
||||
|
||||
// 检查是否需要触发暂停
|
||||
if (!pause_triggered && pause_check(cur_x, cur_y, x1, y1)) {
|
||||
pause_triggered = true;
|
||||
pause_func();
|
||||
}
|
||||
|
||||
if (!bounds_check(cur_x, cur_y)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!move_func(cur_x, cur_y)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// 确保到达终点(如果在边界内)
|
||||
if (bounds_check(x2, y2)) {
|
||||
if (!move_func(x2, y2)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace asst
|
||||
119
src/MaaCore/Controller/Win32ControlUnitLoader.cpp
Normal file
119
src/MaaCore/Controller/Win32ControlUnitLoader.cpp
Normal file
@@ -0,0 +1,119 @@
|
||||
#ifdef _WIN32
|
||||
|
||||
#include "Win32ControlUnitLoader.h"
|
||||
|
||||
#include <Windows.h>
|
||||
|
||||
#include "Utils/Logger.hpp"
|
||||
|
||||
namespace asst
|
||||
{
|
||||
|
||||
Win32ControlUnitLoader::Win32ControlUnitLoader() = default;
|
||||
|
||||
Win32ControlUnitLoader::~Win32ControlUnitLoader()
|
||||
{
|
||||
unload();
|
||||
}
|
||||
|
||||
bool Win32ControlUnitLoader::load(const std::filesystem::path& dll_path)
|
||||
{
|
||||
LogTraceFunction;
|
||||
|
||||
if (m_module) {
|
||||
Log.warn("DLL already loaded");
|
||||
return true;
|
||||
}
|
||||
|
||||
std::filesystem::path full_path = dll_path;
|
||||
if (!full_path.has_extension()) {
|
||||
full_path += ".dll";
|
||||
}
|
||||
|
||||
Log.info("Loading", full_path);
|
||||
|
||||
m_module = LoadLibraryW(full_path.wstring().c_str());
|
||||
if (!m_module) {
|
||||
DWORD error = GetLastError();
|
||||
Log.error("Failed to load DLL, error code:", error);
|
||||
return false;
|
||||
}
|
||||
|
||||
m_get_version = reinterpret_cast<GetVersionFunc>(GetProcAddress(static_cast<HMODULE>(m_module), "MaaWin32ControlUnitGetVersion"));
|
||||
m_create = reinterpret_cast<CreateFunc>(GetProcAddress(static_cast<HMODULE>(m_module), "MaaWin32ControlUnitCreate"));
|
||||
m_destroy = reinterpret_cast<DestroyFunc>(GetProcAddress(static_cast<HMODULE>(m_module), "MaaWin32ControlUnitDestroy"));
|
||||
|
||||
if (!m_create || !m_destroy) {
|
||||
Log.error("Failed to get function pointers from DLL");
|
||||
unload();
|
||||
return false;
|
||||
}
|
||||
|
||||
if (m_get_version) {
|
||||
Log.info("MaaWin32ControlUnit version:", m_get_version());
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void Win32ControlUnitLoader::unload()
|
||||
{
|
||||
if (m_module) {
|
||||
FreeLibrary(static_cast<HMODULE>(m_module));
|
||||
m_module = nullptr;
|
||||
}
|
||||
|
||||
m_get_version = nullptr;
|
||||
m_create = nullptr;
|
||||
m_destroy = nullptr;
|
||||
}
|
||||
|
||||
const char* Win32ControlUnitLoader::get_version() const
|
||||
{
|
||||
if (m_get_version) {
|
||||
return m_get_version();
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void* Win32ControlUnitLoader::create(
|
||||
void* hwnd,
|
||||
Win32ScreencapMethod screencap_method,
|
||||
Win32InputMethod mouse_method,
|
||||
Win32InputMethod keyboard_method)
|
||||
{
|
||||
LogTraceFunction;
|
||||
|
||||
if (!m_create) {
|
||||
Log.error("DLL not loaded or create function not available");
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void* handle = m_create(hwnd, screencap_method, mouse_method, keyboard_method);
|
||||
if (!handle) {
|
||||
Log.error("Failed to create Win32ControlUnit");
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
Log.info("Created Win32ControlUnit:", reinterpret_cast<void*>(handle));
|
||||
return handle;
|
||||
}
|
||||
|
||||
void Win32ControlUnitLoader::destroy(void* handle)
|
||||
{
|
||||
LogTraceFunction;
|
||||
|
||||
if (!m_destroy) {
|
||||
Log.error("DLL not loaded or destroy function not available");
|
||||
return;
|
||||
}
|
||||
|
||||
if (handle) {
|
||||
m_destroy(handle);
|
||||
Log.info("Destroyed Win32ControlUnit:", reinterpret_cast<void*>(handle));
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace asst
|
||||
|
||||
#endif // _WIN32
|
||||
107
src/MaaCore/Controller/Win32ControlUnitLoader.h
Normal file
107
src/MaaCore/Controller/Win32ControlUnitLoader.h
Normal file
@@ -0,0 +1,107 @@
|
||||
#pragma once
|
||||
|
||||
#ifdef _WIN32
|
||||
|
||||
#include <cstdint>
|
||||
#include <filesystem>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
|
||||
#include "MaaUtils/SafeWindows.hpp"
|
||||
|
||||
#include "Common/AsstTypes.h"
|
||||
#include "MaaUtils/NoWarningCVMat.hpp"
|
||||
|
||||
namespace asst
|
||||
{
|
||||
// 与 MaaFramework 的 ControlUnitAPI 接口保持 ABI 兼容
|
||||
// 虚函数表布局必须与 MaaFramework 完全一致
|
||||
class MaaControlUnitInterface
|
||||
{
|
||||
public:
|
||||
virtual ~MaaControlUnitInterface() = default;
|
||||
|
||||
virtual bool connect() = 0;
|
||||
virtual bool connected() const = 0;
|
||||
|
||||
virtual bool request_uuid(std::string& uuid) = 0;
|
||||
virtual uint64_t get_features() const = 0;
|
||||
|
||||
virtual bool start_app(const std::string& intent) = 0;
|
||||
virtual bool stop_app(const std::string& intent) = 0;
|
||||
|
||||
virtual bool screencap(cv::Mat& image) = 0;
|
||||
|
||||
virtual bool click(int x, int y) = 0;
|
||||
virtual bool swipe(int x1, int y1, int x2, int y2, int duration) = 0;
|
||||
|
||||
virtual bool touch_down(int contact, int x, int y, int pressure) = 0;
|
||||
virtual bool touch_move(int contact, int x, int y, int pressure) = 0;
|
||||
virtual bool touch_up(int contact) = 0;
|
||||
|
||||
virtual bool click_key(int key) = 0;
|
||||
virtual bool input_text(const std::string& text) = 0;
|
||||
|
||||
virtual bool key_down(int key) = 0;
|
||||
virtual bool key_up(int key) = 0;
|
||||
|
||||
virtual bool scroll(int dx, int dy) = 0;
|
||||
};
|
||||
|
||||
// 与 MaaFramework 的 MaaControllerFeature 兼容的常量
|
||||
namespace MaaFeature
|
||||
{
|
||||
constexpr uint64_t None = 0;
|
||||
constexpr uint64_t UseMouseDownAndUpInsteadOfClick = 1ULL << 1;
|
||||
constexpr uint64_t UseKeyboardDownAndUpInsteadOfClick = 1ULL << 2;
|
||||
} // namespace MaaFeature
|
||||
|
||||
// 动态加载 MaaWin32ControlUnit.dll
|
||||
class Win32ControlUnitLoader
|
||||
{
|
||||
public:
|
||||
Win32ControlUnitLoader();
|
||||
~Win32ControlUnitLoader();
|
||||
|
||||
Win32ControlUnitLoader(const Win32ControlUnitLoader&) = delete;
|
||||
Win32ControlUnitLoader& operator=(const Win32ControlUnitLoader&) = delete;
|
||||
Win32ControlUnitLoader(Win32ControlUnitLoader&&) = delete;
|
||||
Win32ControlUnitLoader& operator=(Win32ControlUnitLoader&&) = delete;
|
||||
|
||||
// 加载 DLL
|
||||
bool load(const std::filesystem::path& dll_path);
|
||||
|
||||
// 卸载 DLL
|
||||
void unload();
|
||||
|
||||
// 是否已加载
|
||||
bool loaded() const noexcept { return m_module != nullptr; }
|
||||
|
||||
// 获取版本
|
||||
const char* get_version() const;
|
||||
|
||||
// 创建控制单元
|
||||
void* create(
|
||||
void* hwnd,
|
||||
Win32ScreencapMethod screencap_method,
|
||||
Win32InputMethod mouse_method,
|
||||
Win32InputMethod keyboard_method);
|
||||
|
||||
// 销毁控制单元
|
||||
void destroy(void* handle);
|
||||
|
||||
private:
|
||||
void* m_module = nullptr;
|
||||
|
||||
// 函数指针类型
|
||||
using GetVersionFunc = const char* (*)();
|
||||
using CreateFunc = void* (*)(void*, uint64_t, uint64_t, uint64_t);
|
||||
using DestroyFunc = void (*)(void*);
|
||||
|
||||
GetVersionFunc m_get_version = nullptr;
|
||||
CreateFunc m_create = nullptr;
|
||||
DestroyFunc m_destroy = nullptr;
|
||||
};
|
||||
} // namespace asst
|
||||
|
||||
#endif // _WIN32
|
||||
383
src/MaaCore/Controller/Win32Controller.cpp
Normal file
383
src/MaaCore/Controller/Win32Controller.cpp
Normal file
@@ -0,0 +1,383 @@
|
||||
#ifdef _WIN32
|
||||
|
||||
#include "Win32Controller.h"
|
||||
|
||||
#include <sstream>
|
||||
#include <thread>
|
||||
|
||||
#include "Config/GeneralConfig.h"
|
||||
#include "SwipeHelper.hpp"
|
||||
#include "Utils/Logger.hpp"
|
||||
#include "Utils/WorkingDir.hpp"
|
||||
|
||||
namespace asst
|
||||
{
|
||||
Win32Controller::Win32Controller(const AsstCallback& callback, Assistant* inst) :
|
||||
InstHelper(inst),
|
||||
m_callback(callback),
|
||||
m_loader(std::make_unique<Win32ControlUnitLoader>())
|
||||
{
|
||||
LogTraceFunction;
|
||||
}
|
||||
|
||||
Win32Controller::~Win32Controller()
|
||||
{
|
||||
LogTraceFunction;
|
||||
|
||||
if (m_unit_handle && m_loader) {
|
||||
m_loader->destroy(m_unit_handle);
|
||||
m_unit_handle = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
bool Win32Controller::attach(
|
||||
void* hwnd,
|
||||
Win32ScreencapMethod screencap_method,
|
||||
Win32InputMethod mouse_method,
|
||||
Win32InputMethod keyboard_method)
|
||||
{
|
||||
LogTraceFunction;
|
||||
|
||||
m_inited = false;
|
||||
m_hwnd = hwnd;
|
||||
m_screencap_method = screencap_method;
|
||||
m_mouse_method = mouse_method;
|
||||
m_keyboard_method = keyboard_method;
|
||||
|
||||
// 销毁旧的控制单元
|
||||
if (m_unit_handle && m_loader) {
|
||||
m_loader->destroy(m_unit_handle);
|
||||
m_unit_handle = nullptr;
|
||||
}
|
||||
|
||||
// 加载 DLL
|
||||
if (!m_loader->loaded()) {
|
||||
auto dll_path = "MaaWin32ControlUnit";
|
||||
if (!m_loader->load(dll_path)) {
|
||||
Log.error("Failed to load MaaWin32ControlUnit.dll");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// 创建控制单元
|
||||
m_unit_handle = m_loader->create(hwnd, screencap_method, mouse_method, keyboard_method);
|
||||
if (!m_unit_handle) {
|
||||
Log.error("Failed to create Win32ControlUnit");
|
||||
return false;
|
||||
}
|
||||
|
||||
// 连接
|
||||
if (!unit_connect()) {
|
||||
Log.error("Failed to connect Win32ControlUnit");
|
||||
m_loader->destroy(m_unit_handle);
|
||||
m_unit_handle = nullptr;
|
||||
return false;
|
||||
}
|
||||
|
||||
// 获取 UUID
|
||||
auto* unit = static_cast<MaaControlUnitInterface*>(m_unit_handle);
|
||||
if (!unit->request_uuid(m_uuid)) {
|
||||
std::stringstream ss;
|
||||
ss << hwnd;
|
||||
m_uuid = ss.str();
|
||||
}
|
||||
|
||||
// 尝试截图获取屏幕分辨率
|
||||
cv::Mat image;
|
||||
if (unit_screencap(image)) {
|
||||
m_screen_size = { image.cols, image.rows };
|
||||
Log.info("Screen size:", m_screen_size.first, "x", m_screen_size.second);
|
||||
}
|
||||
|
||||
m_inited = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Win32Controller::connect(
|
||||
const std::string& adb_path [[maybe_unused]],
|
||||
const std::string& address [[maybe_unused]],
|
||||
const std::string& config [[maybe_unused]])
|
||||
{
|
||||
Log.error("Win32Controller does not support connect(), use attach() instead");
|
||||
return false;
|
||||
}
|
||||
|
||||
bool Win32Controller::inited() const noexcept
|
||||
{
|
||||
return m_inited && m_unit_handle;
|
||||
}
|
||||
|
||||
const std::string& Win32Controller::get_uuid() const
|
||||
{
|
||||
return m_uuid;
|
||||
}
|
||||
|
||||
bool Win32Controller::screencap(cv::Mat& image_payload, bool allow_reconnect [[maybe_unused]])
|
||||
{
|
||||
LogTraceFunction;
|
||||
|
||||
if (!unit_screencap(image_payload)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (m_screen_size.first == 0) {
|
||||
m_screen_size = { image_payload.cols, image_payload.rows };
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Win32Controller::start_game(const std::string& client_type [[maybe_unused]])
|
||||
{
|
||||
Log.warn("start_game is not supported on Win32Controller");
|
||||
return false;
|
||||
}
|
||||
|
||||
bool Win32Controller::stop_game(const std::string& client_type [[maybe_unused]])
|
||||
{
|
||||
Log.warn("stop_game is not supported on Win32Controller");
|
||||
return false;
|
||||
}
|
||||
|
||||
bool Win32Controller::click(const Point& p)
|
||||
{
|
||||
LogTraceFunction;
|
||||
Log.trace("Win32Controller click:", p);
|
||||
|
||||
// MaaWin32ControlUnit 返回 MaaControllerFeature_UseMouseDownAndUpInsteadOfClick
|
||||
// 需要使用 touch_down/touch_up 替代 click
|
||||
if (!unit_touch_down(0, p.x, p.y, 0)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(50));
|
||||
|
||||
return unit_touch_up(0);
|
||||
}
|
||||
|
||||
bool Win32Controller::input(const std::string& text)
|
||||
{
|
||||
LogTraceFunction;
|
||||
return unit_input_text(text);
|
||||
}
|
||||
|
||||
bool Win32Controller::swipe(
|
||||
const Point& p1,
|
||||
const Point& p2,
|
||||
int duration,
|
||||
bool extra_swipe,
|
||||
double slope_in,
|
||||
double slope_out,
|
||||
bool with_pause [[maybe_unused]])
|
||||
{
|
||||
LogTraceFunction;
|
||||
|
||||
int x1 = p1.x, y1 = p1.y;
|
||||
int x2 = p2.x, y2 = p2.y;
|
||||
|
||||
const auto width = m_screen_size.first;
|
||||
const auto height = m_screen_size.second;
|
||||
|
||||
// 起点不能在屏幕外,但是终点可以
|
||||
if (width > 0 && height > 0) {
|
||||
if (x1 < 0 || x1 >= width || y1 < 0 || y1 >= height) {
|
||||
Log.warn("swipe point1 is out of range", x1, y1);
|
||||
x1 = std::clamp(x1, 0, width - 1);
|
||||
y1 = std::clamp(y1, 0, height - 1);
|
||||
}
|
||||
}
|
||||
|
||||
Log.trace("Win32Controller swipe", p1, p2, duration, extra_swipe, slope_in, slope_out);
|
||||
|
||||
// MaaWin32ControlUnit 返回 MaaControllerFeature_UseMouseDownAndUpInsteadOfClick
|
||||
// 需要使用 touch_down/touch_move/touch_up 实现滑动
|
||||
if (!unit_touch_down(0, x1, y1, 0)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const auto& opt = Config.get_options();
|
||||
int actual_duration = duration > 0 ? duration : opt.minitouch_swipe_default_duration;
|
||||
|
||||
auto bounds_check = [width, height](int x, int y) {
|
||||
if (width <= 0 || height <= 0) {
|
||||
return true;
|
||||
}
|
||||
return x >= 0 && x <= width && y >= 0 && y <= height;
|
||||
};
|
||||
|
||||
auto move_func = [this](int x, int y) {
|
||||
return unit_touch_move(0, x, y, 0);
|
||||
};
|
||||
|
||||
auto do_swipe = [&](int _x1, int _y1, int _x2, int _y2, int _duration) {
|
||||
return interpolate_swipe(
|
||||
_x1,
|
||||
_y1,
|
||||
_x2,
|
||||
_y2,
|
||||
_duration,
|
||||
DefaultSwipeDelay,
|
||||
slope_in,
|
||||
slope_out,
|
||||
move_func,
|
||||
bounds_check);
|
||||
};
|
||||
|
||||
if (!do_swipe(x1, y1, x2, y2, actual_duration)) {
|
||||
unit_touch_up(0);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (extra_swipe && opt.minitouch_extra_swipe_duration > 0) {
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(opt.minitouch_swipe_extra_end_delay));
|
||||
do_swipe(x2, y2, x2, y2 - opt.minitouch_extra_swipe_dist, opt.minitouch_extra_swipe_duration);
|
||||
}
|
||||
|
||||
return unit_touch_up(0);
|
||||
}
|
||||
|
||||
bool Win32Controller::inject_input_event(const InputEvent& event)
|
||||
{
|
||||
LogTraceFunction;
|
||||
|
||||
switch (event.type) {
|
||||
case InputEvent::Type::TOUCH_DOWN:
|
||||
return unit_touch_down(event.pointerId, event.point.x, event.point.y, 0);
|
||||
case InputEvent::Type::TOUCH_UP:
|
||||
return unit_touch_up(event.pointerId);
|
||||
case InputEvent::Type::TOUCH_MOVE:
|
||||
return unit_touch_move(event.pointerId, event.point.x, event.point.y, 0);
|
||||
case InputEvent::Type::KEY_DOWN: {
|
||||
auto* unit = static_cast<MaaControlUnitInterface*>(m_unit_handle);
|
||||
return unit ? unit->key_down(event.keycode) : false;
|
||||
}
|
||||
case InputEvent::Type::KEY_UP: {
|
||||
auto* unit = static_cast<MaaControlUnitInterface*>(m_unit_handle);
|
||||
return unit ? unit->key_up(event.keycode) : false;
|
||||
}
|
||||
case InputEvent::Type::WAIT_MS:
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(event.milisec));
|
||||
return true;
|
||||
case InputEvent::Type::TOUCH_RESET:
|
||||
case InputEvent::Type::COMMIT:
|
||||
return true;
|
||||
case InputEvent::Type::UNKNOWN:
|
||||
default:
|
||||
Log.error("unknown input event type");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
bool Win32Controller::press_esc()
|
||||
{
|
||||
LogTraceFunction;
|
||||
return unit_click_key(VK_ESCAPE); // VK_ESCAPE = 0x1B, defined in WinUser.h
|
||||
}
|
||||
|
||||
ControlFeat::Feat Win32Controller::support_features() const noexcept
|
||||
{
|
||||
return ControlFeat::PRECISE_SWIPE;
|
||||
}
|
||||
|
||||
std::pair<int, int> Win32Controller::get_screen_res() const noexcept
|
||||
{
|
||||
return m_screen_size;
|
||||
}
|
||||
|
||||
void Win32Controller::callback(AsstMsg msg, const json::value& details)
|
||||
{
|
||||
if (m_callback) {
|
||||
m_callback(msg, details, m_inst);
|
||||
}
|
||||
}
|
||||
|
||||
bool Win32Controller::unit_connect()
|
||||
{
|
||||
auto* unit = static_cast<MaaControlUnitInterface*>(m_unit_handle);
|
||||
if (!unit) {
|
||||
return false;
|
||||
}
|
||||
return unit->connect();
|
||||
}
|
||||
|
||||
bool Win32Controller::unit_screencap(cv::Mat& image)
|
||||
{
|
||||
auto* unit = static_cast<MaaControlUnitInterface*>(m_unit_handle);
|
||||
if (!unit) {
|
||||
return false;
|
||||
}
|
||||
return unit->screencap(image);
|
||||
}
|
||||
|
||||
bool Win32Controller::unit_click(int x, int y)
|
||||
{
|
||||
auto* unit = static_cast<MaaControlUnitInterface*>(m_unit_handle);
|
||||
if (!unit) {
|
||||
return false;
|
||||
}
|
||||
return unit->click(x, y);
|
||||
}
|
||||
|
||||
bool Win32Controller::unit_swipe(int x1, int y1, int x2, int y2, int duration)
|
||||
{
|
||||
auto* unit = static_cast<MaaControlUnitInterface*>(m_unit_handle);
|
||||
if (!unit) {
|
||||
return false;
|
||||
}
|
||||
return unit->swipe(x1, y1, x2, y2, duration);
|
||||
}
|
||||
|
||||
bool Win32Controller::unit_touch_down(int contact, int x, int y, int pressure)
|
||||
{
|
||||
auto* unit = static_cast<MaaControlUnitInterface*>(m_unit_handle);
|
||||
if (!unit) {
|
||||
return false;
|
||||
}
|
||||
return unit->touch_down(contact, x, y, pressure);
|
||||
}
|
||||
|
||||
bool Win32Controller::unit_touch_move(int contact, int x, int y, int pressure)
|
||||
{
|
||||
auto* unit = static_cast<MaaControlUnitInterface*>(m_unit_handle);
|
||||
if (!unit) {
|
||||
return false;
|
||||
}
|
||||
return unit->touch_move(contact, x, y, pressure);
|
||||
}
|
||||
|
||||
bool Win32Controller::unit_touch_up(int contact)
|
||||
{
|
||||
auto* unit = static_cast<MaaControlUnitInterface*>(m_unit_handle);
|
||||
if (!unit) {
|
||||
return false;
|
||||
}
|
||||
return unit->touch_up(contact);
|
||||
}
|
||||
|
||||
bool Win32Controller::unit_input_text(const std::string& text)
|
||||
{
|
||||
auto* unit = static_cast<MaaControlUnitInterface*>(m_unit_handle);
|
||||
if (!unit) {
|
||||
return false;
|
||||
}
|
||||
return unit->input_text(text);
|
||||
}
|
||||
|
||||
bool Win32Controller::unit_click_key(int key)
|
||||
{
|
||||
auto* unit = static_cast<MaaControlUnitInterface*>(m_unit_handle);
|
||||
if (!unit) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// MaaWin32ControlUnit 返回 MaaControllerFeature_UseKeyboardDownAndUpInsteadOfClick
|
||||
// 需要使用 key_down/key_up 替代 click_key
|
||||
if (!unit->key_down(key)) {
|
||||
return false;
|
||||
}
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(50));
|
||||
return unit->key_up(key);
|
||||
}
|
||||
} // namespace asst
|
||||
|
||||
#endif // _WIN32
|
||||
102
src/MaaCore/Controller/Win32Controller.h
Normal file
102
src/MaaCore/Controller/Win32Controller.h
Normal file
@@ -0,0 +1,102 @@
|
||||
#pragma once
|
||||
|
||||
#ifdef _WIN32
|
||||
|
||||
#include <memory>
|
||||
#include <string>
|
||||
|
||||
#include "MaaUtils/SafeWindows.hpp"
|
||||
|
||||
#include "Common/AsstMsg.h"
|
||||
#include "ControllerAPI.h"
|
||||
#include "InstHelper.h"
|
||||
#include "Win32ControlUnitLoader.h"
|
||||
|
||||
namespace asst
|
||||
{
|
||||
class Assistant;
|
||||
|
||||
class Win32Controller : public ControllerAPI, private InstHelper
|
||||
{
|
||||
public:
|
||||
Win32Controller(const AsstCallback& callback, Assistant* inst);
|
||||
virtual ~Win32Controller() override;
|
||||
|
||||
Win32Controller(const Win32Controller&) = delete;
|
||||
Win32Controller& operator=(const Win32Controller&) = delete;
|
||||
Win32Controller(Win32Controller&&) = delete;
|
||||
Win32Controller& operator=(Win32Controller&&) = delete;
|
||||
|
||||
// 绑定到窗口(替代 connect)
|
||||
bool attach(
|
||||
void* hwnd,
|
||||
Win32ScreencapMethod screencap_method,
|
||||
Win32InputMethod mouse_method,
|
||||
Win32InputMethod keyboard_method);
|
||||
|
||||
public: // ControllerAPI 接口
|
||||
virtual bool connect(const std::string& adb_path, const std::string& address, const std::string& config) override;
|
||||
virtual bool inited() const noexcept override;
|
||||
|
||||
virtual const std::string& get_uuid() const override;
|
||||
|
||||
virtual size_t get_pipe_data_size() const noexcept override { return 0; }
|
||||
|
||||
virtual size_t get_version() const noexcept override { return 0; }
|
||||
|
||||
virtual bool screencap(cv::Mat& image_payload, bool allow_reconnect = false) override;
|
||||
|
||||
virtual bool start_game(const std::string& client_type) override;
|
||||
virtual bool stop_game(const std::string& client_type) override;
|
||||
|
||||
virtual bool click(const Point& p) override;
|
||||
virtual bool input(const std::string& text) override;
|
||||
virtual bool swipe(
|
||||
const Point& p1,
|
||||
const Point& p2,
|
||||
int duration = 0,
|
||||
bool extra_swipe = false,
|
||||
double slope_in = 1,
|
||||
double slope_out = 1,
|
||||
bool with_pause = false) override;
|
||||
|
||||
virtual bool inject_input_event(const InputEvent& event) override;
|
||||
|
||||
virtual bool press_esc() override;
|
||||
virtual ControlFeat::Feat support_features() const noexcept override;
|
||||
|
||||
virtual std::pair<int, int> get_screen_res() const noexcept override;
|
||||
|
||||
private:
|
||||
void callback(AsstMsg msg, const json::value& details);
|
||||
|
||||
// 封装 MaaWin32ControlUnit 的调用
|
||||
bool unit_connect();
|
||||
bool unit_screencap(cv::Mat& image);
|
||||
bool unit_click(int x, int y);
|
||||
bool unit_swipe(int x1, int y1, int x2, int y2, int duration);
|
||||
bool unit_touch_down(int contact, int x, int y, int pressure);
|
||||
bool unit_touch_move(int contact, int x, int y, int pressure);
|
||||
bool unit_touch_up(int contact);
|
||||
bool unit_input_text(const std::string& text);
|
||||
bool unit_click_key(int key);
|
||||
|
||||
private:
|
||||
static constexpr int DefaultSwipeDelay = 10; // ms
|
||||
|
||||
AsstCallback m_callback = nullptr;
|
||||
std::unique_ptr<Win32ControlUnitLoader> m_loader;
|
||||
void* m_unit_handle = nullptr;
|
||||
void* m_hwnd = nullptr;
|
||||
|
||||
bool m_inited = false;
|
||||
std::string m_uuid;
|
||||
std::pair<int, int> m_screen_size = { 0, 0 };
|
||||
|
||||
Win32ScreencapMethod m_screencap_method = Win32Screencap::None;
|
||||
Win32InputMethod m_mouse_method = Win32Input::None;
|
||||
Win32InputMethod m_keyboard_method = Win32Input::None;
|
||||
};
|
||||
} // namespace asst
|
||||
|
||||
#endif // _WIN32
|
||||
Reference in New Issue
Block a user