-
Notifications
You must be signed in to change notification settings - Fork 543
feat(controller): first-class Cloud controller preset (GeForce NOW) #1400
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -20,6 +20,7 @@ | |||||||||
| #include "MaaUtils/Encoding.h" | ||||||||||
| #include "MaaUtils/Logger.h" | ||||||||||
| #include "MaaUtils/Platform.h" | ||||||||||
| #include "ProjectInterface/CloudProviders.h" | ||||||||||
| #include "ProjectInterface/Runner.h" | ||||||||||
|
|
||||||||||
| static bool s_eof = false; | ||||||||||
|
|
@@ -335,6 +336,7 @@ void Interactor::print_config() const | |||||||||
| std::format("\t\t{}\n\t\t{}\n", config_.configuration().adb.adb_path, config_.configuration().adb.address)); | ||||||||||
| break; | ||||||||||
| case InterfaceData::Controller::Type::Win32: | ||||||||||
| case InterfaceData::Controller::Type::Cloud: | ||||||||||
| if (config_.configuration().win32.hwnd) { | ||||||||||
| std::cout << MAA_NS::utf8_to_crt(std::format("\t\t{}\n", format_win32_config(config_.configuration().win32))); | ||||||||||
| } | ||||||||||
|
|
@@ -624,6 +626,10 @@ void Interactor::select_controller() | |||||||||
| config_.configuration().controller.type = InterfaceData::Controller::Type::Win32; | ||||||||||
| select_win32_hwnd(controller.win32); | ||||||||||
| break; | ||||||||||
| case InterfaceData::Controller::Type::Cloud: | ||||||||||
| config_.configuration().controller.type = InterfaceData::Controller::Type::Cloud; | ||||||||||
| select_cloud_hwnd(controller.cloud); | ||||||||||
| break; | ||||||||||
| case InterfaceData::Controller::Type::MacOS: | ||||||||||
| config_.configuration().controller.type = InterfaceData::Controller::Type::MacOS; | ||||||||||
| select_macos(controller.macos); | ||||||||||
|
|
@@ -881,6 +887,82 @@ bool Interactor::select_win32_hwnd(const MAA_PROJECT_INTERFACE_NS::InterfaceData | |||||||||
| return true; | ||||||||||
| } | ||||||||||
|
|
||||||||||
| bool Interactor::select_cloud_hwnd(const MAA_PROJECT_INTERFACE_NS::InterfaceData::Controller::CloudConfig& cloud_config) | ||||||||||
| { | ||||||||||
| using namespace MAA_PROJECT_INTERFACE_NS; | ||||||||||
|
|
||||||||||
| const CloudProvider* provider = find_cloud_provider(cloud_config.provider); | ||||||||||
| if (!provider) { | ||||||||||
| LogError << "Unknown cloud provider" << VAR(cloud_config.provider); | ||||||||||
| mpause(); | ||||||||||
| return false; | ||||||||||
| } | ||||||||||
|
|
||||||||||
| auto list_handle = MaaToolkitDesktopWindowListCreate(); | ||||||||||
| OnScopeLeave([&]() { MaaToolkitDesktopWindowListDestroy(list_handle); }); | ||||||||||
|
|
||||||||||
| MaaToolkitDesktopWindowFindAll(list_handle); | ||||||||||
|
|
||||||||||
| size_t list_size = MaaToolkitDesktopWindowListSize(list_handle); | ||||||||||
|
|
||||||||||
| // Cloud desugars to Win32: match the provider's window class + composed title, | ||||||||||
| // additionally filtered by the owning process for robustness. The resolved HWND | ||||||||||
| // is stored in the shared win32 config slot. | ||||||||||
| std::string window_regex_str = cloud_window_regex(*provider, cloud_config.game_title); | ||||||||||
| auto class_regex = MAA_NS::regex_valid(MAA_NS::to_u16(provider->class_regex)); | ||||||||||
| auto window_regex = MAA_NS::regex_valid(MAA_NS::to_u16(window_regex_str)); | ||||||||||
| auto process_regex = MAA_NS::regex_valid(MAA_NS::to_u16(provider->process_regex)); | ||||||||||
| if (!class_regex || !window_regex || !process_regex) { | ||||||||||
| LogError << "regex is invalid" << VAR(provider->class_regex) << VAR(window_regex_str) << VAR(provider->process_regex); | ||||||||||
| return false; | ||||||||||
| } | ||||||||||
|
|
||||||||||
| std::vector<Configuration::Win32Config> matched_config; | ||||||||||
| for (size_t i = 0; i < list_size; ++i) { | ||||||||||
| Configuration::Win32Config rt_config; | ||||||||||
|
|
||||||||||
| auto window_handle = MaaToolkitDesktopWindowListAt(list_handle, i); | ||||||||||
| rt_config.hwnd = MaaToolkitDesktopWindowGetHandle(window_handle); | ||||||||||
| rt_config.class_name = MAA_NS::to_u16(MaaToolkitDesktopWindowGetClassName(window_handle)); | ||||||||||
| rt_config.window_name = MAA_NS::to_u16(MaaToolkitDesktopWindowGetWindowName(window_handle)); | ||||||||||
| std::wstring process_path = MAA_NS::to_u16(MaaToolkitDesktopWindowGetProcessPath(window_handle)); | ||||||||||
|
|
||||||||||
| // Process filter is applied only when the process path is available; if it | ||||||||||
| // cannot be queried, fall back to class + title (the baseline that MaaEnd | ||||||||||
| // and MaaNTE both ship). | ||||||||||
| bool process_ok = process_path.empty() || boost::regex_search(process_path, *process_regex); | ||||||||||
|
|
||||||||||
| if (process_ok && boost::regex_search(rt_config.class_name, *class_regex) | ||||||||||
| && boost::regex_search(rt_config.window_name, *window_regex)) { | ||||||||||
| matched_config.emplace_back(std::move(rt_config)); | ||||||||||
| } | ||||||||||
| } | ||||||||||
|
|
||||||||||
| if (matched_config.empty()) { | ||||||||||
| LogError << "Cloud window not found" << VAR(cloud_config.provider) << VAR(provider->class_regex) << VAR(window_regex_str) | ||||||||||
| << VAR(provider->process_regex); | ||||||||||
| mpause(); | ||||||||||
| return false; | ||||||||||
| } | ||||||||||
| size_t matched_size = matched_config.size(); | ||||||||||
| if (matched_size == 1) { | ||||||||||
| config_.configuration().win32 = matched_config.front(); | ||||||||||
| return true; | ||||||||||
| } | ||||||||||
|
|
||||||||||
| std::cout << "### Select HWND ###\n\n"; | ||||||||||
|
|
||||||||||
| for (size_t i = 0; i < matched_size; ++i) { | ||||||||||
| std::cout << MAA_NS::utf8_to_crt(std::format("\t{}. {}\n", i + 1, format_win32_config(matched_config.at(i)))); | ||||||||||
| } | ||||||||||
| std::cout << "\n"; | ||||||||||
|
|
||||||||||
| int index = input(matched_size) - 1; | ||||||||||
| config_.configuration().win32 = matched_config.at(index); | ||||||||||
|
|
||||||||||
| return true; | ||||||||||
| } | ||||||||||
|
|
||||||||||
| void Interactor::select_gamepad(const MAA_PROJECT_INTERFACE_NS::InterfaceData::Controller::GamepadConfig& gamepad_config) | ||||||||||
| { | ||||||||||
| using namespace MAA_PROJECT_INTERFACE_NS; | ||||||||||
|
|
@@ -1695,6 +1777,19 @@ bool Interactor::check_validity() | |||||||||
| return select_win32_hwnd(controller_iter->win32); | ||||||||||
| } | ||||||||||
|
|
||||||||||
| if (config_.configuration().controller.type == InterfaceData::Controller::Type::Cloud | ||||||||||
| && config_.configuration().win32.hwnd == nullptr) { | ||||||||||
| auto& name = config_.configuration().controller.name; | ||||||||||
| auto controller_iter = std::ranges::find(config_.interface_data().controller, name, std::mem_fn(&InterfaceData::Controller::name)); | ||||||||||
|
|
||||||||||
| if (controller_iter == config_.interface_data().controller.end()) { | ||||||||||
| LogError << "Contorller not found" << VAR(name); | ||||||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. nitpick (typo): 缺少控制器时的错误日志字符串中有拼写错误。 日志消息中 "Controller" 拼写不正确。请修正以便日志可被正确检索,并避免调试时造成困扰。
Suggested change
Original comment in Englishnitpick (typo): Typo in error message string for missing controller. The log message spells "Controller" incorrectly. Please correct it to keep logs searchable and avoid confusion during debugging.
Suggested change
|
||||||||||
| return false; | ||||||||||
| } | ||||||||||
|
|
||||||||||
| return select_cloud_hwnd(controller_iter->cloud); | ||||||||||
| } | ||||||||||
|
|
||||||||||
| if (config_.configuration().controller.type == InterfaceData::Controller::Type::MacOS) { | ||||||||||
| auto& mac = config_.configuration().macos; | ||||||||||
| if (mac.window_id == 0 || mac.title.empty()) { | ||||||||||
|
|
||||||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
suggestion: Cloud 控制器目前只打印由 Win32 hwnd 推导出的信息,可能遗漏有用的 provider/游戏上下文。
由于 Cloud 分支直接复用 Win32 的逻辑,它只会在
win32.hwnd已设置时输出,并且忽略 Cloud 特有的元数据(例如 provider 名称、游戏标题)。请扩展 Cloud 分支的打印逻辑,加入 Cloud 配置的相关信息,并确保即使 Win32 句柄尚未解析成功时也能有输出。Suggested implementation:
format_cloud_config(const decltype(config_.configuration().cloud)& cloud)辅助函数(或等价实现),返回包含 provider 名称、游戏标题以及其他 Cloud 相关元数据的人类可读std::string。将其与format_win32_config放在一起,以保持格式化逻辑一致。format_cloud_config中访问字段的方式(config_.configuration().cloud)。select_cloud_hwnd依赖或填充了额外的 Cloud 相关字段,可考虑扩展format_cloud_config,在这些字段可用时选择性地输出它们。Original comment in English
suggestion: Cloud controller prints only Win32 hwnd-derived info, which may omit useful provider/game context.
Since the Cloud case just reuses the Win32 path, it only prints when
win32.hwndis set and omits Cloud-specific metadata (e.g., provider name, game title). Please extend the Cloud printing logic to include Cloud configuration details, and ensure something is printed even when the Win32 handle hasn’t been resolved yet.Suggested implementation:
format_cloud_config(const decltype(config_.configuration().cloud)& cloud)helper (or equivalent) that returns a human-readablestd::stringincluding provider name, game title, and any other relevant Cloud metadata. Place it alongsideformat_win32_configto keep formatting logic consistent.format_cloud_config(config_.configuration().cloud) if the actual configuration type/layout differs (e.g., nested under another struct or different member names).select_cloud_hwnddepends on or populates additional Cloud-related fields, consider extendingformat_cloud_configto optionally include those fields when they are available.