summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/api/lsfgApi.ts1
-rw-r--r--src/components/CollapsibleItemGroup.tsx4
-rw-r--r--src/components/GameConfigurationSelector.tsx6
-rw-r--r--src/utils/steamLaunchOptions.ts41
4 files changed, 42 insertions, 10 deletions
diff --git a/src/api/lsfgApi.ts b/src/api/lsfgApi.ts
index 883f56e..44b72b5 100644
--- a/src/api/lsfgApi.ts
+++ b/src/api/lsfgApi.ts
@@ -41,6 +41,7 @@ export interface InstalledGame {
appid: string;
name: string;
nonSteam: boolean;
+ isFlatpakShortcut?: boolean;
}
export interface GlobalConfig {
diff --git a/src/components/CollapsibleItemGroup.tsx b/src/components/CollapsibleItemGroup.tsx
index 29fe945..a4e4092 100644
--- a/src/components/CollapsibleItemGroup.tsx
+++ b/src/components/CollapsibleItemGroup.tsx
@@ -6,6 +6,7 @@ export interface CollapsibleItem {
id: string;
label: string;
description: string;
+ disabled?: boolean;
}
export const collapsibleItemGroupStyles = `
@@ -91,7 +92,8 @@ export function CollapsibleItemGroup({
<Field
label={item.label}
description={item.description}
- onActivate={() => onSelect(item.id)}
+ disabled={item.disabled}
+ onActivate={item.disabled ? undefined : () => onSelect(item.id)}
highlightOnFocus
/>
</PanelSectionRow>
diff --git a/src/components/GameConfigurationSelector.tsx b/src/components/GameConfigurationSelector.tsx
index ad3537d..91dc3df 100644
--- a/src/components/GameConfigurationSelector.tsx
+++ b/src/components/GameConfigurationSelector.tsx
@@ -20,6 +20,7 @@ const ENABLED_COLLAPSED_KEY = "lsfg-enabled-games-collapsed-v4";
const AVAILABLE_COLLAPSED_KEY = "lsfg-available-games-collapsed-v3";
function targetDescription(game: GameTarget): string {
+ if (game.isFlatpakShortcut) return "Non-Steam | Use Flatpak Tab";
return game.source === "unknown"
? "Unknown source ยท excluded from bulk actions"
: sourceLabel(game.source);
@@ -43,8 +44,8 @@ export function GameConfigurationSelector({
});
const enabledGames = sortGames(targets.filter((game) => game.configured));
const availableGames = sortGames(targets.filter((game) => !game.configured));
- const enableableGames = availableGames.filter((game) => game.source === source);
- const removableGames = enabledGames.filter((game) => game.source === source);
+ const enableableGames = availableGames.filter((game) => game.source === source && !game.isFlatpakShortcut);
+ const removableGames = enabledGames.filter((game) => game.source === source && !game.isFlatpakShortcut);
const sourceName = source === "nonSteam" ? "non-Steam shortcuts" : "Steam games";
const emptyDescription = source === "nonSteam"
? "Steam has not reported any eligible non-Steam shortcuts"
@@ -53,6 +54,7 @@ export function GameConfigurationSelector({
id: game.appid,
label: game.name,
description: targetDescription(game),
+ disabled: game.isFlatpakShortcut,
});
const [enabledCollapsed, toggleEnabled] = usePersistentCollapsed(`${ENABLED_COLLAPSED_KEY}-${source}`);
const [availableCollapsed, toggleAvailable] = usePersistentCollapsed(`${AVAILABLE_COLLAPSED_KEY}-${source}`);
diff --git a/src/utils/steamLaunchOptions.ts b/src/utils/steamLaunchOptions.ts
index f03eeab..83c46f2 100644
--- a/src/utils/steamLaunchOptions.ts
+++ b/src/utils/steamLaunchOptions.ts
@@ -153,7 +153,25 @@ function tokenize(options: string): LaunchToken[] {
}
const serialize = (tokens: readonly LaunchToken[]) => tokens.map(({ raw }) => raw).join(" ");
-const commandIndex = (tokens: readonly LaunchToken[]) => tokens.findIndex((token) => token.raw.toLowerCase() === COMMAND_TOKEN);
+function isCommandToken(token: LaunchToken): boolean {
+ return token.value.toLowerCase() === COMMAND_TOKEN;
+}
+
+function isMalformedCommandToken(token: LaunchToken): boolean {
+ const value = token.value.toLowerCase();
+ return value === "%command" || value === "command%";
+}
+
+function normalizeCommandTokens(tokens: LaunchToken[]): void {
+ for (const token of tokens) {
+ if (isCommandToken(token) || isMalformedCommandToken(token)) {
+ token.raw = COMMAND_TOKEN;
+ token.value = COMMAND_TOKEN;
+ }
+ }
+}
+
+const commandIndex = (tokens: readonly LaunchToken[]) => tokens.findIndex(isCommandToken);
const isAssignment = (token: LaunchToken) => /^[A-Za-z_][A-Za-z0-9_]*=/.test(token.value);
const isLegacyToken = (value: string) => LEGACY_WRAPPER_TOKENS.has(value) || LEGACY_ABSOLUTE_WRAPPER.test(value);
const isWrapperToken = (value: string, wrapperPath: string) => decodeToken(value) === wrapperPath || isLegacyWrapperToken(value);
@@ -173,9 +191,9 @@ function removeMatchingWrappers(tokens: LaunchToken[], predicate: (value: string
function installLaunchOption(
options: string,
wrapperPath = DEFAULT_WRAPPER_PATH,
- shortcutLaunchOptions = false,
) {
const tokens = tokenize(options);
+ normalizeCommandTokens(tokens);
removeMatchingWrappers(tokens, isLegacyToken);
let command = commandIndex(tokens);
if (command >= 0) {
@@ -185,11 +203,15 @@ function installLaunchOption(
tokens.splice(command, 0, { raw: wrapperPath, value: wrapperPath });
return { options: serialize(tokens), commandTokenAdded: false };
}
+
+ const existingWrapper = tokens.findIndex((token) => decodeToken(token.value) === wrapperPath);
+ if (existingWrapper >= 0) {
+ tokens.splice(existingWrapper + 1, 0, { raw: COMMAND_TOKEN, value: COMMAND_TOKEN });
+ return { options: serialize(tokens), commandTokenAdded: true };
+ }
+
let insertion = 0;
while (insertion < tokens.length && isAssignment(tokens[insertion])) insertion++;
- if (!shortcutLaunchOptions && insertion < tokens.length && !tokens[insertion].value.startsWith("-")) {
- throw new Error("Launch options do not contain %command%; refusing to guess a launcher command");
- }
tokens.splice(insertion, 0,
{ raw: wrapperPath, value: wrapperPath },
{ raw: COMMAND_TOKEN, value: COMMAND_TOKEN },
@@ -207,6 +229,7 @@ export function removeWrapperLaunchOption(
commandTokenAdded = false,
): string {
const tokens = tokenize(options);
+ normalizeCommandTokens(tokens);
if (removeMatchingWrappers(tokens, (value) => isWrapperToken(value, wrapperPath)) && commandTokenAdded) {
const command = commandIndex(tokens);
if (command >= 0) tokens.splice(command, 1);
@@ -345,14 +368,18 @@ export function installWrapperIntegration(
const current = await readSteamLaunchOptions(appId, nonSteam);
const cleaned = cleanupPluginAssignments(cleanupLegacyLaunchOptions(current.options));
const alreadyInstalled = hasWrapperLaunchIntegration(current.options, wrapperPath);
- const rewrite = installLaunchOption(cleaned, wrapperPath, nonSteam);
+ const rewrite = installLaunchOption(cleaned, wrapperPath);
if (rewrite.options === current.options) return { snapshot: current, commandTokenAdded, changed: false };
const value = await writeVerified(
appId, nonSteam, current.options, rewrite.options,
(options) => writeOptions(appId, nonSteam, options),
"Steam did not accept the launch options",
);
- return { snapshot: value, commandTokenAdded: alreadyInstalled ? commandTokenAdded : rewrite.commandTokenAdded, changed: true };
+ return {
+ snapshot: value,
+ commandTokenAdded: alreadyInstalled ? commandTokenAdded : commandTokenAdded || rewrite.commandTokenAdded,
+ changed: true,
+ };
});
}