From 9876a4de0fe4d281cc72227cf61669dbb515e6f1 Mon Sep 17 00:00:00 2001 From: Davis-Software Date: Sun, 20 Nov 2022 16:07:31 +0100 Subject: [PATCH] new feature: kill running games by launcher ui --- back/mc-connector.js | 67 ++++++++++++++++++------ back/tools.js | 41 --------------- front_src/src/components/GameInfo.tsx | 73 ++++++++++++++++++++++++--- front_src/src/types/gameInfoType.ts | 13 +++++ package-lock.json | 17 +++++-- package.json | 3 +- preload.js | 6 +++ static/css/index.css | 13 +++-- 8 files changed, 162 insertions(+), 71 deletions(-) create mode 100644 front_src/src/types/gameInfoType.ts diff --git a/back/mc-connector.js b/back/mc-connector.js index e31e1e0..f719a7f 100644 --- a/back/mc-connector.js +++ b/back/mc-connector.js @@ -6,16 +6,25 @@ const path = require("path"); const fs = require("fs"); const {compare} = require("compare-versions"); const fsx = require("fs-extra"); -const {onArrayChange} = require("./tools"); const {getMainWindow} = require("./electron-tools"); +const {randomUUID} = require("crypto") +const ObservableSlim = require("observable-slim") -const runningClients = onArrayChange([], () => { - invoke("mc:runningClients", runningClients.length) -}) +const runningClients = ObservableSlim.create({}, false, sendRunningClients) let mcInstance = null +function sendRunningClients(){ + let clients = {} + for(let key in runningClients){ + let client = runningClients[key] + clients[key] = {version: client.version, pid: client.pid, name: client.name} + } + invoke("mc:runningClients", JSON.parse(JSON.stringify(clients))) +} + + function afterLaunchCalls(){ if(settings.get("minimize-while-playing")){ getMainWindow().hide() @@ -94,7 +103,8 @@ function launchVanilla(version) { } const launcher = new Client() - runningClients.push(launcher) + const launcherUUID = randomUUID() + runningClients[launcherUUID] = {version, launcher, pid: null, kill: null} const opts = { authorization: settings.get("credentials"), @@ -115,18 +125,20 @@ function launchVanilla(version) { } } - launcher.launch(opts).then(() => { + launcher.launch(opts).then((childProcess) => { invoke("mc:gameLaunched") + runningClients[launcherUUID].pid = childProcess.pid + runningClients[launcherUUID].kill = () => childProcess.kill() afterLaunchCalls() }).catch((e) => { - runningClients.splice(runningClients.indexOf(launcher), 1) + delete runningClients[launcherUUID] invoke("mc:gameLaunchError", e) }) launcher.on('arguments', (e) => invoke("mc:arguments", e)) launcher.on('data', (e) => invoke("mc:data", e)) launcher.on('close', (e) => { - runningClients.splice(runningClients.indexOf(launcher), 1) + delete runningClients[launcherUUID] invoke("mc:close", e) afterCloseCalls() }) @@ -180,7 +192,18 @@ function launchModded(manifest) { } const launcher = new Client() - runningClients.push(launcher) + const version = { + number: manifest.mcVersion, + type: manifest.type + } + const launcherUUID = randomUUID() + runningClients[launcherUUID] = { + version, + name: manifest.name, + launcher, + pid: null, + kill: null + } const opts = { clientPackage: installNeeded ? mcPackage : null, @@ -190,10 +213,7 @@ function launchModded(manifest) { authorization: settings.get("credentials"), root: rootPath, - version: { - number: manifest.mcVersion, - type: manifest.type - }, + version, customArgs: settings.get("launch-args"), memory: { max: settings.get("ram"), @@ -209,19 +229,21 @@ function launchModded(manifest) { } } - launcher.launch(opts).then(() => { + launcher.launch(opts).then((childProcess) => { if(installNeeded) fs.writeFileSync(path.join(rootPath, "manifest.json"), JSON.stringify(manifest)) invoke("mc:gameLaunched") + runningClients[launcherUUID].pid = childProcess.pid + runningClients[launcherUUID].kill = () => childProcess.kill() afterLaunchCalls() }).catch((e) => { - runningClients.splice(runningClients.indexOf(launcher), 1) + delete runningClients[launcherUUID] invoke("mc:gameLaunchError", e) }) launcher.on('arguments', (e) => invoke("mc:arguments", e)) launcher.on('data', (e) => invoke("mc:data", e)) launcher.on('close', (e) => { - runningClients.splice(runningClients.indexOf(launcher), 1) + delete runningClients[launcherUUID] invoke("mc:close", e) afterCloseCalls() }) @@ -232,12 +254,25 @@ function launchModded(manifest) { launcher.on('progress', (e) => invoke("mc:progress", e)) } +function killClient(clientUUID) { + if(clientUUID === "all"){ + Object.keys(runningClients).forEach(k => { + runningClients[k].kill() + }) + return + } + if(!runningClients[clientUUID] && runningClients[clientUUID].kill !== null) return + runningClients[clientUUID].kill() +} + registerIpcListener("dialog:askLogin", askLogin) registerIpcListener("dialog:askValidate", askValidate) registerIpcListener("dialog:refreshLogin", refreshLogin) registerIpcListener("dialog:logout", logout) registerIpcListener("mc:launchVanilla", (e, v) => launchVanilla(v)) registerIpcListener("mc:launchModded", (e, v) => launchModded(v)) +registerIpcListener("mc:sendRunningClients", () => sendRunningClients()) +registerIpcListener("mc:killClient", (e, v) => killClient(v)) module.exports = { askLogin, diff --git a/back/tools.js b/back/tools.js index fe12a96..0f454fe 100644 --- a/back/tools.js +++ b/back/tools.js @@ -34,47 +34,6 @@ function isObject(item) { return (item && typeof item === 'object' && !Array.isArray(item)); } -function onObjectChange(object, onChange) { - const handler = { - get(target, property, receiver) { - try { - return new Proxy(target[property], handler) - } catch (err) { - return Reflect.get(target, property, receiver) - } - }, - defineProperty(target, property, descriptor) { - onChange(target, property, descriptor) - return Reflect.defineProperty(target, property, descriptor) - }, - deleteProperty(target, property) { - onChange() - return Reflect.deleteProperty(target, property) - } - } - return new Proxy(object, handler) -} -function onArrayChange(array, onChange){ - return { - push(...args){ - let out = array.push(...args) - onChange() - return out - }, - splice(...args){ - let out = array.splice(...args) - onChange() - return out - }, - indexOf(...args){ - return array.indexOf(...args) - }, - get length(){ - return array.length - } - } -} - function tryOrElse(fn, elseFn) { try { return fn() diff --git a/front_src/src/components/GameInfo.tsx b/front_src/src/components/GameInfo.tsx index 73f627d..31ab84c 100644 --- a/front_src/src/components/GameInfo.tsx +++ b/front_src/src/components/GameInfo.tsx @@ -2,24 +2,85 @@ import ReactDOM from "react-dom"; import React from "react"; import {exposedFunctions} from "../utils/constants"; import {Button} from "@mui/material"; +import GamesInfoType from "../types/gameInfoType"; function GameInfo() { - const [activeClients, setActiveClients] = React.useState(0); + const [activeClients, setActiveClients] = React.useState({}); + const [activeClientsCount, setActiveClientsCount] = React.useState(0); React.useEffect(() => { + exposedFunctions("mc").sendRunningClients() exposedFunctions("mc").on("runningClients", setActiveClients) return () => { exposedFunctions("mc").off("runningClients", setActiveClients) } }, []) + React.useEffect(() => { + setActiveClientsCount(Object.keys(activeClients).length) + }, [activeClients]) + + function ListItem({clientUUID}: {clientUUID: string }) { + const client = activeClients[clientUUID] + + return ( +
  • +
    +
    + {client.name || "Vanilla"}
    + {client.version.number} ({client.version.type}) +
    +
    +
    + +
    +
  • + ) + } + function ListDivider(){ + return

  • + } function GameInfoInner(){ - return activeClients ? ( - + return activeClientsCount ? ( +
    + + +
    ) : <> } diff --git a/front_src/src/types/gameInfoType.ts b/front_src/src/types/gameInfoType.ts new file mode 100644 index 0000000..4e9d27a --- /dev/null +++ b/front_src/src/types/gameInfoType.ts @@ -0,0 +1,13 @@ +import {McLcVersionType} from "./mcVersionType"; + +interface ClientInfoType{ + version: McLcVersionType; + pid: number | null; + name?: string; +} +interface GamesInfoType{ + [uuid: string]: ClientInfoType +} + +export default GamesInfoType; +export type {ClientInfoType}; diff --git a/package-lock.json b/package-lock.json index 2117cd7..7810ec7 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "swc_mclauncher", - "version": "1.1.7", + "version": "1.1.8", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "swc_mclauncher", - "version": "1.1.7", + "version": "1.1.8", "license": "MIT", "dependencies": { "compare-versions": "^5.0.1", @@ -14,7 +14,8 @@ "fs-extra": "^10.1.0", "markdown-it": "^13.0.1", "minecraft-launcher-core": "^3.16.16", - "msmc": "^4.0.0-pre7" + "msmc": "^4.0.0-pre7", + "observable-slim": "^0.1.6" }, "devDependencies": { "electron": "^21.3.0", @@ -2313,6 +2314,11 @@ "node": ">= 0.4" } }, + "node_modules/observable-slim": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/observable-slim/-/observable-slim-0.1.6.tgz", + "integrity": "sha512-a6xYwLaJgGxPxm5tBdKrGSNxiYklbshjlAJE0Nm7iHTMMrkKWvUG63QUhn4urMl5/aPLHIVouh0B3CktyU664w==" + }, "node_modules/once": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", @@ -4911,6 +4917,11 @@ "dev": true, "optional": true }, + "observable-slim": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/observable-slim/-/observable-slim-0.1.6.tgz", + "integrity": "sha512-a6xYwLaJgGxPxm5tBdKrGSNxiYklbshjlAJE0Nm7iHTMMrkKWvUG63QUhn4urMl5/aPLHIVouh0B3CktyU664w==" + }, "once": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", diff --git a/package.json b/package.json index e5d0227..7ed7997 100644 --- a/package.json +++ b/package.json @@ -36,7 +36,8 @@ "msmc": "^4.0.0-pre7", "electron-updater": "^5.3.0", "compare-versions": "^5.0.1", - "fs-extra": "^10.1.0" + "fs-extra": "^10.1.0", + "observable-slim": "^0.1.6" }, "build": { "appId": "org.software-city.projects.swc_mclauncher", diff --git a/preload.js b/preload.js index 528af85..cc53a8d 100644 --- a/preload.js +++ b/preload.js @@ -62,6 +62,12 @@ contextBridge.exposeInMainWorld("mc", { launchModded: (modPack) => { return ipcRenderer.invoke("mc:launchModded", modPack) }, + sendRunningClients: () => { + return ipcRenderer.invoke("mc:sendRunningClients") + }, + killClient: (clientUUID) => { + return ipcRenderer.invoke("mc:killClient", clientUUID) + }, on(event, callback){ ipcRenderer.on(`mc:${event}`, (e, ...args) => callback(...args)) }, diff --git a/static/css/index.css b/static/css/index.css index a879a7b..08d3ce4 100644 --- a/static/css/index.css +++ b/static/css/index.css @@ -36,24 +36,29 @@ body{ height: 36px; display: flex; } -#window-frame #user-dropdown{ +#window-frame #user-dropdown, #window-frame #game-info{ transition: all .15s; -webkit-app-region: no-drag; } +#window-frame #user-dropdown, #window-frame #game-info a{ + cursor: pointer; +} #window-frame #user-dropdown:hover{ background: rgba(65, 65, 65, .25); - cursor: pointer; } #window-frame #user-dropdown:active{ background: rgb(84, 84, 84, .25); } -#window-frame #user-dropdown .button{ - color: white; +#window-frame #user-dropdown .button, #window-frame #game-info .button{ height: 36px; border: none; border-radius: 0; background: none; padding: 0 10px; + cursor: pointer; +} +#window-frame #user-dropdown .button{ + color: white; } #window-frame .frame-drag-section{ position: fixed;