The desktop store put the catalog on ordinary computers, and then asked people to open a terminal — which on Windows is not even a workable ask, because the installer is `curl … | sh`. This is the window: a grid of cards, one click to install a title into the application menu, one to play it, one to remove it. The CLI stays the product. Every action runs `desktop_store.py --json`, so there is one catalog logic, one state file and one delete guard; the window never touches the filesystem itself. That is also why the engine grew `--json` first rather than this app growing a parser for prose. It doubles as the Windows install path: with no store on the machine, the app downloads the engine, the shared core and a config into the same folder the shell installer would use — Node's https, no curl. An engine older than 1.1.0 cannot be driven from a window, so the client checks the version and offers to refresh it instead of failing on the first call. Deliberate choices worth knowing: - No renderer framework and no build step. Plain HTML, CSS and JS, one runtime dependency. The whole UI is readable in one sitting. - `contextIsolation` on, `nodeIntegration` off, `sandbox` on, a CSP that permits only the app's own script and stylesheet plus images over HTTPS. The renderer can do exactly what preload.js exposes and nothing else. - English and Hungarian, following the system language. The CLIs and the docs stay English; this is the one end-user surface where that is not enough. - `ENGINES` is a list with one entry. The RetroArch store has the same command shape, so adding it is an entry, not a rewrite. Two ways to test it without a working installation in the way: `npm run smoke` drives the bridge with no window at all, and `npm run uitest` loads the window once and reports what rendered — the only way a renderer error would otherwise be noticed, since the main process log stays empty. Both accept a sandbox store through STORE_ROOT / SMOKE_HOME. Verified on macOS arm64, including the packaged .app: the store is found, ten titles list, a sync installs three, and the window renders them as installed with their Play and Remove buttons. Linux and Windows are unproven, as they are for the CLI itself. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
106 lines
4.8 KiB
Markdown
106 lines
4.8 KiB
Markdown
# warp-engine-desktop-gui — a window for the desktop store
|
|
|
|
A graphical client for
|
|
[`warp-engine-desktop-store`](https://git.teletypegames.org/stores/warp-engine-desktop-store):
|
|
the catalog as a grid of cards, one click to install a title into your own
|
|
application menu, one to play it, one to remove it. Linux, macOS and Windows.
|
|
|
|
The CLI stays the product; this is its front door. Every action here runs
|
|
`desktop_store.py`, so there is one catalog logic, one state file and one delete
|
|
guard — the window never touches the filesystem itself.
|
|
|
|
It is also **the Windows install path**. The store's own installer is
|
|
`curl … | sh`, which Windows does not have; this app downloads the store engine
|
|
itself, into the same folder the shell installer would use.
|
|
|
|
## What it needs
|
|
|
|
- **Python 3** on the machine, because the store is a Python program. The app
|
|
looks for `python3`, `python` and `py -3`, and says so plainly if none answer.
|
|
- Nothing else at runtime. No Node, no package manager, no admin rights: the
|
|
store installs under your own user account.
|
|
|
|
## Install
|
|
|
|
Grab the package for your machine from the
|
|
[releases](https://git.teletypegames.org/stores/warp-engine-desktop-gui/releases)
|
|
and open it. On first run, if there is no store on the machine yet, the window
|
|
offers to download one — that is the whole setup.
|
|
|
|
The macOS build is **not signed or notarised**, so the first open needs
|
|
*right-click ▸ Open* (or *System Settings ▸ Privacy & Security*). Nothing the
|
|
store itself downloads is affected: those files are fetched by Python, which does
|
|
not set the quarantine flag.
|
|
|
|
## Use
|
|
|
|
- **Install all** fetches everything the catalog offers for this machine.
|
|
- A card's button is **Install**, **Update**, or **Play** / **Open** once it is
|
|
there. **Remove** takes a title back out.
|
|
- Each card says whether it is **native** — unpacked and run locally, works
|
|
offline — or **hosted**: a browser build the catalog serves rather than
|
|
packages, so its entry opens a page and needs the network.
|
|
- The **Log** drawer at the bottom carries the store's own output verbatim, and
|
|
next to it are buttons that open the two folders everything lands in.
|
|
- The language follows the system and can be switched; **English and Hungarian**.
|
|
|
|
Anything installed from the window is a normal menu entry, so it also shows up in
|
|
your launcher, Dock or Start menu — the app does not have to be running to play.
|
|
|
|
## Development
|
|
|
|
```sh
|
|
npm install
|
|
npm start # the window, against whatever store is installed
|
|
npm run smoke # the bridge only: no window, no Electron
|
|
npm run uitest # loads the window once and reports what rendered
|
|
npm run dist:mac # or dist:win / dist:linux
|
|
```
|
|
|
|
**Node 22 or newer is needed to install**, not to run: Electron's own installer
|
|
is ESM-only, and older Node cannot `require()` it. The packaged app carries its
|
|
own runtime.
|
|
|
|
Both test scripts accept a sandbox store instead of the real one, which is how
|
|
this repository is tested without touching a working installation:
|
|
|
|
```sh
|
|
STORE_ROOT=/tmp/sandbox-root npm start
|
|
SMOKE_HOME=/tmp/sandbox-root/ttg-desktop npm run smoke
|
|
```
|
|
|
|
### How it is put together
|
|
|
|
| File | What it does |
|
|
|---|---|
|
|
| `main.js` | the window, the IPC, and the one-call-at-a-time guard |
|
|
| `preload.js` | the entire surface the renderer gets — no Node reaches it |
|
|
| `lib/store.js` | finds the store and Python, runs the CLI, parses its JSON |
|
|
| `lib/bootstrap.js` | downloads the engine, the shared core and a config |
|
|
| `lib/i18n.js` | the two string tables |
|
|
| `renderer/` | plain HTML, CSS and JS — no framework, no build step |
|
|
|
|
`contextIsolation` is on, `nodeIntegration` off, `sandbox` on, and the page
|
|
carries a CSP that allows only its own script and stylesheet plus images over
|
|
HTTPS. Links open in the real browser; the window itself never navigates.
|
|
|
|
`lib/store.js` talks to the CLI through `--json`, which puts data on stdout and
|
|
the human-readable log on stderr. That flag arrived with engine **1.1.0**, and the
|
|
client checks: an older store is met with an offer to refresh it rather than a
|
|
failed call.
|
|
|
|
The bridge keeps an `ENGINES` list with one entry today. The RetroArch store has
|
|
the same command shape, so a second entry is the whole change needed to drive it
|
|
too — that is why the indirection is there.
|
|
|
|
## Verified, and not
|
|
|
|
Exercised on macOS (arm64): the store is discovered, the catalog lists, a sync
|
|
installs, the window renders the installed state, and `npm run uitest` passes with
|
|
the grid rendered and both languages in the picker. The bootstrap download was run
|
|
into an empty directory and the resulting store answered the bridge.
|
|
|
|
**Not tried on Linux or Windows.** The paths and the launch behaviour are written
|
|
for them, and the store CLI itself has the same gap — `.desktop` and `.lnk`
|
|
launchers have been generated and read, but nobody has clicked one.
|