installer: 런처 실행을 URL 스킴으로 보강, 완료 후 자동 종료

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 <noreply@anthropic.com>
This commit is contained in:
2026-05-13 01:13:30 +09:00
parent d440514fdc
commit 536e94474f
3 changed files with 49 additions and 13 deletions

View File

@@ -616,13 +616,21 @@ function renderStep5() {
}) })
} }
section.querySelector('#finish').addEventListener('click', async function () { section.querySelector('#finish').addEventListener('click', async function () {
var finishBtn = section.querySelector('#finish')
finishBtn.disabled = true
finishBtn.textContent = '마무리 중…'
try {
if (multi) { if (multi) {
if (section.querySelector('#shortcut').checked) await installerApi.createDesktopShortcut() if (section.querySelector('#shortcut').checked) await installerApi.createDesktopShortcut()
if (section.querySelector('#startServer').checked) await installerApi.startServer() if (section.querySelector('#startServer').checked) await installerApi.startServer()
} }
if (section.querySelector('#startLauncher').checked) await installerApi.startMinecraftLauncher() if (section.querySelector('#startLauncher').checked) await installerApi.startMinecraftLauncher()
section.querySelector('#finish').disabled = true } catch (err) {
section.querySelector('#finish').textContent = '완료됨' // 마무리 액션 실패는 무시하고 종료 진행
}
finishBtn.textContent = '완료됨'
// 모든 단계가 끝났으므로 설치기 종료
if (installerApi.quitApp) installerApi.quitApp()
}) })
} }

View File

@@ -933,17 +933,44 @@ ipcMain.handle('finish:startServer', async () => {
}) })
ipcMain.handle('finish:startLauncher', 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 = [ const candidates = [
path.join(process.env['ProgramFiles(x86)'] ?? 'C:\\Program Files (x86)', 'Minecraft Launcher', 'MinecraftLauncher.exe'), path.join(programFilesX86, 'Minecraft Launcher', 'MinecraftLauncher.exe'),
path.join(process.env['ProgramFiles'] ?? 'C:\\Program Files', '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)) const target = candidates.find((candidate) => {
if (!target) { try { return fs.existsSync(candidate) } catch { return false }
sendLog('Minecraft Launcher를 찾을 수 없습니다. 직접 실행해 주세요.') })
if (target) {
spawn(target, [], { detached: true, stdio: 'ignore' }).unref()
sendLog(`마인크래프트 런처 실행: ${target}`)
return 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(() => { app.whenReady().then(() => {

View File

@@ -45,6 +45,7 @@ const api = {
createDesktopShortcut: (): Promise<void> => ipcRenderer.invoke('finish:desktopShortcut'), createDesktopShortcut: (): Promise<void> => ipcRenderer.invoke('finish:desktopShortcut'),
startServer: (): Promise<void> => ipcRenderer.invoke('finish:startServer'), startServer: (): Promise<void> => ipcRenderer.invoke('finish:startServer'),
startMinecraftLauncher: (): Promise<void> => ipcRenderer.invoke('finish:startLauncher'), startMinecraftLauncher: (): Promise<void> => ipcRenderer.invoke('finish:startLauncher'),
quitApp: (): Promise<void> => ipcRenderer.invoke('app:quit'),
// log stream // log stream
onLog: (handler: (line: string) => void): (() => void) => { onLog: (handler: (line: string) => void): (() => void) => {