Files
nixos-configs/hosts/pinwheel/modules/hyprland/default.nix
Alexander Heldt 7d2cf97ea6 pinwheel: Move workspaces to monitor on hotplug
Workspace rules alone only affect future workspace creation. Add
moveworkspacetomonitor dispatches so existing workspaces are moved
to the correct monitor when an external display is connected.
2026-03-19 11:02:26 +01:00

262 lines
7.5 KiB
Nix

{
inputs,
pkgs,
lib,
config,
...
}:
let
enabled = config.mod.hyprland.enable;
monitorScript = pkgs.writeShellScript "hyprland-monitor-handler" ''
INTERNAL="eDP-1"
EXTERNAL_MONITORS="HDMI-A-1 DP-3"
HYPRCTL="${pkgs.hyprland}/bin/hyprctl"
JQ="${pkgs.jq}/bin/jq"
get_active_external() {
# Return the first connected external monitor
for mon in $EXTERNAL_MONITORS; do
if $HYPRCTL monitors -j | $JQ -e ".[] | select(.name == \"$mon\")" > /dev/null 2>&1; then
echo "$mon"
return 0
fi
done
return 1
}
bind_workspaces() {
local external
if external=$(get_active_external); then
# External monitor connected: 1-5 on external, 6-10 on internal
for ws in 1 2 3 4 5; do
$HYPRCTL keyword workspace "$ws, monitor:$external, default:true"
$HYPRCTL dispatch moveworkspacetomonitor "$ws $external"
done
for ws in 6 7 8 9 10; do
$HYPRCTL keyword workspace "$ws, monitor:$INTERNAL, default:true"
$HYPRCTL dispatch moveworkspacetomonitor "$ws $INTERNAL"
done
else
# No external monitor: all workspaces on internal
for ws in 1 2 3 4 5 6 7 8 9 10; do
$HYPRCTL keyword workspace "$ws, monitor:$INTERNAL, default:true"
$HYPRCTL dispatch moveworkspacetomonitor "$ws $INTERNAL"
done
fi
}
handle_event() {
case $1 in
monitoradded*|monitorremoved*)
sleep 0.5
bind_workspaces
;;
esac
}
# Bind workspaces on startup
bind_workspaces
${pkgs.socat}/bin/socat -U - UNIX-CONNECT:"$XDG_RUNTIME_DIR/hypr/$HYPRLAND_INSTANCE_SIGNATURE/.socket2.sock" | while read -r line; do
handle_event "$line"
done
'';
in
{
options = {
mod.hyprland = {
enable = lib.mkEnableOption "enable hyprland module";
};
};
config = lib.mkIf enabled {
programs.hyprland = {
enable = true;
package = inputs.hyprland.packages.${pkgs.stdenv.hostPlatform.system}.hyprland;
portalPackage =
inputs.hyprland.packages.${pkgs.stdenv.hostPlatform.system}.xdg-desktop-portal-hyprland;
xwayland = {
enable = true;
};
};
home-manager.users.alex = {
wayland.windowManager.hyprland = {
enable = true;
extraConfig = ''
exec-once=waybar
exec-once=hyprctl setcursor Adwaita 24
env = GDK_DPI_SCALE,1.5
env = HYPRCURSOR_THEME,Adwaita
env = HYPRCURSOR_SIZE,24
monitor=eDP-1, 1920x1200, auto-center-down, 1
monitor=HDMI-A-1, 2560x1440@100, auto-center-up, 1
monitor=DP-3, 2560x1440@60, auto-center-up, 1
workspace = w[tv1], gapsout:0, gapsin:0
workspace = f[1], gapsout:0, gapsin:0
windowrule = border_size 0, match:float 0, match:workspace w[tv1]
windowrule = rounding 0, match:float 0, match:workspace w[tv1]
windowrule = border_size 0, match:float 0, match:workspace f[1]
windowrule = rounding 0, match:float 0, match:workspace f[1]
# https://wiki.archlinux.org/title/Hyprland#Jetbrains_apps_focus_issues
windowrule = match:xwayland true, no_initial_focus on
exec-once=dbus-update-activation-environment --systemd WAYLAND_DISPLAY XDG_CURRENT_DESKTOP
'';
settings = {
"$mod" = "SUPER";
animations.enabled = false;
xwayland = {
force_zero_scaling = true;
};
input = {
kb_layout = "se";
# 2 - Cursor focus will be detached from keyboard focus. Clicking on a window will move keyboard focus to that window.
follow_mouse = 2;
sensitivity = 0.3;
accel_profile = "flat";
touchpad = {
natural_scroll = false;
tap-and-drag = false;
};
};
cursor = {
zoom_factor = 1;
zoom_rigid = true;
};
general = {
layout = "dwindle";
gaps_in = 0; # gaps between windows
gaps_out = 0; # gaps between windows and monitor edges
"col.active_border" = "rgba(${config.lib.colors.foreground}ff)";
"col.inactive_border" = "rgba(${config.lib.colors.background}ff)";
};
dwindle = {
force_split = 2;
};
bind =
let
ws =
x:
let
n = if (x + 1) < 10 then (x + 1) else 0;
in
builtins.toString n;
select = builtins.genList (x: "$mod, ${ws x}, workspace, ${builtins.toString (x + 1)}") 10;
move = builtins.genList (
x: "$mod SHIFT, ${ws x}, movetoworkspacesilent, ${builtins.toString (x + 1)}"
) 10;
magnifier = pkgs.writeShellScript "magnifier" ''
CURRENT=$(${pkgs.hyprland}/bin/hyprctl getoption cursor:zoom_factor -j | ${pkgs.jq}/bin/jq .float)
DELTA=0.1
UPDATED=1
case $1 in
--increase)
UPDATED=$(echo $CURRENT + $DELTA | ${pkgs.bc}/bin/bc) ;;
--decrease)
UPDATED=$(echo $CURRENT - $DELTA | ${pkgs.bc}/bin/bc) ;;
--reset)
UPDATED=1
esac
if (( $(echo "$UPDATED < 1" | bc) )); then UPDATED=1; fi
${pkgs.hyprland}/bin/hyprctl keyword cursor:zoom_factor $UPDATED
'';
in
select
++ move
++ [
"$mod, ESCAPE, killactive"
"$mod, f, fullscreen, 1"
"$mod SHIFT, f, togglefloating, active"
"$mod, h, movefocus, l"
"$mod, j, movefocus, d"
"$mod, k, movefocus, u"
"$mod, l, movefocus, r"
"$mod CONTROL, 1, exec, ${magnifier} --increase"
"$mod CONTROL, 2, exec, ${magnifier} --decrease"
"$mod CONTROL, 3, exec, ${magnifier} --reset"
];
bindm = [
# mouse movements
"$mod, mouse:272, movewindow" # left click
"$mod, mouse:273, resizewindow" # right click
];
misc = {
disable_hyprland_logo = true;
disable_splash_rendering = true;
};
};
};
home.packages = [
pkgs.wdisplays
pkgs.bc
];
systemd.user.services.hyprland-monitors = {
Unit = {
Description = "Hyprland monitor hotplug handler";
PartOf = [ "graphical-session.target" ];
After = [ "graphical-session.target" ];
};
Service = {
Type = "simple";
ExecStart = "${monitorScript}";
Restart = "on-failure";
RestartSec = 5;
};
Install = {
WantedBy = [ "graphical-session.target" ];
};
};
};
# To start electron apps like `chromium` with wayland support
environment.sessionVariables.NIXOS_OZONE_WL = "1";
# The XDG portal is needed for screen sharing
xdg.portal = {
enable = true;
# override "trace: warning: xdg-desktop-portal 1.17 reworked how portal implementations are loaded ..."
config.common.default = "*";
extraPortals = [ pkgs.xdg-desktop-portal-wlr ];
};
# openGL is needed for wayland/hyprland
hardware.graphics.enable = true;
boot.kernelParams = [ "i915.enable_psr=0" ];
};
}