diff --git a/installer/renderer.js b/installer/renderer.js index 6cc4c2b..4492478 100644 --- a/installer/renderer.js +++ b/installer/renderer.js @@ -616,13 +616,21 @@ function renderStep5() { }) } section.querySelector('#finish').addEventListener('click', async function () { - if (multi) { - if (section.querySelector('#shortcut').checked) await installerApi.createDesktopShortcut() - if (section.querySelector('#startServer').checked) await installerApi.startServer() + var finishBtn = section.querySelector('#finish') + finishBtn.disabled = true + finishBtn.textContent = '마무리 중…' + try { + if (multi) { + if (section.querySelector('#shortcut').checked) await installerApi.createDesktopShortcut() + if (section.querySelector('#startServer').checked) await installerApi.startServer() + } + if (section.querySelector('#startLauncher').checked) await installerApi.startMinecraftLauncher() + } catch (err) { + // 마무리 액션 실패는 무시하고 종료 진행 } - if (section.querySelector('#startLauncher').checked) await installerApi.startMinecraftLauncher() - section.querySelector('#finish').disabled = true - section.querySelector('#finish').textContent = '완료됨' + finishBtn.textContent = '완료됨' + // 모든 단계가 끝났으므로 설치기 종료 + if (installerApi.quitApp) installerApi.quitApp() }) } diff --git a/src/installer/main.ts b/src/installer/main.ts index 3aa5c79..ee2a55b 100644 --- a/src/installer/main.ts +++ b/src/installer/main.ts @@ -933,17 +933,44 @@ ipcMain.handle('finish:startServer', async () => { }) ipcMain.handle('finish:startLauncher', async () => { + // 1순위: minecraft:// URL 스킴. UWP(Microsoft Store) / Win32 / Xbox 앱 어떤 형태로 설치돼 + // 있어도 OS의 등록된 프로토콜 핸들러가 처리하므로 가장 견고하다. + try { + sendLog('마인크래프트 런처 실행 요청(URL 스킴 minecraft://)...') + await shell.openExternal('minecraft://') + sendLog('마인크래프트 런처 실행 요청 완료.') + return + } catch (err) { + sendLog(`URL 스킴 실행 실패: ${(err as Error).message}. 직접 경로 탐색으로 폴백합니다.`) + } + + // 2순위: 알려진 설치 경로 탐색. 구버전 .exe 설치판 / Xbox 앱 설치 위치까지 커버. + const programFilesX86 = process.env['ProgramFiles(x86)'] ?? 'C:\\Program Files (x86)' + const programFiles = process.env['ProgramFiles'] ?? 'C:\\Program Files' + const localAppData = process.env['LOCALAPPDATA'] ?? path.join(os.homedir(), 'AppData', 'Local') const candidates = [ - path.join(process.env['ProgramFiles(x86)'] ?? 'C:\\Program Files (x86)', 'Minecraft Launcher', 'MinecraftLauncher.exe'), - path.join(process.env['ProgramFiles'] ?? 'C:\\Program Files', 'Minecraft Launcher', 'MinecraftLauncher.exe') + path.join(programFilesX86, 'Minecraft Launcher', 'MinecraftLauncher.exe'), + path.join(programFiles, 'Minecraft Launcher', 'MinecraftLauncher.exe'), + path.join(programFilesX86, 'Minecraft', 'MinecraftLauncher.exe'), + path.join(programFiles, 'Minecraft', 'MinecraftLauncher.exe'), + 'C:\\XboxGames\\Minecraft Launcher\\Content\\Minecraft.exe', + path.join(localAppData, 'Programs', 'minecraft-launcher', 'MinecraftLauncher.exe') ] - const target = candidates.find((candidate) => fs.existsSync(candidate)) - if (!target) { - sendLog('Minecraft Launcher를 찾을 수 없습니다. 직접 실행해 주세요.') + const target = candidates.find((candidate) => { + try { return fs.existsSync(candidate) } catch { return false } + }) + if (target) { + spawn(target, [], { detached: true, stdio: 'ignore' }).unref() + sendLog(`마인크래프트 런처 실행: ${target}`) return } - spawn(target, [], { detached: true, stdio: 'ignore' }).unref() - sendLog('마인크래프트 런처 실행 요청 완료.') + + sendLog('Minecraft Launcher를 찾을 수 없습니다. Microsoft Store에서 "Minecraft Launcher"를 설치하거나 minecraft.net에서 받은 뒤 직접 실행해 주세요.') +}) + +ipcMain.handle('app:quit', () => { + // 모든 창을 닫고 앱 종료. macOS에서도 종료(설치기는 한 번 쓰고 끝이니 잔류시키지 않음). + app.quit() }) app.whenReady().then(() => { diff --git a/src/installer/preload.ts b/src/installer/preload.ts index 465405e..acdc9a6 100644 --- a/src/installer/preload.ts +++ b/src/installer/preload.ts @@ -45,6 +45,7 @@ const api = { createDesktopShortcut: (): Promise => ipcRenderer.invoke('finish:desktopShortcut'), startServer: (): Promise => ipcRenderer.invoke('finish:startServer'), startMinecraftLauncher: (): Promise => ipcRenderer.invoke('finish:startLauncher'), + quitApp: (): Promise => ipcRenderer.invoke('app:quit'), // log stream onLog: (handler: (line: string) => void): (() => void) => {