diff --git a/apps/ghostty/default.nix b/apps/ghostty/default.nix index bde2170..29fa7d6 100644 --- a/apps/ghostty/default.nix +++ b/apps/ghostty/default.nix @@ -74,7 +74,7 @@ ]; wayland.windowManager.hyprland.settings.exec-once = [ - "[workspace special:preload silent] uwsm app -- xdg-terminal-exec" "[workspace 1] uwsm app -- ghostty -e bash -c 'fastfetch; exec $SHELL'" # TODO: must be xdg-terminal-exec, or default user terminal + "[workspace special:preload silent] uwsm app -- xdg-terminal-exec" ]; } diff --git a/apps/hyprland/scripts/workspace-toggle.nix b/apps/hyprland/scripts/workspace-toggle.nix index bd0f2dc..5350570 100644 --- a/apps/hyprland/scripts/workspace-toggle.nix +++ b/apps/hyprland/scripts/workspace-toggle.nix @@ -3,20 +3,23 @@ #!/usr/bin/env bash target_workspace="$1" - # Get current workspace info - current_info=$(${pkgs.hyprland}/bin/hyprctl activeworkspace -j) - current=$(echo "$current_info" | ${pkgs.jq}/bin/jq -r '.id') + # activeworkspace always returns the underlying workspace, even when a special + # workspace is open. Check the monitor's specialWorkspace field instead. + special=$(${pkgs.hyprland}/bin/hyprctl monitors -j | ${pkgs.jq}/bin/jq -r '.[] | select(.focused) | .specialWorkspace.name') - # Check if we're in a special workspace (negative ID) - if [[ $current -lt 0 ]]; then - # We're in a special workspace, force switch to target workspace - ${pkgs.hyprland}/bin/hyprctl dispatch focusworkspaceoncurrentmonitor "$target_workspace" - elif [[ $current -eq $target_workspace ]]; then - # We're already on the target workspace, toggle back to previous - ${pkgs.hyprland}/bin/hyprctl dispatch workspace previous + if [[ -n "$special" ]]; then + ${pkgs.hyprland}/bin/hyprctl dispatch togglespecialworkspace "''${special#special:}" + current=$(${pkgs.hyprland}/bin/hyprctl activeworkspace -j | ${pkgs.jq}/bin/jq -r '.id') + if [[ $current -ne $target_workspace ]]; then + ${pkgs.hyprland}/bin/hyprctl dispatch focusworkspaceoncurrentmonitor "$target_workspace" + fi else - # We're on a different normal workspace, switch to target using split:workspace - ${pkgs.hyprland}/bin/hyprctl dispatch split:workspace "$target_workspace" + current=$(${pkgs.hyprland}/bin/hyprctl activeworkspace -j | ${pkgs.jq}/bin/jq -r '.id') + if [[ $current -eq $target_workspace ]]; then + ${pkgs.hyprland}/bin/hyprctl dispatch workspace previous + else + ${pkgs.hyprland}/bin/hyprctl dispatch split:workspace "$target_workspace" + fi fi ''; in { diff --git a/modules/home/gaming.nix b/modules/home/gaming.nix index 0206c8a..ecc78ee 100644 --- a/modules/home/gaming.nix +++ b/modules/home/gaming.nix @@ -4,15 +4,62 @@ 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 + ${pkgs.hyprland}/bin/hyprctl --batch "dispatch focusmonitor ${gamingMonitor};dispatch togglespecialworkspace ''${special#special:};dispatch focusmonitor ${gamingMonitor};dispatch workspace name:gaming" + 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 + ${pkgs.hyprland}/bin/hyprctl --batch "dispatch focusmonitor ${gamingMonitor};dispatch togglespecialworkspace ''${special#special:};dispatch focusmonitor ${gamingMonitor};dispatch workspace name:gaming" + 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" + # "Deadlock" + # "project8" + # "citadel" ]; mkGameRules = selector: [ - "monitor 0, ${selector}" + "monitor ${gamingMonitor}, ${selector}" "fullscreen, ${selector}" "immediate, ${selector}" "tile, ${selector}" @@ -30,6 +77,8 @@ in { protonup-qt protontricks mangohud + gaming-focus + game-focus-watcher # via ]; @@ -47,17 +96,18 @@ in { wayland.windowManager.hyprland.settings = { workspace = [ - "name:gaming, monitor:0, default:true" + "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, workspace, name:gaming" + "SUPER, G, Switch to Gaming Workspace, exec, gaming-focus" "SUPER SHIFT, G, Move to Gaming Workspace, movetoworkspace, name:gaming" ]; @@ -71,16 +121,19 @@ in { "float, class:^(steam)$" # Suppress focus stealing from dialogs/etc. "noinitialfocus, class:^(steam)$" - "suppressevent activate, 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)$" - "workspace special:steam, 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+)$"