update icon & change PLGL to PLWL

This commit is contained in:
olcxja 2026-09-25 20:04:00 +02:00
commit 1aa603aeac
18 changed files with 117 additions and 110 deletions

View file

@ -1,4 +1,4 @@
# PLGL — Portable Linux Wine Launcher # PLWL — Portable Linux Wine Launcher
A portable launcher for Windows games and applications on Linux. A portable launcher for Windows games and applications on Linux.
@ -26,7 +26,7 @@ npm start
- **System Wine** — uses the `wine` binary from PATH. - **System Wine** — uses the `wine` binary from PATH.
- **Imported builds** — drop any wine or Proton build into `files/wines/`. The folder may contain the build directly, or one level of nesting (zip-extracted layouts are detected automatically). - **Imported builds** — drop any wine or Proton build into `files/wines/`. The folder may contain the build directly, or one level of nesting (zip-extracted layouts are detected automatically).
- **Proton without Steam** — fully supported. Proton is invoked like Steam does it: `STEAM_COMPAT_CLIENT_INSTALL_PATH` points to a fake steam client folder, `STEAM_COMPAT_DATA_PATH` is a per-build compat folder, and the prefix lives at `<compat>/pfx` which PLGL links to its own prefix folder. No Steam installation required (GE builds work out of the box; for a few Valve builds that refuse to run without a client, PLGL fakes a minimal steam folder in `files/data/fake-steam`). - **Proton without Steam** — fully supported. Proton is invoked like Steam does it: `STEAM_COMPAT_CLIENT_INSTALL_PATH` points to a fake steam client folder, `STEAM_COMPAT_DATA_PATH` is a per-build compat folder, and the prefix lives at `<compat>/pfx` which PLWL links to its own prefix folder. No Steam installation required (GE builds work out of the box; for a few Valve builds that refuse to run without a client, PLWL fakes a minimal steam folder in `files/data/fake-steam`).
## Translations ## Translations

View file

@ -3,36 +3,36 @@ set -e
cd "$(dirname "$0")" cd "$(dirname "$0")"
VERSION=$(node -p "require('./package.json').version") VERSION=$(node -p "require('./package.json').version")
OUT="build/PLGL-$VERSION.tar.gz" OUT="build/PLWL-$VERSION.tar.gz"
bash tools/svg2png.sh renderer/plgl.svg renderer/plgl.png 256 bash tools/svg2png.sh renderer/plwl.svg renderer/plwl.png 256
rm -rf build/dist rm -rf build/dist
mkdir -p build/dist/PLGL build/files mkdir -p build/dist/PLWL build/files
cp -r src renderer tools files package.json build/dist/PLGL/ cp -r src renderer tools files package.json build/dist/PLWL/
cp -r files/translations build/files/ 2>/dev/null || true cp -r files/translations build/files/ 2>/dev/null || true
if [ ! -d node_modules/electron/dist ]; then if [ ! -d node_modules/electron/dist ]; then
npm install npm install
fi fi
mkdir -p build/dist/PLGL/node_modules/electron mkdir -p build/dist/PLWL/node_modules/electron
cp -r node_modules/electron/dist build/dist/PLGL/node_modules/electron/ cp -r node_modules/electron/dist build/dist/PLWL/node_modules/electron/
cp node_modules/electron/package.json build/dist/PLGL/node_modules/electron/ cp node_modules/electron/package.json build/dist/PLWL/node_modules/electron/
cp package.json build/dist/PLGL/ cp package.json build/dist/PLWL/
cp package-lock.json build/dist/PLGL/ 2>/dev/null || true cp package-lock.json build/dist/PLWL/ 2>/dev/null || true
cat > build/dist/PLGL/run.sh << 'EOF' cat > build/dist/PLWL/run.sh << 'EOF'
#!/usr/bin/env bash #!/usr/bin/env bash
set -e set -e
cd "$(dirname "$0")" cd "$(dirname "$0")"
bash tools/svg2png.sh renderer/plgl.svg renderer/plgl.png 256 bash tools/svg2png.sh renderer/plwl.svg renderer/plwl.png 256
exec ./node_modules/electron/dist/electron . "$@" exec ./node_modules/electron/dist/electron . "$@"
EOF EOF
chmod +x build/dist/PLGL/run.sh chmod +x build/dist/PLWL/run.sh
mkdir -p build mkdir -p build
tar -czf "$OUT" -C build/dist PLGL tar -czf "$OUT" -C build/dist PLWL
rm -rf build/dist rm -rf build/dist
echo "Built $OUT" echo "Built $OUT"

View file

