Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
52 changes: 52 additions & 0 deletions src/services/cloudProviders.ts
Original file line number Diff line number Diff line change
@@ -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<string, CloudProvider> = {
// 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,
};
}
31 changes: 30 additions & 1 deletion src/services/interfaceLoader.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -363,10 +364,11 @@ function getPlatformFlags(os: string) {
function getUnsupportedControllerTypes(os: string): Set<ControllerType> {
const { isWindows, isMacOS, isLinux } = getPlatformFlags(os);
const unsupported = new Set<ControllerType>();
// 非 Windows 系统不支持 Win32 和 Gamepad
// 非 Windows 系统不支持 Win32、GamepadCloud(云串流原生客户端仅 Windows)
if (!isWindows) {
unsupported.add('Win32');
unsupported.add('Gamepad');
unsupported.add('Cloud');
}
// 非 macOS 系统不支持 PlayCover
if (!isMacOS) {
Expand All @@ -379,12 +381,39 @@ function getUnsupportedControllerTypes(os: string): Set<ControllerType> {
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;

Expand Down
14 changes: 13 additions & 1 deletion src/types/interface.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -132,6 +132,7 @@ export interface ControllerItem {
wlroots?: WlRootsConfig;
playcover?: PlayCoverConfig;
gamepad?: GamepadConfig;
cloud?: CloudConfig;
}

export interface Win32Config {
Expand All @@ -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;
Expand Down