1{ config, lib, pkgs, ... }:
2
3let
4 cfg = config.programs.steam;
5 gamescopeCfg = config.programs.gamescope;
6
7 steam-gamescope = let
8 exports = builtins.attrValues (builtins.mapAttrs (n: v: "export ${n}=${v}") cfg.gamescopeSession.env);
9 in
10 pkgs.writeShellScriptBin "steam-gamescope" ''
11 ${builtins.concatStringsSep "\n" exports}
12 gamescope --steam ${builtins.toString cfg.gamescopeSession.args} -- steam -tenfoot -pipewire-dmabuf
13 '';
14
15 gamescopeSessionFile =
16 (pkgs.writeTextDir "share/wayland-sessions/steam.desktop" ''
17 [Desktop Entry]
18 Name=Steam
19 Comment=A digital distribution platform
20 Exec=${steam-gamescope}/bin/steam-gamescope
21 Type=Application
22 '').overrideAttrs (_: { passthru.providedSessions = [ "steam" ]; });
23in {
24 options.programs.steam = {
25 enable = lib.mkEnableOption "steam";
26
27 package = lib.mkOption {
28 type = lib.types.package;
29 default = pkgs.steam;
30 defaultText = lib.literalExpression "pkgs.steam";
31 example = lib.literalExpression ''
32 pkgs.steam-small.override {
33 extraEnv = {
34 MANGOHUD = true;
35 OBS_VKCAPTURE = true;
36 RADV_TEX_ANISO = 16;
37 };
38 extraLibraries = p: with p; [
39 atk
40 ];
41 }
42 '';
43 apply = steam: steam.override (prev: {
44 extraEnv = (lib.optionalAttrs (cfg.extraCompatPackages != [ ]) {
45 STEAM_EXTRA_COMPAT_TOOLS_PATHS = lib.makeSearchPathOutput "steamcompattool" "" cfg.extraCompatPackages;
46 }) // (lib.optionalAttrs cfg.extest.enable {
47 LD_PRELOAD = "${pkgs.pkgsi686Linux.extest}/lib/libextest.so";
48 }) // (prev.extraEnv or {});
49 extraLibraries = pkgs: let
50 prevLibs = if prev ? extraLibraries then prev.extraLibraries pkgs else [ ];
51 additionalLibs = with config.hardware.opengl;
52 if pkgs.stdenv.hostPlatform.is64bit
53 then [ package ] ++ extraPackages
54 else [ package32 ] ++ extraPackages32;
55 in prevLibs ++ additionalLibs;
56 } // lib.optionalAttrs (cfg.gamescopeSession.enable && gamescopeCfg.capSysNice)
57 {
58 buildFHSEnv = pkgs.buildFHSEnv.override {
59 # use the setuid wrapped bubblewrap
60 bubblewrap = "${config.security.wrapperDir}/..";
61 };
62 });
63 description = ''
64 The Steam package to use. Additional libraries are added from the system
65 configuration to ensure graphics work properly.
66
67 Use this option to customise the Steam package rather than adding your
68 custom Steam to {option}`environment.systemPackages` yourself.
69 '';
70 };
71
72 extraCompatPackages = lib.mkOption {
73 type = lib.types.listOf lib.types.package;
74 default = [ ];
75 example = lib.literalExpression ''
76 with pkgs; [
77 proton-ge-bin
78 ]
79 '';
80 description = ''
81 Extra packages to be used as compatibility tools for Steam on Linux. Packages will be included
82 in the `STEAM_EXTRA_COMPAT_TOOLS_PATHS` environmental variable. For more information see
83 https://github.com/ValveSoftware/steam-for-linux/issues/6310.
84
85 These packages must be Steam compatibility tools that have a `steamcompattool` output.
86 '';
87 };
88
89 remotePlay.openFirewall = lib.mkOption {
90 type = lib.types.bool;
91 default = false;
92 description = ''
93 Open ports in the firewall for Steam Remote Play.
94 '';
95 };
96
97 dedicatedServer.openFirewall = lib.mkOption {
98 type = lib.types.bool;
99 default = false;
100 description = ''
101 Open ports in the firewall for Source Dedicated Server.
102 '';
103 };
104
105 localNetworkGameTransfers.openFirewall = lib.mkOption {
106 type = lib.types.bool;
107 default = false;
108 description = ''
109 Open ports in the firewall for Steam Local Network Game Transfers.
110 '';
111 };
112
113 gamescopeSession = lib.mkOption {
114 description = "Run a GameScope driven Steam session from your display-manager";
115 default = {};
116 type = lib.types.submodule {
117 options = {
118 enable = lib.mkEnableOption "GameScope Session";
119 args = lib.mkOption {
120 type = lib.types.listOf lib.types.str;
121 default = [ ];
122 description = ''
123 Arguments to be passed to GameScope for the session.
124 '';
125 };
126
127 env = lib.mkOption {
128 type = lib.types.attrsOf lib.types.str;
129 default = { };
130 description = ''
131 Environmental variables to be passed to GameScope for the session.
132 '';
133 };
134 };
135 };
136 };
137
138 extest.enable = lib.mkEnableOption ''
139 Load the extest library into Steam, to translate X11 input events to
140 uinput events (e.g. for using Steam Input on Wayland)
141 '';
142 };
143
144 config = lib.mkIf cfg.enable {
145 hardware.opengl = { # this fixes the "glXChooseVisual failed" bug, context: https://github.com/NixOS/nixpkgs/issues/47932
146 enable = true;
147 driSupport = true;
148 driSupport32Bit = true;
149 };
150
151 security.wrappers = lib.mkIf (cfg.gamescopeSession.enable && gamescopeCfg.capSysNice) {
152 # needed or steam fails
153 bwrap = {
154 owner = "root";
155 group = "root";
156 source = "${pkgs.bubblewrap}/bin/bwrap";
157 setuid = true;
158 };
159 };
160
161 programs.gamescope.enable = lib.mkDefault cfg.gamescopeSession.enable;
162 services.displayManager.sessionPackages = lib.mkIf cfg.gamescopeSession.enable [ gamescopeSessionFile ];
163
164 # optionally enable 32bit pulseaudio support if pulseaudio is enabled
165 hardware.pulseaudio.support32Bit = config.hardware.pulseaudio.enable;
166
167 hardware.steam-hardware.enable = true;
168
169 environment.systemPackages = [
170 cfg.package
171 cfg.package.run
172 ] ++ lib.optional cfg.gamescopeSession.enable steam-gamescope;
173
174 networking.firewall = lib.mkMerge [
175 (lib.mkIf (cfg.remotePlay.openFirewall || cfg.localNetworkGameTransfers.openFirewall) {
176 allowedUDPPorts = [ 27036 ]; # Peer discovery
177 })
178
179 (lib.mkIf cfg.remotePlay.openFirewall {
180 allowedTCPPorts = [ 27036 ];
181 allowedUDPPortRanges = [ { from = 27031; to = 27035; } ];
182 })
183
184 (lib.mkIf cfg.dedicatedServer.openFirewall {
185 allowedTCPPorts = [ 27015 ]; # SRCDS Rcon port
186 allowedUDPPorts = [ 27015 ]; # Gameplay traffic
187 })
188
189 (lib.mkIf cfg.localNetworkGameTransfers.openFirewall {
190 allowedTCPPorts = [ 27040 ]; # Data transfers
191 })
192 ];
193 };
194
195 meta.maintainers = lib.teams.steam.members;
196}