@ -4,7 +4,7 @@
"nameSelf": "English" "nameSelf": "English"
}, },
"texts": { "texts": {
"app.title": "PLGL", "app.title": "PLWL",
"app.subtitle": "Portable Linux Wine Launcher", "app.subtitle": "Portable Linux Wine Launcher",
"nav.library": "Apps", "nav.library": "Apps",

View file

@ -4,7 +4,7 @@
"nameSelf": "Polski" "nameSelf": "Polski"
}, },
"texts": { "texts": {
"app.title": "PLGL", "app.title": "PLWL",
"app.subtitle": "Przenośny launcher Wine dla Linuksa", "app.subtitle": "Przenośny launcher Wine dla Linuksa",
"nav.library": "Aplikacje", "nav.library": "Aplikacje",

4
package-lock.json generated
View file

@ -1,11 +1,11 @@
{ {
"name": "plgl", "name": "plwl",
"version": "0.1.0", "version": "0.1.0",
"lockfileVersion": 3, "lockfileVersion": 3,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "plgl", "name": "plwl",
"version": "0.1.0", "version": "0.1.0",
"devDependencies": { "devDependencies": {
"electron": "^33.2.0" "electron": "^33.2.0"

View file

@ -1,6 +1,6 @@
{ {
"name": "plgl", "name": "plwl",
"productName": "PLGL", "productName": "PLWL",
"version": "0.1.0", "version": "0.1.0",
"description": "Portable Linux Wine Launcher", "description": "Portable Linux Wine Launcher",
"main": "src/main.js", "main": "src/main.js",

View file

@ -234,11 +234,11 @@ function showView(name) {
async function loadAll() { async function loadAll() {
const [langs, settings, wines, prefixes, games] = await Promise.all([ const [langs, settings, wines, prefixes, games] = await Promise.all([
window.plgl.i18nList(), window.plwl.i18nList(),
window.plgl.settingsGet(), window.plwl.settingsGet(),
window.plgl.winesList(), window.plwl.winesList(),
window.plgl.prefixesList(), window.plwl.prefixesList(),
window.plgl.gamesList() window.plwl.gamesList()
]) ])
state.langs = langs state.langs = langs
state.settings = settings state.settings = settings
@ -250,7 +250,7 @@ async function loadAll() {
} }
async function loadLangTexts(code) { async function loadLangTexts(code) {
const lang = await window.plgl.i18nGet(code) const lang = await window.plwl.i18nGet(code)
if (lang && lang.texts) { if (lang && lang.texts) {
state.texts = lang.texts state.texts = lang.texts
state.lang = code state.lang = code
@ -263,7 +263,7 @@ function wineLabel(id) {
} }
function iconUrl(id) { function iconUrl(id) {
return `plgl-icon://${encodeURIComponent(id)}/icon` return `plwl-icon://${encodeURIComponent(id)}/icon`
} }
function fmtPlaytime(seconds) { function fmtPlaytime(seconds) {
@ -276,7 +276,7 @@ function fmtPlaytime(seconds) {
} }
async function attachCardIcon(card, game) { async function attachCardIcon(card, game) {
const p = await window.plgl.gamesIconPath(game.id) const p = await window.plwl.gamesIconPath(game.id)
if (!p) return if (!p) return
const img = document.createElement('img') const img = document.createElement('img')
img.className = 'game-icon' img.className = 'game-icon'
@ -324,26 +324,26 @@ function renderLibrary() {
killBtn.style.display = isRunning ? '' : 'none' killBtn.style.display = isRunning ? '' : 'none'
} }
runningCards.set(game.id, { applyRunning, lastMeta, game }) runningCards.set(game.id, { applyRunning, lastMeta, game })
window.plgl.gamesIsRunning(game.id).then(applyRunning) window.plwl.gamesIsRunning(game.id).then(applyRunning)
playBtn.onclick = () => playGame(game) playBtn.onclick = () => playGame(game)
killBtn.onclick = async () => { killBtn.onclick = async () => {
await window.plgl.gamesKill(game.id) await window.plwl.gamesKill(game.id)
applyRunning(false) applyRunning(false)
} }
card.querySelector('[data-act=config]').onclick = () => openGameConfig(game.id) card.querySelector('[data-act=config]').onclick = () => openGameConfig(game.id)
card.querySelector('[data-act=folder]').onclick = () => window.plgl.shellOpenPath(game.workdir) card.querySelector('[data-act=folder]').onclick = () => window.plwl.shellOpenPath(game.workdir)
card.querySelector('[data-act=logs]').onclick = async () => { card.querySelector('[data-act=logs]').onclick = async () => {
const logs = await window.plgl.gamesGetLogs(game.id) const logs = await window.plwl.gamesGetLogs(game.id)
if (logs.length) window.plgl.shellShowItemInFolder(logs[logs.length - 1]) if (logs.length) window.plwl.shellShowItemInFolder(logs[logs.length - 1])
else toast(t('library.viewLogs')) else toast(t('library.viewLogs'))
} }
card.querySelector('[data-act=remove]').onclick = async () => { card.querySelector('[data-act=remove]').onclick = async () => {
const note = game.mode === 'copy' || game.mode === 'move' ? t('library.removeFilesNote') : '' const note = game.mode === 'copy' || game.mode === 'move' ? t('library.removeFilesNote') : ''
const ok = await confirmModal(t('library.removeConfirm', game.name, note), t('library.remove')) const ok = await confirmModal(t('library.removeConfirm', game.name, note), t('library.remove'))
if (!ok) return if (!ok) return
await window.plgl.gamesRemove(game.id) await window.plwl.gamesRemove(game.id)
toast(t('toast.gameRemoved', game.name), 'success') toast(t('toast.gameRemoved', game.name), 'success')
state.games = await window.plgl.gamesList() state.games = await window.plwl.gamesList()
renderLibrary() renderLibrary()
} }
list.appendChild(card) list.appendChild(card)
@ -352,7 +352,7 @@ function renderLibrary() {
} }
async function playGame(game) { async function playGame(game) {
const res = await window.plgl.gamesRun(game.id) const res = await window.plwl.gamesRun(game.id)
if (res.ok) { if (res.ok) {
const entry = runningCards.get(game.id) const entry = runningCards.get(game.id)
if (entry) entry.applyRunning(true) if (entry) entry.applyRunning(true)
@ -367,8 +367,8 @@ async function playGame(game) {
} }
async function refreshLibraryData() { async function refreshLibraryData() {
state.games = await window.plgl.gamesList() state.games = await window.plwl.gamesList()
const runningList = await window.plgl.gamesListRunning() const runningList = await window.plwl.gamesListRunning()
const runningSet = new Set(runningList) const runningSet = new Set(runningList)
for (const [id, entry] of runningCards) { for (const [id, entry] of runningCards) {
entry.applyRunning(runningSet.has(id)) entry.applyRunning(runningSet.has(id))
@ -381,7 +381,7 @@ async function refreshLibraryData() {
} }
function wireRealtime() { function wireRealtime() {
window.plgl.onGamesChanged(() => { window.plwl.onGamesChanged(() => {
if (state.currentView === 'library') refreshLibraryData() if (state.currentView === 'library') refreshLibraryData()
}) })
} }
@ -404,7 +404,7 @@ async function startImportFlow() {
btn.onclick = async () => { btn.onclick = async () => {
const mode = btn.dataset.mode const mode = btn.dataset.mode
closeModal() closeModal()
const srcDir = await window.plgl.gamesPickFolder() const srcDir = await window.plwl.gamesPickFolder()
if (!srcDir) return if (!srcDir) return
await doImport(srcDir, mode) await doImport(srcDir, mode)
} }
@ -419,16 +419,16 @@ async function doImport(srcDir, mode) {
<p id="import-status"></p> <p id="import-status"></p>
`) `)
let count = 0 let count = 0
window.plgl.onImportProgress(() => { window.plwl.onImportProgress(() => {
count++ count++
const status = modal.querySelector('#import-status') const status = modal.querySelector('#import-status')
if (status) status.textContent = t('import.fileDone', count) if (status) status.textContent = t('import.fileDone', count)
}) })
try { try {
const game = await window.plgl.gamesImport(srcDir, mode) const game = await window.plwl.gamesImport(srcDir, mode)
closeModal() closeModal()
toast(t('toast.importDone', game.name), 'success') toast(t('toast.importDone', game.name), 'success')
state.games = await window.plgl.gamesList() state.games = await window.plwl.gamesList()
renderLibrary() renderLibrary()
await openGameConfig(game.id) await openGameConfig(game.id)
} catch (err) { } catch (err) {
@ -466,9 +466,9 @@ function renderPrefixes() {
const defBtn = row.querySelector('[data-act=default]') const defBtn = row.querySelector('[data-act=default]')
defBtn.textContent = t('prefixes.makeDefault') defBtn.textContent = t('prefixes.makeDefault')
defBtn.onclick = async () => { defBtn.onclick = async () => {
const res = await window.plgl.prefixesSetDefault(p.name) const res = await window.plwl.prefixesSetDefault(p.name)
if (res && res.ok !== false) toast(t('toast.prefixDefault', p.name), 'success') if (res && res.ok !== false) toast(t('toast.prefixDefault', p.name), 'success')
state.prefixes = await window.plgl.prefixesList() state.prefixes = await window.plwl.prefixesList()
renderPrefixes() renderPrefixes()
} }
const delBtn = row.querySelector('[data-act=delete]') const delBtn = row.querySelector('[data-act=delete]')
@ -476,11 +476,11 @@ function renderPrefixes() {
delBtn.onclick = async () => { delBtn.onclick = async () => {
const ok = await confirmModal(t('prefixes.deleteConfirm', p.name), t('prefixes.delete')) const ok = await confirmModal(t('prefixes.deleteConfirm', p.name), t('prefixes.delete'))
if (!ok) return if (!ok) return
const res = await window.plgl.prefixesDelete(p.name) const res = await window.plwl.prefixesDelete(p.name)
if (res.ok) toast(t('toast.prefixDeleted', p.name), 'success') if (res.ok) toast(t('toast.prefixDeleted', p.name), 'success')
else if (res.error === 'isMain') toast(t('prefixes.mainLocked'), 'error') else if (res.error === 'isMain') toast(t('prefixes.mainLocked'), 'error')
else toast(t('toast.prefixDeleteFailed', p.name), 'error') else toast(t('toast.prefixDeleteFailed', p.name), 'error')
state.prefixes = await window.plgl.prefixesList() state.prefixes = await window.plwl.prefixesList()
renderPrefixes() renderPrefixes()
} }
list.appendChild(row) list.appendChild(row)
@ -521,9 +521,9 @@ function renderWines() {
const defBtn = row.querySelector('[data-act=default]') const defBtn = row.querySelector('[data-act=default]')
defBtn.textContent = t('wines.makeDefault') defBtn.textContent = t('wines.makeDefault')
defBtn.onclick = async () => { defBtn.onclick = async () => {
const res = await window.plgl.winesSetDefault(w.id) const res = await window.plwl.winesSetDefault(w.id)
if (res && res.ok) { if (res && res.ok) {
state.settings = await window.plgl.settingsGet() state.settings = await window.plwl.settingsGet()
toast(t('wines.defaultSet', w.label), 'success') toast(t('wines.defaultSet', w.label), 'success')
} }
renderWines() renderWines()
@ -534,11 +534,11 @@ function renderWines() {
delBtn.onclick = async () => { delBtn.onclick = async () => {
const ok = await confirmModal(t('wines.deleteConfirm', w.label), t('wines.delete')) const ok = await confirmModal(t('wines.deleteConfirm', w.label), t('wines.delete'))
if (!ok) return if (!ok) return
const res = await window.plgl.winesDelete(w.id) const res = await window.plwl.winesDelete(w.id)
if (res.ok) { if (res.ok) {
toast(t('wines.deleted', w.label), 'success') toast(t('wines.deleted', w.label), 'success')
state.wines = await window.plgl.winesList() state.wines = await window.plwl.winesList()
state.settings = await window.plgl.settingsGet() state.settings = await window.plwl.settingsGet()
} else if (res.error === 'isSystem') { } else if (res.error === 'isSystem') {
toast(t('wines.systemLocked'), 'error') toast(t('wines.systemLocked'), 'error')
} }
@ -561,12 +561,12 @@ function renderSettings() {
} }
async function saveSettings() { async function saveSettings() {
state.settings = await window.plgl.settingsSet({ state.settings = await window.plwl.settingsSet({
language: selects['set-language'].value, language: selects['set-language'].value,
defaultWine: selects['set-wine'].value defaultWine: selects['set-wine'].value
}) })
await window.plgl.prefixesSetDefault(selects['set-prefix'].value || state.prefixes.default) await window.plwl.prefixesSetDefault(selects['set-prefix'].value || state.prefixes.default)
state.prefixes = await window.plgl.prefixesList() state.prefixes = await window.plwl.prefixesList()
const lang = selects['set-language'].value const lang = selects['set-language'].value
if (lang !== state.lang) { if (lang !== state.lang) {
await loadLangTexts(lang) await loadLangTexts(lang)
@ -601,7 +601,7 @@ function renderPrefixOptions(selectId, selected) {
async function refreshConfigIcon() { async function refreshConfigIcon() {
const game = state.currentGame const game = state.currentGame
if (!game) return if (!game) return
const p = await window.plgl.gamesIconPath(game.id) const p = await window.plwl.gamesIconPath(game.id)
const img = qs('#gc-icon') const img = qs('#gc-icon')
const ph = qs('#gc-icon-placeholder') const ph = qs('#gc-icon-placeholder')
img.onerror = () => { img.onerror = () => {
@ -703,8 +703,8 @@ async function saveGameConfig() {
env, env,
dllOverrides: readOverrides() dllOverrides: readOverrides()
} }
await window.plgl.gamesUpdate(game.id, patch) await window.plwl.gamesUpdate(game.id, patch)
state.games = await window.plgl.gamesList() state.games = await window.plwl.gamesList()
toast(t('config.saved'), 'success') toast(t('config.saved'), 'success')
if (state.currentView === 'gameconfig') { if (state.currentView === 'gameconfig') {
state.currentGame = { ...state.currentGame, ...patch } state.currentGame = { ...state.currentGame, ...patch }
@ -722,7 +722,7 @@ function renderTools() {
btn.innerHTML = `${ICONS.terminal()}<span>${tool}</span>` btn.innerHTML = `${ICONS.terminal()}<span>${tool}</span>`
btn.onclick = async () => { btn.onclick = async () => {
const game = state.currentGame const game = state.currentGame
const res = await window.plgl.gamesRunTool(game.id, tool, '') const res = await window.plwl.gamesRunTool(game.id, tool, '')
if (res.ok) toast(t('toast.started', tool)) if (res.ok) toast(t('toast.started', tool))
else toast(t('toast.startFailed', tool, res.error || ''), 'error') else toast(t('toast.startFailed', tool, res.error || ''), 'error')
} }
@ -735,7 +735,7 @@ async function runCustomTool() {
if (!input) return if (!input) return
const [tool, ...rest] = input.split(/\s+/) const [tool, ...rest] = input.split(/\s+/)
const game = state.currentGame const game = state.currentGame
const res = await window.plgl.gamesRunTool(game.id, tool, rest.join(' ')) const res = await window.plwl.gamesRunTool(game.id, tool, rest.join(' '))
if (res.ok) toast(t('toast.started', tool)) if (res.ok) toast(t('toast.started', tool))
else toast(t('toast.startFailed', tool, res.error || ''), 'error') else toast(t('toast.startFailed', tool, res.error || ''), 'error')
} }
@ -751,54 +751,54 @@ function wireEvents() {
const input = qs('#prefix-name-input') const input = qs('#prefix-name-input')
const name = input.value.trim() const name = input.value.trim()
if (!name) return if (!name) return
const res = await window.plgl.prefixesCreate(name) const res = await window.plwl.prefixesCreate(name)
if (!res.ok) { if (!res.ok) {
toast(t(res.error === 'alreadyExists' ? 'prefixes.exists' : 'prefixes.invalidName'), 'error') toast(t(res.error === 'alreadyExists' ? 'prefixes.exists' : 'prefixes.invalidName'), 'error')
return return
} }
input.value = '' input.value = ''
toast(t('toast.prefixCreated', res.name), 'success') toast(t('toast.prefixCreated', res.name), 'success')
state.prefixes = await window.plgl.prefixesList() state.prefixes = await window.plwl.prefixesList()
renderPrefixes() renderPrefixes()
}) })
qs('#btn-wines-rescan').addEventListener('click', async () => { qs('#btn-wines-rescan').addEventListener('click', async () => {
await window.plgl.winesRefresh() await window.plwl.winesRefresh()
state.wines = await window.plgl.winesList() state.wines = await window.plwl.winesList()
renderWines() renderWines()
}) })
qs('#btn-wines-import').addEventListener('click', async () => { qs('#btn-wines-import').addEventListener('click', async () => {
const srcDir = await window.plgl.dialogPickFile({ properties: ['openDirectory'], title: t('wines.importPick') }) const srcDir = await window.plwl.dialogPickFile({ properties: ['openDirectory'], title: t('wines.importPick') })
if (!srcDir) return if (!srcDir) return
const modal = showModal(` const modal = showModal(`
<h2>${t('wines.importing')}</h2> <h2>${t('wines.importing')}</h2>
<div class="progress-row"><progress></progress></div> <div class="progress-row"><progress></progress></div>
`) `)
const res = await window.plgl.winesImport(srcDir) const res = await window.plwl.winesImport(srcDir)
closeModal() closeModal()
if (res.ok) { if (res.ok) {
toast(t('wines.imported', res.path), 'success') toast(t('wines.imported', res.path), 'success')
state.wines = await window.plgl.winesList() state.wines = await window.plwl.winesList()
} else { } else {
toast(t('toast.importFailed') + ': ' + (res.error || ''), 'error') toast(t('toast.importFailed') + ': ' + (res.error || ''), 'error')
} }
renderWines() renderWines()
}) })
qs('#btn-settings-save').addEventListener('click', saveSettings) qs('#btn-settings-save').addEventListener('click', saveSettings)
qs('#btn-open-files').addEventListener('click', () => window.plgl.shellOpenPath(window.plgl.FILES_DIR)) qs('#btn-open-files').addEventListener('click', () => window.plwl.shellOpenPath(window.plwl.FILES_DIR))
qs('#btn-gc-back').addEventListener('click', () => showView('library')) qs('#btn-gc-back').addEventListener('click', () => showView('library'))
qs('#btn-gc-save').addEventListener('click', saveGameConfig) qs('#btn-gc-save').addEventListener('click', saveGameConfig)
qs('#gc-override-add').addEventListener('click', addOverrideRow) qs('#gc-override-add').addEventListener('click', addOverrideRow)
qs('#gc-exe-browse').addEventListener('click', async () => { qs('#gc-exe-browse').addEventListener('click', async () => {
const p = await window.plgl.dialogPickFile({ properties: ['openFile'], defaultPath: qs('#gc-workdir').value || window.plgl.FILES_DIR }) const p = await window.plwl.dialogPickFile({ properties: ['openFile'], defaultPath: qs('#gc-workdir').value || window.plwl.FILES_DIR })
if (p) qs('#gc-exe').value = p if (p) qs('#gc-exe').value = p
}) })
qs('#gc-workdir-browse').addEventListener('click', async () => { qs('#gc-workdir-browse').addEventListener('click', async () => {
const p = await window.plgl.dialogPickFile({ properties: ['openDirectory'], defaultPath: qs('#gc-exe').value || window.plgl.FILES_DIR }) const p = await window.plwl.dialogPickFile({ properties: ['openDirectory'], defaultPath: qs('#gc-exe').value || window.plwl.FILES_DIR })
if (p) qs('#gc-workdir').value = p if (p) qs('#gc-workdir').value = p
}) })
qs('#gc-tool-run').addEventListener('click', runCustomTool) qs('#gc-tool-run').addEventListener('click', runCustomTool)
qs('#gc-icon-auto').addEventListener('click', async () => { qs('#gc-icon-auto').addEventListener('click', async () => {
const res = await window.plgl.gamesAutoIcon(state.currentGame.id) const res = await window.plwl.gamesAutoIcon(state.currentGame.id)
if (res.ok) { if (res.ok) {
toast(t('config.icon.set'), 'success') toast(t('config.icon.set'), 'success')
refreshConfigIcon() refreshConfigIcon()
@ -807,7 +807,7 @@ function wireEvents() {
} }
}) })
qs('#gc-icon-browse').addEventListener('click', async () => { qs('#gc-icon-browse').addEventListener('click', async () => {
const p = await window.plgl.dialogPickFile({ const p = await window.plwl.dialogPickFile({
properties: ['openFile'], properties: ['openFile'],
title: t('config.icon.pick'), title: t('config.icon.pick'),
filters: [{ name: t('config.icon.pick'), extensions: ['png', 'jpg', 'jpeg', 'svg', 'ico', 'webp', 'gif', 'bmp', 'exe'] }] filters: [{ name: t('config.icon.pick'), extensions: ['png', 'jpg', 'jpeg', 'svg', 'ico', 'webp', 'gif', 'bmp', 'exe'] }]
@ -815,25 +815,25 @@ function wireEvents() {
if (!p) return if (!p) return
let res let res
if (p.toLowerCase().endsWith('.exe')) { if (p.toLowerCase().endsWith('.exe')) {
res = await window.plgl.gamesSetIconFile(state.currentGame.id, p) res = await window.plwl.gamesSetIconFile(state.currentGame.id, p)
if (res.ok) toast(t('config.icon.set'), 'success') if (res.ok) toast(t('config.icon.set'), 'success')
else toast(t('config.icon.noIcon'), 'error') else toast(t('config.icon.noIcon'), 'error')
} else { } else {
res = await window.plgl.gamesSetIconFile(state.currentGame.id, p) res = await window.plwl.gamesSetIconFile(state.currentGame.id, p)
if (res.ok) toast(t('config.icon.set'), 'success') if (res.ok) toast(t('config.icon.set'), 'success')
else toast(t('config.icon.noIcon'), 'error') else toast(t('config.icon.noIcon'), 'error')
} }
refreshConfigIcon() refreshConfigIcon()
}) })
qs('#gc-icon-reset').addEventListener('click', async () => { qs('#gc-icon-reset').addEventListener('click', async () => {
await window.plgl.gamesResetIcon(state.currentGame.id) await window.plwl.gamesResetIcon(state.currentGame.id)
refreshConfigIcon() refreshConfigIcon()
}) })
qs('#btn-gc-clear-playtime').addEventListener('click', async () => { qs('#btn-gc-clear-playtime').addEventListener('click', async () => {
const ok = await confirmModal(t('config.clearPlaytimeConfirm'), t('config.clearPlaytime')) const ok = await confirmModal(t('config.clearPlaytimeConfirm'), t('config.clearPlaytime'))
if (!ok) return if (!ok) return
await window.plgl.gamesClearPlaytime(state.currentGame.id) await window.plwl.gamesClearPlaytime(state.currentGame.id)
state.games = await window.plgl.gamesList() state.games = await window.plwl.gamesList()
state.currentGame = state.games.find(g => g.id === state.currentGame.id) state.currentGame = state.games.find(g => g.id === state.currentGame.id)
toast(t('config.playtimeCleared'), 'success') toast(t('config.playtimeCleared'), 'success')
}) })

View file

@ -2,14 +2,14 @@
<html lang="en"> <html lang="en">
<head> <head>
<meta charset="UTF-8"> <meta charset="UTF-8">
<meta http-equiv="Content-Security-Policy" content="default-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data: plgl-icon:;"> <meta http-equiv="Content-Security-Policy" content="default-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data: plwl-icon:;">
<title>PLGL</title> <title>PLWL</title>
<link rel="stylesheet" href="style.css"> <link rel="stylesheet" href="style.css">
</head> </head>
<body> <body>
<loading id="loading"> <loading id="loading">
<div class="loading-inner"> <div class="loading-inner">
<div class="loading-logo">PLGL</div> <div class="loading-logo">PLWL</div>
<p id="loadingstatus"></p> <p id="loadingstatus"></p>
</div> </div>
</loading> </loading>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 35 KiB

View file

@ -1,14 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 256 256">
<defs>
<linearGradient id="bg" x1="0" y1="0" x2="1" y2="1">
<stop offset="0" stop-color="#2a2a2e"/>
<stop offset="1" stop-color="#161618"/>
</linearGradient>
</defs>
<rect x="16" y="16" width="224" height="224" rx="48" fill="url(#bg)" stroke="#3c3c42" stroke-width="4"/>
<g fill="none" stroke="#eaeaf0" stroke-width="14" stroke-linecap="round" stroke-linejoin="round">
<path d="M78 88l40 40-40 40"/>
<path d="M132 168h46"/>
</g>
<path d="M186 64c0 22-14 34-30 38v18h-16v-18c-16-4-30-16-30-38 0-4 3-8 8-8s8 4 8 8c0 12 8 20 22 20s22-8 22-20c0-4 3-8 8-8s8 4 8 8z" fill="#eaeaf0"/>
</svg>

Before

Width:  |  Height:  |  Size: 688 B

BIN
renderer/plwl.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.3 KiB

21
renderer/plwl.svg Normal file
View file

@ -0,0 +1,21 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 256 256">
<defs>
<linearGradient id="bg" x1="0" y1="0" x2="1" y2="1">
<stop offset="0%" stop-color="#2a2a2e"/>
<stop offset="1" stop-color="#161618"/>
</linearGradient>
</defs>
<rect x="16" y="16" width="224" height="224" rx="48" fill="url(#bg)" stroke="#3c3c42" stroke-width="4"/>
<path d="M 145 108 C 148 126 159 135 168 135 C 177 135 188 126 191 108 Z" fill="#eaeaf0"/>
<g fill="none" stroke="#eaeaf0" stroke-width="10" stroke-linecap="round" stroke-linejoin="round">
<path d="M 66 100 L 94 128 L 66 156"/>
<path d="M 140 78 C 140 120 158 138 168 138 C 178 138 196 120 196 78"/>
<path d="M 168 138 V 178"/>
<path d="M 146 178 H 190"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 755 B

2
run.sh
View file

@ -7,7 +7,7 @@ if [ ! -d node_modules ]; then
npm install npm install
fi fi
bash tools/svg2png.sh renderer/plgl.svg renderer/plgl.png 256 bash tools/svg2png.sh renderer/plwl.svg renderer/plwl.png 256
mkdir -p build/files mkdir -p build/files
cp -r files/translations build/files/ 2>/dev/null || true cp -r files/translations build/files/ 2>/dev/null || true

View file

@ -3,10 +3,10 @@
const path = require('path') const path = require('path')
const fs = require('fs') const fs = require('fs')
const APP_NAME = 'PLGL' const APP_NAME = 'PLWL'
function findPng() { function findPng() {
const p = path.resolve(__dirname, '..', 'renderer', 'plgl.png') const p = path.resolve(__dirname, '..', 'renderer', 'plwl.png')
try { try {
fs.accessSync(p) fs.accessSync(p)
return p return p
@ -24,7 +24,7 @@ function writeIconThemeDirs(png) {
for (const [dir, size] of Object.entries(sizes)) { for (const [dir, size] of Object.entries(sizes)) {
const themeDir = path.join(base, 'hicolor', dir, 'apps') const themeDir = path.join(base, 'hicolor', dir, 'apps')
fs.mkdirSync(themeDir, { recursive: true }) fs.mkdirSync(themeDir, { recursive: true })
fs.copyFileSync(png, path.join(themeDir, 'plgl.png')) fs.copyFileSync(png, path.join(themeDir, 'plwl.png'))
} }
const desktopDir = path.join(home, '.local', 'share', 'applications') const desktopDir = path.join(home, '.local', 'share', 'applications')
fs.mkdirSync(desktopDir, { recursive: true }) fs.mkdirSync(desktopDir, { recursive: true })
@ -33,12 +33,12 @@ function writeIconThemeDirs(png) {
'Type=Application', 'Type=Application',
`Name=${APP_NAME}`, `Name=${APP_NAME}`,
'Comment=Portable Linux Wine Launcher', 'Comment=Portable Linux Wine Launcher',
'Exec=plgl', 'Exec=plwl',
`Icon=plgl`, `Icon=plwl`,
'Terminal=false', 'Terminal=false',
'Categories=Game;' 'Categories=Game;'
].join('\n') ].join('\n')
fs.writeFileSync(path.join(desktopDir, 'plgl.desktop'), desktop) fs.writeFileSync(path.join(desktopDir, 'plwl.desktop'), desktop)
} catch {} } catch {}
} }

View file

@ -12,7 +12,7 @@ const importer = require('./importer')
const i18n = require('./i18n') const i18n = require('./i18n')
const { createMainWindow } = require('./window') const { createMainWindow } = require('./window')
app.setName('PLGL') app.setName('PLWL')
let win = null let win = null
@ -46,7 +46,7 @@ function scanGamesFolder(dir, depth, out) {
for (const e of entries) { for (const e of entries) {
if (!e.isDirectory()) continue if (!e.isDirectory()) continue
const full = path.join(dir, e.name) const full = path.join(dir, e.name)
if (require('fs').existsSync(path.join(full, '.plgl-imported'))) { if (require('fs').existsSync(path.join(full, '.plwl-imported'))) {
out.push(full) out.push(full)
} else { } else {
scanGamesFolder(full, depth - 1, out) scanGamesFolder(full, depth - 1, out)
@ -190,7 +190,7 @@ ipcMain.handle('games:import', async (e, srcDir, mode) => {
} }
const game = await importer.importGame(srcDir, mode, onProgress) const game = await importer.importGame(srcDir, mode, onProgress)
library.addGame(game) library.addGame(game)
require('fs').writeFileSync(path.join(game.workdir, '.plgl-imported'), '') require('fs').writeFileSync(path.join(game.workdir, '.plwl-imported'), '')
return game return game
}) })
@ -309,7 +309,7 @@ app.whenReady().then(async () => {
setAppIcon(app) setAppIcon(app)
} catch {} } catch {}
} }
protocol.handle('plgl-icon', request => { protocol.handle('plwl-icon', request => {
const id = decodeURIComponent(new URL(request.url).hostname) const id = decodeURIComponent(new URL(request.url).hostname)
const dir = path.join(DIRS.DATA, 'icons', id) const dir = path.join(DIRS.DATA, 'icons', id)
let file = null let file = null

View file

@ -5,7 +5,7 @@ const fs = require('fs')
const APP_ROOT = path.resolve(__dirname, '..') const APP_ROOT = path.resolve(__dirname, '..')
const BUILD = process.env.PLGL_BUILD_DIR || path.join(APP_ROOT, 'build') const BUILD = process.env.PLWL_BUILD_DIR || path.join(APP_ROOT, 'build')
const BUNDLED = path.join(APP_ROOT, 'files') const BUNDLED = path.join(APP_ROOT, 'files')
const FILES = path.join(BUILD, 'files') const FILES = path.join(BUILD, 'files')

View file

@ -3,7 +3,7 @@
const { contextBridge, ipcRenderer } = require('electron') const { contextBridge, ipcRenderer } = require('electron')
const { DIRS } = require('./paths') const { DIRS } = require('./paths')
contextBridge.exposeInMainWorld('plgl', { contextBridge.exposeInMainWorld('plwl', {
FILES_DIR: DIRS.FILES, FILES_DIR: DIRS.FILES,
i18nList: () => ipcRenderer.invoke('i18n:list'), i18nList: () => ipcRenderer.invoke('i18n:list'),
i18nGet: code => ipcRenderer.invoke('i18n:get', code), i18nGet: code => ipcRenderer.invoke('i18n:get', code),

View file

@ -7,8 +7,8 @@ const DIST = path.resolve(__dirname, '..', 'renderer')
function resolveIcon() { function resolveIcon() {
const candidates = [ const candidates = [
path.join(DIST, 'plgl.png'), path.join(DIST, 'plwl.png'),
path.join(DIST, 'plgl.svg') path.join(DIST, 'plwl.svg')
] ]
for (const c of candidates) { for (const c of candidates) {
try { try {