From 536e94474f974849bc3c62cfe725ead05f47683e Mon Sep 17 00:00:00 2001 From: claude-bot Date: Wed, 13 May 2026 01:13:30 +0900 Subject: [PATCH] =?UTF-8?q?installer:=20=EB=9F=B0=EC=B2=98=20=EC=8B=A4?= =?UTF-8?q?=ED=96=89=EC=9D=84=20URL=20=EC=8A=A4=ED=82=B4=EC=9C=BC=EB=A1=9C?= =?UTF-8?q?=20=EB=B3=B4=EA=B0=95,=20=EC=99=84=EB=A3=8C=20=ED=9B=84=20?= =?UTF-8?q?=EC=9E=90=EB=8F=99=20=EC=A2=85=EB=A3=8C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Minecraft Launcher 실행 핸들러가 옛 Program Files 경로 두 곳만 보고 있어서 Microsoft Store/UWP/Xbox 앱 설치 등 최근 설치 형태에서 거의 못 찾았다. - 1순위로 shell.openExternal('minecraft://') 사용. OS에 등록된 프로토콜 핸들러가 설치 형태(UWP/Win32/Xbox)에 무관하게 처리. - 폴백 경로 후보 확장: Program Files / Program Files (x86) 양쪽의 Minecraft, Minecraft Launcher, XboxGames 경로, LOCALAPPDATA\Programs\minecraft-launcher까지 검사. - 못 찾았을 때 메시지에 설치처(Microsoft Store/minecraft.net) 안내 추가. 5단계 완료 버튼: 모든 단계가 끝난 뒤이므로 마무리 액션(바로가기/서버 실행/런처 실행)을 처리한 다음 app.quit으로 설치기를 자동 종료한다. 'app:quit' IPC 핸들러와 preload 노출(quitApp) 추가. Co-Authored-By: Claude Opus 4.7 --- installer/renderer.js | 20 ++++++++++++++------ src/installer/main.ts | 41 +++++++++++++++++++++++++++++++++------- src/installer/preload.ts | 1 + 3 files changed, 49 insertions(+), 13 deletions(-) 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) => {