diff --git a/renderer/app.js b/renderer/app.js index 8250fd0..0fe34c4 100644 --- a/renderer/app.js +++ b/renderer/app.js @@ -1678,7 +1678,10 @@ function wireEvents() { if (res.ok) refreshConfigIcon() } qs('#gc-icon-auto').addEventListener('click', async () => { - applyIconResult(await window.plwl.gamesAutoIcon(state.currentGame.id)) + // extract from the exe the field shows, not the saved one - the user may + // have just pointed the app at another exe and expects its icon + const exe = absGamePathInput(qs('#gc-exe').value.trim(), state.currentGame) + applyIconResult(await window.plwl.gamesAutoIcon(state.currentGame.id, exe)) }) qs('#gc-icon-browse').addEventListener('click', async () => { // start in the app's own folder (prefix apps included) - that is where diff --git a/renderer/style.css b/renderer/style.css index 54f3d52..5da250a 100644 --- a/renderer/style.css +++ b/renderer/style.css @@ -490,8 +490,8 @@ select { margin: 1rem 0; } .game-card { - flex: 1 1 15rem; - min-width: 15rem; + flex: 1 1 17rem; + min-width: 17rem; max-width: 24rem; background: var(--panel-color); border: var(--border-width) solid var(--light-border-color); diff --git a/src/ipc/index.js b/src/ipc/index.js index cedccea..aa13757 100644 --- a/src/ipc/index.js +++ b/src/ipc/index.js @@ -25,10 +25,13 @@ function errorResult(err) { const iconDir = id => path.join(DIRS.DATA, 'icons', id) -function clearIconDir(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. +function clearIconDir(id, keep) { try { for (const f of fs.readdirSync(iconDir(id))) { - if (f.startsWith('icon.')) fs.rmSync(path.join(iconDir(id), f), { force: true }) + if (f.startsWith('icon.') && f !== keep) fs.rmSync(path.join(iconDir(id), f), { force: true }) } } catch {} } @@ -36,7 +39,11 @@ function clearIconDir(id) { function extractGameIcon(id, exePath) { const out = path.join(iconDir(id), 'icon.ico') const res = extractIconFromExe(exePath, out) - if (res.ok) library.updateGame(id, { iconSource: out }) + // on failure nothing is cleared, so the previous icon survives a wrong exe + if (res.ok) { + clearIconDir(id, 'icon.ico') + library.updateGame(id, { iconSource: out }) + } return res } @@ -152,11 +159,14 @@ function registerIpc({ getWindow, onGamesChanged }) { if (!game || !game.exePath) return { ok: false, error: 'bad-request' } return extractGameIcon(id, game.exePath) }) - ipcMain.handle('games:autoIcon', (e, id) => { + // 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 + ipcMain.handle('games:autoIcon', (e, id, exePath) => { const game = library.getGame(id) - if (!game || !game.exePath) return { ok: false, error: 'bad-request' } - clearIconDir(id) - return extractGameIcon(id, game.exePath) + if (!game) return { ok: false, error: 'bad-request' } + const exe = exePath || game.exePath + if (!exe) return { ok: false, error: 'bad-request' } + return extractGameIcon(id, exe) }) // "Open with PLWL" ------------------------------------------------------------------ @@ -253,7 +263,7 @@ function registerIpc({ getWindow, onGamesChanged }) { }) // game icons as a protocol so the renderer can display them safely --------------------------- - protocol.handle('plwl-icon', request => { + protocol.handle('plwl-icon', async request => { const id = decodeURIComponent(new URL(request.url).hostname) let file = null try { @@ -265,7 +275,16 @@ function registerIpc({ getWindow, onGamesChanged }) { if (pick) file = path.join(iconDir(id), pick) } catch {} if (!file) return new Response('', { status: 404 }) - return net.fetch('file://' + file) + // 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 + const res = await net.fetch('file://' + file) + return new Response(await res.arrayBuffer(), { + headers: { + 'content-type': res.headers.get('content-type') || 'image/x-icon', + 'cache-control': 'no-store' + } + }) }) return { games } diff --git a/src/preload.js b/src/preload.js index f8cc490..b10241c 100644 --- a/src/preload.js +++ b/src/preload.js @@ -69,7 +69,7 @@ contextBridge.exposeInMainWorld('plwl', { gamesClearPlaytime: id => ipcRenderer.invoke('games:clearPlaytime', id), gamesSetIconFile: (id, p) => ipcRenderer.invoke('games:setIconFile', id, p), gamesExtractIcon: id => ipcRenderer.invoke('games:extractIcon', id), - gamesAutoIcon: id => ipcRenderer.invoke('games:autoIcon', id), + gamesAutoIcon: (id, exePath) => ipcRenderer.invoke('games:autoIcon', id, exePath), gamesIconPath: id => ipcRenderer.invoke('games:iconPath', id), gamesResetIcon: id => ipcRenderer.invoke('games:resetIcon', id), gamesRemove: id => ipcRenderer.invoke('games:remove', id),