diff --git a/configs/nixos/hosts/zeus/default.nix b/configs/nixos/hosts/zeus/default.nix index 9e49cd8..aa73189 100644 --- a/configs/nixos/hosts/zeus/default.nix +++ b/configs/nixos/hosts/zeus/default.nix @@ -49,8 +49,19 @@ modules.desktop.apps.vencord.enable = true; home-manager.users.${config.user.name}.wayland.windowManager.hyprland.settings.monitor = [ - "HDMI-A-1,1920x1080@120,0x0,1,transform,3" - "DP-3,2560x1440@239.96,1080x0,1" + { + output = "DP-3"; + mode = "2560x1440@239.96"; + position = "1080x0"; + scale = 1; + } + { + output = "HDMI-A-1"; + mode = "1920x1080@120"; + position = "0x0"; + scale = 1; + transform = 3; + } ]; } diff --git a/configs/nixos/modules/desktop/session/hyprland/default.nix b/configs/nixos/modules/desktop/session/hyprland/default.nix index 7cc25db..2d335e8 100644 --- a/configs/nixos/modules/desktop/session/hyprland/default.nix +++ b/configs/nixos/modules/desktop/session/hyprland/default.nix @@ -8,22 +8,31 @@ let cfg = config.modules.desktop.session.hyprland; # NOLINT(let_order) cursor = config.stylix.cursor; + mkBind = key: dispatcher: options: { + _args = [ + (lua key) + dispatcher + ] + ++ lib.optional (options != { }) options; + }; + lua = lib.generators.mkLuaInline; # Numbered-workspace switch and move for 1..9, the operator's i3 muscle memory. workspaceBinds = lib.concatMap (n: [ - "$mod, ${toString n}, workspace, ${toString n}" - "$mod SHIFT, ${toString n}, movetoworkspace, ${toString n}" + (mkBind ''mod .. " + ${toString n}"'' (lua ''hl.dsp.focus({ workspace = "${toString n}" })'') { }) + (mkBind ''mod .. " + SHIFT + ${toString n}"'' + (lua ''hl.dsp.window.move({ workspace = "${toString n}", follow = false })'') + { } + ) ]) (lib.range 1 9); - user = config.user.name; - # Flip the tiling strategy between the two built-in layouts, since neither a # dispatcher nor a keyword toggles it on its own. toggleLayout = pkgs.writeShellScript "hypr-toggle-layout" '' - if [ "$(hyprctl getoption -j general:layout | ${pkgs.jq}/bin/jq -r .str)" = dwindle ]; then - hyprctl keyword general:layout master + if [ "$(${config.programs.hyprland.package}/bin/hyprctl getoption -j general:layout | ${pkgs.jq}/bin/jq -r .str)" = dwindle ]; then + ${config.programs.hyprland.package}/bin/hyprctl keyword general:layout master else - hyprctl keyword general:layout dwindle + ${config.programs.hyprland.package}/bin/hyprctl keyword general:layout dwindle fi ''; in @@ -44,7 +53,7 @@ in withUWSM = true; }; - home-manager.users.${user}.wayland.windowManager.hyprland = { + home-manager.users.${config.user.name}.wayland.windowManager.hyprland = { enable = true; # One package drives the whole session, so there is never a version split. @@ -56,134 +65,237 @@ in # uwsm owns the systemd graphical-session targets. systemd.enable = false; - # Write the native hyprlang hyprland.conf, whose variable and bind syntax - # the settings below are expressed in. - configType = "hyprlang"; + configType = "lua"; settings = { - "$mod" = "SUPER"; - "$terminal" = "ghostty"; + mod = { + _var = "SUPER"; + }; + terminal = { + _var = "ghostty"; + }; # Session variables handed to the compositor directly. # UWSM launches the session without the shell profile that would carry # them, so a variable the compositor or its children must see is set # here rather than through home-manager's sessionVariables. env = [ - # Chromium and Electron apps read this to select native Wayland; - # nixpkgs wrappers (Obsidian's included) gate their Wayland flags on - # it, so without it they run under XWayland and blur at this DPI. - "NIXOS_OZONE_WL,1" + { + _args = [ + "NIXOS_OZONE_WL" + "1" + ]; + } ] # Bibata ships XCursor only. # The hyprcursor variables name the same theme, which Hyprland # resolves through its XCursor fallback. ++ lib.optionals (cursor != null) [ - "XCURSOR_THEME,${cursor.name}" - "XCURSOR_SIZE,${toString cursor.size}" - "HYPRCURSOR_THEME,${cursor.name}" - "HYPRCURSOR_SIZE,${toString cursor.size}" + { + _args = [ + "XCURSOR_THEME" + cursor.name + ]; + } + { + _args = [ + "XCURSOR_SIZE" + (toString cursor.size) + ]; + } + { + _args = [ + "HYPRCURSOR_THEME" + cursor.name + ]; + } + { + _args = [ + "HYPRCURSOR_SIZE" + (toString cursor.size) + ]; + } ]; - input = { - kb_layout = "us"; - # Caps is a second Escape. - # Shift+Caps still toggles a real CapsLock. - kb_options = "caps:escape_shifted_capslock"; - # Snappy key repeat. - repeat_delay = 250; - repeat_rate = 45; - accel_profile = "flat"; - touchpad = { - natural_scroll = true; - tap-to-click = true; - disable_while_typing = true; + config = { + input = { + kb_layout = "us"; + # Caps is a second Escape. + # Shift+Caps still toggles a real CapsLock. + kb_options = "caps:escape_shifted_capslock"; + # Snappy key repeat. + repeat_delay = 250; + repeat_rate = 45; + accel_profile = "flat"; + touchpad = { + natural_scroll = true; + tap_to_click = true; + disable_while_typing = true; + }; + }; + + general = { + gaps_in = 4; + gaps_out = 8; + border_size = 2; + layout = "dwindle"; + }; + + decoration = { + rounding = 6; + blur.enabled = cfg.blur; + }; + + # XWayland clients render at the panel's native resolution instead of + # being raster-scaled by the compositor at the fractional monitor scale. + xwayland.force_zero_scaling = true; + + animations.enabled = true; + + dwindle = { + preserve_split = true; }; }; - general = { - gaps_in = 4; - gaps_out = 8; - border_size = 2; - layout = "dwindle"; - }; - - decoration = { - rounding = 6; - blur.enabled = cfg.blur; - }; - - # XWayland clients render at the panel's native resolution instead of - # being raster-scaled by the compositor at the fractional monitor scale. - xwayland.force_zero_scaling = true; - - animations = { - enabled = true; - bezier = [ "ease, 0.25, 0.1, 0.25, 1.0" ]; - # Durations are in centiseconds. - # Short values keep the motion subtle. - animation = [ - "windows, 1, 3, ease" - "fade, 1, 3, ease" - # Layer surfaces like the launcher fade in a touch quicker than windows. - "layersIn, 1, 2, ease" - "fadeLayersIn, 1, 2, ease" - "workspaces, 1, 3, ease" - "border, 1, 3, ease" + curve = { + _args = [ + "ease" + { + type = "bezier"; + points = [ + [ + 0.25 + 0.1 + ] + [ + 0.25 + 1.0 + ] + ]; + } ]; }; - dwindle = { - preserve_split = true; - }; + # Durations are in centiseconds. + # Short values keep the motion subtle. + animation = [ + { + leaf = "windows"; + enabled = true; + speed = 3; + bezier = "ease"; + } + { + leaf = "fade"; + enabled = true; + speed = 3; + bezier = "ease"; + } + # Layer surfaces like the launcher fade in a touch quicker than windows. + { + leaf = "layersIn"; + enabled = true; + speed = 2; + bezier = "ease"; + } + { + leaf = "fadeLayersIn"; + enabled = true; + speed = 2; + bezier = "ease"; + } + { + leaf = "workspaces"; + enabled = true; + speed = 3; + bezier = "ease"; + } + { + leaf = "border"; + enabled = true; + speed = 3; + bezier = "ease"; + } + ]; bind = [ - "$mod, Return, exec, $terminal" + (mkBind ''mod .. " + RETURN"'' (lua "hl.dsp.exec_cmd(terminal)") { }) # Move focus. - "$mod, H, movefocus, l" - "$mod, J, movefocus, d" - "$mod, K, movefocus, u" - "$mod, L, movefocus, r" + (mkBind ''mod .. " + H"'' (lua ''hl.dsp.focus({ direction = "left" })'') { }) + (mkBind ''mod .. " + J"'' (lua ''hl.dsp.focus({ direction = "down" })'') { }) + (mkBind ''mod .. " + K"'' (lua ''hl.dsp.focus({ direction = "up" })'') { }) + (mkBind ''mod .. " + L"'' (lua ''hl.dsp.focus({ direction = "right" })'') { }) # Move the window within the layout. - "$mod SHIFT, H, movewindow, l" - "$mod SHIFT, J, movewindow, d" - "$mod SHIFT, K, movewindow, u" - "$mod SHIFT, L, movewindow, r" + (mkBind ''mod .. " + SHIFT + H"'' (lua ''hl.dsp.window.move({ direction = "l" })'') { }) + (mkBind ''mod .. " + SHIFT + J"'' (lua ''hl.dsp.window.move({ direction = "d" })'') { }) + (mkBind ''mod .. " + SHIFT + K"'' (lua ''hl.dsp.window.move({ direction = "u" })'') { }) + (mkBind ''mod .. " + SHIFT + L"'' (lua ''hl.dsp.window.move({ direction = "r" })'') { }) # Resize the active window. - "$mod ALT, H, resizeactive, -40 0" - "$mod ALT, J, resizeactive, 0 40" - "$mod ALT, K, resizeactive, 0 -40" - "$mod ALT, L, resizeactive, 40 0" + (mkBind ''mod .. " + ALT + H"'' (lua "hl.dsp.window.resize({ x = -40, y = 0, relative = true })") + { } + ) + (mkBind ''mod .. " + ALT + J"'' (lua "hl.dsp.window.resize({ x = 0, y = 40, relative = true })") + { } + ) + (mkBind ''mod .. " + ALT + K"'' (lua "hl.dsp.window.resize({ x = 0, y = -40, relative = true })") + { } + ) + (mkBind ''mod .. " + ALT + L"'' (lua "hl.dsp.window.resize({ x = 40, y = 0, relative = true })") + { } + ) - "$mod, Space, togglefloating," - "$mod, F, fullscreen," + (mkBind ''mod .. " + SPACE"'' (lua "hl.dsp.window.float()") { }) + (mkBind ''mod .. " + F"'' (lua "hl.dsp.window.fullscreen()") { }) # togglesplit is a dwindle layout message, reached through layoutmsg. - "$mod, T, layoutmsg, togglesplit" - "$mod SHIFT, T, exec, ${toggleLayout}" - "$mod SHIFT, Q, killactive," - "$mod CTRL, Q, forcekillactive," + (mkBind ''mod .. " + T"'' (lua ''hl.dsp.layout("togglesplit")'') { }) + (mkBind ''mod .. " + SHIFT + T"'' (lua "hl.dsp.exec_cmd(${builtins.toJSON toggleLayout})") { }) + (mkBind ''mod .. " + SHIFT + Q"'' (lua "hl.dsp.window.close()") { }) + (mkBind ''mod .. " + CTRL + Q"'' (lua "hl.dsp.window.kill()") { }) ] - ++ workspaceBinds; - + ++ workspaceBinds # Volume and brightness keys repeat while held, each raising a popup # through the OSD client. # Volume is capped at 100 percent. # The client floors brightness so a full hold cannot black the screen out. - binde = [ - ", XF86AudioRaiseVolume, exec, ${pkgs.swayosd}/bin/swayosd-client --output-volume raise --max-volume 100" - ", XF86AudioLowerVolume, exec, ${pkgs.swayosd}/bin/swayosd-client --output-volume lower" - ", XF86MonBrightnessUp, exec, ${pkgs.swayosd}/bin/swayosd-client --brightness raise" - ", XF86MonBrightnessDown, exec, ${pkgs.swayosd}/bin/swayosd-client --brightness lower" - ]; - + ++ [ + (mkBind (builtins.toJSON "XF86AudioRaiseVolume") + (lua "hl.dsp.exec_cmd(${builtins.toJSON "${pkgs.swayosd}/bin/swayosd-client --output-volume raise --max-volume 100"})") + { repeating = true; } + ) + (mkBind (builtins.toJSON "XF86AudioLowerVolume") + (lua "hl.dsp.exec_cmd(${builtins.toJSON "${pkgs.swayosd}/bin/swayosd-client --output-volume lower"})") + { repeating = true; } + ) + (mkBind (builtins.toJSON "XF86MonBrightnessUp") + (lua "hl.dsp.exec_cmd(${builtins.toJSON "${pkgs.swayosd}/bin/swayosd-client --brightness raise"})") + { repeating = true; } + ) + (mkBind (builtins.toJSON "XF86MonBrightnessDown") + (lua "hl.dsp.exec_cmd(${builtins.toJSON "${pkgs.swayosd}/bin/swayosd-client --brightness lower"})") + { repeating = true; } + ) + ] # Mute and media transport still fire while the session is locked. - bindl = [ - ", XF86AudioMute, exec, ${pkgs.swayosd}/bin/swayosd-client --output-volume mute-toggle" - ", XF86AudioPlay, exec, ${pkgs.playerctl}/bin/playerctl play-pause" - ", XF86AudioNext, exec, ${pkgs.playerctl}/bin/playerctl next" - ", XF86AudioPrev, exec, ${pkgs.playerctl}/bin/playerctl previous" + ++ [ + (mkBind (builtins.toJSON "XF86AudioMute") + (lua "hl.dsp.exec_cmd(${builtins.toJSON "${pkgs.swayosd}/bin/swayosd-client --output-volume mute-toggle"})") + { locked = true; } + ) + (mkBind (builtins.toJSON "XF86AudioPlay") + (lua "hl.dsp.exec_cmd(${builtins.toJSON "${pkgs.playerctl}/bin/playerctl play-pause"})") + { locked = true; } + ) + (mkBind (builtins.toJSON "XF86AudioNext") + (lua "hl.dsp.exec_cmd(${builtins.toJSON "${pkgs.playerctl}/bin/playerctl next"})") + { locked = true; } + ) + (mkBind (builtins.toJSON "XF86AudioPrev") + (lua "hl.dsp.exec_cmd(${builtins.toJSON "${pkgs.playerctl}/bin/playerctl previous"})") + { locked = true; } + ) ]; }; }; diff --git a/configs/nixos/modules/desktop/session/hyprland/hyprlock.nix b/configs/nixos/modules/desktop/session/hyprland/hyprlock.nix index b83a453..f47ca85 100644 --- a/configs/nixos/modules/desktop/session/hyprland/hyprlock.nix +++ b/configs/nixos/modules/desktop/session/hyprland/hyprlock.nix @@ -86,8 +86,18 @@ in # The guard drops the keypress when a locker is already up, so a second # hyprlock never stacks over the first. wayland.windowManager.hyprland.settings.bind = [ - "$mod, X, exec, ${pkgs.procps}/bin/pidof hyprlock || ${hyprlock}" - "$mod CTRL, X, exec, ${idleLockToggle}" + { + _args = [ + (lib.generators.mkLuaInline ''mod .. " + X"'') + (lib.generators.mkLuaInline "hl.dsp.exec_cmd(${builtins.toJSON "${pkgs.procps}/bin/pidof hyprlock || ${hyprlock}"})") + ]; + } + { + _args = [ + (lib.generators.mkLuaInline ''mod .. " + CTRL + X"'') + (lib.generators.mkLuaInline "hl.dsp.exec_cmd(${builtins.toJSON idleLockToggle})") + ]; + } ]; }; }; diff --git a/configs/nixos/modules/desktop/session/login.nix b/configs/nixos/modules/desktop/session/login.nix index 7e6c0dc..8147850 100644 --- a/configs/nixos/modules/desktop/session/login.nix +++ b/configs/nixos/modules/desktop/session/login.nix @@ -6,6 +6,10 @@ }: # Text login: greetd running the tuigreet greeter, which starts the session # through the universal Wayland session manager. +let + hyprlandConfig = + config.home-manager.users.${config.user.name}.xdg.configFile."hypr/hyprland.lua".source; +in { options.modules.desktop.session.login.enable = lib.mkEnableOption "greetd with the tuigreet text greeter"; @@ -15,7 +19,7 @@ enable = true; settings.default_session.command = "${lib.getExe pkgs.tuigreet} --time --remember " - + "--cmd 'uwsm start -e -D Hyprland hyprland.desktop'"; + + "--cmd 'uwsm start -e -D Hyprland ${config.programs.hyprland.package}/bin/Hyprland --config ${hyprlandConfig}'"; }; }; } diff --git a/configs/nixos/modules/desktop/session/mako.nix b/configs/nixos/modules/desktop/session/mako.nix index f335717..4f290f9 100644 --- a/configs/nixos/modules/desktop/session/mako.nix +++ b/configs/nixos/modules/desktop/session/mako.nix @@ -34,9 +34,13 @@ in # Recall the last notification from history, keyboard-only like the rest # of the session. - # $mod is defined by the compositor config these binds share. wayland.windowManager.hyprland.settings.bind = [ - "$mod, N, exec, ${makoctl} restore" + { + _args = [ + (lib.generators.mkLuaInline ''mod .. " + N"'') + (lib.generators.mkLuaInline "hl.dsp.exec_cmd(${builtins.toJSON "${makoctl} restore"})") + ]; + } ]; }; }; diff --git a/configs/nixos/modules/desktop/session/rofi.nix b/configs/nixos/modules/desktop/session/rofi.nix index 261cb7d..284fca4 100644 --- a/configs/nixos/modules/desktop/session/rofi.nix +++ b/configs/nixos/modules/desktop/session/rofi.nix @@ -6,6 +6,7 @@ }: # rofi as the desktop launcher and dmenu-style menu frontend. let + lua = lib.generators.mkLuaInline; user = config.user.name; # A session menu built on the same themed rofi, so the power actions look @@ -41,10 +42,19 @@ in }; # The launcher and the session menu, kept next to the tool they drive. - # $mod is defined by the compositor config these binds share. wayland.windowManager.hyprland.settings.bind = [ - "$mod, R, exec, ${pkgs.rofi}/bin/rofi -show drun" - "$mod SHIFT, X, exec, ${powerMenu}" + { + _args = [ + (lua ''mod .. " + R"'') + (lua "hl.dsp.exec_cmd(${builtins.toJSON "${pkgs.rofi}/bin/rofi -show drun"})") + ]; + } + { + _args = [ + (lua ''mod .. " + SHIFT + X"'') + (lua "hl.dsp.exec_cmd(${builtins.toJSON powerMenu})") + ]; + } ]; }; }; diff --git a/configs/nixos/modules/desktop/utilities/clipboard.nix b/configs/nixos/modules/desktop/utilities/clipboard.nix index 4579982..8504ed7 100644 --- a/configs/nixos/modules/desktop/utilities/clipboard.nix +++ b/configs/nixos/modules/desktop/utilities/clipboard.nix @@ -39,9 +39,13 @@ in # session so uwsm starts and stops them with it. services.cliphist.enable = true; - # $mod is defined by the compositor config these binds share. wayland.windowManager.hyprland.settings.bind = [ - "$mod SHIFT, V, exec, ${picker}" + { + _args = [ + (lib.generators.mkLuaInline ''mod .. " + SHIFT + V"'') + (lib.generators.mkLuaInline "hl.dsp.exec_cmd(${builtins.toJSON picker})") + ]; + } ]; }; }; diff --git a/configs/nixos/modules/desktop/utilities/recording.nix b/configs/nixos/modules/desktop/utilities/recording.nix index 50385d5..78780c6 100644 --- a/configs/nixos/modules/desktop/utilities/recording.nix +++ b/configs/nixos/modules/desktop/utilities/recording.nix @@ -45,9 +45,13 @@ in config = lib.mkIf config.modules.desktop.utilities.recording.enable { home-manager.users.${user} = { - # $mod is defined by the compositor config these binds share. wayland.windowManager.hyprland.settings.bind = [ - "$mod SHIFT, R, exec, ${toggle}" + { + _args = [ + (lib.generators.mkLuaInline ''mod .. " + SHIFT + R"'') + (lib.generators.mkLuaInline "hl.dsp.exec_cmd(${builtins.toJSON toggle})") + ]; + } ]; }; }; diff --git a/configs/nixos/modules/desktop/utilities/screenshot.nix b/configs/nixos/modules/desktop/utilities/screenshot.nix index 1d723ef..43c7535 100644 --- a/configs/nixos/modules/desktop/utilities/screenshot.nix +++ b/configs/nixos/modules/desktop/utilities/screenshot.nix @@ -38,21 +38,34 @@ in config = lib.mkIf config.modules.desktop.utilities.screenshot.enable { home-manager.users.${user} = { - # satty is a one-shot annotation surface, so float it rather than letting - # it claim a tile in the layout. - # windowrule v3 syntax: space-separated field/value tokens, comma - # separated, with matchers under a match: prefix and effects bare. - wayland.windowManager.hyprland.settings.windowrule = [ - "float 1, match:class ^(com\\.gabm\\.satty)$" - ]; + # Satty is a one-shot annotation surface, so it floats above the layout. + wayland.windowManager.hyprland.settings.window_rule = { + float = true; + match.class = "^(com\\.gabm\\.satty)$"; + }; # Print with plain/Shift/Ctrl for region/window/full. # Super+L is already the hjkl focus and movement bind, so screenshots take # the Print key instead. wayland.windowManager.hyprland.settings.bind = [ - ", Print, exec, ${capture "area"}" - "SHIFT, Print, exec, ${capture "active"}" - "CTRL, Print, exec, ${capture "screen"}" + { + _args = [ + "PRINT" + (lib.generators.mkLuaInline "hl.dsp.exec_cmd(${builtins.toJSON (capture "area")})") + ]; + } + { + _args = [ + "SHIFT + PRINT" + (lib.generators.mkLuaInline "hl.dsp.exec_cmd(${builtins.toJSON (capture "active")})") + ]; + } + { + _args = [ + "CTRL + PRINT" + (lib.generators.mkLuaInline "hl.dsp.exec_cmd(${builtins.toJSON (capture "screen")})") + ]; + } ]; }; };