Files
nix-config-ryan4yin/modules/nixos/desktop/gaming.nix
T
ryan4yin | 二花 b726c28c14 feat(nixpak): sandbox Steam and Heroic in ~/Gaming (#520)
* feat(nixpak): sandbox Steam and Heroic in ~/Gaming

idols-ai stops preserving ~/.local/share/Steam, ~/.steam, ~/.config/heroic
and their game data, which must be moved into ~/Gaming before deploying.

Assisted-by: DeepSeek Harness (deepseek-flash)

* refactor(nixpak): replace steam-fix.nu with sandboxed protontricks

Assisted-by: DeepSeek Harness (claude-opus-5-5)

* fix(nixpak): hide host documents and GTK config from game sandboxes

Assisted-by: DeepSeek Harness (claude-opus-5-5)

* docs(ai): locate the Steam library under ~/Gaming

Assisted-by: DeepSeek Harness (claude-opus-5-5)

* fix(umu-game): confine writes to ~/Gaming and keep foreign entries

Assisted-by: DeepSeek Harness (claude-opus-5-5)

* fix(nixpak): keep Steam and Heroic in the application launcher

The wrappers only carried bin/, so neither launcher had a desktop entry
after replacing programs.steam.

Assisted-by: DeepSeek Harness (deepseek-flash)

* fix(nixpak): give the game sandboxes a fontconfig that sees CJK fonts

Steam rendered Chinese as tofu: the FHS layers build their own /etc/fonts,
whose config lists only DejaVu and the XDG font directory, so neither the
host fonts nor the FHS /usr/share/fonts were ever scanned. Point
FONTCONFIG_FILE at a small config that includes /usr/share/fonts, and ship
WenQuanYi Zen Hei and Noto Sans CJK in all three FHS layers.

Assisted-by: DeepSeek Harness (deepseek-flash)

* fix(nixpak): grant the game launchers a StatusNotifier tray icon

Steam and Heroic register a tray icon over D-Bus, and the game sandboxes
granted no StatusNotifierWatcher access, so the desktop shell had nothing
to show for them. Enable desktopIntegration.tray for all three launchers
and teach the policy suites to expect it.

Assisted-by: DeepSeek Harness (deepseek-flash)

* fix(nixpak): drop the duplicate host CJK font and tidy the font overrides

Steam reads the fonts inside its own FHS layer, so the system-wide wqy_zenhei
added for it no longer serves any sandbox. Give gaming-policy its own comment
for FONTCONFIG_FILE instead of leaving it under the compat-tool one, make the
desktop-base value a default, and note the SNI tray grant in the README table.

Assisted-by: DeepSeek Harness (deepseek-flash)
2026-11-06 02:18:33 -06:00

113 lines
4.1 KiB
Nix

{
pkgs,
config,
lib,
...
}:
with lib;
let
cfg = config.modules.desktop.gaming;
in
{
options.modules.desktop = {
gaming = {
enable = mkEnableOption "desktop gaming support";
};
};
config = mkIf cfg.enable {
# ==========================================================================
# Gaming on Linux
#
# <https://www.protondb.com/> can give you an idea what works where and how.
# Begineer Guide: <https://www.reddit.com/r/linux_gaming/wiki/faq/>
# ==========================================================================
# Steam runs in a nixpak sandbox (hardening/nixpaks/steam.nix) that the
# home manager installs, so `programs.steam` is off: its `package` option
# calls `.override` on whatever it is given, which a sandbox script cannot
# answer, and it would install a second, unsandboxed steam plus `steam-run`.
# What is kept from it: 32-bit graphics, which the game sandboxes mount as
# /run/opengl-driver-32, and the controller/VR udev rules. Dropped with it:
# `extest` (needs /dev/uinput, which no sandbox gets) and the host
# protontricks (it would run Wine on game-writable prefixes; the umu
# sandbox carries one instead).
hardware.graphics.enable32Bit = true;
hardware.steam-hardware.enable = true;
# Expose DW-Proton to umu-run: umu ignores Steam's
# STEAM_EXTRA_COMPAT_TOOLS_PATHS, and a store path baked into a generated
# launcher goes stale on every dwproton-bin update. The nix-config-umu-game
# installer reads this on the host; the umu sandbox
# (hardening/nixpaks/umu.nix) sets the same path for the run/exec scripts.
environment.variables.UMU_PROTONPATH = "${pkgs.dwproton-bin.steamcompattool}";
# GameScope: run a game in its own nested compositor and let it renice
# itself for steadier frame pacing. GPU-specific PRIME render offload belongs
# to the host, not here (see programs.gamescope.env in
# hosts/idols-ai/hardware-nvidia.nix).
# https://github.com/ValveSoftware/gamescope
programs.gamescope = {
enable = true;
capSysNice = true;
};
# The sysctls SteamOS ships in steamos-customizations-jupiter (file names
# below), as carried by nix-gaming's platformOptimizations module.
boot.kernel.sysctl = {
# 20-shed.conf
"kernel.sched_cfs_bandwidth_slice_us" = 3000;
# 20-net-timeout.conf: lets a game that was killed and restarted quickly
# rebind its TCP ports.
"net.ipv4.tcp_fin_timeout" = 5;
# 30-splitlock.conf: no deliberate slowdown when a game hits a split lock.
"kernel.split_lock_mitigate" = 0;
# 30-vm.conf: MAX_INT - MAPCOUNT_ELF_CORE_MARGIN, see include/linux/mm.h.
"vm.max_map_count" = 2147483642;
};
# Low-latency PipeWire: a 64/48000 minimum quantum plus realtime scheduling,
# as in nix-gaming's pipewireLowLatency module.
services.pipewire.extraConfig = {
pipewire."99-lowlatency" = {
"context.properties"."default.clock.min-quantum" = 64;
"context.modules" = [
{
name = "libpipewire-module-rt";
flags = [
"ifexists"
"nofail"
];
args = {
"nice.level" = -15;
"rt.prio" = 88;
"rt.time.soft" = 200000;
"rt.time.hard" = 200000;
};
}
];
};
pipewire-pulse."99-lowlatency"."pulse.properties" = {
"server.address" = [ "unix:native" ];
"pulse.min.req" = "64/48000";
"pulse.min.quantum" = "64/48000";
"pulse.min.frag" = "64/48000";
};
client."99-lowlatency"."stream.properties" = {
"node.latency" = "64/48000";
"resample.quality" = 1;
};
};
# Optimise Linux system performance on demand
# https://github.com/FeralInteractive/GameMode
# https://wiki.archlinux.org/title/Gamemode
#
# Usage:
# 1. For games/launchers which integrate GameMode support:
# https://github.com/FeralInteractive/GameMode#apps-with-gamemode-integration
# simply running the game will automatically activate GameMode.
programs.gamemode.enable = true;
};
}