Skip to content

Commit

Permalink
new feature: kill running games by launcher ui
Browse files Browse the repository at this point in the history
  • Loading branch information
Davis-Software committed Nov 20, 2022
1 parent 8dc405b commit 9876a4d
Show file tree
Hide file tree
Showing 8 changed files with 162 additions and 71 deletions.
67 changes: 51 additions & 16 deletions back/mc-connector.js
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down Expand Up @@ -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"),
Expand All @@ -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()
})
Expand Down Expand Up @@ -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,
Expand All @@ -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"),
Expand All @@ -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()
})
Expand All @@ -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,
Expand Down
41 changes: 0 additions & 41 deletions back/tools.js
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down
73 changes: 67 additions & 6 deletions front_src/src/components/GameInfo.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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<GamesInfoType>({});
const [activeClientsCount, setActiveClientsCount] = React.useState<number>(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 (
<li className="d-flex p-2">
<div className="flex-grow-1">
<div>
<span>{client.name || "Vanilla"}</span> <br/>
<span>{client.version.number} ({client.version.type})</span>
</div>
</div>
<div className="d-flex align-items-center">
<Button
disabled={client.pid === null}
color="error"
onClick={() => {
exposedFunctions("mc").killClient(clientUUID)
}}
>
Kill
</Button>
</div>
</li>
)
}
function ListDivider(){
return <li><hr className="dropdown-divider"></hr></li>
}

function GameInfoInner(){
return activeClients ? (
<Button
sx={{height: "100%"}}
color="success"
>{activeClients > 1 ? `${activeClients} Games running` : "Game running"}</Button>
return activeClientsCount ? (
<div className="dropdown">
<Button
className="button dropdown-toggle attach-candle"
id="dropdownMenuButton2"
data-bs-toggle="dropdown"
data-bs-auto-close="outside"
aria-expanded="false"
color="success"
>{activeClientsCount > 1 ? `${activeClientsCount} Games running` : "Game running"}</Button>
<ul
className="dropdown-menu"
aria-labelledby="dropdownMenuButton2"
style={{width: "300px"}}
>
{Object.keys(activeClients).map((clientUUID, index) =>
<ListItem clientUUID={clientUUID} key={index} />
)}
<ListDivider />
<li>
<a
className="dropdown-item attach-candle text-danger"
onClick={() => {
exposedFunctions("mc").killClient("all")
}}
>
Kill all
</a>
</li>
</ul>
</div>
) : <></>
}

Expand Down
13 changes: 13 additions & 0 deletions front_src/src/types/gameInfoType.ts
Original file line number Diff line number Diff line change
@@ -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};
17 changes: 14 additions & 3 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
6 changes: 6 additions & 0 deletions preload.js
Original file line number Diff line number Diff line change
Expand Up @@ -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))
},
Expand Down
Loading

0 comments on commit 9876a4d

Please sign in to comment.