Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature/rpc #1299

Merged
merged 27 commits into from
Dec 18, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
b8b000d
refactor: change game process set to map
JackEnx Dec 16, 2024
19485b5
feature: find game in execution executable path
JackEnx Dec 16, 2024
d17abdc
refactor: games id without path get only games objectID
JackEnx Dec 16, 2024
08d320a
fix: win32 path
JackEnx Dec 16, 2024
c2a52d3
refactor: reduce code redundance
JackEnx Dec 16, 2024
e87f287
refactor: remove redundant plataform validation
JackEnx Dec 16, 2024
5607f6d
refactor: move executable name replate outside the forof
JackEnx Dec 16, 2024
d9adc49
fix: executable slice
JackEnx Dec 16, 2024
fee1a92
fix: executable lastIndeOf
JackEnx Dec 16, 2024
1ffb982
refactor: get system process map
JackEnx Dec 16, 2024
431ad2f
refactor: Set descontruction to Set foreach
JackEnx Dec 16, 2024
efcf778
refactor: merged game queries to a single query
JackEnx Dec 16, 2024
311b011
feature: linux game observe
JackEnx Dec 16, 2024
fcfaae7
refactor: move exec command to a function
JackEnx Dec 16, 2024
64745b9
feature: linux find opened game and wine path
JackEnx Dec 16, 2024
9f8269d
refactor: linux get game exe logic
JackEnx Dec 17, 2024
66a8170
fix: lint
JackEnx Dec 17, 2024
f2714bd
fix: process map promise return
JackEnx Dec 17, 2024
347e38c
refactor: move rpc game executables url to a env
JackEnx Dec 17, 2024
4c3eb98
refactor: rpc url to a generic url
JackEnx Dec 17, 2024
e95407e
refactor: add URL to end of the env main external resources
JackEnx Dec 18, 2024
1f3a6a9
refactor: add bundle.js in external script environment
JackEnx Dec 18, 2024
5222d31
refactor: add bundle.js in external script environment
JackEnx Dec 18, 2024
ad59fcd
Merge branch 'main' into feature/rpc
zamitto Dec 18, 2024
4f7255a
refactor: process watcher commands
JackEnx Dec 18, 2024
e2d16ba
Merge branch 'feature/rpc' of https://github.com/hydralauncher/hydra …
JackEnx Dec 18, 2024
bc98f7c
fix: closed game pestis in the watch processes
JackEnx Dec 18, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ jobs:
RENDERER_VITE_INTERCOM_APP_ID: ${{ vars.RENDERER_VITE_INTERCOM_APP_ID }}
RENDERER_VITE_EXTERNAL_RESOURCES_URL: ${{ vars.RENDERER_VITE_EXTERNAL_RESOURCES_URL }}
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
MAIN_VITE_EXTERNAL_RESOURCES_URL: ${{ vars.MAIN_VITE_EXTERNAL_RESOURCES_URL }}

- name: Build Windows
if: matrix.os == 'windows-latest'
Expand All @@ -62,6 +63,7 @@ jobs:
RENDERER_VITE_INTERCOM_APP_ID: ${{ vars.RENDERER_VITE_INTERCOM_APP_ID }}
RENDERER_VITE_EXTERNAL_RESOURCES_URL: ${{ vars.RENDERER_VITE_EXTERNAL_RESOURCES_URL }}
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
MAIN_VITE_EXTERNAL_RESOURCES_URL: ${{ vars.MAIN_VITE_EXTERNAL_RESOURCES_URL }}

- name: Create artifact
uses: actions/upload-artifact@v4
Expand Down
2 changes: 2 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ jobs:
RENDERER_VITE_INTERCOM_APP_ID: ${{ vars.RENDERER_VITE_INTERCOM_APP_ID }}
RENDERER_VITE_EXTERNAL_RESOURCES_URL: ${{ vars.RENDERER_VITE_EXTERNAL_RESOURCES_URL }}
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
MAIN_VITE_EXTERNAL_RESOURCES_URL: ${{ vars.MAIN_VITE_EXTERNAL_RESOURCES_URL }}
- name: Build Windows
if: matrix.os == 'windows-latest'
run: yarn build:win
Expand All @@ -60,6 +61,7 @@ jobs:
RENDERER_VITE_INTERCOM_APP_ID: ${{ vars.RENDERER_VITE_INTERCOM_APP_ID }}
RENDERER_VITE_EXTERNAL_RESOURCES_URL: ${{ vars.RENDERER_VITE_EXTERNAL_RESOURCES_URL }}
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
MAIN_VITE_EXTERNAL_RESOURCES_URL: ${{ vars.MAIN_VITE_EXTERNAL_RESOURCES_URL }}
- name: Create artifact
uses: actions/upload-artifact@v4
with:
Expand Down
153 changes: 136 additions & 17 deletions src/main/services/process-watcher.ts
Original file line number Diff line number Diff line change
@@ -1,53 +1,172 @@
import { IsNull, Not } from "typeorm";
import { gameRepository } from "@main/repository";
import { WindowManager } from "./window-manager";
import { createGame, updateGamePlaytime } from "./library-sync";
import type { GameRunning } from "@types";
import { PythonInstance } from "./download";
import { Game } from "@main/entity";
import axios from "axios";
import { exec } from "child_process";

