PLWL/README.md

11 KiB

PLWL — Portable Linux Wine Launcher

A portable launcher for Windows games and applications on Linux.

Portable layout

Everything the launcher needs lives next to the executable:

./files/
  wines/           imported Wine / Proton builds
  wineprefixes/    wine prefixes
  apps/            imported app folders (optional)
  data/            launcher config (apps.json, settings.json)
  translations/    translation files

Running

npm install
npm start

Building & installing

./build.sh produces build/PLWL-<version>.tar.gz: the Electron runtime renamed to olcxja.plwl/ (binary olcxja.plwl/olcxja.plwl) with the whole app (code, renderer and bundled files/) packed into olcxja.plwl/resources/app.asar, plus the olcxja.plwl.png icon, install.sh and createshortcut.sh:

tar -xzf PLWL-<version>.tar.gz
cd PLWL
./install.sh        # copies everything to ~/.local/bin/olcxja.plwl/,
                    # installs the icon (olcxja.plwl.png) + shortcut
                    # removes the extraction folder, launches

./createshortcut.sh # portable mode: only installs the icon + shortcut

# or run without any shortcuts:
./olcxja.plwl/olcxja.plwl

Importing from a wine prefix

Besides copy/move/leave, an app can be imported from a wine prefix: pick the prefix, then the app folder inside its drive_c. Nothing is copied — paths are stored relative to the prefix, so wine sees the app as if it were normally installed on C:\ (registry entries pointing to C:\... keep working). Both layouts work: plain wine prefixes (drive_c at the top) and Proton-managed ones (drive_c under pfx/). The app stays tied to that prefix; removing the launcher entry never deletes files from the prefix. A prefix can be renamed later (Wine prefixes): the folder moves on disk and everything running in it follows the new name, because those paths are stored relative to the prefix. Edits wait for running apps: while anything from a prefix is playing, renaming or deleting it (and switching the default prefix away from an app that has none of its own) is refused with the name of the app to close first — wine keeps its registry and user files open in that folder. Deleting is also refused while a prefix still has apps registered in it, so an app is never silently orphaned into the default prefix. Imports keep the folder as it was: symlinks are recreated (rewritten to relative whenever the target is part of the launcher) and file permissions are carried over, so an imported game or build runs straight away.

"Open with PLWL" in the file manager

The desktop entry (MimeType + Exec %f) makes PLWL show up in Dolphin/Nautilus under Open With for Windows executables (.exe, .msi, .bat, .cmd, .lnk). Opening a file that way shows a small popup in the launcher where you pick the Wine/Proton version and the prefix for this run — both default to the launcher's defaults, nothing is imported into the library. A second launcher instance hands the path to the running one and exits (same single-instance mechanism the shortcuts use).

LAN syncing

Two PLWL installs on the same LAN can mirror each other's content - no cloud, no accounts. Open LAN syncing in the sidebar (under Settings):

  • Device ID - 24 characters, generated once per install (current timestamp + random characters) and re-rollable at any time.
  • Sync code - 8 characters, letters and digits, case-insensitive. A device only accepts a connection when the caller proves it knows its own code; each verification costs two seconds and a device may only have one in flight, so guessing over the LAN is pointless. Both the ID and the code can be re-rolled. A correct code is still not enough: the receiving device shows an Accept / Decline popup before anything moves, and without a click within a minute the sender is turned down. Declining (or ignoring) the popup never touches this machine and does not unpair the two devices.
  • Devices on your network - other PLWL installs, found over UDP; a device shows up only while PLWL is running there, and disappears a few seconds after it is closed.

Pick a device and choose what to push: portable apps, apps imported from a prefix, Wine/Proton builds, prefixes and Default settings. Only what is ticked travels - a sync without Default settings never touches the other machine's defaults. The code has to be typed once - an accepted code is stored next to that device ID, so every later sync needs no typing. If the other side re-rolled its code, the stored one is forgotten and the launcher asks for the new one instead of failing silently.

Both machines show what is happening. The sender gets a progress popup and the receiving device gets the same popup titled Receiving from X…, driven by the transfer as it arrives. Both show the current phase and unit, the files being moved, a sent/total counter per category (Apps: 2/5 · Prefixes: 1/2) and the transferred vs. planned size. The bar follows bytes, not file counts, and it belongs to the phase being worked on: hashing fills its own bar and the transfer starts again from empty, so nothing ever looks finished while it has just begun (the byte line next to it keeps the overall transferred vs. planned total). Both popups warn that the launcher must stay open, and a running sync is not cancellable from the outside: clicking the backdrop or pressing Escape does nothing. If the other device goes quiet for a few seconds (it hashes before it sends) the popup says Waiting for the other device… rather than closing; it closes itself when the transfer ends, or after fifteen minutes of complete silence when the other side has vanished.

