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:
MistEO
2026-01-18 21:17:59 +08:00
committed by GitHub
parent 99bde4818e
commit a11660cbac
34 changed files with 2042 additions and 353 deletions

View File

@@ -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);

View File

@@ -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;

View File

@@ -15,6 +15,9 @@ enum class ControllerType
Minitouch,
Maatouch,
MacPlayTools,
#ifdef _WIN32
Win32,
#endif
};
class ControllerAPI

View File

@@ -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;

View File

@@ -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);
}

View 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

View 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

View 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

View 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

View 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