Skip to content
Merged
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
57 changes: 36 additions & 21 deletions src/components/ConnectionPanel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import type { AdbDevice, Win32Window, GamescopeInstance, ControllerConfig } from
import type { ControllerItem, ResourceItem } from '@/types/interface';
import { computeResourcePaths } from '@/utils/resourcePath';
import { buildPlayCoverControllerConfig } from '@/utils/playCoverController';
import { hasValidUinputDimensions, parseUinputDimension } from '@/utils/linuxUinput';
import {
buildDesktopWindowControllerConfig,
buildLinuxControllerConfig,
Expand All @@ -42,14 +43,6 @@ import {
autoReconnectAttempted,
} from './connection';

/** 将 uinput 尺寸输入字符串解析为正整数;空串或非法输入返回 undefined */
function parseUinputDimension(value: string): number | undefined {
const trimmed = value.trim();
if (!trimmed) return undefined;
const n = Number.parseInt(trimmed, 10);
return Number.isFinite(n) && n > 0 ? n : undefined;
}

export function ConnectionPanel() {
const { t } = useTranslation();
const {
Expand Down Expand Up @@ -1040,6 +1033,14 @@ export function ConnectionPanel() {
// Linux:判断给定选择是否满足当前配置所需的全部设备
const isLinuxReadyWith = (instance: GamescopeInstance | null, wlr: string | null) => {
if (!linuxNeeds) return false;
if (
isUinputInput &&
!hasValidUinputDimensions(
parseUinputDimension(uinputScreenWidth),
parseUinputDimension(uinputScreenHeight),
)
)
return false;
if (linuxNeeds.needGamescopeNode || linuxNeeds.needEisSocket) {
if (!instance) return false;
if (linuxNeeds.needGamescopeNode && instance.pipewire_node_id === 0) return false;
Expand All @@ -1055,6 +1056,21 @@ export function ConnectionPanel() {
setDeviceError(null);

try {
const parsedWidth = parseUinputDimension(uinputScreenWidth);
const parsedHeight = parseUinputDimension(uinputScreenHeight);

const config = buildLinuxControllerConfig(
currentController,
{
wlrSocketPath: wlr ?? undefined,
pwNodeId: instance?.pipewire_node_id,
eisSocketPath: instance?.eis_socket_path || undefined,
uinputScreenWidth: parsedWidth,
uinputScreenHeight: parsedHeight,
},
t('controller.uinputDimensionsRequired'),
);

if (isConnected) {
await maaService.destroyInstance(instanceId).catch(() => {});
setIsConnected(false);
Expand All @@ -1068,17 +1084,6 @@ export function ConnectionPanel() {

await maaService.createInstance(instanceId).catch(() => {});

const parsedWidth = parseUinputDimension(uinputScreenWidth);
const parsedHeight = parseUinputDimension(uinputScreenHeight);

const config = buildLinuxControllerConfig(currentController, {
wlrSocketPath: wlr ?? undefined,
pwNodeId: instance?.pipewire_node_id,
eisSocketPath: instance?.eis_socket_path || undefined,
uinputScreenWidth: parsedWidth,
uinputScreenHeight: parsedHeight,
});

saveLinuxDeviceInfo({
uinputScreenWidth: parsedWidth,
uinputScreenHeight: parsedHeight,
Expand Down Expand Up @@ -1614,7 +1619,12 @@ export function ConnectionPanel() {
type="number"
min={1}
value={uinputScreenWidth}
onChange={(e) => setUinputScreenWidth(e.target.value)}
onChange={(e) => {
setUinputScreenWidth(e.target.value);
saveLinuxDeviceInfo({
uinputScreenWidth: parseUinputDimension(e.target.value),
});
}}
placeholder={t('controller.uinputWidth')}
disabled={isConnected || isConnecting || isRunning}
className={clsx(
Expand All @@ -1628,7 +1638,12 @@ export function ConnectionPanel() {
type="number"
min={1}
value={uinputScreenHeight}
onChange={(e) => setUinputScreenHeight(e.target.value)}
onChange={(e) => {
setUinputScreenHeight(e.target.value);
saveLinuxDeviceInfo({
uinputScreenHeight: parseUinputDimension(e.target.value),
});
}}
placeholder={t('controller.uinputHeight')}
disabled={isConnected || isConnecting || isRunning}
className={clsx(
Expand Down
1 change: 1 addition & 0 deletions src/components/Toolbar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -1290,6 +1290,7 @@ export function Toolbar({ showAddPanel, onToggleAddPanel, className }: ToolbarPr
const cancelled = errMsg === PRE_ACTION_CANCELLED_ERROR;
if (!cancelled) {
const localizedErrMsg = errMsg
.replace('UINPUT_SCREEN_SIZE_REQUIRED', t('controller.uinputDimensionsRequired'))
.replace(
' [[hint:spawn_file_not_found]]',
` ${t('taskList.autoConnect.agentSpawnHintFileNotFound')}`,
Expand Down
2 changes: 2 additions & 0 deletions src/i18n/locales/en-US.ts
Original file line number Diff line number Diff line change
Expand Up @@ -412,6 +412,8 @@ export default {
portal: 'Portal',
uinputWidth: 'Width (px)',
uinputHeight: 'Height (px)',
uinputDimensionsRequired:
'Enter valid UInput screen width and height (positive integers) first.',
playcover: 'PlayCover (macOS)',
macos: 'macOS Window',
macosPermissionsRequired:
Expand Down
1 change: 1 addition & 0 deletions src/i18n/locales/ja-JP.ts
Original file line number Diff line number Diff line change
Expand Up @@ -407,6 +407,7 @@ export default {
portal: 'Portal',
uinputWidth: '幅 (px)',
uinputHeight: '高さ (px)',
uinputDimensionsRequired: 'UInput の画面の幅と高さを正の整数で入力してください。',
playcover: 'PlayCover (macOS)',
macos: 'macOS ウィンドウ',
macosPermissionsRequired:
Expand Down
1 change: 1 addition & 0 deletions src/i18n/locales/ko-KR.ts
Original file line number Diff line number Diff line change
Expand Up @@ -402,6 +402,7 @@ export default {
portal: 'Portal',
uinputWidth: '너비 (px)',
uinputHeight: '높이 (px)',
uinputDimensionsRequired: 'UInput 화면 너비와 높이를 양의 정수로 먼저 입력하세요.',
playcover: 'PlayCover (macOS)',
macos: 'macOS 창',
macosPermissionsRequired:
Expand Down
1 change: 1 addition & 0 deletions src/i18n/locales/zh-CN.ts
Original file line number Diff line number Diff line change
Expand Up @@ -399,6 +399,7 @@ export default {
portal: 'Portal',
uinputWidth: '宽 (px)',
uinputHeight: '高 (px)',
uinputDimensionsRequired: '请先填写有效的 UInput 屏幕宽度和高度(正整数)',
playcover: 'PlayCover (macOS)',
macos: 'macOS 窗口',
macosPermissionsRequired:
Expand Down
1 change: 1 addition & 0 deletions src/i18n/locales/zh-TW.ts
Original file line number Diff line number Diff line change
Expand Up @@ -395,6 +395,7 @@ export default {
portal: 'Portal',
uinputWidth: '寬 (px)',
uinputHeight: '高 (px)',
uinputDimensionsRequired: '請先填寫有效的 UInput 螢幕寬度和高度(正整數)',
playcover: 'PlayCover (macOS)',
macos: 'macOS 視窗',
macosPermissionsRequired:
Expand Down
8 changes: 8 additions & 0 deletions src/utils/controller.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import type { ControllerItem, ControllerType, SavedDeviceInfo } from '@/types/interface';
import type { AdbDevice, ControllerConfig } from '@/types/maa';
import { hasValidUinputDimensions } from '@/utils/linuxUinput';
import {
LinuxInputMethod,
LinuxScreencapMethod,
Expand Down Expand Up @@ -248,8 +249,15 @@ export function buildLinuxControllerConfig(
uinputScreenWidth?: number;
uinputScreenHeight?: number;
},
uinputDimensionsError = 'UINPUT_SCREEN_SIZE_REQUIRED',
): ControllerConfig {
const linux = controller?.linux;
if (
parseLinuxInputMethod(linux?.input) === LinuxInputMethod.UInput &&
!hasValidUinputDimensions(discovery.uinputScreenWidth, discovery.uinputScreenHeight)
) {
throw new Error(uinputDimensionsError);
}

return {
type: 'Linux',
Expand Down
11 changes: 11 additions & 0 deletions src/utils/linuxUinput.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
export function hasValidUinputDimensions(width?: number, height?: number): boolean {
return [width, height].every(
(value) => value !== undefined && Number.isInteger(value) && value > 0 && value <= 2147483647,
);
}

/** 空值或非法尺寸不能作为 UInput 的绝对坐标范围。 */
export function parseUinputDimension(value: string): number | undefined {
const n = Number(value.trim());
return hasValidUinputDimensions(n, n) ? n : undefined;
}
31 changes: 31 additions & 0 deletions tests/linuxController.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import { test } from 'node:test';
import assert from 'node:assert/strict';

import { parseUinputDimension, hasValidUinputDimensions } from '../src/utils/linuxUinput.ts';

test('empty or malformed UInput sizes cannot reach the controller', () => {
for (const input of ['', ' ', '0', '-1', '1.5', '1920px', 'Infinity', '2147483648']) {
assert.equal(parseUinputDimension(input), undefined, input);
}
});

test('UInput sizes are parsed without truncation', () => {
assert.equal(parseUinputDimension(' 2560 '), 2560);
assert.equal(parseUinputDimension('1440'), 1440);
assert.equal(parseUinputDimension('2147483647'), 2147483647);
});

test('both UInput sizes must be positive signed 32-bit integers', () => {
for (const value of [undefined, 0, -1, 1.5, NaN, Infinity, 2147483648]) {
assert.equal(hasValidUinputDimensions(value, 1440), false);
assert.equal(hasValidUinputDimensions(2560, value), false);
}
assert.equal(hasValidUinputDimensions(2560, 1440), true);
});

test('clearing one previously valid size invalidates the saved dimensions', () => {
const saved = { width: parseUinputDimension('2560'), height: parseUinputDimension('1440') };
assert.equal(hasValidUinputDimensions(saved.width, saved.height), true);
saved.width = parseUinputDimension('');
assert.equal(hasValidUinputDimensions(saved.width, saved.height), false);
});
Loading