cleanup comments, update translations, bump to 0.4.1

This commit is contained in:
olcxja 2026-10-05 15:42:44 +02:00
commit 7d8ab87eee
28 changed files with 412 additions and 1114 deletions

View file

@ -25,9 +25,7 @@ function errorResult(err) {
const iconDir = id => path.join(DIRS.DATA, 'icons', id)
// `keep` spares one file (the icon just written): the icon protocol picks the
// LAST icon.* alphabetically, so a leftover icon.png from an earlier pick
// would otherwise keep showing the old icon.
// `keep` spares the icon just written: the protocol picks the LAST icon.*.
function clearIconDir(id, keep) {
try {
for (const f of fs.readdirSync(iconDir(id))) {
@ -77,8 +75,7 @@ function registerIpc({ getWindow, onGamesChanged }) {
ipcMain.handle('wines:setDisplayName', (e, id, name) => wines.setWineDisplayName(id, name))
ipcMain.handle('wines:setDefault', (e, id) => wines.setDefaultWine(id))
ipcMain.handle('wines:delete', async (e, id) => {
// the build's files back the running prefix (dlls, stub exes) - deleting
// it under a running game breaks that game
// the build's files back a running prefix; deleting it breaks that game
if (library.isWineInUse(id, games.runningInfo())) {
const apps = games.runningInfo().filter(r => r.wine === id).map(r => r.name)
return { ok: false, error: 'inUse', apps }
@ -94,10 +91,7 @@ function registerIpc({ getWindow, onGamesChanged }) {
})
// prefixes ---------------------------------------------------------------------
// A prefix folder must not move or disappear while a game runs in it: wine
// keeps the registry and user files open and would write into a folder that
// no longer exists (or, for a delete, lose them mid-run). The renderer shows
// these as a toast naming the blocking app.
// A prefix must not move/disappear while a game runs in it.
const prefixInUse = name => {
const apps = library.appsUsingPrefix(name, games.runningInfo())
return apps.length ? { ok: false, error: 'inUse', apps: apps.map(r => r.name) } : null
@ -107,17 +101,14 @@ function registerIpc({ getWindow, onGamesChanged }) {
ipcMain.handle('prefixes:delete', async (e, name) => {
const busy = prefixInUse(name)
if (busy) return busy
// deleting a prefix that still has apps registered in it would orphan
// them (they fall back to the default prefix and lose their files)
// deleting a prefix with registered apps would orphan them
const users = library.gamesUsingPrefix(name).map(g => g.name)
if (users.length) return { ok: false, error: 'hasApps', apps: users }
return prefixes.deletePrefix(name, data => sendToWindow(getWindow(), 'import:progress', data))
})
ipcMain.handle('prefixes:rename', (e, oldName, newName) => prefixInUse(oldName) || prefixes.renamePrefix(oldName, newName))
ipcMain.handle('prefixes:setDefault', (e, name) => {
// apps without a prefix field of their own resolve the default at launch;
// switching it now would silently move their next run (a prefix import is
// pinned to its own name)
// switching the default would silently move apps that follow it
const defs = prefixes.listPrefixes()
if (name !== defs.default) {
const affected = library.gamesFollowingDefault(games.runningInfo())
@ -129,8 +120,7 @@ function registerIpc({ getWindow, onGamesChanged }) {
// games: library -----------------------------------------------------------------
ipcMain.handle('games:list', () => library.loadGames())
ipcMain.handle('games:get', (e, id) => library.getGame(id))
// prefix pin is enforced inside library.updateGame (prefix/importSource
// fields are ignored for prefix imports), so nothing extra needed here
// the prefix pin is enforced inside library.updateGame
ipcMain.handle('games:update', (e, id, patch) => library.updateGame(id, patch))
ipcMain.handle('games:reorder', (e, ids) => library.reorderGames(ids))
ipcMain.handle('games:remove', (e, id) => {
@ -187,8 +177,7 @@ function registerIpc({ getWindow, onGamesChanged }) {
if (!game || !game.exePath) return { ok: false, error: 'bad-request' }
return extractGameIcon(id, game.exePath)
})
// exePath comes from the config field, which may hold a path the user just
// picked but has not saved yet - the icon has to come from THAT exe
// use the exe the field holds (maybe picked but not saved yet)
ipcMain.handle('games:autoIcon', (e, id, exePath) => {
const game = library.getGame(id)
if (!game) return { ok: false, error: 'bad-request' }
@ -198,9 +187,7 @@ function registerIpc({ getWindow, onGamesChanged }) {
})
// "Open with PLWL" ------------------------------------------------------------------
// The renderer asks for the file handed in via `--open` (Exec %f), then
// launches it with the wine/prefix picked in the popup. Nothing is imported
// into the library.
// `--open` (Exec %f) hands the file over; nothing is imported.
ipcMain.handle('openwith:take', async () => {
const p = openwith.getPending()
if (!p) return null
@ -246,14 +233,9 @@ function registerIpc({ getWindow, onGamesChanged }) {
return errorResult(err)
}
})
// import from an existing wine prefix: pick the prefix first, then the app
// folder inside it (drive_c) - paths are stored prefix-relative so the app
// shows up for wine as a regular C:\ install
// import from an existing prefix: paths are stored prefix-relative
ipcMain.handle('games:pickPrefixFolder', async (e, prefix) => {
// open the picker straight in the prefix's C: drive (plain wine: top
// level, Proton: under pfx/); a prefix used with BOTH kinds has two of
// them, so it opens at the prefix folder - the only spot that keeps
// drive_c and pfx/drive_c one click away
// open the picker in the prefix's C: drive (mixed layouts: prefix folder)
const driveRoots = prefixes.prefixDriveRoots(prefix)
const start = driveRoots.length === 1 ? driveRoots[0] : prefixes.prefixPath(prefix)
const res = await dialog.showOpenDialog(getWindow(), {
@ -277,10 +259,7 @@ function registerIpc({ getWindow, onGamesChanged }) {
// dialogs & shell ---------------------------------------------------------------------------
ipcMain.handle('dialog:pickFile', async (e, opts) => {
const o = { ...(opts || {}) }
// Electron resolves a RELATIVE defaultPath against the process cwd (the
// install root in packaged mode), which lands the picker somewhere
// random like '/'. Renderer paths are FILES-relative by convention, so
// anchor them there instead.
// anchor FILES-relative paths, or Electron resolves them against cwd
if (o.defaultPath && typeof o.defaultPath === 'string' && !path.isAbsolute(o.defaultPath)) {
o.defaultPath = path.join(DIRS.FILES, o.defaultPath)
}
@ -306,9 +285,7 @@ function registerIpc({ getWindow, onGamesChanged }) {
if (pick) file = path.join(iconDir(id), pick)
} catch {}
if (!file) return new Response('', { status: 404 })
// a new icon lands at the SAME url, so the browser would keep its cached
// copy and the library card would only catch up after a restart - never
// let the icon request be cached
// the icon url never changes: send no-cache headers
const res = await net.fetch('file://' + file)
return new Response(await res.arrayBuffer(), {
headers: {