fix(hypr): reliable gaming workspace focus across monitors
- Add game-focus-watcher: listens on hyprland socket, auto-focuses gaming workspace when a game opens, closing any special workspace on DP-1 first via --batch to avoid async dispatch races - Add gaming-focus script: SUPER+G now handles special workspaces on DP-1 regardless of focused monitor, with toggle-back behavior - Fix steam windowrule to send all class:steam windows to special:steam, not just title:Steam, preventing dialogs leaking to normal workspaces - Fix monitor 0 -> monitor DP-1 in mkGameRules and steam_app rules so games always launch at correct resolution on the gaming monitor - Extract gamingMonitor variable as single source of truth
This commit is contained in:
@@ -74,7 +74,7 @@
|
|||||||
];
|
];
|
||||||
|
|
||||||
wayland.windowManager.hyprland.settings.exec-once = [
|
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 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"
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,20 +3,23 @@
|
|||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
target_workspace="$1"
|
target_workspace="$1"
|
||||||
|
|
||||||
# Get current workspace info
|
# activeworkspace always returns the underlying workspace, even when a special
|
||||||
current_info=$(${pkgs.hyprland}/bin/hyprctl activeworkspace -j)
|
# workspace is open. Check the monitor's specialWorkspace field instead.
|
||||||
current=$(echo "$current_info" | ${pkgs.jq}/bin/jq -r '.id')
|
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 [[ -n "$special" ]]; then
|
||||||
if [[ $current -lt 0 ]]; then
|
${pkgs.hyprland}/bin/hyprctl dispatch togglespecialworkspace "''${special#special:}"
|
||||||
# We're in a special workspace, force switch to target workspace
|
current=$(${pkgs.hyprland}/bin/hyprctl activeworkspace -j | ${pkgs.jq}/bin/jq -r '.id')
|
||||||
${pkgs.hyprland}/bin/hyprctl dispatch focusworkspaceoncurrentmonitor "$target_workspace"
|
if [[ $current -ne $target_workspace ]]; then
|
||||||
elif [[ $current -eq $target_workspace ]]; then
|
${pkgs.hyprland}/bin/hyprctl dispatch focusworkspaceoncurrentmonitor "$target_workspace"
|
||||||
# We're already on the target workspace, toggle back to previous
|
fi
|
||||||
${pkgs.hyprland}/bin/hyprctl dispatch workspace previous
|
|
||||||
else
|
else
|
||||||
# We're on a different normal workspace, switch to target using split:workspace
|
current=$(${pkgs.hyprland}/bin/hyprctl activeworkspace -j | ${pkgs.jq}/bin/jq -r '.id')
|
||||||
${pkgs.hyprland}/bin/hyprctl dispatch split:workspace "$target_workspace"
|
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
|
fi
|
||||||
'';
|
'';
|
||||||
in {
|
in {
|
||||||
|
|||||||
+61
-8
@@ -4,15 +4,62 @@
|
|||||||
myConfig,
|
myConfig,
|
||||||
...
|
...
|
||||||
}: let
|
}: 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)
|
# Games that should have `stayfocused` applied (to avoid multi-monitor focus issues)
|
||||||
stayFocusedGames = [
|
stayFocusedGames = [
|
||||||
"Deadlock"
|
# "Deadlock"
|
||||||
"project8"
|
# "project8"
|
||||||
"citadel"
|
# "citadel"
|
||||||
];
|
];
|
||||||
|
|
||||||
mkGameRules = selector: [
|
mkGameRules = selector: [
|
||||||
"monitor 0, ${selector}"
|
"monitor ${gamingMonitor}, ${selector}"
|
||||||
"fullscreen, ${selector}"
|
"fullscreen, ${selector}"
|
||||||
"immediate, ${selector}"
|
"immediate, ${selector}"
|
||||||
"tile, ${selector}"
|
"tile, ${selector}"
|
||||||
@@ -30,6 +77,8 @@ in {
|
|||||||
protonup-qt
|
protonup-qt
|
||||||
protontricks
|
protontricks
|
||||||
mangohud
|
mangohud
|
||||||
|
gaming-focus
|
||||||
|
game-focus-watcher
|
||||||
# via
|
# via
|
||||||
];
|
];
|
||||||
|
|
||||||
@@ -47,17 +96,18 @@ in {
|
|||||||
|
|
||||||
wayland.windowManager.hyprland.settings = {
|
wayland.windowManager.hyprland.settings = {
|
||||||
workspace = [
|
workspace = [
|
||||||
"name:gaming, monitor:0, default:true"
|
"name:gaming, monitor:${gamingMonitor}, default:true"
|
||||||
];
|
];
|
||||||
|
|
||||||
exec-once = [
|
exec-once = [
|
||||||
"[workspace special:steam silent] uwsm app -- steam"
|
"[workspace special:steam silent] uwsm app -- steam"
|
||||||
|
"game-focus-watcher"
|
||||||
];
|
];
|
||||||
|
|
||||||
bindd = [
|
bindd = [
|
||||||
"SUPER, A, Toggle Steam, togglespecialworkspace, steam"
|
"SUPER, A, Toggle Steam, togglespecialworkspace, steam"
|
||||||
"SUPER SHIFT, A, Move to Steam Special Workspace, movetoworkspace, special: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"
|
"SUPER SHIFT, G, Move to Gaming Workspace, movetoworkspace, name:gaming"
|
||||||
];
|
];
|
||||||
|
|
||||||
@@ -71,16 +121,19 @@ in {
|
|||||||
"float, class:^(steam)$"
|
"float, class:^(steam)$"
|
||||||
# Suppress focus stealing from dialogs/etc.
|
# Suppress focus stealing from dialogs/etc.
|
||||||
"noinitialfocus, class:^(steam)$"
|
"noinitialfocus, class:^(steam)$"
|
||||||
"suppressevent activate, class:^(steam)$"
|
"suppressevent activate fullscreen maximize, class:^(steam)$"
|
||||||
|
|
||||||
# --- STEAM CLIENT OVERRIDE ---
|
# --- STEAM CLIENT OVERRIDE ---
|
||||||
# Override the float for the main Steam client, tile it, and move it to the special workspace.
|
# Override the float for the main Steam client, tile it, and move it to the special workspace.
|
||||||
"tile, class:^(steam)$, title:^(Steam)$"
|
"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 ---
|
# --- STEAM GAME OVERRIDES ---
|
||||||
# Override the float for actual games and move them to the gaming workspace.
|
# Override the float for actual games and move them to the gaming workspace.
|
||||||
# 1. Auto-detected steam_app games (like Deadlock).
|
# 1. Auto-detected steam_app games (like Deadlock).
|
||||||
|
"monitor ${gamingMonitor}, class:^(steam_app_\\d+)$"
|
||||||
"tile, class:^(steam_app_\\d+)$"
|
"tile, class:^(steam_app_\\d+)$"
|
||||||
"fullscreen, class:^(steam_app_\\d+)$"
|
"fullscreen, class:^(steam_app_\\d+)$"
|
||||||
"immediate, class:^(steam_app_\\d+)$"
|
"immediate, class:^(steam_app_\\d+)$"
|
||||||
|
|||||||
Reference in New Issue
Block a user