Skip to content

Commit

Permalink
Merge pull request #14 from fhilipecrash/feat/add-wine-lutris-integra…
Browse files Browse the repository at this point in the history
…tion

Added modal to warning user if Wine or Lutris is not installed
  • Loading branch information
Hydra authored Apr 15, 2024
2 parents 03ee6b8 + 8b6fa51 commit 66a1153
Show file tree
Hide file tree
Showing 13 changed files with 140 additions and 19 deletions.
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,8 @@ out/

.vscode/

.venv

dev.db

__pycache__
Expand Down
11 changes: 9 additions & 2 deletions forge.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ const config: ForgeConfig = {
packagerConfig: {
asar: true,
icon: "./images/icon.png",
executableName: "Hydra",
extraResource: [
"./resources/hydra.db",
"./resources/icon_tray.png",
Expand All @@ -34,11 +35,17 @@ const config: ForgeConfig = {
new MakerSquirrel({
setupIcon: "./images/icon.ico",
}),
new MakerZIP({}, ["darwin"]),
new MakerRpm({}),
new MakerZIP({}, ["darwin", "linux"]),
new MakerRpm({
options: {
mimeType: ["x-scheme-handler/hydralauncher"],
bin: './Hydra'
},
}),
new MakerDeb({
options: {
mimeType: ["x-scheme-handler/hydralauncher"],
bin: './Hydra'
},
}),
],
Expand Down
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,7 @@
"uuid": "^9.0.1",
"vite-plugin-svgr": "^4.2.0",
"vite-tsconfig-paths": "^4.3.2",
"winston": "^3.12.0"
"winston": "^3.12.0",
"yaml": "^2.4.1"
}
}
5 changes: 5 additions & 0 deletions src/locales/en/translation.json
Original file line number Diff line number Diff line change
Expand Up @@ -107,5 +107,10 @@
},
"game_card": {
"no_downloads": "No downloads available"
},
"binary_not_found_modal": {
"title": "Programs not installed",
"description": "Wine or Lutris executables were not found on your system",
"instructions": "Check the correct way to install any of them on your Linux distro so that the game can run normally"
}
}
5 changes: 5 additions & 0 deletions src/locales/es/translation.json
Original file line number Diff line number Diff line change
Expand Up @@ -107,5 +107,10 @@
},
"game_card": {
"no_downloads": "No hay descargas disponibles"
},
"binary_not_found_modal": {
"title": "Programas no instalados",
"description": "Los ejecutables de Wine o Lutris no se encontraron en su sistema",
"instructions": "Comprueba la forma correcta de instalar cualquiera de ellos en tu distro Linux para que el juego pueda ejecutarse con normalidad"
}
}
5 changes: 5 additions & 0 deletions src/locales/pt/translation.json
Original file line number Diff line number Diff line change
Expand Up @@ -107,5 +107,10 @@
},
"game_card": {
"no_downloads": "Sem downloads disponíveis"
},
"binary_not_found_modal": {
"title": "Programas não instalados",
"description": "Não foram encontrados no seu sistema os executáveis do Wine ou Lutris",
"instructions": "Verifique a forma correta de instalar algum deles na sua distro Linux para que o jogo possa ser executado normalmente"
}
}
44 changes: 32 additions & 12 deletions src/main/events/library/open-game.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
import { gameRepository } from "@main/repository";
import { generateYML } from "../misc/generate-lutris-yaml";
import path from "node:path";
import fs from "node:fs";
import { writeFile } from "node:fs/promises";
import { spawnSync, exec } from "node:child_process";