The sending device is the source of truth: files that differ are overwritten, missing ones are written, and anything living only on the receiving side inside the synced trees (files/apps/, files/wines/, files/wineprefixes/, files/data/icons/) is deleted. Files are compared with a fast, collision-resistant hash (BLAKE2b-512, SHA-512/SHA-256 fallback); Force sync all files skips the comparison and overwrites everything instead. Hashes are remembered per file (size + modification time), so a repeat sync of a library that did not change reads nothing at all.

Wine builds and prefixes survive the trip. Permissions travel with a sync, so the proton, wine and wineserver binaries are still executable on the other machine (a build whose executable bit was lost is additionally repaired at startup, without re-importing it), and symlinks are sent as RELATIVE paths whenever the file they point at belongs to the launcher: a Proton prefix is full of links into its build, and the receiving side gets links that resolve to its own copy instead of the sender's /home/... folders. Links that leave the launcher on purpose (dosdevices drive mappings, a mounted drive, the user's home folder) stay absolute, and links into a folder that does not exist yet on the other machine are still written relative, so they start working the moment that folder appears. Wine and Proton rewrite a prefix's links with local absolute targets while they update it; PLWL relativizes them again on the wire, on import and before every run.

A sync also carries how the apps are set up: the Wine/Proton build and prefix each app runs in, its arguments, environment variables and DLL overrides, plus playtime and last played. The launcher's default Wine build and its default prefix travel with the Default settings category, and only if that build or prefix exists on the other machine - an id it does not have is ignored instead of leaving a dangling default behind. Playtime only ever grows - the higher counter wins - so pushing from a freshly installed machine cannot wipe a library's play history. Everything else stays local: language and window settings, logs, translations, and external (leave) apps - prefixes are always kept usable, so app entries pointing inside a prefix keep their prefix-relative paths and the receiving side never loses its own Main prefix.

Wine & Proton support

  • 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). After the copy, PLWL rewires the build's absolute symlinks to relative ones and restores the executable bit on the binaries it needs to run (proton, wine, wineserver, winetricks), so a build that arrived from a zip or another machine works without further fiddling. A build can also be given a displayed name of its own (Wine versions): it is used everywhere the build is offered — library config, settings and the Open with PLWL popup — while the folder in files/wines/ keeps its name and the symlinks inside prefixes stay valid.
  • Proton without Steam — fully supported. Proton is invoked like Steam does it: STEAM_COMPAT_DATA_PATH is a per-build compat folder linked to the launcher's own prefix, and STEAM_COMPAT_CLIENT_INSTALL_PATH points at an installed Steam client when there is one (Proton reads its legacy DLLs and steamclient.so from there, read-only) and at the empty files/data/fake-steam otherwise. GE builds work out of the box: PLWL launches through Proton's built-in UMU path (UMU_ID), which needs no Steam client at all. An app that breaks under umu.exe has a per-app Disable UMU switch in its config — PLWL then runs the build's own wine directly (Proton's runinprefix verb): no umu.exe/steam.exe launcher and no Proton prefix setup take part, so the run stays as close to plain wine as possible. (Proton's session init still reads STEAM_COMPAT_CLIENT_INSTALL_PATH on every verb; a disableUmu run gets the empty fake-steam folder and nothing is copied out of it.)
  • One C: drive per prefix — Proton normally keeps its prefix one level deeper (<compat>/pfx/drive_c) than wine (<prefix>/drive_c), which left a prefix used with both kinds with two separate C: drives (two registries, two sets of saves). Whenever a prefix has no pfx yet (a fresh prefix, or a plain-wine prefix first used through Proton), PLWL makes pfx a relative symlink back to the prefix itself, so Proton and wine share one drive_c, one registry and one dosdevices. Existing layouts are left exactly as they are — prefixes from older versions keep their real pfx/ folder, and a prefix with both drive_c trees keeps both, because two live Windows installs cannot be merged safely (both stay visible to the prefix import picker).

Translations

All UI text comes from JSON files in files/translations/. The data object carries the language names; texts maps keys to strings, with {0}, {1} ... placeholders filled at runtime.

The translations bundled with a release are launcher-owned: on every start they are refreshed into files/translations/ whenever their content changed, so launcher updates deliver new/changed keys to existing installs. Extra language files the bundle does not ship (user-added) are never touched.

Development

Codebase overview, conventions and rules for contributors: development.md.