play.nix/modules/home/wrappers.nix

142 lines
4.5 KiB
Nix

{
lib,
config,
pkgs,
...
}:
let
# Extend lib with play utilities
playLib = import ../../lib { inherit lib; };
cfg = config.play.wrappers;
# Use shared lib function
inherit (playLib) toCliArgs;
# Function to create a wrapper for an application
mkWrapper =
name: wrapperCfg:
let
# Determine the command to execute
baseCommand =
if wrapperCfg.command != null then wrapperCfg.command else lib.getExe wrapperCfg.package;
# Convert wrapper-specific environment to a single string for gamescoperun
gamescopeWrapperEnv = lib.optionalString (wrapperCfg.environment != { }) (
let
envList = lib.mapAttrsToList (name: value: "${name}=${toString value}") wrapperCfg.environment;
in
# Set as a global exported variable so gamescoperun can see it
"set -gx GAMESCOPE_WRAPPER_ENV '${lib.concatStringsSep ";" envList}'"
);
# Convert extraOptions to CLI args
extraArgs = lib.optionalString (
wrapperCfg.extraOptions != { }
) "-x \"${toCliArgs wrapperCfg.extraOptions}\"";
wrapperScript = pkgs.writeScriptBin name ''
#!${lib.getExe pkgs.fish}
# Set environment for gamescoperun to consume
${gamescopeWrapperEnv}
# Execute with gamescoperun
exec ${lib.getExe config.play.gamescoperun.package} ${extraArgs} ${baseCommand} $argv
'';
in
wrapperScript;
in
{
options.play.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.nullOr lib.types.package;
default = null;
description = "The package to wrap (used with lib.getExe if command is not specified)";
};
command = lib.mkOption {
type = lib.types.nullOr lib.types.str;
default = null;
example = "\${lib.getExe osConfig.programs.steam.package} -tenfoot -bigpicture";
description = ''
The exact command to execute after gamescoperun and its options.
If specified, this takes precedence over the package option.
Can include arguments and flags.
'';
};
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.play.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
play.gamescoperun.enable = lib.mkIf (lib.length (lib.attrNames cfg) > 0) true;
assertions = lib.mapAttrsToList (name: wrapperCfg: {
assertion = wrapperCfg.enable -> (wrapperCfg.command != null || wrapperCfg.package != null);
message = "play.wrappers.${name}: Either 'command' or 'package' must be specified when enabled";
}) cfg;
};
}