{ config, lib, pkgs, ... }: let cfg = config.programs.steam-rom-manager; # Single source of truth for built-in emulator metadata. knownEmulators = import ./emulators.nix pkgs; # Valid image provider identifiers accepted by SRM. validProviders = [ "sgdb" "steamCDN" ]; # Parser types that do not scan ROM files and therefore need no package. platformParserTypes = [ "Epic" "Legendary" "GOG Galaxy" "Amazon Games" "UPlay" "itch.io" "UWP" "EA Desktop" "Battle.net" "Non-SRM Shortcuts" "Steam" "Manual" ]; isPlatformParser = type: builtins.elem type platformParserTypes; in { options.programs.steam-rom-manager = { enable = lib.mkEnableOption "Steam ROM Manager"; # ------------------------------------------------------------------------- # Fuzzy matcher # ------------------------------------------------------------------------- fuzzyMatcher = { timestamps = { check = lib.mkOption { type = lib.types.int; default = 0; description = "Epoch timestamp of the last fuzzy-matcher data check."; }; download = lib.mkOption { type = lib.types.int; default = 0; description = "Epoch timestamp of the last fuzzy-matcher data download."; }; }; verbose = lib.mkOption { type = lib.types.bool; default = false; description = "Enable verbose logging for the fuzzy matcher."; }; filterProviders = lib.mkOption { type = lib.types.bool; default = true; description = "Restrict image lookups to the configured providers."; }; }; # ------------------------------------------------------------------------- # Environment variables written into userSettings.json # ------------------------------------------------------------------------- environmentVariables = { steamDirectory = lib.mkOption { type = lib.types.str; default = "${config.home.homeDirectory}/.local/share/Steam"; description = "Path to the Steam data directory."; }; romsDirectory = lib.mkOption { type = lib.types.str; default = "${config.home.homeDirectory}/Emulation/roms"; description = "Root directory that contains per-system ROM sub-folders."; }; retroarchPath = lib.mkOption { type = lib.types.str; default = ""; description = "Path to the RetroArch executable (leave empty if not using RetroArch)."; }; raCoresDirectory = lib.mkOption { type = lib.types.str; default = ""; description = "Directory containing RetroArch cores."; }; localImagesDirectory = lib.mkOption { type = lib.types.str; default = ""; description = "Directory for locally stored artwork."; }; }; # ------------------------------------------------------------------------- # Preview settings # ------------------------------------------------------------------------- previewSettings = { retrieveCurrentSteamImages = lib.mkOption { type = lib.types.bool; default = true; description = "Download existing Steam artwork when previewing."; }; disableCategories = lib.mkOption { type = lib.types.bool; default = false; description = "Do not apply Steam category tags to shortcuts."; }; deleteDisabledShortcuts = lib.mkOption { type = lib.types.bool; default = false; description = "Remove shortcuts for parsers that are disabled."; }; imageZoomPercentage = lib.mkOption { type = lib.types.int; default = 30; description = "Zoom level (%) used when displaying artwork in the preview pane."; }; preload = lib.mkOption { type = lib.types.bool; default = false; description = "Pre-fetch artwork while the preview loads."; }; hideUserAccount = lib.mkOption { type = lib.types.bool; default = false; description = "Hide the Steam account name in the preview pane."; }; }; # ------------------------------------------------------------------------- # Image providers # ------------------------------------------------------------------------- enabledProviders = lib.mkOption { type = lib.types.listOf (lib.types.enum validProviders); default = [ "sgdb" "steamCDN" ]; description = '' Ordered list of image providers SRM should query. Valid values: ${lib.concatStringsSep ", " (map (p: ''"${p}"'') validProviders)}. ''; }; imageProviderSettings = { sgdb = { nsfw = lib.mkOption { type = lib.types.bool; default = false; description = "Include NSFW artwork from SteamGridDB."; }; humor = lib.mkOption { type = lib.types.bool; default = false; description = "Include humour-tagged artwork from SteamGridDB."; }; styles = lib.mkOption { type = lib.types.listOf lib.types.str; default = [ ]; description = "Preferred grid artwork styles (e.g. \"alternate\", \"blurred\")."; }; stylesHero = lib.mkOption { type = lib.types.listOf lib.types.str; default = [ ]; description = "Preferred hero artwork styles."; }; stylesLogo = lib.mkOption { type = lib.types.listOf lib.types.str; default = [ ]; description = "Preferred logo styles."; }; stylesIcon = lib.mkOption { type = lib.types.listOf lib.types.str; default = [ ]; description = "Preferred icon styles."; }; imageMotionTypes = lib.mkOption { type = lib.types.listOf lib.types.str; default = [ "static" ]; description = "Allowed motion types (\"static\", \"animated\")."; }; sizes = lib.mkOption { type = lib.types.listOf lib.types.str; default = [ ]; description = "Preferred grid image sizes."; }; sizesHero = lib.mkOption { type = lib.types.listOf lib.types.str; default = [ ]; description = "Preferred hero image sizes."; }; sizesIcon = lib.mkOption { type = lib.types.listOf lib.types.str; default = [ ]; description = "Preferred icon sizes."; }; }; }; # ------------------------------------------------------------------------- # Miscellaneous top-level settings # ------------------------------------------------------------------------- batchDownloadSize = lib.mkOption { type = lib.types.int; default = 50; description = "Number of images to download per batch."; }; dnsServers = lib.mkOption { type = lib.types.listOf lib.types.str; default = [ ]; description = "Custom DNS servers used for artwork downloads (leave empty for system DNS)."; }; language = lib.mkOption { type = lib.types.str; default = "en-US"; example = "de-DE"; description = "BCP-47 language tag for the SRM UI."; }; theme = lib.mkOption { type = lib.types.str; default = "Deck"; example = "Default"; description = "SRM UI theme name."; }; emudeckInstall = lib.mkOption { type = lib.types.bool; default = false; description = "Set to true when SRM is managed alongside EmuDeck."; }; autoUpdate = lib.mkOption { type = lib.types.bool; default = false; description = "Allow SRM to update itself automatically (not recommended in a Nix-managed setup)."; }; offlineMode = lib.mkOption { type = lib.types.bool; default = false; description = "Run SRM without fetching remote artwork."; }; navigationWidth = lib.mkOption { type = lib.types.int; default = 0; description = "Width in pixels of the navigation sidebar (0 = default)."; }; clearLogOnTest = lib.mkOption { type = lib.types.bool; default = true; description = "Clear the log panel each time a parser test is run."; }; steamUsername = lib.mkOption { type = lib.types.str; example = "john"; description = '' Steam account username used to build the SRM environment-variable reference ''${} in userSettings.json. This must match the account name shown in Steam (not the display name). ''; }; # ------------------------------------------------------------------------- # Emulator / parser configurations # ------------------------------------------------------------------------- emulators = lib.mkOption { type = lib.types.attrsOf ( lib.types.submodule ( { name, config, ... }: { options = { enable = lib.mkEnableOption "parser for ${name}"; disabled = lib.mkOption { type = lib.types.bool; default = false; description = '' Write the parser into userConfigurations.json but mark it as disabled inside SRM (equivalent to unchecking it in the UI). Useful for keeping a config around without having it run. ''; }; package = lib.mkOption { type = lib.types.nullOr lib.types.package; # Resolve against knownEmulators, then try pkgs., then null # for pure platform parsers that have no associated binary. default = if knownEmulators ? ${name} && knownEmulators.${name} ? package then knownEmulators.${name}.package else if isPlatformParser config.parserType then null else pkgs.${name}; description = '' Package providing the emulator binary. Set to null for platform parsers (Epic, GOG, etc.) that do not need a separate package managed by Nix. ''; }; parserType = lib.mkOption { type = lib.types.str; default = if knownEmulators ? ${name} && knownEmulators.${name} ? parserType then knownEmulators.${name}.parserType else "Glob"; description = '' SRM parser type. One of: Glob, Glob-regex, Manual, Steam, Non-SRM Shortcuts, Epic, Legendary, GOG Galaxy, Amazon Games, UPlay, itch.io, UWP, EA Desktop, Battle.net ''; }; configTitle = lib.mkOption { type = lib.types.str; default = name; description = "Human-readable label shown inside SRM for this parser."; }; romFolder = lib.mkOption { type = lib.types.str; default = ""; description = '' Sub-folder under environmentVariables.romsDirectory containing ROMs for this emulator. Defaults to the built-in value for known emulators; must be set explicitly for custom entries. Leave empty for platform/artwork parsers. ''; }; steamCategories = lib.mkOption { type = lib.types.listOf lib.types.str; default = if knownEmulators ? ${name} && knownEmulators.${name} ? steamCategory then [ knownEmulators.${name}.steamCategory ] else [ "" ]; description = "Steam categories / collection tags to apply to shortcuts."; }; extraArgs = lib.mkOption { type = lib.types.str; default = if knownEmulators ? ${name} && knownEmulators.${name} ? executableArgs then knownEmulators.${name}.executableArgs else "\"\${filePath}\""; description = "Command-line arguments passed to the emulator."; }; executableModifier = lib.mkOption { type = lib.types.str; default = "\"\${exePath}\""; description = "SRM executable modifier expression."; }; titleModifier = lib.mkOption { type = lib.types.str; default = "\${fuzzyTitle}"; description = "SRM title modifier expression."; }; steamInputEnabled = lib.mkOption { type = lib.types.bool; default = false; description = "Enable Steam Input controller remapping for shortcuts."; }; onlineImageQueries = lib.mkOption { type = lib.types.listOf lib.types.str; default = [ "\${fuzzyTitle}" ]; description = "Query strings used to search for artwork online."; }; imagePool = lib.mkOption { type = lib.types.str; default = "\${fuzzyTitle}"; description = "SRM image pool identifier for artwork caching."; }; drmProtected = lib.mkOption { type = lib.types.bool; default = false; description = "Mark shortcuts as DRM-protected."; }; userAccounts = lib.mkOption { type = lib.types.listOf lib.types.str; default = [ "Global" ]; description = "Steam accounts to apply this parser to (\"Global\" applies to all)."; }; fileTypes = lib.mkOption { type = lib.types.listOf lib.types.str; default = [ ]; description = '' File extensions matched by the glob parser (e.g. ".iso"). Defaults to the built-in list for known emulators. Leave empty for platform/artwork parsers. ''; }; shortcutPassthrough = lib.mkOption { type = lib.types.bool; default = false; description = "Pass through existing Steam shortcuts rather than replacing them."; }; appendArgsToExecutable = lib.mkOption { type = lib.types.bool; default = true; description = "Append extraArgs to the executable path in the shortcut."; }; # Extra parser-specific inputs (used by platform parsers and Manual). # For Glob parsers the glob pattern is generated automatically from # fileTypes; you do not need to set parserInputs.glob manually. parserInputs = lib.mkOption { type = lib.types.attrsOf lib.types.anything; default = if knownEmulators ? ${name} && knownEmulators.${name} ? parserInputs then knownEmulators.${name}.parserInputs else { }; description = '' Additional parser-specific input fields merged into parserInputs. For platform parsers (Epic, Legendary, GOG, itch.io, etc.) this carries launcher-specific settings. For Glob parsers the "glob" key is generated automatically. ''; }; }; } ) ); default = { }; description = "Attribute set of emulator/parser configurations keyed by name."; }; }; }