commit b9ec00c18cada9d2bfcd8654a567ac0c04cd8452 Author: Chris Toph Date: Sun Jun 29 19:34:36 2025 -0400 Initial configuration, untested. diff --git a/.vscode/extensions.json b/.vscode/extensions.json new file mode 100644 index 0000000..4aa30a5 --- /dev/null +++ b/.vscode/extensions.json @@ -0,0 +1,23 @@ +{ + "recommendations": [ + "bierner.emojisense", + "mkhl.direnv", + "irongeek.vscode-env", + "bmalehorn.vscode-fish", + "github.copilot", + "github.copilot-chat", + "golang.go", + "eamodio.gitlens", + "mblet.highlight-regex", + "oderwat.indent-rainbow", + "visualstudioexptteam.vscodeintellicode", + "visualstudioexptteam.intellicode-api-usage-examples", + "yzhang.markdown-all-in-one", + "shd101wyy.markdown-preview-enhanced", + "bierner.markdown-preview-github-styles", + "bbenoist.nix", + "brettm12345.nixfmt-vscode", + "ryu1kn.partial-diff", + "jeffersonlicet.snipped" + ] +} \ No newline at end of file diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..04744cf --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,597 @@ +{ + "editor.formatOnSave": true, + "editor.formatOnSaveMode": "file", + "explorer.fileNesting.patterns": { + ".gitignore": ".gitattributes, .envrc, readme", + "flake.nix": "flake.lock, *.nix" + }, + "files.exclude": { + ".git-crypt": true + }, + "terminal.integrated.defaultProfile.linux": "fish-fhs", + "terminal.integrated.profiles.linux": { + "fish-fhs": { + "args": [ + "--user", + "--pty", + "--quiet", + "--same-dir", + "--service-type=exec", + "fish" + ], + "path": "systemd-run" + } + }, + "highlight.regex.regexes": [ + { + "languageIds": [ + "css", + "fish", + "go", + "java", + "javascript", + "jsonc", + "nix", + "postcss", + "rust", + "shellscript", + "svelte", + "typescript", + "yuck" + ], + "regexes": [ + { + "decorations": [ + { + "backgroundColor": "#4ebbff99", + "isWholeLine": true, + "overviewRulerColor": "#4ebbff" + } + ], + "regex": "(<|]" + }, + { + "decorations": [ + { + "color": "#ffa07a" + } + ], + "regex": "script" + } + ], + "regexFlag": "gm" + }, + { + "decorations": [ + { + "backgroundColor": "#ffa07a99", + "isWholeLine": true, + "overviewRulerColor": "#ffa07a" + } + ], + "regex": "(<|]" + }, + { + "decorations": [ + { + "color": "#7ad9ff" + } + ], + "regex": "style" + } + ], + "regexFlag": "gm" + }, + { + "regex": "(/\\*|<\\!--)(.|[\r\n])*?((\\*/|-->)|-->)", + "regexes": [ + { + "decorations": [ + { + "color": "#d4be98", + "fontWeight": "bold", + "index": 0 + } + ], + "regex": "\\B:D" + }, + { + "decorations": [ + { + "color": "#f00", + "fontWeight": "bold", + "index": 0 + } + ], + "regex": "\\!\\!\\!" + }, + { + "decorations": [ + { + "color": "#D8A657", + "fontWeight": "bold", + "index": 0 + } + ], + "regex": "\\^\\^" + }, + { + "decorations": [ + { + "color": "#E78A4E", + "fontWeight": "bold", + "index": 1 + }, + { + "color": "#D3869B", + "fontWeight": "bold", + "index": 2 + } + ], + "regex": "(<)(>)" + }, + { + "decorations": [ + { + "fontWeight": "bold", + "index": 0 + }, + { + "backgroundColor": "#804600", + "color": "#d4be98", + "index": 1, + "overviewRulerColor": "#804600" + } + ], + "regex": "(.*TODO:)((.|\\r|\\n)*?(\\*/|-->)$)", + "regexes": [ + { + "decorations": [ + { + "index": 0 + }, + { + "backgroundColor": "#80460099", + "color": "#d4be98", + "index": 2, + "overviewRulerColor": "#80460099" + } + ], + "index": 2, + "regex": "([\\s]+)?( .*)" + } + ] + }, + { + "decorations": [ + { + "fontWeight": "bold", + "index": 0 + }, + { + "backgroundColor": "#fffacd", + "color": "#282828", + "index": 1, + "overviewRulerColor": "#fffacd" + } + ], + "regex": "(.*NOTE:)((.|\\r|\\n)*?(\\*/|-->)$)", + "regexes": [ + { + "decorations": [ + { + "index": 0 + }, + { + "backgroundColor": "#fffacd99", + "color": "#282828", + "index": 2, + "overviewRulerColor": "#fffacd99" + } + ], + "index": 2, + "regex": "([\\s]+)?( .*)" + } + ] + }, + { + "decorations": [ + { + "fontWeight": "bold", + "index": 0 + }, + { + "backgroundColor": "#6495ed", + "color": "#282828", + "index": 1, + "overviewRulerColor": "#6495ed" + } + ], + "regex": "(.*IDEA:)((.|\\r|\\n)*?(\\*/|-->)$)", + "regexes": [ + { + "decorations": [ + { + "index": 0 + }, + { + "backgroundColor": "#6495ed99", + "color": "#282828", + "index": 2, + "overviewRulerColor": "#6495ed99" + } + ], + "index": 2, + "regex": "([\\s]+)?( .*)" + } + ] + }, + { + "decorations": [ + { + "fontWeight": "bold", + "index": 0 + }, + { + "backgroundColor": "#66cc99", + "color": "#282828", + "index": 1, + "overviewRulerColor": "#66cc99" + } + ], + "regex": "(.*INFO:)((.|\\r|\\n)*?(\\*/|-->)$)", + "regexes": [ + { + "decorations": [ + { + "index": 0 + }, + { + "backgroundColor": "#66cc9999", + "color": "#282828", + "index": 2, + "overviewRulerColor": "#66cc9999" + } + ], + "index": 2, + "regex": "([\\s]+)?( .*)" + } + ] + }, + { + "decorations": [ + { + "fontWeight": "bold", + "index": 0 + }, + { + "backgroundColor": "#591d77", + "color": "#d4be98", + "index": 1, + "overviewRulerColor": "#591d77" + } + ], + "regex": "(.*(?:ABOUT|EXP|\\?+):)((.|\\r|\\n)*?(\\*/|-->)$)", + "regexes": [ + { + "decorations": [ + { + "index": 0 + }, + { + "backgroundColor": "#591d7799", + "color": "#d4be98", + "index": 2, + "overviewRulerColor": "#591d7799" + } + ], + "index": 2, + "regex": "([\\s]+)?( .*)" + } + ] + }, + { + "decorations": [ + { + "fontWeight": "bold", + "index": 0 + }, + { + "backgroundColor": "#a74165", + "color": "#d4be98", + "index": 1, + "overviewRulerColor": "#a74165" + } + ], + "regex": "(.*(?:FIXME|FIX|BUG|DEBUG|HACK|REMOVE):)((.|\\r|\\n)*?(\\*/|-->)$)", + "regexes": [ + { + "decorations": [ + { + "index": 0 + }, + { + "backgroundColor": "#a7416599", + "color": "#d4be98", + "index": 2, + "overviewRulerColor": "#a7416599" + } + ], + "index": 2, + "regex": "([\\s]+)?( .*)" + } + ] + }, + { + "decorations": [ + { + "fontWeight": "bold", + "index": 0 + }, + { + "backgroundColor": "#c36c5c", + "color": "#d4be98", + "index": 1, + "overviewRulerColor": "#c36c5c" + } + ], + "regex": "(.*(?:SKELETON|COMPONENT):)((.|\\r|\\n)*?(\\*/|-->)$)", + "regexes": [ + { + "decorations": [ + { + "index": 0 + }, + { + "backgroundColor": "#c36c5c99", + "color": "#d4be98", + "index": 2, + "overviewRulerColor": "#c36c5c99" + } + ], + "index": 2, + "regex": "([\\s]+)?( .*)" + } + ] + } + ], + "regexFlag": "gm", + "regexLimit": 25000 + }, + { + "regex": "(?<=\\s*)(//|#|;)(.|\r\n)*?$", + "regexes": [ + { + "decorations": [ + { + "color": "#d4be98", + "fontWeight": "bold", + "index": 0 + } + ], + "regex": "\\B:D" + }, + { + "decorations": [ + { + "fontWeight": "bold", + "index": 0 + }, + { + "color": "#D8A657", + "index": 1 + }, + { + "color": "#d4be98", + "index": 2 + }, + { + "color": "#d4be98", + "index": 4 + }, + { + "color": "#D8A657", + "index": 5 + } + ], + "regex": "(^/|#|;)(/|#|;)(.*)(/|#|;)(/|#|;$)" + }, + { + "decorations": [ + { + "color": "#f00", + "fontWeight": "bold", + "index": 0 + } + ], + "regex": "\\!\\!\\!" + }, + { + "decorations": [ + { + "color": "#D8A657", + "fontWeight": "bold", + "index": 0 + } + ], + "regex": "\\^\\^" + }, + { + "decorations": [ + { + "color": "#E78A4E", + "fontWeight": "bold", + "index": 1 + }, + { + "color": "#D3869B", + "fontWeight": "bold", + "index": 2 + } + ], + "regex": "(<)(>)" + }, + { + "decorations": [ + { + "color": "#d4be98", + "fontWeight": "bold", + "index": 0 + }, + { + "backgroundColor": "#804600", + "index": 1, + "overviewRulerColor": "#804600" + }, + { + "backgroundColor": "#80460099", + "index": 2, + "overviewRulerColor": "#80460099" + } + ], + "regex": "(.*TODO:)(.*)" + }, + { + "decorations": [ + { + "color": "#282828", + "fontWeight": "bold", + "index": 0 + }, + { + "backgroundColor": "#fffacd", + "index": 1, + "overviewRulerColor": "#fffacd" + }, + { + "backgroundColor": "#fffacd99", + "index": 2, + "overviewRulerColor": "#fffacd99" + } + ], + "regex": "(.*NOTE:)(.*)" + }, + { + "decorations": [ + { + "color": "#282828", + "fontWeight": "bold", + "index": 0 + }, + { + "backgroundColor": "#6495ed", + "index": 1, + "overviewRulerColor": "#6495ed" + }, + { + "backgroundColor": "#6495ed99", + "index": 2, + "overviewRulerColor": "#6495ed99" + } + ], + "regex": "(.*IDEA:)(.*)" + }, + { + "decorations": [ + { + "color": "#282828", + "fontWeight": "bold", + "index": 0 + }, + { + "backgroundColor": "#66cc99", + "index": 1, + "overviewRulerColor": "#66cc99" + }, + { + "backgroundColor": "#66cc9999", + "index": 2, + "overviewRulerColor": "#66cc9999" + } + ], + "regex": "(.*INFO:)(.*)" + }, + { + "decorations": [ + { + "color": "#d4be98", + "fontWeight": "bold", + "index": 0 + }, + { + "backgroundColor": "#591d77", + "index": 1, + "overviewRulerColor": "#591d77" + }, + { + "backgroundColor": "#591d7799", + "index": 2, + "overviewRulerColor": "#591d7799" + } + ], + "regex": "(.*(?:ABOUT|EXP|\\?+):)(.*)" + }, + { + "decorations": [ + { + "color": "#d4be98", + "fontWeight": "bold", + "index": 0 + }, + { + "backgroundColor": "#a74165", + "index": 1, + "overviewRulerColor": "#a74165" + }, + { + "backgroundColor": "#a7416599", + "index": 2, + "overviewRulerColor": "#a7416599" + } + ], + "regex": "(.*(?:FIXME|FIX|BUG|DEBUG|HACK|REMOVE):)(.*)" + }, + { + "decorations": [ + { + "color": "#d4be98", + "fontWeight": "bold", + "index": 0 + }, + { + "backgroundColor": "#c36c5c", + "index": 1, + "overviewRulerColor": "#c36c5c" + }, + { + "backgroundColor": "#c36c5c99", + "index": 2, + "overviewRulerColor": "#c36c5c99" + } + ], + "regex": "(.*(?:SKELETON|COMPONENT):)(.*)" + } + ], + "regexFlag": "gm", + "regexLimit": 25000 + } + ] + } + ] +} diff --git a/flake.nix b/flake.nix new file mode 100644 index 0000000..a6e8706 --- /dev/null +++ b/flake.nix @@ -0,0 +1,61 @@ +{ + description = "Wayming - Gaming setup for Wayland with configurable options and working defaults"; + + inputs = { + nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable"; + chaotic.url = "github:chaotic-cx/nyx/nyxpkgs-unstable"; + home-manager = { + url = "github:nix-community/home-manager"; + inputs.nixpkgs.follows = "nixpkgs"; + }; + flake-utils.url = "github:numtide/flake-utils"; + }; + + outputs = + { + self, + nixpkgs, + home-manager, + flake-utils, + chaotic, + }: + flake-utils.lib.eachDefaultSystem ( + system: + let + pkgs = import nixpkgs { + inherit system; + overlays = [ self.overlays.default ]; + }; + + # Extend lib with wayming utilities + lib = nixpkgs.lib.extend ( + final: prev: { + wayming = import ./lib { lib = final; }; + } + ); + + in + { + packages = { + proton-cachyos = pkgs.callPackage ./pkgs/proton-cachyos { }; + default = self.packages.${system}.proton-cachyos; + }; + + # Expose the extended lib + lib = lib; + } + ) + // { + homeManagerModules.wayming = import ./modules/home-manager.nix; + nixosModules.wayming = import ./modules/nixos.nix; + + # Default module (home-manager) + homeManagerModules.default = self.homeManagerModules.wayming; + + # Overlay for packages + overlays.default = final: prev: { + proton-cachyos = final.callPackage ./pkgs/proton-cachyos { }; + # Add other packages here as needed + }; + }; +} diff --git a/lib/defualt.nix b/lib/defualt.nix new file mode 100644 index 0000000..ad2f4b7 --- /dev/null +++ b/lib/defualt.nix @@ -0,0 +1,53 @@ +{ lib }: + +{ + # Helper to convert Nix attrs to gamescope command-line arguments + toCliArgs = + attrs: + let + argToString = + name: value: + if builtins.isBool value then + lib.optionalString value "--${name}" + else + "--${name} ${toString value}"; + in + lib.concatStringsSep " " (lib.mapAttrsToList argToString attrs); + + # Helper to convert Nix attrs to fish 'set -x' commands + toEnvCommands = + attrs: + lib.concatStringsSep "\n" ( + lib.mapAttrsToList (name: value: "set -x ${name} '${toString value}'") attrs + ); + + getPrimaryMonitor = monitors: lib.findFirst (m: m.primary) null monitors; + + getMonitorDefaults = monitors: { + WIDTH = + let + pm = lib.wayming.getPrimaryMonitor monitors; + in + if pm != null then pm.width else 1920; + HEIGHT = + let + pm = lib.wayming.getPrimaryMonitor monitors; + in + if pm != null then pm.height else 1080; + REFRESH_RATE = + let + pm = lib.wayming.getPrimaryMonitor monitors; + in + if pm != null then pm.refreshRate else 60; + VRR = + let + pm = lib.wayming.getPrimaryMonitor monitors; + in + if pm != null then pm.vrr else false; + HDR = + let + pm = lib.wayming.getPrimaryMonitor monitors; + in + if pm != null then pm.hdr else false; + }; +} diff --git a/modules/home/default.nix b/modules/home/default.nix new file mode 100644 index 0000000..2b262bb --- /dev/null +++ b/modules/home/default.nix @@ -0,0 +1,6 @@ +{ + imports = [ + ./monitors.nix + ./gamescoperun.nix + ./wrappers.nix + ]; diff --git a/modules/home/gamescoperun.nix b/modules/home/gamescoperun.nix new file mode 100644 index 0000000..a9290c0 --- /dev/null +++ b/modules/home/gamescoperun.nix @@ -0,0 +1,207 @@ +{ + lib, + config, + pkgs, + inputs, + ... +}: + +let + cfg = config.wayming.gamescoperun; + + # Use shared lib functions + inherit (lib.wayming) toCliArgs toEnvCommands getMonitorDefaults; + + # Get monitor defaults + monitorDefaults = getMonitorDefaults config.wayming.monitors; + inherit (monitorDefaults) + WIDTH + HEIGHT + REFRESH_RATE + VRR + HDR + ; + + # Select gamescope packages based on useGit option + gamescopePackages = + if cfg.useGit then + { + gamescope = inputs.chaotic.legacyPackages.${pkgs.system}.gamescope_git; + gamescope-wsi = inputs.chaotic.legacyPackages.${pkgs.system}.gamescope-wsi_git; + } + else + { + gamescope = pkgs.gamescope; + gamescope-wsi = pkgs.gamescope-wsi or null; + }; + + # Base options with monitor-derived defaults + defaultBaseOptions = + { + backend = "sdl"; + fade-out-duration = 200; + fullscreen = true; + immediate-flips = true; + nested-refresh = REFRESH_RATE; + output-height = HEIGHT; + output-width = WIDTH; + rt = true; + } + // lib.optionalAttrs HDR { + hdr-enabled = true; + hdr-debug-force-output = true; + hdr-itm-enable = true; + } + // lib.optionalAttrs VRR { + adaptive-sync = true; + }; + + # Merge user options with defaults + finalBaseOptions = defaultBaseOptions // cfg.baseOptions; + + defaultEnvironment = + { + ENABLE_GAMESCOPE_WSI = 1; + GAMESCOPE_WAYLAND_DISPLAY = "gamescope-0"; + PROTON_USE_SDL = 1; + PROTON_USE_WAYLAND = 1; + SDL_VIDEODRIVER = "wayland"; + AMD_VULKAN_ICD = "RADV"; + RADV_PERFTEST = "aco"; + DISABLE_LAYER_AMD_SWITCHABLE_GRAPHICS_1 = 1; + DISABLE_LAYER_NV_OPTIMUS_1 = 1; + } + // lib.optionalAttrs HDR { + ENABLE_HDR_WSI = 1; + DXVK_HDR = 1; + PROTON_ENABLE_HDR = 1; + }; + + # Merge user environment with defaults + finalEnvironment = defaultEnvironment // cfg.environment; + + # The gamescoperun script + gamescoperun = pkgs.writeScriptBin "gamescoperun" '' + #!${lib.getExe pkgs.fish} + + # Check if we're already inside a Gamescope session + if set -q GAMESCOPE_WAYLAND_DISPLAY + echo "Already inside Gamescope session ($GAMESCOPE_WAYLAND_DISPLAY), running command directly..." + exec $argv + end + + # Set environment variables for the gamescope session + ${toEnvCommands finalEnvironment} + + # Define and parse arguments using fish's built-in argparse + argparse -i 'x/extra-args=' -- $argv + if test $status -ne 0 + exit 1 + end + + # Check if we have a command to run + if test (count $argv) -eq 0 + echo "Usage: gamescoperun [-x|--extra-args \"\"] [args...]" + echo "" + echo "Examples:" + echo " gamescoperun heroic" + echo " gamescoperun -x \"--fsr-upscaling-sharpness 5\" steam" + echo " GAMESCOPE_EXTRA_OPTS=\"--fsr\" gamescoperun steam (legacy)" + exit 1 + end + + # Combine base args, extra args from CLI, and extra args from env (for legacy) + set -l final_args ${toCliArgs finalBaseOptions} + + # Add args from -x/--extra-args flag, splitting the string into a list + if set -q _flag_extra_args + set -a final_args (string split ' ' -- $_flag_extra_args) + end + + # For legacy support, add args from GAMESCOPE_EXTRA_OPTS if it exists + if set -q GAMESCOPE_EXTRA_OPTS + set -a final_args (string split ' ' -- $GAMESCOPE_EXTRA_OPTS) + end + + # Show the command being executed + echo -e "\033[1;36m[gamescoperun]\033[0m Running: \033[1;34m${lib.getExe gamescopePackages.gamescope}\033[0m $final_args \033[1;32m--\033[0m $argv" + + # Execute gamescope with the final arguments and the command + exec ${lib.getExe gamescopePackages.gamescope} $final_args -- $argv + ''; + +in +{ + options.wayming.gamescoperun = { + enable = lib.mkEnableOption "gamescoperun, a wrapper for gamescope"; + + useGit = lib.mkOption { + type = lib.types.bool; + default = true; + description = "Use git versions of gamescope from chaotic-nyx for latest features"; + }; + + baseOptions = lib.mkOption { + type = + with lib.types; + attrsOf (oneOf [ + str + int + bool + ]); + default = { }; + example = { + "fsr-upscaling" = true; + "output-width" = 2560; + }; + description = '' + Base command-line options to always pass to gamescope. + Option names must match gamescope's flags exactly (e.g., "hdr-enabled"). + Monitor-derived options (width, height, refresh rate, HDR, VRR) are set automatically + but can be overridden here. + ''; + }; + + environment = lib.mkOption { + type = + with lib.types; + attrsOf (oneOf [ + str + int + ]); + default = { }; + description = '' + Environment variables to set within the gamescoperun script. + HDR-related variables are set automatically based on monitor configuration + but can be overridden here. + ''; + }; + + package = lib.mkOption { + type = lib.types.package; + readOnly = true; + default = gamescoperun; + description = "The configured gamescoperun package"; + }; + }; + + config = lib.mkIf cfg.enable { + # Install both gamescope and gamescope-wsi + home.packages = + [ cfg.package ] + ++ [ gamescopePackages.gamescope ] + ++ lib.optionals (gamescopePackages.gamescope-wsi != null) [ gamescopePackages.gamescope-wsi ]; + + # Assertion to ensure monitors are configured if gamescoperun is enabled + assertions = [ + { + assertion = cfg.enable -> (lib.length config.wayming.monitors > 0); + message = "wayming.gamescoperun requires at least one monitor to be configured in wayming.monitors"; + } + { + assertion = cfg.enable -> (lib.length (lib.filter (m: m.primary) config.wayming.monitors) == 1); + message = "wayming.gamescoperun requires exactly one primary monitor to be configured"; + } + ]; + }; +} diff --git a/modules/home/monitors.nix b/modules/home/monitors.nix new file mode 100644 index 0000000..63f077e --- /dev/null +++ b/modules/home/monitors.nix @@ -0,0 +1,73 @@ +{ lib, config, ... }: +{ + options.wayming.monitors = lib.mkOption { + type = lib.types.listOf ( + lib.types.submodule { + options = { + name = lib.mkOption { + type = lib.types.str; + example = "DP-1"; + }; + primary = lib.mkOption { + type = lib.types.bool; + default = false; + }; + width = lib.mkOption { + type = lib.types.int; + example = 1920; + }; + height = lib.mkOption { + type = lib.types.int; + example = 1080; + }; + refreshRate = lib.mkOption { + type = lib.types.int; + default = 60; + }; + x = lib.mkOption { + type = lib.types.int; + default = 0; + }; + y = lib.mkOption { + type = lib.types.int; + default = 0; + }; + scale = lib.mkOption { + type = lib.types.number; + default = 1.0; + }; + transform = lib.mkOption { + type = lib.types.int; + default = 0; + description = "Screen orientation: 0 = landscape, 1 = portrait left, 2 = portrait right, 3 = landscape flipped"; + }; + enabled = lib.mkOption { + type = lib.types.bool; + default = true; + }; + hdr = lib.mkOption { + type = lib.types.bool; + default = false; + }; + vrr = lib.mkOption { + type = lib.types.bool; + description = "Variable Refresh Rate aka Adaptive Sync aka AMD FreeSync."; + default = false; + }; + }; + } + ); + default = [ ]; + }; + + config = { + assertions = [ + { + assertion = + ((lib.length config.wayming.monitors) != 0) + -> ((lib.length (lib.filter (m: m.primary) config.wayming.monitors)) == 1); + message = "Exactly one monitor must be set to primary."; + } + ]; + }; +} diff --git a/modules/home/wrapper.nix b/modules/home/wrapper.nix new file mode 100644 index 0000000..0161563 --- /dev/null +++ b/modules/home/wrapper.nix @@ -0,0 +1,118 @@ +{ + lib, + config, + pkgs, + ... +}: + +let + cfg = config.wayming.wrappers; + + # Use shared lib function + inherit (lib.wayming) toCliArgs; + + # Function to create a wrapper for an application + mkWrapper = + name: wrapperCfg: + let + # Get the original package + originalPackage = wrapperCfg.package; + + # Create environment variable exports + envExports = lib.concatStringsSep "\n" ( + lib.mapAttrsToList (name: value: "set -x ${name} '${toString value}'") wrapperCfg.environment + ); + + # Convert extraOptions to CLI args + extraArgs = lib.optionalString ( + wrapperCfg.extraOptions != { } + ) "-x \"${toCliArgs wrapperCfg.extraOptions}\""; + + # The wrapper script + wrapperScript = pkgs.writeScriptBin name '' + #!${lib.getExe pkgs.fish} + + # Set additional environment variables + ${envExports} + + # Execute with gamescoperun + exec ${lib.getExe config.wayming.gamescoperun.package} ${extraArgs} ${lib.getExe originalPackage} $argv + ''; + + in + wrapperScript; +in +{ + options.wayming.wrappers = lib.mkOption { + type = lib.types.attrsOf ( + lib.types.submodule ( + { name, ... }: + { + options = { + enable = lib.mkEnableOption "wrapper for this application"; + + package = lib.mkOption { + type = lib.types.package; + description = "The package to wrap"; + }; + + extraOptions = lib.mkOption { + type = + with lib.types; + attrsOf (oneOf [ + str + int + bool + ]); + default = { }; + example = { + "fsr-upscaling" = true; + "force-windows-fullscreen" = true; + "fsr-upscaling-sharpness" = 5; + }; + description = '' + Additional gamescope command-line options for this specific wrapper. + Option names must match gamescope's flags exactly (e.g., "hdr-enabled"). + These will be passed via the -x flag to gamescoperun. + ''; + }; + + environment = lib.mkOption { + type = + with lib.types; + attrsOf (oneOf [ + str + int + ]); + default = { }; + example = { + STEAM_FORCE_DESKTOPUI_SCALING = 1; + STEAM_GAMEPADUI = 1; + }; + description = "Additional environment variables for this specific wrapper"; + }; + + # Readonly package option that exposes the configured wrapper + wrappedPackage = lib.mkOption { + type = lib.types.package; + readOnly = true; + default = mkWrapper name config.wayming.wrappers.${name}; + description = "The configured wrapper package for this application"; + }; + }; + } + ) + ); + default = { }; + description = "Application wrappers that run through gamescoperun"; + }; + + config = { + home.packages = lib.mapAttrsToList mkWrapper ( + lib.filterAttrs (name: wrapperCfg: wrapperCfg.enable) cfg + ); + + # Ensure gamescoperun is enabled if any wrappers are enabled + wayming.gamescoperun.enable = lib.mkIf (lib.length (lib.attrNames cfg) > 0) true; + }; +} diff --git a/modules/nixos/amd.nix b/modules/nixos/amd.nix new file mode 100644 index 0000000..6dae21c --- /dev/null +++ b/modules/nixos/amd.nix @@ -0,0 +1,32 @@ +{ + pkgs, + lib, + config, + ... +}: + +let + cfg = config.wayming.amd; +in +{ + options.wayming.amd = { + enable = lib.mkEnableOption "AMD GPU configuration with LACT (Linux AMD Control Tool)"; + }; + + config = lib.mkIf cfg.enable { + # Enable graphics with 32-bit support for gaming + hardware.graphics = { + enable = lib.mkDefault true; + enable32Bit = lib.mkDefault true; + }; + + # Install LACT for AMD GPU control + environment.systemPackages = with pkgs; [ lact ]; + + # Enable LACT daemon + systemd = { + packages = with pkgs; [ lact ]; + services.lactd.wantedBy = [ "multi-user.target" ]; + }; + }; +} diff --git a/modules/nixos/ananicy.nix b/modules/nixos/ananicy.nix new file mode 100644 index 0000000..9fc0ab0 --- /dev/null +++ b/modules/nixos/ananicy.nix @@ -0,0 +1,47 @@ +{ + pkgs, + lib, + config, + ... +}: + +let + cfg = config.wayming.ananicy; + + # Default extra rules for gaming processes + defaultExtraRules = [ + { + "name" = "gamescope"; + "nice" = -20; + } + ]; + + # Combine defaults with user extras + finalExtraRules = defaultExtraRules ++ cfg.extraRules; +in +{ + options.wayming.ananicy = { + enable = lib.mkEnableOption "ananicy process scheduler optimization"; + + extraRules = lib.mkOption { + type = lib.types.listOf lib.types.attrs; + default = [ ]; + example = [ + { + "name" = "steam"; + "nice" = -10; + } + ]; + description = "Additional ananicy rules for gaming processes (added to defaults)"; + }; + }; + + config = lib.mkIf cfg.enable { + services.ananicy = { + enable = true; + package = lib.mkDefault pkgs.ananicy-cpp; + rulesProvider = lib.mkDefault pkgs.ananicy-cpp; + extraRules = finalExtraRules; + }; + }; +} diff --git a/modules/nixos/default.nix b/modules/nixos/default.nix new file mode 100644 index 0000000..6fbe3ee --- /dev/null +++ b/modules/nixos/default.nix @@ -0,0 +1,9 @@ +{ + imports = [ + ./amd.nix + ./ananicy.nix + ./gamemode.nix + ./lutris.nix + ./steam.nix + ]; +} diff --git a/modules/nixos/gamemode.nix b/modules/nixos/gamemode.nix new file mode 100644 index 0000000..77be221 --- /dev/null +++ b/modules/nixos/gamemode.nix @@ -0,0 +1,43 @@ +{ + pkgs, + lib, + config, + ... +}: + +let + cfg = config.wayming.gamemode; +in +{ + options.wayming.gamemode = { + enable = lib.mkEnableOption "gamemode configuration for gaming optimization"; + + settings = lib.mkOption { + type = lib.types.attrs; + default = { + general = { + softrealtime = "auto"; + inhibit_screensaver = 1; + renice = 15; + }; + gpu = { + apply_gpu_optimisations = "accept-responsibility"; + gpu_device = 1; + amd_performance_level = "high"; + }; + custom = { + start = "${pkgs.libnotify}/bin/notify-send 'GameMode started'"; + end = "${pkgs.libnotify}/bin/notify-send 'GameMode ended'"; + }; + }; + description = "Gamemode configuration settings"; + }; + }; + + config = lib.mkIf cfg.enable { + programs.gamemode = { + enable = true; + settings = cfg.settings; + }; + }; +} diff --git a/modules/nixos/lutris.nix b/modules/nixos/lutris.nix new file mode 100644 index 0000000..2099762 --- /dev/null +++ b/modules/nixos/lutris.nix @@ -0,0 +1,49 @@ +{ + pkgs, + lib, + config, + ... +}: + +let + cfg = config.wayming.lutris; + + # Default extra packages + defaultExtraPkgs = with pkgs; [ + wineWowPackages.waylandFull + winetricks + vulkan-tools + xterm + ]; + + # Create the configured lutris package + configuredLutris = pkgs.lutris.override { + extraPkgs = pkgs: defaultExtraPkgs ++ cfg.extraPkgs; + }; +in +{ + options.wayming.lutris = { + enable = lib.mkEnableOption "Install Lutris game manager"; + + extraPkgs = lib.mkOption { + type = lib.types.listOf lib.types.package; + default = [ ]; + example = with pkgs; [ + mangohud + gamemode + ]; + description = "Additional extra packages for Lutris runtime (added to defaults)"; + }; + + package = lib.mkOption { + type = lib.types.package; + readOnly = true; + default = configuredLutris; + description = "The configured Lutris package with extra packages"; + }; + }; + + config = lib.mkIf cfg.enable { + environment.systemPackages = [ cfg.package ]; + }; +} diff --git a/modules/nixos/steam.nix b/modules/nixos/steam.nix new file mode 100644 index 0000000..11aba95 --- /dev/null +++ b/modules/nixos/steam.nix @@ -0,0 +1,85 @@ +{ + pkgs, + lib, + config, + ... +}: + +let + cfg = config.wayming.steam; + + # Default compatibility packages + defaultCompatPackages = with pkgs; [ + proton-ge-custom + proton-cachyos + ]; + + # Combine defaults with user extras + finalCompatPackages = defaultCompatPackages ++ cfg.extraCompatPackages; + + # Create the configured steam package + configuredSteam = pkgs.steam.override { + extraPkgs = cfg.extraPkgs; + }; +in +{ + options.wayming.steam = { + enable = lib.mkEnableOption "Steam with gaming optimizations"; + + extraCompatPackages = lib.mkOption { + type = lib.types.listOf lib.types.package; + default = [ ]; + example = with pkgs; [ proton-ge-bin ]; + description = "Additional Proton compatibility packages to add to the defaults"; + }; + + extraPkgs = lib.mkOption { + type = lib.types.functionTo (lib.types.listOf lib.types.package); + default = + pkgs: with pkgs; [ + # X11 libraries + xorg.libXcursor + xorg.libXi + xorg.libXinerama + xorg.libXScrnSaver + + # System libraries + stdenv.cc.cc.lib + gamemode + gperftools + keyutils + libkrb5 + libpng + libpulseaudio + libvorbis + mangohud + ]; + description = "Extra packages to include in Steam's runtime"; + }; + + package = lib.mkOption { + type = lib.types.package; + readOnly = true; + default = configuredSteam; + description = "The configured Steam package with extra packages"; + }; + }; + + config = lib.mkIf cfg.enable { + programs.steam = { + enable = true; + remotePlay.openFirewall = lib.mkDefault false; + dedicatedServer.openFirewall = lib.mkDefault false; + + protontricks = { + enable = lib.mkDefault true; + package = lib.mkDefault pkgs.protontricks; + }; + + package = lib.mkDefault cfg.package; + + # Use the combined list of default + user extras + extraCompatPackages = finalCompatPackages; + }; + }; +} diff --git a/pkgs/proton-cachyos/package.nix b/pkgs/proton-cachyos/package.nix new file mode 100644 index 0000000..8a80cad --- /dev/null +++ b/pkgs/proton-cachyos/package.nix @@ -0,0 +1,47 @@ +{ + callPackage, + stdenv, + lib, + fetchurl, +}: +let + protonGeTitle = "Proton-CachyOS"; + protonGeVersions = lib.importJSON ./versions.json; +in +stdenv.mkDerivation { + name = "proton-cachyos"; + version = "${protonGeVersions.base}.${protonGeVersions.release}"; + + src = + let + tagName = "cachyos-${protonGeVersions.base}-${protonGeVersions.release}-slr"; + fileName = "proton-cachyos-${protonGeVersions.base}-${protonGeVersions.release}-slr-x86_64.tar.xz"; + in + fetchurl { + url = "https://github.com/CachyOS/proton-cachyos/releases/download/${tagName}/${fileName}"; + inherit (protonGeVersions) hash; + }; + + buildCommand = + '' + mkdir -p $out/bin + tar -C $out/bin --strip=1 -x -f $src + '' + # Replace the internal name and display name + + lib.strings.optionalString (protonGeTitle != null) '' + sed -i -r 's|"proton-cachyos-[^"]*"|"${protonGeTitle}"|g' $out/bin/compatibilitytool.vdf + sed -i -r 's|"display_name"[[:space:]]*"[^"]*"|"display_name" "${protonGeTitle}"|' $out/bin/compatibilitytool.vdf + ''; + + passthru.updateScript = callPackage ./update.nix { }; + + meta = with lib; { + description = "Compatibility tool for Steam Play based on Wine and additional components. CachyOS fork."; + homepage = "https://github.com/CachyOS/proton-cachyos"; + license = licenses.bsd3; + platforms = [ "x86_64-linux" ]; + maintainers = with maintainers; [ + tophc7 + ]; + }; +} diff --git a/pkgs/proton-cachyos/update.nix b/pkgs/proton-cachyos/update.nix new file mode 100644 index 0000000..c085f2d --- /dev/null +++ b/pkgs/proton-cachyos/update.nix @@ -0,0 +1,55 @@ +{ + writeShellScript, + lib, + coreutils, + findutils, + gnugrep, + curl, + jq, + git, + nix, + nix-prefetch-git, + moreutils, + yq, +}: +let + path = lib.makeBinPath [ + coreutils + curl + findutils + gnugrep + jq + moreutils + git + nix-prefetch-git + nix + yq + ]; +in +writeShellScript "update-proton-cachyos" '' + set -euo pipefail + PATH=${path} + + srcJson=pkgs/proton-cachyos/versions.json + localBase=$(jq -r .base < $srcJson) + localRelease=$(jq -r .release < $srcJson) + + latestVer=$(curl 'https://github.com/GloriousEggroll/proton-cachyos/tags.atom' | xq -r '.feed.entry[0].link."@href"' | grep -Po '(?<=/)[^/]+$') + + if [ "GE-Proton''${localBase}-''${localRelease}" == "$latestVer" ]; then + exit 0 + fi + + latestBase=$(echo $latestVer | grep -Po '(?<=GE-Proton)[^-]+') + latestRelease=$(echo $latestVer | grep -Po '(?<=-)[^-]+$') + latestSha256=$(nix-prefetch-url --type sha256 "https://github.com/CachyOS/proton-cachyos/releases/download/''${latestVer}/''${latestVer}.tar.gz") + latestHash=$(nix-hash --to-sri --type sha256 $latestSha256) + + jq \ + --arg latestBase "$latestBase" --arg latestRelease "$latestRelease" --arg latestHash "$latestHash" \ + '.base = $latestBase | .release = $latestRelease | .hash = $latestHash' \ + "$srcJson" | sponge "$srcJson" + + git add $srcJson + git commit -m "proton-cachyos: ''${localBase}.''${localRelease} -> ''${latestBase}.''${latestRelease}" +'' diff --git a/pkgs/proton-cachyos/versions.json b/pkgs/proton-cachyos/versions.json new file mode 100644 index 0000000..9417a13 --- /dev/null +++ b/pkgs/proton-cachyos/versions.json @@ -0,0 +1,5 @@ +{ + "base": "10.0", + "release": "20250623", + "hash": "sha256-HLWWR2h+AdGfqt3Aay9Xyz7Q06NRoi3/Mp5vJtoqLlg=" +} \ No newline at end of file