update icon & change PLGL to PLWL
This commit is contained in:
parent
e017187af5
commit
1aa603aeac
18 changed files with 117 additions and 110 deletions
|
|
@ -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
|
||||||
|
|
||||||
|
|
|
||||||
26
build.sh
26
build.sh
|
|
@ -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"
|
||||||
|
|
|
||||||
|
|
@ -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",
|
||||||
|
|
|
||||||
|
|
@ -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
4
package-lock.json
generated
|
|
@ -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"
|
||||||
|
|
|
||||||
|
|
@ -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",
|
||||||
|
|
|
||||||
114
renderer/app.js
114
renderer/app.js
|
|
@ -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')
|
||||||
})
|
})
|
||||||
|
|
|
||||||
|
|
@ -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 |
|
|
@ -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
BIN
renderer/plwl.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 9.3 KiB |
21
renderer/plwl.svg
Normal file
21
renderer/plwl.svg
Normal 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
2
run.sh
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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 {}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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')
|
||||||
|
|
|
||||||
|
|
@ -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),
|
||||||
|
|
|
||||||
|
|
@ -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 {
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue