diff --git a/src/main/core/commandLauncher/windowsLauncher.ts b/src/main/core/commandLauncher/windowsLauncher.ts index 80bc614b..7f36e129 100644 --- a/src/main/core/commandLauncher/windowsLauncher.ts +++ b/src/main/core/commandLauncher/windowsLauncher.ts @@ -64,6 +64,34 @@ async function openApplicationViaExplorer( await openWithElectronShell(appPath, fallback) } +async function openUwpApplicationViaExplorer(appId: string): Promise { + const target = `shell:AppsFolder\\${appId}` + + try { + const result = await WindowsShellLauncher.launch({ target, showCommand: 1 }) + if (result.success) { + console.log(`[Launcher] 已通过 Explorer 启动 UWP 应用: ${appId}`) + return + } + + console.warn('[Launcher] Explorer COM 启动 UWP 应用失败,回退原生激活 API:', { + appId, + hresult: result.hresult, + stage: result.stage + }) + } catch (error) { + console.warn('[Launcher] Explorer COM 启动 UWP 应用异常,回退原生激活 API:', { + appId, + error + }) + } + + if (!UwpManager.launchUwpApp(appId)) { + throw new Error(`启动 UWP 应用失败: ${appId}`) + } + console.log(`[Launcher] 已通过原生 API 启动 UWP 应用: ${appId}`) +} + /** * 执行系统命令(不等待进程结束,适用于 GUI 应用) * @param command 完整命令字符串(如 "rundll32 shell32.dll,Control_RunDLL")或命令名 @@ -125,8 +153,7 @@ export async function launchApp( if (appPath.startsWith('uwp:')) { const appId = appPath.slice(4) try { - UwpManager.launchUwpApp(appId) - console.log(`[Launcher] 成功启动 UWP 应用: ${appId}`) + await openUwpApplicationViaExplorer(appId) return } catch (error) { console.error('[Launcher] 启动 UWP 应用失败:', error) diff --git a/tests/main/windowsLauncher.test.ts b/tests/main/windowsLauncher.test.ts new file mode 100644 index 00000000..b9755654 --- /dev/null +++ b/tests/main/windowsLauncher.test.ts @@ -0,0 +1,43 @@ +import { beforeEach, describe, expect, it, vi } from 'vitest' + +const { mockShellLaunch, mockLaunchUwpApp } = vi.hoisted(() => ({ + mockShellLaunch: vi.fn(), + mockLaunchUwpApp: vi.fn() +})) + +vi.mock('electron', () => ({ + dialog: { showMessageBox: vi.fn() }, + shell: { openExternal: vi.fn(), openPath: vi.fn() } +})) +vi.mock('../../src/main/core/native', () => ({ + UwpManager: { launchUwpApp: mockLaunchUwpApp }, + WindowsShellLauncher: { launch: mockShellLaunch } +})) + +import { launchApp } from '../../src/main/core/commandLauncher/windowsLauncher' + +describe('Windows UWP 应用启动', () => { + beforeEach(() => { + vi.clearAllMocks() + mockShellLaunch.mockResolvedValue({ success: true, hresult: 0, stage: 'test' }) + mockLaunchUwpApp.mockReturnValue(true) + }) + + it('通过 Explorer Shell 启动以获得正常的前台焦点交接', async () => { + await launchApp('uwp:Microsoft.WindowsTerminal_8wekyb3d8bbwe!App') + + expect(mockShellLaunch).toHaveBeenCalledWith({ + target: 'shell:AppsFolder\\Microsoft.WindowsTerminal_8wekyb3d8bbwe!App', + showCommand: 1 + }) + expect(mockLaunchUwpApp).not.toHaveBeenCalled() + }) + + it('Explorer Shell 启动失败时回退原生 UWP API', async () => { + mockShellLaunch.mockResolvedValue({ success: false, hresult: -1, stage: 'execute' }) + + await launchApp('uwp:Contoso.App_123!App') + + expect(mockLaunchUwpApp).toHaveBeenCalledWith('Contoso.App_123!App') + }) +})