Deno desktop stuff
This commit is contained in:
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"server": "bworld.moder.fans"
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
// deno run --allow-read --allow-write desktop/linux_launcher.ts dist/bworld
|
||||
// deno desktop has no setting for chromium's command line, but its linux launcher passes its arguments on to chromium.
|
||||
// so this moves the launcher to bworld-bin and puts a script in its place that starts it with the switches webgpu
|
||||
// needs on some linux setups. run it on the app folder after deno desktop builds it; running it again does nothing
|
||||
const SWITCHES = [
|
||||
"--enable-unsafe-webgpu",
|
||||
"--ozone-platform=x11",
|
||||
"--use-angle=vulkan",
|
||||
"--enable-features=Vulkan,VulkanFromANGLE",
|
||||
];
|
||||
|
||||
const dir = Deno.args[0];
|
||||
if (!dir) {
|
||||
console.error("usage: linux_launcher.ts <app folder>, like dist/bworld");
|
||||
Deno.exit(1);
|
||||
}
|
||||
const name = dir.replace(/\/+$/, "").split("/").pop()!;
|
||||
const launcher = `${dir}/${name}`;
|
||||
const binary = `${launcher}-bin`;
|
||||
|
||||
// already wrapped: the launcher is a script now
|
||||
const head = new Uint8Array(4);
|
||||
const file = Deno.openSync(launcher);
|
||||
file.readSync(head);
|
||||
file.close();
|
||||
const is_elf = head[0] === 0x7f && head[1] === 0x45 && head[2] === 0x4c && head[3] === 0x46;
|
||||
if (!is_elf) {
|
||||
console.log(`${launcher} is already wrapped`);
|
||||
Deno.exit(0);
|
||||
}
|
||||
|
||||
Deno.renameSync(launcher, binary);
|
||||
Deno.writeTextFileSync(
|
||||
launcher,
|
||||
`#!/bin/sh
|
||||
# starts ${name} with the chromium switches webgpu needs on some linux setups, see desktop/linux_launcher.ts.
|
||||
# BWORLD_CHROMIUM_FLAGS replaces them, set it to "" to start without any
|
||||
here="$(dirname "$(readlink -f "$0")")"
|
||||
# the binary finds its runtime by its own name, which is ${name}.so and not ${name}-bin.so
|
||||
export LAUFEY_RUNTIME_PATH="$here/${name}.so"
|
||||
exec "$here/${name}-bin" \${BWORLD_CHROMIUM_FLAGS-${SWITCHES.join(" ")}} "$@"
|
||||
`,
|
||||
);
|
||||
Deno.chmodSync(launcher, 0o755);
|
||||
console.log(`Wrapped ${launcher}: ${SWITCHES.join(" ")}`);
|
||||
@@ -0,0 +1,86 @@
|
||||
/// <reference lib="deno.desktop" />
|
||||
|
||||
// the desktop app: the web client in a bundled chromium (deno desktop's cef backend), so people whose browser has no
|
||||
// webgpu (firefox) can still play. it joins servers like the browser does, and runs its own game server for
|
||||
// singleplayer, like minecraft java. build it with deno task desktop, see the desktop/ docs for how deno desktop works
|
||||
import { fromFileUrl, join } from "@std/path";
|
||||
import { type Host, serve_static, start_host } from "../server/host.ts";
|
||||
|
||||
// where the project was built from: build/ and server_mods/ are bundled into the app with --include
|
||||
const ROOT = fromFileUrl(new URL("../", import.meta.url));
|
||||
const BUILD_DIR = join(ROOT, "build");
|
||||
|
||||
// the server the address field starts with. "" leaves it empty for the player to fill in
|
||||
const config: { server?: string } = JSON.parse(
|
||||
await Deno.readTextFile(new URL("./config.json", import.meta.url)),
|
||||
);
|
||||
const default_server = Deno.env.get("BWORLD_SERVER") ?? config.server ?? "";
|
||||
|
||||
const win = new Deno.BrowserWindow({ title: "bworld", width: 1280, height: 720 });
|
||||
|
||||
// the singleplayer game server, started the first time singleplayer connects so players who only join other servers
|
||||
// don't get a world made for them
|
||||
let host: Host | undefined;
|
||||
|
||||
function singleplayer_host(): Host {
|
||||
if (!host) {
|
||||
const worlds = join(data_dir(), "worlds");
|
||||
Deno.mkdirSync(worlds, { recursive: true });
|
||||
host = start_host({
|
||||
build_dir: BUILD_DIR,
|
||||
server_mods_dir: join(ROOT, "server_mods"),
|
||||
root: ROOT,
|
||||
world_file: join(worlds, "world.json"),
|
||||
on_fatal(message) {
|
||||
console.error(message);
|
||||
alert(`The singleplayer world stopped: ${message}`);
|
||||
Deno.exit(1);
|
||||
},
|
||||
});
|
||||
}
|
||||
return host;
|
||||
}
|
||||
|
||||
// the world gets saved before the app closes
|
||||
win.addEventListener("close", async (event) => {
|
||||
if (!host) {
|
||||
return;
|
||||
}
|
||||
event.preventDefault();
|
||||
try {
|
||||
await host.shutdown();
|
||||
} catch (e) {
|
||||
console.error((e as Error).message);
|
||||
}
|
||||
Deno.exit(0);
|
||||
});
|
||||
|
||||
// deno desktop binds this to a private local address and opens the window on it, the port given here is ignored
|
||||
Deno.serve((req) => {
|
||||
const url = new URL(req.url);
|
||||
if (url.pathname === "/") {
|
||||
// ?server= fills in the address field, without it the field would start with this app's own address.
|
||||
// ?singleplayer shows the singleplayer button, which joins this app's own server
|
||||
const start = new URL("/client/", url);
|
||||
start.searchParams.set("server", default_server);
|
||||
start.searchParams.set("singleplayer", "");
|
||||
return Response.redirect(start, 302);
|
||||
}
|
||||
if (url.pathname === "/ws") {
|
||||
return singleplayer_host().handle(req);
|
||||
}
|
||||
return serve_static(BUILD_DIR, req, url);
|
||||
});
|
||||
|
||||
// where the app keeps its worlds, the usual place for app data on each os
|
||||
function data_dir() {
|
||||
const home = Deno.env.get("HOME") ?? Deno.env.get("USERPROFILE") ?? ".";
|
||||
switch (Deno.build.os) {
|
||||
case "windows":
|
||||
return join(Deno.env.get("APPDATA") ?? join(home, "AppData", "Roaming"), "bworld");
|
||||
case "darwin":
|
||||
return join(home, "Library", "Application Support", "bworld");
|
||||
default:
|
||||
return join(Deno.env.get("XDG_DATA_HOME") ?? join(home, ".local", "share"), "bworld");
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user