diff --git a/hosts/deck/configuration.nix b/hosts/deck/configuration.nix index a7d8082..0d0d4b4 100644 --- a/hosts/deck/configuration.nix +++ b/hosts/deck/configuration.nix @@ -7,10 +7,13 @@ { imports = [ # Include the results of the hardware scan. + ../default.nix ./boot.nix ./jovian.nix - ./steam-shortcuts.nix + # ./steam-games.nix # ./steam-game + # ./steam.nix + # ./steam-rom-manager.nix ./hardware-configuration.nix ]; @@ -165,27 +168,6 @@ }; }; - # services.steamRomManager = { - # enable = true; - # user = "deck"; - - # romSources = [ - # { - # name = "Nintendo Switch ROMs"; - # path = "/home/deck/Emulation/roms/switch"; - # platform = "nintendoswitch"; - # emulator = "ryujinx"; - # } - # ]; - - # emulators = [ - # { - # name = "Ryujinx"; - # executable = "${pkgs.ryujinx-greemdev}/bin/ryujinx"; - # } - # ]; - # }; - # Some programs need SUID wrappers, can be configured further or are # started in user sessions. # programs.mtr.enable = true; diff --git a/hosts/deck/home.nix b/hosts/deck/home.nix index 8eef0d7..0005bb1 100644 --- a/hosts/deck/home.nix +++ b/hosts/deck/home.nix @@ -22,6 +22,38 @@ let }; in { + imports = [ + ./steam-rom-manager.nix + ]; + + programs.steam-rom-manager = { + enable = true; + steamUsername = "mjallen18"; + + # Optional: override default paths if needed + romsDirectory = "/home/deck/Emulation/roms"; + steamDirectory = "/home/deck/.local/share/Steam"; + + emulators = { + ryujinx = { + enable = true; + package = pkgs.ryujinx-greemdev; + }; + + dolphin-gamecube = { + enable = true; + package = pkgs.dolphin-emu; + romFolder = "gc"; + binaryName = "dolphin-emu"; + fileTypes = [ ".iso" ".ISO" ".gcm" ".GCM" ".ciso" ".CISO" "rvz" ]; + }; + + pcsx2 = { + enable = true; + package = pkgs.pcsx2; + }; + }; + }; home.username = "deck"; home.homeDirectory = "/home/deck"; diff --git a/hosts/deck/steam-games.nix b/hosts/deck/steam-games.nix deleted file mode 100644 index bfaffd4..0000000 --- a/hosts/deck/steam-games.nix +++ /dev/null @@ -1,35 +0,0 @@ -{ pkgs, ... }: -{ - # You'll need to get an API key from https://www.steamgriddb.com/profile/preferences/api - steamGridDbApiKey = "ada6729c6cd321a4e3792ddd0b83c1ba"; - # List your non-Steam games here - games = [ - { - name = "Ryujinx"; - path = "${pkgs.ryujinx-greemdev}/bin/ryujinx"; - extraLaunchOptions = ""; - steamGridDbIds = { - grid = "121085"; - hero = "63924"; - logo = "29735"; - list = "5517"; - }; - } - { - name = "Prismlauncher"; - path = "${pkgs.prismlauncher}/bin/prismlauncher"; - extraLaunchOptions = ""; - steamGridDbIds = { - grid = "267141"; - hero = "64303"; - logo = "68299"; - list = "23582"; - }; - } - { - name = "Mario Kart 8"; - path = "${pkgs.ryujinx-greemdev}/bin/ryujinx"; - extraLaunchOptions = "--fullscreen '/home/deck/Emulation/roms/switch/Mario Kart 8 Deluxe [B+U720896].nsp'"; - } - ]; -} \ No newline at end of file diff --git a/hosts/deck/steam-rom-manager.nix b/hosts/deck/steam-rom-manager.nix new file mode 100644 index 0000000..1cc5b20 --- /dev/null +++ b/hosts/deck/steam-rom-manager.nix @@ -0,0 +1,321 @@ +# steam-rom-manager.nix +{ config, lib, pkgs, ... }: + +with lib; + +let + cfg = config.programs.steam-rom-manager; + + # Common emulator configurations + commonEmulatorConfigs = { + ryujinx = { + romFolder = "switch"; + binaryName = "Ryujinx"; + fileTypes = [ ".nca" ".NCA" ".nro" ".NRO" ".nso" ".NSO" ".nsp" ".NSP" ".xci" ".XCI" ]; + }; + yuzu = { + romFolder = "switch"; + binaryName = "yuzu"; + fileTypes = [ ".nsp" ".NSP" ".xci" ".XCI" ]; + }; + pcsx2 = { + romFolder = "ps2"; + binaryName = "pcsx2-qt"; + fileTypes = [ ".iso" ".ISO" ".bin" ".BIN" ".chd" ".CHD" ]; + }; + rpcs3 = { + romFolder = "ps3"; + binaryName = "rpcs3"; + fileTypes = [ ".iso" ".ISO" ".bin" ".BIN" ".pkg" ".PKG" ]; + }; + dolphin-emu = { + romFolder = "gc"; + binaryName = "dolphin-emu"; + fileTypes = [ ".iso" ".ISO" ".gcm" ".GCM" ".ciso" ".CISO" ]; + }; + duckstation = { + romFolder = "psx"; + binaryName = "duckstation-qt"; + fileTypes = [ ".iso" ".ISO" ".bin" ".BIN" ".chd" ".CHD" ".pbp" ".PBP" ]; + }; + melonDS = { + romFolder = "nds"; + binaryName = "melonDS"; + fileTypes = [ ".nds" ".NDS" ]; + }; + cemu = { + romFolder = "wiiu"; + binaryName = "cemu"; + fileTypes = [ ".wud" ".WUD" ".wux" ".WUX" ".rpx" ".RPX" ]; + }; + }; + + getFileTypes = name: emu: + if (emu.fileTypes or []) != [] + then emu.fileTypes + else commonEmulatorConfigs.${name}.fileTypes or []; + + mkParserConfig = name: emu: + let + # We create an ordered list of key-value pairs that will maintain the exact order + # in the generated JSON output. Each field is documented for clarity. + orderedConfig = [ + # Basic parser configuration + { name = "parserType"; value = "Glob"; } + { name = "configTitle"; value = name; } + { name = "steamDirectory"; value = "\${steamdirglobal}"; } + { name = "romDirectory"; value = "${cfg.romsDirectory}/${if emu.romFolder != "" then emu.romFolder else commonEmulatorConfigs.${name}.romFolder}"; } + { name = "steamCategories"; value = [""]; } + + # Executable configuration + { name = "executableArgs"; value = emu.extraArgs; } + { name = "executableModifier"; value = "\"\${exePath}\""; } + { name = "startInDirectory"; value = "${cfg.romsDirectory}/${if emu.romFolder != "" then emu.romFolder else commonEmulatorConfigs.${name}.romFolder}"; } + { name = "titleModifier"; value = "\${fuzzyTitle}"; } + + # Controller settings + { name = "fetchControllerTemplatesButton"; value = null; } + { name = "removeControllersButton"; value = null; } + { name = "steamInputEnabled"; value = "1"; } + + # Image provider configuration + { name = "imageProviders"; value = [ "sgdb" ]; } + { name = "onlineImageQueries"; value = [ "\${fuzzyTitle}" ]; } + { name = "imagePool"; value = "\${fuzzyTitle}"; } + + # DRM and user account settings + { name = "drmProtect"; value = false; } + { name = "userAccounts"; value = { + specifiedAccounts = [ "Global" ]; + }; } + + # Parser-specific settings + { name = "parserInputs"; value = { + glob = "\${title}@(${concatStringsSep "|" (getFileTypes name emu)})"; + }; } + + # Executable details + { name = "executable"; value = { + path = "${emu.package}/bin/${if emu.binaryName != "" then emu.binaryName else commonEmulatorConfigs.${name}.binaryName}"; + shortcutPassthrough = false; + appendArgsToExecutable = true; + }; } + + # Title and fuzzy matching configuration + { name = "titleFromVariable"; value = { + limitToGroups = []; + caseInsensitiveVariables = false; + skipFileIfVariableWasNotFound = false; + }; } + + { name = "fuzzyMatch"; value = { + replaceDiacritics = true; + removeCharacters = true; + removeBrackets = true; + }; } + + # Controller configuration + { name = "controllers"; value = { + ps4 = null; + ps5 = null; + ps5_edge = null; + xbox360 = null; + xboxone = null; + xboxelite = null; + switch_joycon_left = null; + switch_joycon_right = null; + switch_pro = null; + neptune = null; + steamcontroller_gordon = null; + }; } + + # Image provider API configuration + { name = "imageProviderAPIs"; value = { + sgdb = { + nsfw = false; + humor = false; + styles = []; + stylesHero = []; + stylesLogo = []; + stylesIcon = []; + imageMotionTypes = [ "static" ]; + sizes = []; + sizesHero = []; + sizesTall = null; + sizesIcon = []; + }; + }; } + + # Default and local image settings + { name = "defaultImage"; value = { + tall = ""; + long = ""; + hero = ""; + logo = ""; + icon = ""; + }; } + + { name = "localImages"; value = { + tall = ""; + long = ""; + hero = ""; + logo = ""; + icon = ""; + }; } + + # Parser identification + { name = "parserId"; value = name; } + { name = "version"; value = 25; } + ]; + + # Function to convert our ordered list into properly formatted JSON + makeOrderedJSON = pairs: + let + # Create JSON key-value pairs while preserving order + joined = builtins.concatStringsSep "," + (map (pair: "\"${pair.name}\":${builtins.toJSON pair.value}") pairs); + in + "{${joined}}"; + in + makeOrderedJSON orderedConfig; + + # Create AppImage package + steam-rom-manager = pkgs.fetchurl { + name = "steam-rom-manager-2.5.29.AppImage"; + url = "https://github.com/SteamGridDB/steam-rom-manager/releases/download/v2.5.29/Steam-ROM-Manager-2.5.29.AppImage"; + hash = "sha256-6ZJ+MGIgr2osuQuqD6N9NnPiJFNq/HW6ivG8tyXUhvs="; + }; +in { + options.programs.steam-rom-manager = { + enable = mkEnableOption "Steam ROM Manager"; + + package = mkOption { + type = types.package; + default = steam-rom-manager; + description = "Steam ROM Manager package"; + }; + + steamUsername = mkOption { + type = types.str; + description = "Steam username for configuration"; + }; + + romsDirectory = mkOption { + type = types.str; + default = "${config.home.homeDirectory}/Emulation/roms"; + description = "Base directory for ROM files"; + }; + + steamDirectory = mkOption { + type = types.str; + default = "${config.home.homeDirectory}/.local/share/Steam"; + description = "Steam installation directory"; + }; + + emulators = mkOption { + type = types.attrsOf (types.submodule { + options = { + enable = mkEnableOption "emulator configuration"; + package = mkOption { + type = types.package; + description = "Emulator package"; + }; + binaryName = mkOption { + type = types.str; + default = ""; + description = "Name of the emulator binary (defaults to common configuration)"; + }; + romFolder = mkOption { + type = types.str; + default = ""; + description = "Name of the ROM folder (defaults to common configuration)"; + }; + fileTypes = mkOption { + type = types.listOf types.str; + default = []; + description = "List of ROM file types (defaults to common configuration)"; + }; + extraArgs = mkOption { + type = types.str; + default = "--fullscreen \"\${filePath}\""; + description = "Additional emulator arguments"; + }; + }; + }); + default = {}; + description = "Emulator configurations"; + }; + }; + + config = mkIf cfg.enable { + + home.packages = [ pkgs.appimage-run ] + ++ mapAttrsToList (_: v: v.package) (filterAttrs (_: v: v.enable) cfg.emulators); + + xdg.desktopEntries.steam-rom-manager = { + name = "Steam ROM Manager"; + exec = "${pkgs.appimage-run}/bin/appimage-run ${cfg.package}"; + icon = "steam"; + categories = [ "Game" "Utility" ]; + type = "Application"; + }; + + # Configuration files using xdg.configFile + xdg.configFile = { + # User settings configuration + "steam-rom-manager/userData/userSettings.json".text = builtins.toJSON { + fuzzyMatcher = { + timestamps = { + check = 0; + download = 0; + }; + verbose = false; + filterProviders = true; + }; + environmentVariables = { + steamDirectory = cfg.steamDirectory; + userAccounts = "\${${cfg.steamUsername}}"; + romsDirectory = cfg.romsDirectory; + retroarchPath = ""; + raCoresDirectory = ""; + localImagesDirectory = ""; + }; + previewSettings = { + retrieveCurrentSteamImages = true; + disableCategories = false; + deleteDisabledShortcuts = false; + imageZoomPercentage = 30; + preload = false; + hideUserAccount = false; + }; + enabledProviders = [ "sgdb" "steamCDN" ]; + batchDownloadSize = 50; + dnsServers = []; + language = "en-US"; + theme = "Deck"; + emudeckInstall = false; + autoUpdate = false; + offlineMode = false; + navigationWidth = 0; + clearLogOnTest = true; + version = 8; + }; + + # Parser configurations + "steam-rom-manager/userData/userConfigurations.json".text = + let + # Generate configurations + configs = mapAttrsToList (name: emu: + mkParserConfig name (emu // { + romFolder = if emu.romFolder != "" then emu.romFolder else commonEmulatorConfigs.${name}.romFolder; + binaryName = if emu.binaryName != "" then emu.binaryName else commonEmulatorConfigs.${name}.binaryName; + }) + ) (filterAttrs (_: v: v.enable) cfg.emulators); + + # Join configurations into a valid JSON array + configsJson = "[${concatStringsSep "," configs}]"; + in + configsJson; + }; + }; +} \ No newline at end of file diff --git a/hosts/deck/steam-shortcuts.nix b/hosts/deck/steam-shortcuts.nix deleted file mode 100644 index a39b410..0000000 --- a/hosts/deck/steam-shortcuts.nix +++ /dev/null @@ -1,371 +0,0 @@ -{ config, lib, pkgs, ... }: - -let - # Import the games configuration - gamesConfig = import ./steam-games.nix { inherit pkgs; }; - - # Create the VDF generator script - vdfGeneratorScript = pkgs.writeText "generate_vdf.py" '' - #!/usr/bin/env python3 - import sys - import json - import struct - import time - import hashlib - - def generate_app_id(name): - """Generate Steam app ID for a game using Steam's apparent format.""" - # Get the current timestamp - timestamp = int(time.time()) - - # Create a hash of the name and timestamp - hash_input = f"{name}{timestamp}".encode('utf-8') - hash_obj = hashlib.md5(hash_input) - - # Take first 6 bytes of hash and combine with a fixed pattern - hash_bytes = hash_obj.digest()[:6] - - # Create a 64-bit number where: - # - First 6 bytes come from hash - # - Last 2 bytes are zeros (matching Steam's pattern) - full_bytes = hash_bytes + b'\x00\x00' - - # Convert to 64-bit integer - app_id = struct.unpack(">Q", full_bytes)[0] - - # Ensure the number is in the correct range by setting specific bits - app_id |= (1 << 62) # Set the second-to-highest bit - app_id &= ~(1 << 63) # Clear the highest bit - - return app_id - - def write_vdf(shortcuts): - """Write VDF format matching the exact byte sequence.""" - output = bytearray() - - # Header - output.extend(b'\x00shortcuts\x00') - - for i, shortcut in enumerate(shortcuts): - # Index - output.extend(f'\x00{i}\x00'.encode('utf-8')) - - # AppName - output.extend(b'\x01appname\x00') - output.extend(shortcut['AppName'].encode('utf-8') + b'\x00') - - # App ID (this is the key addition) - output.extend(b'\x02appid\x00') - output.extend(f'"{shortcut["Id"]}"'.encode('utf-8') + b'\x00') - - # Exe - output.extend(b'\x01exe\x00') - output.extend(f'"{shortcut["ExePath"]}"'.encode('utf-8') + b'\x00') - - # StartDir - output.extend(b'\x01StartDir\x00') - output.extend(b'"/home/deck"\x00') - - # Icon - output.extend(b'\x01icon\x00\x00') - - # ShortcutPath - output.extend(b'\x01ShortcutPath\x00\x00') - - # LaunchOptions - output.extend(b'\x01LaunchOptions\x00') - output.extend(f'"{shortcut["LaunchOptions"]}"'.encode('utf-8') + b'\x00') - - # IsHidden - output.extend(b'\x02IsHidden\x00\x00\x00\x00\x00') - - # AllowDesktopConfig - output.extend(b'\x02AllowDesktopConfig\x00\x01\x00\x00\x00') - - # AllowOverlay - output.extend(b'\x02AllowOverlay\x00\x01\x00\x00\x00') - - # OpenVR - output.extend(b'\x02OpenVR\x00\x00\x00\x00\x00') - - # Tags - output.extend(b'\x00tags\x00\x08') - - # Entry terminator - output.extend(b'\x08') - - # Final terminators - output.extend(b'\x08\x08') - - return output - - def main(): - try: - shortcuts = json.load(sys.stdin) - vdf_data = write_vdf(shortcuts) - sys.stdout.buffer.write(vdf_data) - except Exception as e: - print(f"Error: {e}", file=sys.stderr) - sys.exit(1) - - if __name__ == '__main__': - main() - ''; - - # Create the SteamGridDB image downloader script - steamGridDbScript = pkgs.writeText "fetch_steamgriddb.py" '' - #!/usr/bin/env python3 - import sys - import json - import requests - import time - import hashlib - import struct - from pathlib import Path - - def generate_app_id(name): - """Generate Steam app ID for a game using Steam's apparent format.""" - # Get the current timestamp - timestamp = int(time.time()) - - # Create a hash of the name and timestamp - hash_input = f"{name}{timestamp}".encode('utf-8') - hash_obj = hashlib.md5(hash_input) - - # Take first 6 bytes of hash and combine with a fixed pattern - hash_bytes = hash_obj.digest()[:6] - - # Create a 64-bit number where: - # - First 6 bytes come from hash - # - Last 2 bytes are zeros (matching Steam's pattern) - full_bytes = hash_bytes + b'\x00\x00' - - # Convert to 64-bit integer - app_id = struct.unpack(">Q", full_bytes)[0] - - # Ensure the number is in the correct range by setting specific bits - app_id |= (1 << 62) # Set the second-to-highest bit - app_id &= ~(1 << 63) # Clear the highest bit - - return app_id - - def download_image(url, path, headers=None): - """Download an image from a URL to a specified path.""" - if url: - try: - response = requests.get(url) # Don't use auth headers for actual download - if response.status_code == 200: - path.write_bytes(response.content) - return True - else: - pass - except Exception as e: - print(f"Error downloading: {e}") - return False - - def fetch_images(api_key, game_name, grid_ids, output_dir): - """Fetch images for a game from SteamGridDB.""" - print(f"\nProcessing images for {game_name}") - headers = {"Authorization": f"Bearer {api_key}"} - base_url = "https://www.steamgriddb.com/api/v2" - app_id = generate_app_id(game_name) - - # Create output directories - grid_dir = output_dir / "config/grid" - hero_dir = output_dir / "config/heroes" - logo_dir = output_dir / "config/logos" - - print(f"Using directories:") - print(f"Grid: {grid_dir}") - print(f"Hero: {hero_dir}") - print(f"Logo: {logo_dir}") - - grid_dir.mkdir(parents=True, exist_ok=True) - hero_dir.mkdir(parents=True, exist_ok=True) - logo_dir.mkdir(parents=True, exist_ok=True) - - # Test API connection - test_response = requests.get(f"{base_url}/grids/game/1", headers=headers) - print(f"API test response: {test_response.status_code}") - if test_response.status_code == 401: - print("API authentication failed. Check your API key.") - return - - image_types = { - "grid": (grid_dir / f"{app_id}.jpg", grid_ids.get("grid"), "grids"), - "hero": (hero_dir / f"{app_id}.jpg", grid_ids.get("hero"), "heroes"), - "logo": (logo_dir / f"{app_id}.png", grid_ids.get("logo"), "logos"), - "list": (grid_dir / f"{app_id}_list.jpg", grid_ids.get("list"), "grids") - } - - for img_type, (output_path, specific_id, asset_type) in image_types.items(): - if specific_id: - # Get direct file URL from the API - url = f"{base_url}/{asset_type}/{specific_id}" - try: - response = requests.get(url, headers=headers) - if response.status_code == 200: - data = response.json() - if data.get("success"): - image_url = data["data"]["url"] - if download_image(image_url, output_path): - print(f"Successfully downloaded {img_type} image") - else: - print(f"Failed to download {img_type} image") - else: - print(f"API error: {data}") - else: - print(f"API request failed: status {response.status_code}") - if response.headers.get('content-type', "").startswith('application/json'): - print(f"Response: {response.json()}") - except Exception as e: - print(f"Error processing {img_type}: {e}") - time.sleep(1) # Rate limiting - - def main(): - if len(sys.argv) != 4: - print("Usage: script.py API_KEY game_data.json output_dir", file=sys.stderr) - sys.exit(1) - - api_key = sys.argv[1] - game_data_file = sys.argv[2] - output_dir = Path(sys.argv[3]) - - print(f"Loading game data from: {game_data_file}") - print(f"Output directory: {output_dir}") - - try: - with open(game_data_file) as f: - games = json.load(f) - print(f"Loaded {len(games)} games") - - for game in games: - if "steamGridDbIds" in game: - fetch_images(api_key, game["name"], game["steamGridDbIds"], output_dir) - else: - print(f"No steamGridDbIds for {game['name']}, skipping") - - except Exception as e: - print(f"Error: {e}", file=sys.stderr) - sys.exit(1) - - if __name__ == "__main__": - main() - ''; - - # Python environment with required packages - pythonEnv = pkgs.python3.withPackages (ps: with ps; [ - requests - ]); - - # Helper function to convert hex char to decimal - hexCharToInt = c: - let - hexChars = { - "0" = 0; "1" = 1; "2" = 2; "3" = 3; "4" = 4; - "5" = 5; "6" = 6; "7" = 7; "8" = 8; "9" = 9; - "a" = 10; "b" = 11; "c" = 12; "d" = 13; "e" = 14; "f" = 15; - "A" = 10; "B" = 11; "C" = 12; "D" = 13; "E" = 14; "F" = 15; - }; - in hexChars.${c}; - - # Convert hex string to decimal number - hexToInt = str: - let - len = builtins.stringLength str; - chars = lib.stringToCharacters str; - go = pos: nums: - if pos == len then 0 - else - let - char = builtins.elemAt chars pos; - num = hexCharToInt char; - exp = len - pos - 1; - factor = builtins.foldl' (a: b: a * 16) 1 (lib.range 1 exp); - in - (num * factor) + (go (pos + 1) nums); - in go 0 chars; - - # Helper function for the ID generation - handling large numbers - generateAppId = name: - let - hash = builtins.hashString "md5" name; - # Instead of trying to create a massive number, we'll generate a unique pattern - # that will create a consistent large number when interpreted by Python - sixteenHex = builtins.substring 0 16 hash; - # Format it as a string that Python will interpret as a large number - shortcutAppId = "0x${sixteenHex}0000"; - in shortcutAppId; - - # Convert games to shortcuts format - gameToShortcut = game: { - Id = generateAppId game.name; - AppName = game.name; - ExePath = game.path; - LaunchOptions = game.extraLaunchOptions; - }; - - # Script to update shortcuts and fetch images - updateSteamShortcuts = pkgs.writeShellScriptBin "update-steam-shortcuts" '' - STEAM_DIR="$HOME/.local/share/Steam" - USERDATA_DIR="$STEAM_DIR/userdata" - CACHE_DIR="$HOME/.cache/steam-shortcuts" - - if [ ! -d "$USERDATA_DIR" ]; then - echo "Steam userdata directory not found!" - exit 1 - fi - - # Make sure Steam is not running - if pgrep -x "steam" > /dev/null; then - echo "Please close Steam before updating shortcuts" - exit 1 - fi - - mkdir -p "$CACHE_DIR" - - # Process each Steam user directory - for USER_DIR in "$USERDATA_DIR"/*; do - if [ -d "$USER_DIR" ]; then - USER_ID="$(basename "$USER_DIR")" - CONFIG_DIR="$USER_DIR/config" - mkdir -p "$CONFIG_DIR" - - echo "Processing Steam user $USER_ID..." - - # Create games JSON file - GAMES_JSON="$CACHE_DIR/games.json" - echo '${builtins.toJSON (map gameToShortcut gamesConfig.games)}' > "$GAMES_JSON" - - # Generate shortcuts.vdf - cat "$GAMES_JSON" | ${pythonEnv}/bin/python3 ${vdfGeneratorScript} > "$CONFIG_DIR/shortcuts.vdf" - - # Download images if API key is set - if [ "${gamesConfig.steamGridDbApiKey}" != "YOUR_API_KEY_HERE" ]; then - echo '${builtins.toJSON gamesConfig.games}' > "$CACHE_DIR/games_full.json" - ${pythonEnv}/bin/python3 ${steamGridDbScript} "${gamesConfig.steamGridDbApiKey}" "$CACHE_DIR/games_full.json" "$USER_DIR" - fi - - echo "Updated shortcuts and images for $USER_ID" - fi - done - - echo "Done! You can now start Steam." - ''; - -in { - # Add the update script to system packages - environment.systemPackages = [ updateSteamShortcuts ]; - - # Optional: Create a systemd user service to update shortcuts on login - systemd.user.services.update-steam-shortcuts = { - enable = true; - description = "Update Steam shortcuts"; - wantedBy = [ "graphical-session.target" ]; - serviceConfig = { - Type = "oneshot"; - ExecStart = "${updateSteamShortcuts}/bin/update-steam-shortcuts"; - RemainAfterExit = true; - }; - }; -} \ No newline at end of file