diff --git a/src/services/cloudProviders.ts b/src/services/cloudProviders.ts new file mode 100644 index 00000000..bbd1e34f --- /dev/null +++ b/src/services/cloudProviders.ts @@ -0,0 +1,52 @@ +import type { CloudConfig, Win32Config } from '@/types/interface'; + +/** + * 云串流服务商的窗口签名与截图/输入方式。Cloud 控制器本质是一个特定配置的 Win32 + * 窗口:按 窗口类 + 标题 定位,使用给定的截图/输入方式驱动。签名与 MaaFramework + * 内置的 provider 注册表保持一致(见 CloudProviders.h)。 + */ +interface CloudProvider { + /** 窗口类名正则 */ + class_regex: string; + /** 标题正则模板,含 {game} 占位符 */ + title_template: string; + /** Win32 截图方式 */ + screencap: string; + /** Win32 鼠标/键盘输入方式 */ + input: string; +} + +const CLOUD_PROVIDERS: Record = { + // GeForce NOW 原生桌面客户端(CEF 流窗口)。签名由 MaaEnd / MaaNTE 实测确认。 + geforce_now: { + class_regex: 'CEFCLIENT', + title_template: '{game}.*on GeForce NOW', + screencap: 'PrintWindow', + input: 'Seize', + }, +}; + +/** 是否为已知云服务商。 */ +export function isKnownCloudProvider(name: string): boolean { + return Object.prototype.hasOwnProperty.call(CLOUD_PROVIDERS, name); +} + +/** + * 把 Cloud 控制器脱糖为等价的 Win32 配置:由 provider 决定窗口类/截图/输入方式, + * 并把 game_title 代入标题模板。未知 provider 返回 null。 + */ +export function cloudToWin32Config(cloud: CloudConfig): Win32Config | null { + const provider = CLOUD_PROVIDERS[cloud.provider]; + if (!provider) { + return null; + } + const game = cloud.game_title ?? ''; + const window_regex = provider.title_template.replace(/\{game\}/g, game); + return { + class_regex: provider.class_regex, + window_regex, + screencap: provider.screencap, + mouse: provider.input, + keyboard: provider.input, + }; +} diff --git a/src/services/interfaceLoader.ts b/src/services/interfaceLoader.ts index f85893fd..9c228f98 100644 --- a/src/services/interfaceLoader.ts +++ b/src/services/interfaceLoader.ts @@ -10,6 +10,7 @@ import type { PretaskItem, } from '@/types/interface'; import { normalizePretaskConfigs } from '@/types/interface'; +import { cloudToWin32Config } from '@/services/cloudProviders'; import { loggers } from '@/utils/logger'; import { parseJsonc } from '@/utils/jsonc'; import { isTauri } from '@/utils/paths'; @@ -363,10 +364,11 @@ function getPlatformFlags(os: string) { function getUnsupportedControllerTypes(os: string): Set { const { isWindows, isMacOS, isLinux } = getPlatformFlags(os); const unsupported = new Set(); - // 非 Windows 系统不支持 Win32 和 Gamepad + // 非 Windows 系统不支持 Win32、Gamepad 和 Cloud(云串流原生客户端仅 Windows) if (!isWindows) { unsupported.add('Win32'); unsupported.add('Gamepad'); + unsupported.add('Cloud'); } // 非 macOS 系统不支持 PlayCover if (!isMacOS) { @@ -379,12 +381,39 @@ function getUnsupportedControllerTypes(os: string): Set { return unsupported; } +/** + * 将 Cloud 控制器脱糖为等价的 Win32 控制器:provider 决定窗口类/标题正则与截图/输入 + * 方式(game_title 代入标题模板)。脱糖后,选择/连接/平台过滤等所有下游逻辑都按 Win32 + * 处理,无需为 Cloud 单独分支。未知 provider 保持原样(不可选,属配置错误)。 + */ +function expandCloudControllers(pi: ProjectInterface): void { + for (const controller of pi.controller) { + if (controller.type !== 'Cloud') continue; + if (!controller.cloud) { + log.warn(`Cloud 控制器 ${controller.name} 缺少 cloud 配置,跳过`); + continue; + } + const win32 = cloudToWin32Config(controller.cloud); + if (!win32) { + log.warn(`未知云服务商 ${controller.cloud.provider}(控制器 ${controller.name})`); + continue; + } + controller.type = 'Win32'; + controller.win32 = win32; + delete controller.cloud; + log.info(`Cloud 控制器 ${controller.name} 已脱糖为 Win32 (${win32.window_regex})`); + } +} + /** * 过滤掉指定平台不支持的控制器 * 在解析阶段直接移除,使后续所有消费 controller 的地方都只看到兼容的控制器 * @param os 后端真实 OS;空串时回退到浏览器平台(dev 预览) */ function filterControllersByPlatform(pi: ProjectInterface, os: string): void { + // 先把 Cloud 脱糖为 Win32,再按平台过滤(GFN 等云串流客户端归为 Windows-only) + expandCloudControllers(pi); + const unsupported = getUnsupportedControllerTypes(os); if (unsupported.size === 0) return; diff --git a/src/types/interface.ts b/src/types/interface.ts index 4a9a56ff..ebc638ee 100644 --- a/src/types/interface.ts +++ b/src/types/interface.ts @@ -111,7 +111,7 @@ export function normalizeAgentConfigs( return Array.isArray(agent) ? agent : [agent]; } -export type ControllerType = 'Adb' | 'Win32' | 'WlRoots' | 'PlayCover' | 'Gamepad'; +export type ControllerType = 'Adb' | 'Win32' | 'WlRoots' | 'PlayCover' | 'Gamepad' | 'Cloud'; export interface ControllerItem { name: string; @@ -132,6 +132,7 @@ export interface ControllerItem { wlroots?: WlRootsConfig; playcover?: PlayCoverConfig; gamepad?: GamepadConfig; + cloud?: CloudConfig; } export interface Win32Config { @@ -158,6 +159,17 @@ export interface GamepadConfig { screencap?: string; } +/** + * 云串流控制器预设(type 为 'Cloud')。框架内置各服务商的窗口签名(进程/窗口类/ + * 标题模板)与截图/输入方式,项目只需声明服务商与游戏名。加载时脱糖为 Win32 控制器。 + */ +export interface CloudConfig { + /** 云服务商标识,目前支持 'geforce_now' */ + provider: string; + /** 游戏名的正则片段,代入服务商标题模板匹配窗口(如 'Endfield') */ + game_title?: string; +} + export interface ResourceItem { name: string; label?: string;