{ 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" done for ws in 6 7 8 9 10; do $HYPRCTL keyword workspace "$ws, monitor:$INTERNAL, default:true" 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" done fi } handle_event() { case $1 in monitoradded*|monitorremoved*) sleep 0.5 # Give monitor time to initialize 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" ]; }; }