import { registerEvent } from "../register-event";
import { shell } from "electron";
Expand All @@ -12,25 +15,42 @@ const openGame = async (
) => {
const game = await gameRepository.findOne({ where: { id: gameId } });

if (!game) return;
if (!game) return true;

const gamePath = path.join(
game.downloadPath ?? (await getDownloadsPath()),
game.folderName
);

if (fs.existsSync(gamePath)) {
const setupPath = path.join(gamePath, "setup.exe");
if (fs.existsSync(setupPath)) {
shell.openExternal(setupPath);
} else {
shell.openPath(gamePath);
}
} else {
await gameRepository.delete({
id: gameId,
});
if (!fs.existsSync(gamePath)) {
await gameRepository.delete({ id: gameId, });
return true;
}

const setupPath = path.join(gamePath, "setup.exe");
if (!fs.existsSync(setupPath)) {
shell.openPath(gamePath);
return true;
}

if (process.platform === "win32") {
shell.openExternal(setupPath);
return true;
}

if (spawnSync("which", ["lutris"]).status === 0) {
const ymlPath = path.join(gamePath, "setup.yml");
await writeFile(ymlPath, generateYML(game));
exec(`lutris --install "${ymlPath}"`);
return true;
}

if (spawnSync("which", ["wine"]).status === 0) {
exec(`wine "${setupPath}"`);
return true;
}

return false;
};

registerEvent(openGame, {
Expand Down
37 changes: 37 additions & 0 deletions src/main/events/misc/generate-lutris-yaml.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import { Document as YMLDocument } from "yaml";
import { Game } from "@main/entity";
import path from "node:path";

export const generateYML = (game: Game) => {
const slugifiedGameTitle = game.title.replace(/\s/g, "-").toLocaleLowerCase();

const doc = new YMLDocument({
name: game.title,
game_slug: slugifiedGameTitle,
slug: `${slugifiedGameTitle}-installer`,
version: "Installer",
runner: "wine",
script: {
game: {
prefix: "$GAMEDIR",
arch: "win64",
working_dir: "$GAMEDIR"
},
installer: [{
task: {
name: "create_prefix",
arch: "win64",
prefix: "$GAMEDIR"
}
}, {
task: {
executable: path.join(game.downloadPath, game.folderName, "setup.exe"),
name: "wineexec",
prefix: "$GAMEDIR"
}
}]
}
});

return doc.toString();
}
2 changes: 1 addition & 1 deletion src/renderer/declaration.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ declare global {
/* Library */
getLibrary: () => Promise<Game[]>;
getRepackersFriendlyNames: () => Promise<Record<string, string>>;
openGame: (gameId: number) => Promise<void>;
openGame: (gameId: number) => Promise<boolean>;
removeGame: (gameId: number) => Promise<void>;
deleteGameFolder: (gameId: number) => Promise<unknown>;
getGameByObjectID: (objectID: string) => Promise<Game | null>;
Expand Down
6 changes: 5 additions & 1 deletion src/renderer/pages/downloads/downloads.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import type { Game } from "@types";

import * as styles from "./downloads.css";
import { useEffect, useState } from "react";
import { BinaryNotFoundModal } from "../shared-modals/binary-not-found-modal";

export function Downloads() {
const { library, updateLibrary } = useLibrary();
Expand All @@ -18,6 +19,7 @@ export function Downloads() {
const navigate = useNavigate();

const [filteredLibrary, setFilteredLibrary] = useState<Game[]>([]);
const [showBinaryNotFoundModal, setShowBinaryNotFoundModal] = useState(false);

const {
game: gameDownloading,
Expand All @@ -37,7 +39,8 @@ export function Downloads() {
}, [library]);

const openGame = (gameId: number) =>
window.electron.openGame(gameId).then(() => {
window.electron.openGame(gameId).then(isBinaryInPath => {
if (!isBinaryInPath) setShowBinaryNotFoundModal(true);
updateLibrary();
});

Expand Down Expand Up @@ -202,6 +205,7 @@ export function Downloads() {

return (
<section className={styles.downloadsContainer}>
<BinaryNotFoundModal visible={showBinaryNotFoundModal} onClose={() => setShowBinaryNotFoundModal(false)} />
<TextField placeholder={t("filter")} onChange={handleFilter} />

<ul className={styles.downloads}>
Expand Down
9 changes: 7 additions & 2 deletions src/renderer/pages/game-details/hero-panel.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { useMemo } from "react";
import { useMemo, useState } from "react";
import { useTranslation } from "react-i18next";
import prettyBytes from "pretty-bytes";
import { format } from "date-fns";
Expand All @@ -9,6 +9,7 @@ import type { Game, ShopDetails } from "@types";

import * as styles from "./hero-panel.css";
import { formatDownloadProgress } from "@renderer/helpers";
import { BinaryNotFoundModal } from "../shared-modals/binary-not-found-modal";

export interface HeroPanelProps {
game: Game | null;
Expand All @@ -27,6 +28,8 @@ export function HeroPanel({
}: HeroPanelProps) {
const { t } = useTranslation("game_details");

const [showBinaryNotFoundModal, setShowBinaryNotFoundModal] = useState(false);

const {
game: gameDownloading,
isDownloading,
Expand All @@ -46,7 +49,8 @@ export function HeroPanel({
const isGameDownloading = isDownloading && gameDownloading?.id === game?.id;

const openGame = (gameId: number) =>
window.electron.openGame(gameId).then(() => {
window.electron.openGame(gameId).then(isBinaryInPath => {
if (!isBinaryInPath) setShowBinaryNotFoundModal(true);
updateLibrary();
});

Expand Down Expand Up @@ -202,6 +206,7 @@ export function HeroPanel({

return (
<div style={{ backgroundColor: color }} className={styles.panel}>
<BinaryNotFoundModal visible={showBinaryNotFoundModal} onClose={() => setShowBinaryNotFoundModal(false)} />
<div className={styles.content}>{getInfo()}</div>
<div className={styles.actions}>{getActions()}</div>
</div>
Expand Down
25 changes: 25 additions & 0 deletions src/renderer/pages/shared-modals/binary-not-found-modal.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import { Modal } from "@renderer/components"
import { useTranslation } from "react-i18next";

interface BinaryNotFoundModalProps {
visible: boolean;
onClose: () => void;
}

export const BinaryNotFoundModal = ({
visible,
onClose
}: BinaryNotFoundModalProps) => {
const { t } = useTranslation("binary_not_found_modal");

return (
<Modal
visible={visible}
title={t("title")}
description={t("description")}
onClose={onClose}
>
{t("instructions")}
</Modal>
)
}
5 changes: 5 additions & 0 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -10460,6 +10460,11 @@ yaml@^1.10.0:
resolved "https://registry.npmjs.org/yaml/-/yaml-1.10.2.tgz"
integrity sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==

yaml@^2.4.1:
version "2.4.1"
resolved "https://registry.yarnpkg.com/yaml/-/yaml-2.4.1.tgz#2e57e0b5e995292c25c75d2658f0664765210eed"
integrity sha512-pIXzoImaqmfOrL7teGUBt/T7ZDnyeGBWyXQBvOVhLkWLN37GXv8NMLK406UY6dS51JfcQHsmcW5cJ441bHg6Lg==

yargs-parser@^20.2.2:
version "20.2.9"
resolved "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.9.tgz"
Expand Down

0 comments on commit 66a1153

Please sign in to comment.