[EXPERIMENTAL] linux apps support

This commit is contained in:
olcxja 2026-10-06 20:54:16 +02:00
commit e7636afe3d
20 changed files with 1377 additions and 101 deletions

View file

@ -9,7 +9,9 @@ const library = require('../library')
const wines = require('../wines')
const prefixes = require('../prefixes')
const runner = require('../runner')
const { extractIconFromExe } = require('../icons')
const linuxrunner = require('../linuxrunner')
const { createSandboxProbe } = require('../sandbox')
const { extractIconFromExe, iconFromLinuxApp } = require('../icons')
const { createGamesService, sendToWindow } = require('./games')
const shortcuts = require('../shortcuts')
const openwith = require('../openwith')
@ -34,13 +36,15 @@ function clearIconDir(id, keep) {
} catch {}
}
function extractGameIcon(id, exePath) {
const out = path.join(iconDir(id), 'icon.ico')
const res = extractIconFromExe(exePath, out)
function extractGameIcon(id, exePath, kind) {
const isLinux = kind === 'linux'
// the Linux helper picks the extension itself (png/svg), the PE one is ico
const out = path.join(iconDir(id), isLinux ? 'icon' : 'icon.ico')
const res = isLinux ? iconFromLinuxApp(exePath, out) : extractIconFromExe(exePath, 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 })
clearIconDir(id, path.basename(res.path))
library.updateGame(id, { iconSource: res.path })
}
return res
}
@ -48,7 +52,16 @@ function extractGameIcon(id, exePath) {
// --- registration -----------------------------------------------------------
function registerIpc({ getWindow, onGamesChanged }) {
const games = createGamesService({ getWindow, onGamesChanged })
// one probe per launcher, cached: it decides bwrap vs plain run
const sandbox = createSandboxProbe()
const games = createGamesService({ getWindow, onGamesChanged, sandbox })
// sandbox --------------------------------------------------------------------
ipcMain.handle('sandbox:status', async () => sandbox.status() || (await sandbox.probe()))
ipcMain.handle('sandbox:probe', async () => {
sandbox.invalidate()
return sandbox.probe()
})
// i18n ---------------------------------------------------------------------
ipcMain.handle('i18n:list', async () => {
@ -107,6 +120,12 @@ function registerIpc({ getWindow, onGamesChanged }) {
return prefixes.deletePrefix(name, data => sendToWindow(getWindow(), 'import:progress', data))
})
ipcMain.handle('prefixes:rename', (e, oldName, newName) => prefixInUse(oldName) || prefixes.renamePrefix(oldName, newName))
// The seeded desktop config is ours to drop (app data is not a seed), but a
// running app would lose the files under its feet.
ipcMain.handle('prefixes:resetLinuxConfig', (e, name) => {
if (!prefixes.listPrefixes().prefixes.some(p => p.name === name)) return { ok: false, error: 'notFound' }
return prefixInUse(name) || linuxrunner.resetSeededConfig(name)
})
ipcMain.handle('prefixes:setDefault', (e, name) => {
// switching the default would silently move apps that follow it
const defs = prefixes.listPrefixes()
@ -175,7 +194,7 @@ function registerIpc({ getWindow, onGamesChanged }) {
ipcMain.handle('games:extractIcon', (e, id) => {
const game = library.getGame(id)
if (!game || !game.exePath) return { ok: false, error: 'bad-request' }
return extractGameIcon(id, game.exePath)
return extractGameIcon(id, game.exePath, game.kind)
})
// use the exe the field holds (maybe picked but not saved yet)
ipcMain.handle('games:autoIcon', (e, id, exePath) => {
@ -183,7 +202,19 @@ function registerIpc({ getWindow, onGamesChanged }) {
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)
return extractGameIcon(id, exe, game.kind)
})
// Linux apps keep their data next to the prefix's Windows side
ipcMain.handle('games:openDataFolder', async (e, id) => {
const game = library.getGame(id)
if (!library.isLinuxGame(game)) return { ok: false, error: 'not-linux' }
try {
const root = linuxrunner.ensureLinuxPfx(library.prefixNameForGame(game))
const err = await shell.openPath(root)
return err ? { ok: false, error: err } : { ok: true, path: root }
} catch (err) {
return errorResult(err)
}
})
// "Open with PLWL" ------------------------------------------------------------------
@ -226,9 +257,9 @@ function registerIpc({ getWindow, onGamesChanged }) {
})
return res.canceled || !res.filePaths[0] ? null : res.filePaths[0]
})
ipcMain.handle('games:import', async (e, srcDir, mode) => {
ipcMain.handle('games:import', async (e, srcDir, mode, kind) => {
try {
return await games.import(getWindow(), srcDir, mode)
return await games.import(getWindow(), srcDir, mode, kind)
} catch (err) {
return errorResult(err)
}