summaryrefslogtreecommitdiff
path: root/src/hooks
diff options
context:
space:
mode:
Diffstat (limited to 'src/hooks')
-rw-r--r--src/hooks/useGameConfiguration.ts99
-rw-r--r--src/hooks/useInstallationActions.ts12
-rw-r--r--src/hooks/useLsfgHooks.ts110
-rw-r--r--src/hooks/useProfileManagement.ts194
4 files changed, 130 insertions, 285 deletions
diff --git a/src/hooks/useGameConfiguration.ts b/src/hooks/useGameConfiguration.ts
new file mode 100644
index 0000000..d279a8c
--- /dev/null
+++ b/src/hooks/useGameConfiguration.ts
@@ -0,0 +1,99 @@
+import { useCallback, useEffect, useMemo, useRef, useState } from "react";
+import { Router } from "@decky/ui";
+import { getGameConfigs, getInstalledGames, updateGameConfig, resetGameConfig, resetAllGameConfigs, type GameConfigEntry, type GlobalConfig, type InstalledGame } from "../api/lsfgApi";
+import { ConfigurationData, getDefaults } from "../config/configSchema";
+
+export interface GameTarget extends InstalledGame { configured: boolean; }
+
+export function useGameConfiguration() {
+ const [games, setGames] = useState<GameConfigEntry[]>([]);
+ const [globalConfig, setGlobalConfig] = useState<GlobalConfig>({ dll: "", no_fp16: false });
+ const [installedGames, setInstalledGames] = useState<InstalledGame[]>([]);
+ const [configsLoaded, setConfigsLoaded] = useState(false);
+ const [selectedAppId, setSelectedAppId] = useState("");
+ const [runningGame, setRunningGame] = useState<GameTarget | null>(null);
+ const previousRunningAppId = useRef<string | null>(null);
+
+ const load = useCallback(async () => {
+ const [result, installed] = await Promise.all([getGameConfigs(), getInstalledGames()]);
+ if (result.success) {
+ setGlobalConfig(result.global_config || { dll: "", no_fp16: false });
+ setGames(result.games || []);
+ }
+ if (installed.success) setInstalledGames(installed.games || []);
+ setConfigsLoaded(true);
+ }, []);
+
+ useEffect(() => { load(); }, [load]);
+ useEffect(() => {
+ const poll = () => {
+ if (!configsLoaded) return;
+ const app = Router.MainRunningApp as any;
+ if (!app?.appid) return setRunningGame(null);
+ const appid = String(app.appid);
+ const installed = installedGames.find((game) => game.appid === appid);
+ const name = app.display_name || installed?.name;
+ if (!name) return setRunningGame(null);
+ setRunningGame((current) => current?.appid === appid ? current : {
+ ...(installed || { appid, name, nonSteam: false }),
+ name,
+ configured: games.some((game) => game.appid === appid),
+ });
+ };
+ poll();
+ const interval = window.setInterval(poll, 2000);
+ return () => window.clearInterval(interval);
+ }, [configsLoaded, games, installedGames]);
+ useEffect(() => {
+ const appid = runningGame?.appid || null;
+ if (appid !== previousRunningAppId.current) {
+ previousRunningAppId.current = appid;
+ setSelectedAppId(appid || "");
+ }
+ }, [runningGame?.appid]);
+
+ const targets = useMemo<GameTarget[]>(() => {
+ const configured = installedGames.map((game) => ({ ...game, configured: games.some((item) => item.appid === game.appid) }));
+ for (const game of games) if (!configured.some((item) => item.appid === game.appid)) configured.push({ appid: game.appid, name: game.profile, nonSteam: false, configured: true });
+ if (runningGame && !configured.some((game) => game.appid === runningGame.appid)) configured.unshift(runningGame);
+ return configured;
+ }, [games, installedGames, runningGame]);
+ const template = useMemo(() => ({ ...getDefaults(), ...globalConfig }), [globalConfig]);
+ const config = games.find((game) => game.appid === selectedAppId)?.config || template;
+
+ const save = useCallback(async (next: ConfigurationData) => {
+ const selectedTarget = targets.find((target) => target.appid === selectedAppId);
+ if (!selectedTarget?.name) return;
+ const result = await updateGameConfig(selectedAppId, selectedTarget.name, next);
+ if (result.success) await load();
+ }, [load, selectedAppId, targets]);
+
+ const enable = useCallback(async (appid: string) => {
+ const target = targets.find((item) => item.appid === appid);
+ if (!target?.name) return false;
+ const result = await updateGameConfig(appid, target.name, template);
+ if (result.success) await load();
+ return result.success;
+ }, [load, targets, template]);
+
+ const resetSelected = useCallback(async () => {
+ if (selectedAppId) {
+ const result = await resetGameConfig(selectedAppId);
+ if (result.success) {
+ setRunningGame((current) => current?.appid === selectedAppId ? { ...current, configured: false } : current);
+ setSelectedAppId("");
+ await load();
+ }
+ }
+ }, [load, selectedAppId]);
+ const resetAll = useCallback(async () => {
+ const result = await resetAllGameConfigs();
+ if (result.success) {
+ setRunningGame((current) => current ? { ...current, configured: false } : current);
+ setSelectedAppId("");
+ await load();
+ }
+ }, [load]);
+
+ return { config, games, targets, runningGame, selectedAppId, setSelectedAppId, save, enable, resetSelected, resetAll, reload: load };
+}
diff --git a/src/hooks/useInstallationActions.ts b/src/hooks/useInstallationActions.ts
index f184145..41189bd 100644
--- a/src/hooks/useInstallationActions.ts
+++ b/src/hooks/useInstallationActions.ts
@@ -14,7 +14,8 @@ export function useInstallationActions() {
const handleInstall = async (
setIsInstalled: (value: boolean) => void,
setInstallationStatus: (value: string) => void,
- reloadConfig?: () => Promise<void>
+ reloadConfig?: () => Promise<void>,
+ reloadStatus?: () => Promise<boolean>
) => {
setIsInstalling(true);
setInstallationStatus("Installing lsfg-vk...");
@@ -30,6 +31,9 @@ export function useInstallationActions() {
if (reloadConfig) {
await reloadConfig();
}
+ if (reloadStatus) {
+ await reloadStatus();
+ }
} else {
setInstallationStatus(`Installation failed: ${result.error}`);
showInstallErrorToast(result.error);
@@ -44,7 +48,8 @@ export function useInstallationActions() {
const handleUninstall = async (
setIsInstalled: (value: boolean) => void,
- setInstallationStatus: (value: string) => void
+ setInstallationStatus: (value: string) => void,
+ reloadStatus?: () => Promise<boolean>
) => {
setIsUninstalling(true);
setInstallationStatus("Uninstalling lsfg-vk...");
@@ -54,6 +59,9 @@ export function useInstallationActions() {
if (result.success) {
setIsInstalled(false);
setInstallationStatus("lsfg-vk uninstalled successfully!");
+ if (reloadStatus) {
+ await reloadStatus();
+ }
showUninstallSuccessToast();
} else {
setInstallationStatus(`Uninstallation failed: ${result.error}`);
diff --git a/src/hooks/useLsfgHooks.ts b/src/hooks/useLsfgHooks.ts
index d9bbe3e..0b71ee9 100644
--- a/src/hooks/useLsfgHooks.ts
+++ b/src/hooks/useLsfgHooks.ts
@@ -1,22 +1,30 @@
-import { useState, useEffect, useCallback } from "react";
+import { useState, useEffect } from "react";
import {
checkLsfgVkInstalled,
- checkLosslessScalingDll,
- getLsfgConfig,
- updateLsfgConfigFromObject,
- type ConfigUpdateResult
+ getLosslessScalingBranchStatus,
+ type SteamBranchStatus
} from "../api/lsfgApi";
-import { ConfigurationData, getDefaults } from "../config/configSchema";
-import { showErrorToast, ToastMessages } from "../utils/toastUtils";
export function useInstallationStatus() {
const [isInstalled, setIsInstalled] = useState<boolean>(false);
const [installationStatus, setInstallationStatus] = useState<string>("");
+ const [losslessScalingInstalled, setLosslessScalingInstalled] = useState<boolean>(false);
+ const [losslessScalingStatus, setLosslessScalingStatus] = useState<string>("");
+ const [steamBranchStatus, setSteamBranchStatus] = useState<SteamBranchStatus | null>(null);
const checkInstallation = async () => {
try {
+ setSteamBranchStatus(await getLosslessScalingBranchStatus());
+ } catch (error) {
+ console.error("Error checking Lossless Scaling Steam branch:", error);
+ setSteamBranchStatus(null);
+ }
+
+ try {
const status = await checkLsfgVkInstalled();
setIsInstalled(status.installed);
+ setLosslessScalingInstalled(status.lossless_scaling_installed);
+ setLosslessScalingStatus(status.lossless_scaling_status || "Lossless Scaling Not Installed");
if (status.installed) {
setInstallationStatus("lsfg-vk Installed");
} else {
@@ -24,6 +32,9 @@ export function useInstallationStatus() {
}
return status.installed;
} catch (error) {
+ setSteamBranchStatus(null);
+ setLosslessScalingInstalled(false);
+ setLosslessScalingStatus("Lossless Scaling Not Installed");
setInstallationStatus("lsfg-vk Not Installed");
return false;
}
@@ -38,88 +49,9 @@ export function useInstallationStatus() {
installationStatus,
setIsInstalled,
setInstallationStatus,
+ losslessScalingInstalled,
+ losslessScalingStatus,
+ steamBranchStatus,
checkInstallation
};
}
-
-export function useDllDetection() {
- const [dllDetected, setDllDetected] = useState<boolean>(false);
- const [dllDetectionStatus, setDllDetectionStatus] = useState<string>("");
-
- const checkDllDetection = async () => {
- try {
- const result = await checkLosslessScalingDll();
- setDllDetected(result.detected);
- if (result.detected) {
- setDllDetectionStatus("Lossless Scaling Installed");
- } else {
- setDllDetectionStatus("Lossless Scaling Not Installed");
- }
- } catch (error) {
- setDllDetectionStatus("Lossless Scaling Not Installed");
- }
- };
-
- useEffect(() => {
- checkDllDetection();
- }, []);
-
- return {
- dllDetected,
- dllDetectionStatus
- };
-}
-
-export function useLsfgConfig() {
- const [config, setConfig] = useState<ConfigurationData>(() => getDefaults());
-
- const loadLsfgConfig = useCallback(async () => {
- try {
- const result = await getLsfgConfig();
- if (result.success && result.config) {
- setConfig(result.config);
- } else {
- console.log("lsfg config not available, using defaults:", result.error);
- setConfig(getDefaults());
- }
- } catch (error) {
- console.error("Error loading lsfg config:", error);
- setConfig(getDefaults());
- }
- }, []);
-
- const updateConfig = useCallback(async (newConfig: ConfigurationData): Promise<ConfigUpdateResult> => {
- try {
- const result = await updateLsfgConfigFromObject(newConfig);
- if (result.success) {
- setConfig(newConfig);
- } else {
- showErrorToast(
- ToastMessages.CONFIG_UPDATE_ERROR.title,
- result.error || ToastMessages.CONFIG_UPDATE_ERROR.body
- );
- }
- return result;
- } catch (error) {
- showErrorToast(ToastMessages.CONFIG_UPDATE_ERROR.title, String(error));
- return { success: false, error: String(error) };
- }
- }, []);
-
- const updateField = useCallback(async (fieldName: keyof ConfigurationData, value: boolean | number | string): Promise<ConfigUpdateResult> => {
- const newConfig = { ...config, [fieldName]: value };
- return updateConfig(newConfig);
- }, [config, updateConfig]);
-
- useEffect(() => {
- loadLsfgConfig();
- }, []);
-
- return {
- config,
- setConfig,
- loadLsfgConfig,
- updateConfig,
- updateField
- };
-}
diff --git a/src/hooks/useProfileManagement.ts b/src/hooks/useProfileManagement.ts
deleted file mode 100644
index a5f2a07..0000000
--- a/src/hooks/useProfileManagement.ts
+++ /dev/null
@@ -1,194 +0,0 @@
-import { useState, useEffect, useCallback } from "react";
-import {
- getProfiles,
- createProfile,
- deleteProfile,
- renameProfile,
- setCurrentProfile,
- updateProfileConfig,
- type ProfilesResult,
- type ProfileResult,
- type ConfigUpdateResult
-} from "../api/lsfgApi";
-import { ConfigurationData } from "../config/configSchema";
-import { showSuccessToast, showErrorToast } from "../utils/toastUtils";
-
-export function useProfileManagement() {
- const [profiles, setProfiles] = useState<string[]>([]);
- const [currentProfile, setCurrentProfileState] = useState<string>("decky-lsfg-vk");
- const [isLoading, setIsLoading] = useState(false);
-
- // Load profiles on hook initialization
- const loadProfiles = useCallback(async () => {
- try {
- const result: ProfilesResult = await getProfiles();
- if (result.success && result.profiles) {
- setProfiles(result.profiles);
- if (result.current_profile) {
- setCurrentProfileState(result.current_profile);
- }
- return result;
- } else {
- console.error("Failed to load profiles:", result.error);
- showErrorToast("Failed to load profiles", result.error || "Unknown error");
- return result;
- }
- } catch (error) {
- console.error("Error loading profiles:", error);
- showErrorToast("Error loading profiles", String(error));
- return { success: false, error: String(error) };
- }
- }, []);
-
- // Create a new profile
- const handleCreateProfile = useCallback(async (profileName: string, sourceProfile?: string) => {
- setIsLoading(true);
- try {
- const result: ProfileResult = await createProfile(profileName, sourceProfile || currentProfile);
- if (result.success) {
- // Use the normalized name returned from backend (spaces converted to dashes)
- const actualProfileName = result.profile_name || profileName;
- showSuccessToast("Profile created", `Created profile: ${actualProfileName}`);
- await loadProfiles();
- return result;
- } else {
- console.error("Failed to create profile:", result.error);
- showErrorToast("Failed to create profile", result.error || "Unknown error");
- return result;
- }
- } catch (error) {
- console.error("Error creating profile:", error);
- showErrorToast("Error creating profile", String(error));
- return { success: false, error: String(error) };
- } finally {
- setIsLoading(false);
- }
- }, [currentProfile, loadProfiles]);
-
- // Delete a profile
- const handleDeleteProfile = useCallback(async (profileName: string) => {
- if (profileName === "decky-lsfg-vk") {
- showErrorToast("Cannot delete default profile", "The default profile cannot be deleted");
- return { success: false, error: "Cannot delete default profile" };
- }
-
- setIsLoading(true);
- try {
- const result: ProfileResult = await deleteProfile(profileName);
- if (result.success) {
- showSuccessToast("Profile deleted", `Deleted profile: ${profileName}`);
- await loadProfiles();
- // If we deleted the current profile, it should have switched to default
- if (currentProfile === profileName) {
- setCurrentProfileState("decky-lsfg-vk");
- }
- return result;
- } else {
- console.error("Failed to delete profile:", result.error);
- showErrorToast("Failed to delete profile", result.error || "Unknown error");
- return result;
- }
- } catch (error) {
- console.error("Error deleting profile:", error);
- showErrorToast("Error deleting profile", String(error));
- return { success: false, error: String(error) };
- } finally {
- setIsLoading(false);
- }
- }, [currentProfile, loadProfiles]);
-
- // Rename a profile
- const handleRenameProfile = useCallback(async (oldName: string, newName: string) => {
- if (oldName === "decky-lsfg-vk") {
- showErrorToast("Cannot rename default profile", "The default profile cannot be renamed");
- return { success: false, error: "Cannot rename default profile" };
- }
-
- setIsLoading(true);
- try {
- const result: ProfileResult = await renameProfile(oldName, newName);
- if (result.success) {
- // Use the normalized name returned from backend (spaces converted to dashes)
- const actualNewName = result.profile_name || newName;
- showSuccessToast("Profile renamed", `Renamed profile to: ${actualNewName}`);
- await loadProfiles();
- // Update current profile if it was renamed
- if (currentProfile === oldName) {
- setCurrentProfileState(actualNewName);
- }
- return result;
- } else {
- console.error("Failed to rename profile:", result.error);
- showErrorToast("Failed to rename profile", result.error || "Unknown error");
- return result;
- }
- } catch (error) {
- console.error("Error renaming profile:", error);
- showErrorToast("Error renaming profile", String(error));
- return { success: false, error: String(error) };
- } finally {
- setIsLoading(false);
- }
- }, [currentProfile, loadProfiles]);
-
- // Set the current active profile
- const handleSetCurrentProfile = useCallback(async (profileName: string) => {
- setIsLoading(true);
- try {
- const result: ProfileResult = await setCurrentProfile(profileName);
- if (result.success) {
- setCurrentProfileState(profileName);
- showSuccessToast("Profile switched", `Switched to profile: ${profileName}`);
- return result;
- } else {
- console.error("Failed to switch profile:", result.error);
- showErrorToast("Failed to switch profile", result.error || "Unknown error");
- return result;
- }
- } catch (error) {
- console.error("Error switching profile:", error);
- showErrorToast("Error switching profile", String(error));
- return { success: false, error: String(error) };
- } finally {
- setIsLoading(false);
- }
- }, []);
-
- // Update configuration for a specific profile
- const handleUpdateProfileConfig = useCallback(async (profileName: string, config: ConfigurationData) => {
- setIsLoading(true);
- try {
- const result: ConfigUpdateResult = await updateProfileConfig(profileName, config);
- if (result.success) {
- return result;
- } else {
- console.error("Failed to update profile config:", result.error);
- showErrorToast("Failed to update profile config", result.error || "Unknown error");
- return result;
- }
- } catch (error) {
- console.error("Error updating profile config:", error);
- showErrorToast("Error updating profile config", String(error));
- return { success: false, error: String(error) };
- } finally {
- setIsLoading(false);
- }
- }, [currentProfile]);
-
- // Initialize profiles on mount
- useEffect(() => {
- loadProfiles();
- }, [loadProfiles]);
-
- return {
- profiles,
- currentProfile,
- isLoading,
- loadProfiles,
- createProfile: handleCreateProfile,
- deleteProfile: handleDeleteProfile,
- renameProfile: handleRenameProfile,
- setCurrentProfile: handleSetCurrentProfile,
- updateProfileConfig: handleUpdateProfileConfig
- };
-}