{ pkgs, lib, myConfig, ... }: let gamingMonitor = "DP-1"; gaming-focus = pkgs.writeShellScriptBin "gaming-focus" '' # If already on gaming workspace on the gaming monitor, go back current=$(${pkgs.hyprland}/bin/hyprctl activeworkspace -j | ${pkgs.jq}/bin/jq -r '.name') focused=$(${pkgs.hyprland}/bin/hyprctl monitors -j | ${pkgs.jq}/bin/jq -r '.[] | select(.focused) | .name') if [[ "$current" == "gaming" && "$focused" == "${gamingMonitor}" ]]; then ${pkgs.hyprland}/bin/hyprctl dispatch workspace previous exit 0 fi # Close special workspace on gaming monitor if open special=$(${pkgs.hyprland}/bin/hyprctl monitors -j | ${pkgs.jq}/bin/jq -r '.[] | select(.name == "${gamingMonitor}") | .specialWorkspace.name') if [[ -n "$special" ]]; then # Gaming workspace is already underneath — just close the special overlay ${pkgs.hyprland}/bin/hyprctl --batch "dispatch focusmonitor ${gamingMonitor};dispatch togglespecialworkspace ''${special#special:}" else ${pkgs.hyprland}/bin/hyprctl --batch "dispatch focusmonitor ${gamingMonitor};dispatch workspace name:gaming" fi ''; game-focus-watcher = pkgs.writeShellScriptBin "game-focus-watcher" '' handle() { case $1 in openwindow*) data="''${1#openwindow>>}" class=$(echo "$data" | cut -d',' -f3) if [[ "$class" =~ ^steam_app_[0-9]+ ]] || \ [[ "$class" == "gamescope" ]] || \ [[ "$class" =~ ^wine- ]] || \ [[ "$class" == "lutris" ]] || \ [[ "$class" == "heroic" ]]; then special=$(${pkgs.hyprland}/bin/hyprctl monitors -j | ${pkgs.jq}/bin/jq -r '.[] | select(.name == "${gamingMonitor}") | .specialWorkspace.name') if [[ -n "$special" ]]; then # Gaming workspace is already underneath — just close the special overlay ${pkgs.hyprland}/bin/hyprctl --batch "dispatch focusmonitor ${gamingMonitor};dispatch togglespecialworkspace ''${special#special:}" else ${pkgs.hyprland}/bin/hyprctl --batch "dispatch focusmonitor ${gamingMonitor};dispatch workspace name:gaming" fi fi ;; esac } ${pkgs.socat}/bin/socat - "UNIX-CONNECT:$XDG_RUNTIME_DIR/hypr/$HYPRLAND_INSTANCE_SIGNATURE/.socket2.sock" | while read -r line; do handle "$line" done ''; # Games that should have `stayfocused` applied (to avoid multi-monitor focus issues) stayFocusedGames = [ # "Deadlock" # "project8" # "citadel" ]; mkGameRules = selector: [ "monitor ${gamingMonitor}, ${selector}" "fullscreen, ${selector}" "immediate, ${selector}" "tile, ${selector}" ]; stayFocusedRules = lib.flatten (map ( game: [ "stayfocused, title:^(${game})$" "stayfocused, class:^(${game})$" ] ) stayFocusedGames); in { home.packages = with pkgs; [ protonup-qt protontricks mangohud gaming-focus game-focus-watcher # via ]; programs.mangohud = { enable = true; settings = { full = true; no_display = true; # Don't show by default (toggle with Shift+F12) cpu_temp = true; gpu_temp = true; ram = true; vram = true; }; }; wayland.windowManager.hyprland.settings = { workspace = [ "name:gaming, monitor:${gamingMonitor}, default:true" ]; exec-once = [ "[workspace special:steam silent] uwsm app -- steam" "game-focus-watcher" ]; bindd = [ "SUPER, A, Toggle Steam, togglespecialworkspace, steam" "SUPER SHIFT, A, Move to Steam Special Workspace, movetoworkspace, special:steam" "SUPER, G, Switch to Gaming Workspace, exec, gaming-focus" "SUPER SHIFT, G, Move to Gaming Workspace, movetoworkspace, name:gaming" ]; windowrulev2 = [ "plugin:hyprbars:nobar, class:^(steam)$" "plugin:hyprbars:nobar, class:^(steam_app_\\d+)$" # --- STEAM GENERAL RULES --- # Default ALL steam class windows to float. This catches all dialogs & properties windows. "float, class:^(steam)$" # Suppress focus stealing from dialogs/etc. "noinitialfocus, class:^(steam)$" "suppressevent activate fullscreen maximize, class:^(steam)$" # --- STEAM CLIENT OVERRIDE --- # Override the float for the main Steam client, tile it, and move it to the special workspace. "tile, class:^(steam)$, title:^(Steam)$" # All steam class windows go to special:steam (dialogs, store, friends, etc.) # Game overrides below take precedence for actual games. "workspace special:steam, class:^(steam)$" # --- STEAM GAME OVERRIDES --- # Override the float for actual games and move them to the gaming workspace. # 1. Auto-detected steam_app games (like Deadlock). "monitor ${gamingMonitor}, class:^(steam_app_\\d+)$" "tile, class:^(steam_app_\\d+)$" "fullscreen, class:^(steam_app_\\d+)$" "immediate, class:^(steam_app_\\d+)$" "workspace name:gaming, class:^(steam_app_\\d+)$" # 2. Behavior-detected manual games (e.g. ARC Raiders). # This moves any steam game to the gaming workspace and tiles it when it becomes fullscreen. "tile, class:^(steam)$, fullscreen:1" "workspace name:gaming, class:^(steam)$, fullscreen:1" ] # Other auto-detected non-steam games ++ (mkGameRules "class:^(gamescope)$") ++ (mkGameRules "class:^(lutris)$") ++ (mkGameRules "class:^(heroic)$") ++ (mkGameRules "class:^wine-.*$") ++ (mkGameRules "title:^Wine .*$") ++ (mkGameRules "initialTitle:^(?i)godot.*$") # ++ [ # "monitor 0, initialTitle:^(?i)godot.*$" # "fullscreen, initialTitle:^(?i)godot.*$" # "tile, initialTitle:^(?i)godot.*$" # ] # Stayfocused rules ++ stayFocusedRules; }; }