const commands = {
findWineDir: `lsof -c wine 2>/dev/null | grep '/drive_c/windows$' | head -n 1 | awk '{for(i=9;i<=NF;i++) printf "%s ", $i; print ""}'`,
findWineExecutables: `lsof -c wine 2>/dev/null | grep '\\.exe$' | awk '{for(i=9;i<=NF;i++) printf "%s ", $i; print ""}'`,
};

export const gamesPlaytime = new Map<
number,
{ lastTick: number; firstTick: number; lastSyncTick: number }
>();

interface ExecutableInfo {
name: string;
os: string;
}

interface GameExecutables {
[key: string]: ExecutableInfo[];
}

const TICKS_TO_UPDATE_API = 120;
let currentTick = 1;

const getSystemProcessSet = async () => {
const processes = await PythonInstance.getProcessList();
const gameExecutables = (
await axios
.get(
import.meta.env.MAIN_VITE_EXTERNAL_RESOURCES_URL +
"/game-executables.json"
)
.catch(() => {
return { data: {} };
})
).data as GameExecutables;

const findGamePathByProcess = (
processMap: Map<string, Set<string>>,
gameId: string
) => {
const executables = gameExecutables[gameId].filter((info) => {
if (process.platform === "linux" && info.os === "linux") return true;
return info.os === "win32";
});

for (const executable of executables) {
const exe = executable.name.slice(executable.name.lastIndexOf("/") + 1);

if (process.platform === "linux")
return new Set(processes.map((process) => process.name));
return new Set(processes.map((process) => process.exe));
if (!exe) continue;

const pathSet = processMap.get(exe);

if (pathSet) {
const executableName =
process.platform === "win32"
? executable.name.replace(/\//g, "\\")
: executable.name;

pathSet.forEach((path) => {
if (path.toLowerCase().endsWith(executableName)) {
gameRepository.update(
{ objectID: gameId, shop: "steam" },
{ executablePath: path }
);

if (process.platform === "linux") {
exec(commands.findWineDir, (err, out) => {
if (err) return;

gameRepository.update(
{ objectID: gameId, shop: "steam" },
{
winePrefixPath: out.trim().replace("/drive_c/windows", ""),
}
);
});
}
}
});
}
}
};

const getExecutable = (game: Game) => {
if (process.platform === "linux")
return game.executablePath?.split("/").at(-1);
return game.executablePath;
const getSystemProcessMap = async () => {
const processes = await PythonInstance.getProcessList();

const map = new Map<string, Set<string>>();

processes.forEach((process) => {
const key = process.name.toLowerCase();
const value = process.exe;

if (!key || !value) return;

const currentSet = map.get(key) ?? new Set();
map.set(key, currentSet.add(value));
});

if (process.platform === "linux") {
await new Promise((res) => {
exec(commands.findWineExecutables, (err, out) => {
if (err) {
res(null);
return;
}

const pathSet = new Set(
out
.trim()
.split("\n")
.map((path) => path.trim())
);

pathSet.forEach((path) => {
if (path.startsWith("/usr")) return;

const key = path.slice(path.lastIndexOf("/") + 1).toLowerCase();

if (!key || !path) return;

const currentSet = map.get(key) ?? new Set();
map.set(key, currentSet.add(path));
});

res(null);
});
});
}

return map;
};

export const watchProcesses = async () => {
const games = await gameRepository.find({
where: {
executablePath: Not(IsNull()),
isDeleted: false,
},
});

if (games.length === 0) return;
if (!games.length) return;

const processSet = await getSystemProcessSet();
const processMap = await getSystemProcessMap();

for (const game of games) {
const executable = getExecutable(game);
const executablePath = game.executablePath;

if (!executablePath) {
if (gameExecutables[game.objectID]) {
findGamePathByProcess(processMap, game.objectID);
}
continue;
}

if (!executable) continue;
const executable = executablePath
.slice(
executablePath.lastIndexOf(process.platform === "win32" ? "\\" : "/") +
1
)
.toLowerCase();

const gameProcess = processSet.has(executable);
const hasProcess = processMap.get(executable)?.has(executablePath);

if (gameProcess) {
if (hasProcess) {
if (gamesPlaytime.has(game.id)) {
onTickGame(game);
} else {
Expand Down
1 change: 1 addition & 0 deletions src/main/vite-env.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ interface ImportMetaEnv {
readonly MAIN_VITE_ANALYTICS_API_URL: string;
readonly MAIN_VITE_AUTH_URL: string;
readonly MAIN_VITE_CHECKOUT_URL: string;
readonly MAIN_VITE_EXTERNAL_RESOURCES_URL: string;
}

interface ImportMeta {
Expand Down
2 changes: 1 addition & 1 deletion src/renderer/src/app.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -126,7 +126,7 @@ export function App() {

const $script = document.createElement("script");
$script.id = "external-resources";
$script.src = `${import.meta.env.RENDERER_VITE_EXTERNAL_RESOURCES_URL}?t=${Date.now()}`;
$script.src = `${import.meta.env.RENDERER_VITE_EXTERNAL_RESOURCES_URL}/bundle.js?t=${Date.now()}`;
document.head.appendChild($script);
});
}, [fetchUserDetails, syncFriendRequests, updateUserDetails, dispatch]);
Expand Down
Loading