[feat-578] setup protonFolder on startup

This commit is contained in:
silentrald
2024-09-22 15:21:19 +08:00
parent 0398a957ad
commit de7a054f89
19 changed files with 275 additions and 16 deletions
+9
View File
@@ -16,3 +16,12 @@ export const CACHE_PATH = path.join(app.getPath("userData"), "CachedData");
export const IMAGE_CACHE_PATH = path.join(CACHE_PATH, "imagescache");
export const HTTP_STATUS_CODES = constants;
// Linux related stuff
export const PROTON_BINARY_PREFIX = "proton";
export const WINE_BINARY_PREFIX = path.join(
"files", "bin", "wine"
);
+1
View File
@@ -14,3 +14,4 @@ import "./model-saber.ipcs";
import "./bs-model-ipcs";
import "./bs-version-download/bs-download-ipcs";
import "./static-configuration.ipcs";
import "./linux.ipcs.ts";
+11
View File
@@ -0,0 +1,11 @@
import { LinuxService } from "main/services/linux.service";
import { IpcService } from "../services/ipc.service";
import { of } from "rxjs";
const ipc = IpcService.getInstance();
const linuxService = LinuxService.getInstance();
ipc.on("linux.verify-proton-folder", (_, reply) => {
reply(of(linuxService.verifyProtonPath()));
});
@@ -4,7 +4,7 @@ import { StoreLauncherInterface } from "./store-launcher.interface";
import { pathExists, pathExistsSync, rename } from "fs-extra";
import { SteamService } from "../steam.service";
import path from "path";
import { BS_APP_ID, BS_EXECUTABLE, STEAMVR_APP_ID } from "../../constants";
import { BS_APP_ID, BS_EXECUTABLE, PROTON_BINARY_PREFIX, STEAMVR_APP_ID } from "../../constants";
import log from "electron-log";
import { AbstractLauncherService } from "./abstract-launcher.service";
import { CustomError } from "../../../shared/models/exceptions/custom-error.class";
@@ -135,7 +135,7 @@ export class SteamLauncherService extends AbstractLauncherService implements Sto
if (!this.staticConfig.has("proton-folder")) {
throw CustomError.fromError(new Error("Proton folder not set"), BSLaunchError.PROTON_NOT_SET);
}
exePath = path.join(this.staticConfig.get("proton-folder"), "proton");
exePath = path.join(this.staticConfig.get("proton-folder"), PROTON_BINARY_PREFIX);
if (!pathExistsSync(exePath)) {
throw CustomError.fromError(
new Error("Could not locate proton binary"),
+36
View File
@@ -0,0 +1,36 @@
import path from "path";
import { StaticConfigurationService } from "./static-configuration.service";
import { pathExistsSync } from "fs-extra";
import { PROTON_BINARY_PREFIX, WINE_BINARY_PREFIX } from "main/constants";
// Linux handling
export class LinuxService {
private static instance: LinuxService;
public static getInstance(): LinuxService {
if (!LinuxService.instance) {
LinuxService.instance = new LinuxService();
}
return LinuxService.instance;
}
private readonly staticConfig: StaticConfigurationService;
private constructor() {
this.staticConfig = StaticConfigurationService.getInstance();
}
public verifyProtonPath(): boolean {
if (!this.staticConfig.has("proton-folder")) {
return false;
}
const protonFolder = this.staticConfig.get("proton-folder");
const protonPath = path.join(protonFolder, PROTON_BINARY_PREFIX);
const winePath = path.join(protonFolder, WINE_BINARY_PREFIX);
return pathExistsSync(protonPath) && pathExistsSync(winePath);
}
}
@@ -6,7 +6,7 @@ import path from "path";
import md5File from "md5-file";
import { RequestService } from "../request.service";
import { spawn } from "child_process";
import { BS_EXECUTABLE } from "../../constants";
import { BS_EXECUTABLE, WINE_BINARY_PREFIX } from "../../constants";
import log from "electron-log";
import { deleteFolder, pathExist, Progression, unlinkPath } from "../../helpers/fs.helpers";
import { lastValueFrom, Observable } from "rxjs";
@@ -153,7 +153,7 @@ export class BsModsManagerService {
winePath = path.join(
this.staticConfig.get("proton-folder"),
"files", "bin", "wine"
WINE_BINARY_PREFIX
);
if (!pathExistsSync(winePath)) {
@@ -1,6 +1,7 @@
import ElectronStore from "electron-store";
import { pathExistsSync } from "fs-extra";
import path from "path";
import { PROTON_BINARY_PREFIX, WINE_BINARY_PREFIX } from "main/constants";
import { Observable, Subject } from "rxjs";
import { BSVersion } from "shared/bs-version.interface";
import { CustomError } from "shared/models/exceptions/custom-error.class";
@@ -58,8 +59,8 @@ export class StaticConfigurationService {
// Setters with validation
private validateProtonFolder(protonFolder: string): void {
const protonPath = path.join(protonFolder, "proton");
const winePath = path.join(protonFolder, "files", "bin", "wine");
const protonPath = path.join(protonFolder, PROTON_BINARY_PREFIX);
const winePath = path.join(protonFolder, WINE_BINARY_PREFIX);
if (!pathExistsSync(protonPath) || !pathExistsSync(winePath)) {
throw new CustomError("Invalid proton folder path", "invalid-folder");
}
@@ -0,0 +1,99 @@
import { lastValueFrom } from "rxjs";
import { useState } from "react";
import { useTranslation } from "renderer/hooks/use-translation.hook";
import { useService } from "renderer/hooks/use-service.hook";
import { IpcService } from "renderer/services/ipc.service";
import { ModalComponent, ModalExitCode } from "renderer/services/modale.service";
import { NotificationService } from "renderer/services/notification.service";
import { StaticConfigurationService } from "renderer/services/static-configuration.service";
import { BsmButton } from "renderer/components/shared/bsm-button.component";
import { SettingContainer } from "renderer/components/settings/setting-container.component";
export const LinuxSetupModal: ModalComponent<{}, {}> = ({ resolver }) => {
const t = useTranslation();
const ipcService = useService(IpcService);
const notificationService = useService(NotificationService);
const staticConfigService = useService(StaticConfigurationService);
const [protonFolder, setProtonFolder] = useState("");
const validateFields = () => {
return !!protonFolder;
}
const selectProtonPath = async () => {
const response = await lastValueFrom(ipcService.sendV2("choose-folder"));
if (response.canceled || !response.filePaths?.length) {
return;
}
const path = response.filePaths[0];
try {
await staticConfigService.set("proton-folder", path);
setProtonFolder(path);
validateFields();
} catch (error: any) {
notificationService.notifyError({
title: "pages.settings.proton-folder.errors.title",
desc: ["invalid-folder"].includes(error?.code)
? `pages.settings.proton-folder.errors.${error.code}`
: "misc.unknown"
});
}
}
const onConfirmButtonPressed = async () => {
resolver({
exitCode: ModalExitCode.COMPLETED
});
}
return (
<form
className="max-w-xl w-max"
onSubmit={event => {
event.preventDefault();
onConfirmButtonPressed();
}}>
<h1 className="tracking-wide w-full uppercase text-3xl text-center mb-4">
{t("modals.linux-setup.title")}
</h1>
<SettingContainer
os="linux"
title="modals.linux-setup.proton-folder"
description="modals.linux-setup.proton-folder-description"
>
<div className="relative flex items-center justify-between w-full h-8 bg-light-main-color-1 dark:bg-main-color-1 rounded-md pl-2 py-1">
<span
className="block text-ellipsis overflow-hidden min-w-0 whitespace-nowrap"
title={protonFolder}
>
{protonFolder}
</span>
<BsmButton
onClick={selectProtonPath}
className="shrink-0 whitespace-nowrap mr-2 px-2 font-bold italic text-sm rounded-md"
text="modals.linux-setup.choose-folder"
withBar={false}
/>
</div>
</SettingContainer>
<div className="h-8 grid grid-flow-col grid-cols-1">
<BsmButton
typeColor="primary"
className="rounded-md text-center transition-all"
type="submit"
withBar={false}
text="misc.confirm"
disabled={!validateFields()}
/>
</div>
</form>
)
}
+22 -1
View File
@@ -6,7 +6,8 @@ import { InstallationLocationService } from "./installation-location.service";
import { IpcService } from "./ipc.service";
import { ModalService } from "./modale.service";
import { AskInstallPathModal } from "renderer/components/modal/modal-types/ask-install-path.component";
import { AskInstallPathModal } from "renderer/components/modal/modal-types/setup/ask-install-path.component";
import { LinuxSetupModal } from "renderer/components/modal/modal-types/setup/linux-setup-modal.component";
// Handle setup modals/prompts, ordering of the modals/prompts may be done here
export class SetupService {
@@ -35,6 +36,10 @@ export class SetupService {
try {
// NOTE: for modal sequencing
await this.checkInstallationPath();
if (window.electron.platform === "linux") {
await this.checkProtonFolder();
}
} catch (error) {
logRenderError(error);
}
@@ -61,4 +66,20 @@ export class SetupService {
}
}
private async checkProtonFolder(): Promise<void> {
try {
const valid = await lastValueFrom(this.ipcService.sendV2("linux.verify-proton-folder"));
if (valid) {
return;
}
await this.modalService.openModal(
LinuxSetupModal,
{ closable: false }
);
} catch (error) {
logRenderError(error);
}
}
}
+3
View File
@@ -150,6 +150,9 @@ export interface IpcChannelMapping {
"static-configuration.get": StaticConfigGetIpcRequestResponse<StaticConfigKeys>;
"static-configuration.set": StaticConfigSetIpcRequest<StaticConfigKeys>;
/* ** linux.ipcs ** */
"linux.verify-proton-folder": { request: void, response: boolean };
/* ** OTHERS (if your IPC channel is not in a "-ipcs" file, put it here) ** */
"shortcut-launch-options": { request: void, response: LaunchOption };
}