diff --git a/flake.lock b/flake.lock index 0e7f67a..9fb2094 100644 --- a/flake.lock +++ b/flake.lock @@ -277,6 +277,26 @@ "type": "gitlab" } }, + "home-manager": { + "inputs": { + "nixpkgs": [ + "nixpkgs" + ] + }, + "locked": { + "lastModified": 1779027260, + "narHash": "sha256-ZbgWWFQmSyM3HQ31nAZk2hJ7OSeNr9uRFHL8jCifY9M=", + "owner": "nix-community", + "repo": "home-manager", + "rev": "bcb774cfc3268120cd61808629f9aa7dad3750a2", + "type": "github" + }, + "original": { + "owner": "nix-community", + "repo": "home-manager", + "type": "github" + } + }, "home-manager-stable": { "inputs": { "nixpkgs": [ @@ -819,6 +839,7 @@ }, "root": { "inputs": { + "home-manager": "home-manager", "home-manager-stable": "home-manager-stable", "hyprland": "hyprland", "nix-cachyos-kernel": "nix-cachyos-kernel", diff --git a/flake.nix b/flake.nix index cf899d2..25a4479 100644 --- a/flake.nix +++ b/flake.nix @@ -9,6 +9,13 @@ inputs.nixpkgs.follows = "nixpkgs-stable"; }; + # Unstable HM for Hyprland Lua config support (configType = "lua", + # available since HM master / future 26.05). + home-manager = { + url = "github:nix-community/home-manager"; + inputs.nixpkgs.follows = "nixpkgs"; + }; + zen-browser = { url = "github:0xc000022070/zen-browser-flake"; inputs = { @@ -36,6 +43,7 @@ , nixpkgs-stable , nixpkgs , home-manager-stable + , home-manager , zen-browser , nix-cachyos-kernel , hyprland @@ -58,9 +66,9 @@ in { nixosConfigurations = { - FredOS-Gaming = mkHost "FredOS-Gaming" nixpkgs-stable home-manager-stable; + FredOS-Gaming = mkHost "FredOS-Gaming" nixpkgs-stable home-manager; FredOS-Mediaserver = mkHost "FredOS-Mediaserver" nixpkgs-stable home-manager-stable; - FredOS-Macbook = mkHost "FredOS-Macbook" nixpkgs-stable home-manager-stable; + FredOS-Macbook = mkHost "FredOS-Macbook" nixpkgs-stable home-manager; }; }; } diff --git a/hosts/FredOS-Gaming.nix b/hosts/FredOS-Gaming.nix index 9705c29..58bc41e 100644 --- a/hosts/FredOS-Gaming.nix +++ b/hosts/FredOS-Gaming.nix @@ -79,8 +79,12 @@ }; home-manager.users.fred = { ... }: { - wayland.windowManager.hyprland.settings.monitor = - [ "DP-2,3440x1440@180,0x0,1" ]; + wayland.windowManager.hyprland.settings.monitor = [{ + output = "DP-2"; + mode = "3440x1440@180"; + position = "0x0"; + scale = 1; + }]; home.file.".local/share/Steam/compatibilitytools.d/Proton-CachyOS Latest".source = inputs.proton-cachyos-nix.packages.x86_64-linux.proton-cachyos-x86_64-v3.steamcompattool; diff --git a/hosts/FredOS-Macbook.nix b/hosts/FredOS-Macbook.nix index 5ef0ee2..1620587 100644 --- a/hosts/FredOS-Macbook.nix +++ b/hosts/FredOS-Macbook.nix @@ -34,8 +34,12 @@ }; home-manager.users.fred = { pkgs, ... }: { - wayland.windowManager.hyprland.settings.monitor = - [ ",preferred,auto,auto" ]; + wayland.windowManager.hyprland.settings.monitor = [{ + output = ""; + mode = "preferred"; + position = "auto"; + scale = "auto"; + }]; # wob reads 0-100 integers from a FIFO and shows a progress bar overlay systemd.user.services.wob = { diff --git a/settings/hyprland.nix b/settings/hyprland.nix index 277f3db..0f118ea 100644 --- a/settings/hyprland.nix +++ b/settings/hyprland.nix @@ -4,13 +4,7 @@ let hyprland-pkgs = inputs.hyprland.packages.${pkgs.stdenv.hostPlatform.system}; isMacbook = config.networking.hostName == "FredOS-Macbook"; - - # GPU pinning env vars — only needed on the dual-GPU gaming box (card1 = Navi 22). - gpuEnv = lib.optionals (config.networking.hostName == "FredOS-Gaming") [ - "AQ_DRM_DEVICES,/dev/dri/card1" - "WLR_DRM_DEVICES,/dev/dri/card1" - "DRI_PRIME,pci-0000_03_00_0" - ]; + isGaming = !isMacbook; # hyprutils-0.13.1 requires GLIBCXX_3.4.34 (GCC 15) but Hyprland's RPATH # was patched to gcc-14.3.0-lib which only provides up to GLIBCXX_3.4.33. @@ -50,7 +44,7 @@ in programs.hyprland = { enable = true; xwayland.enable = true; - package = hyprland-wrapped; + package = hyprland-wrapped; portalPackage = hyprland-pkgs.xdg-desktop-portal-hyprland; }; @@ -103,157 +97,84 @@ in home-manager.users.fred = { config, lib, pkgs, inputs, ... }: { wayland.windowManager.hyprland = { enable = true; + configType = "lua"; systemd.variables = [ "--all" ]; package = hyprland-wrapped; settings = { - # monitor is set per-host in hosts/FredOS-{Gaming,Macbook}.nix + # hl.config({...}) — all static named-section configuration. + # monitor is set per-host in hosts/FredOS-{Gaming,Macbook}.nix. + config = { + general = { + gaps_in = 6; + gaps_out = 12; + border_size = 2; + layout = "dwindle"; + resize_on_border = true; + }; - # Battle.net tray icon leaks as a tiny floating XWayland window with - # class steam_app_0 and an empty title — send it to the void. - windowrulev2 = [ - "workspace special silent, class:^(steam_app_0)$, title:^()$, floating:1" - ]; + decoration = { + rounding = 8; + blur = { + enabled = true; + }; + }; - env = [ - # Clear the LD_PRELOAD set by the start-hyprland wrapper so it - # doesn't leak into child processes (Steam, games, etc.). - "LD_PRELOAD," + render = { + direct_scanout = false; + }; - # Keep these in sync with stylix.cursor in settings/stylix.nix. - "XCURSOR_THEME,Bibata-Modern-Ice" - "XCURSOR_SIZE,24" - "HYPRCURSOR_THEME,Bibata-Modern-Ice" - "HYPRCURSOR_SIZE,24" - - # Prefer native Wayland backends where the app supports it. - "ELECTRON_OZONE_PLATFORM_HINT,wayland" # Vesktop, VSCodium - "MOZ_ENABLE_WAYLAND,1" # Zen / Firefox family - "QT_QPA_PLATFORM,wayland;xcb" # Qt apps, XWayland fallback - "SDL_VIDEODRIVER,wayland" # SDL apps - "_JAVA_AWT_WM_NONREPARENTING,1" # Java tiling fix - ] ++ gpuEnv; - - "$mod" = "SUPER"; - "$term" = "ghostty"; - "$menu" = "killall anyrun || anyrun"; - - exec-once = [ - "mako" - "nm-applet --indicator" - "wl-paste --type text --watch cliphist store" - "wl-paste --type image --watch cliphist store" - "hyprctl setcursor Bibata-Modern-Ice 24" - "swayosd-server" - ] ++ lib.optionals isMacbook [ "hypridle" ]; - - general = { - gaps_in = 6; - gaps_out = 12; - border_size = 2; - layout = "dwindle"; - resize_on_border = true; - }; - - decoration = { - rounding = 8; - blur = { + animations = { enabled = true; }; - }; - render = { - direct_scanout = false; - }; + input = { + kb_layout = "gb,no"; + kb_options = "grp:alt_shift_toggle"; + follow_mouse = 1; + accel_profile = "flat"; + sensitivity = 0; + } // lib.optionalAttrs isMacbook { + touchpad = { + "tap-to-click" = true; + tap_button_map = "lrm"; + natural_scroll = true; + }; + }; - animations = { - enabled = true; - # speed = tenths of a second; 1 = 0.1s ≈ instant. - bezier = [ - "snap, 0.05, 0.9, 0.1, 1.0" - ]; - animation = [ - "windows, 1, 1, snap" - "windowsOut, 1, 1, snap, popin 80%" - "layers, 1, 1, snap" - "border, 1, 2, default" - "fade, 1, 1, default" - "workspaces, 1, 1, snap" - ]; - }; + cursor = { + no_warps = true; + }; - input = { - kb_layout = "gb,no"; - kb_options = "grp:alt_shift_toggle"; - follow_mouse = 1; - accel_profile = "flat"; - sensitivity = 0; - } // lib.optionalAttrs isMacbook { - touchpad = { - tap-to-click = true; - tap_button_map = "lrm"; # 1-finger=left, 2-finger=right, 3-finger=middle - natural_scroll = true; + dwindle = { + preserve_split = true; + }; + + misc = { + disable_hyprland_logo = true; + disable_splash_rendering = true; + # Apps demanding attention don't get to yank focus — they'll + # show as urgent in the bar instead. + focus_on_activate = false; + vrr = 2; + }; + + # vfr moved from misc: to debug: in 0.55.0 + debug = { + vfr = false; # keep compositor ticking, don't idle between frames + disable_logs = false; }; }; + }; - cursor = { - no_warps = true; # don't teleport the cursor on focus changes - }; - - dwindle = { - preserve_split = true; - # New windows split the focused container 50/50 — your usual - # 2-way layout falls out of dwindle's defaults. - }; - - misc = { - disable_hyprland_logo = true; - disable_splash_rendering = true; - # Apps demanding attention don't get to yank focus — they'll - # show as urgent in the bar instead. Pairs with cursor.no_warps - # below; without that, focus jumps drag the cursor with them. - focus_on_activate = false; - vrr = 2; # always on — should engage at the 180 Hz EDID mode - }; - - # vfr moved from misc: to debug: in 0.55.0 - debug = { - vfr = false; # keep compositor ticking, don't idle between frames - disable_logs = false; - }; - - # Mirror the GNOME bindings so muscle memory carries over - bind = [ - "$mod, T, exec, $term" - "$mod, E, exec, nemo" - "$mod, R, exec, $menu" - "$mod, Q, killactive" - "$mod SHIFT, E, exit" - - # Floating / layout - "$mod, V, togglefloating" - "$mod, F, fullscreen, 0" - "$mod, P, pseudo" - "$mod, S, layoutmsg, togglesplit" - - - # Focus - "$mod, left, movefocus, l" - "$mod, right, movefocus, r" - "$mod, up, movefocus, u" - "$mod, down, movefocus, d" - "$mod, H, movefocus, l" - "$mod, K, movefocus, u" - "$mod, J, movefocus, d" - # L freed for hyprlock — arrow keys still handle right-focus. - - # Power menu — toggle: kills anyrun if already open - "$mod, L, exec, killall anyrun || ${pkgs.writeShellScript "power-menu" '' + extraConfig = + let + powerMenu = pkgs.writeShellScript "power-menu" '' choice=$(printf '%s\n' \ - $' Lock' \ - $' Logout' \ - $' Reboot' \ - $' Shutdown' \ + $' Lock' \ + $' Logout' \ + $' Reboot' \ + $' Shutdown' \ | ${pkgs.anyrun}/bin/anyrun \ --plugins "${pkgs.anyrun}/lib/libstdin.so" \ --show-results-immediately true \ @@ -265,82 +186,153 @@ in *Reboot) systemctl reboot ;; *Shutdown) systemctl poweroff ;; esac - ''}" - - # Move windows - "$mod SHIFT, left, movewindow, l" - "$mod SHIFT, right, movewindow, r" - "$mod SHIFT, up, movewindow, u" - "$mod SHIFT, down, movewindow, d" - - # Workspaces - "$mod, 1, workspace, 1" - "$mod, 2, workspace, 2" - "$mod, 3, workspace, 3" - "$mod, 4, workspace, 4" - "$mod, 5, workspace, 5" - "$mod, 6, workspace, 6" - "$mod, 7, workspace, 7" - "$mod, 8, workspace, 8" - "$mod, 9, workspace, 9" - "$mod, 0, workspace, 10" - - "$mod SHIFT, 1, movetoworkspacesilent, 1" - "$mod SHIFT, 2, movetoworkspacesilent, 2" - "$mod SHIFT, 3, movetoworkspacesilent, 3" - "$mod SHIFT, 4, movetoworkspacesilent, 4" - "$mod SHIFT, 5, movetoworkspacesilent, 5" - "$mod SHIFT, 6, movetoworkspacesilent, 6" - "$mod SHIFT, 7, movetoworkspacesilent, 7" - "$mod SHIFT, 8, movetoworkspacesilent, 8" - "$mod SHIFT, 9, movetoworkspacesilent, 9" - "$mod SHIFT, 0, movetoworkspacesilent, 10" - - # Screenshots — Shift+Super+S matches your GNOME binding - "$mod SHIFT, S, exec, hyprshot -m region --clipboard-only" - ", Print, exec, hyprshot -m output --clipboard-only" - - # Settings shortcut — Super+I matches your GNOME binding - "$mod, I, exec, pavucontrol" - - # Custom - shortcuts: - "$mod, z, exec, zen-beta" - - ]; - - bindm = [ - "$mod, mouse:272, movewindow" - "$mod, mouse:273, resizewindow" - ]; - - bindel = [ - ", XF86AudioRaiseVolume, exec, swayosd-client --output-volume raise" - ", XF86AudioLowerVolume, exec, swayosd-client --output-volume lower" - ", XF86AudioMute, exec, swayosd-client --output-volume mute-toggle" - ", XF86MonBrightnessUp, exec, swayosd-client --brightness raise" - ", XF86MonBrightnessDown, exec, swayosd-client --brightness lower" - ] ++ lib.optionals isMacbook [ - ", XF86KbdBrightnessUp, exec, ${pkgs.writeShellScript "kbd-bright-up" '' + ''; + kbdBrightUp = pkgs.writeShellScript "kbd-bright-up" '' ${pkgs.brightnessctl}/bin/brightnessctl -d smc::kbd_backlight set +10% brightness=$(${pkgs.brightnessctl}/bin/brightnessctl -d smc::kbd_backlight get) max=$(${pkgs.brightnessctl}/bin/brightnessctl -d smc::kbd_backlight max) echo $(( brightness * 100 / max )) > "$XDG_RUNTIME_DIR/wob.fifo" - ''}" - ", XF86KbdBrightnessDown, exec, ${pkgs.writeShellScript "kbd-bright-down" '' + ''; + kbdBrightDown = pkgs.writeShellScript "kbd-bright-down" '' ${pkgs.brightnessctl}/bin/brightnessctl -d smc::kbd_backlight set 10%- brightness=$(${pkgs.brightnessctl}/bin/brightnessctl -d smc::kbd_backlight get) max=$(${pkgs.brightnessctl}/bin/brightnessctl -d smc::kbd_backlight max) echo $(( brightness * 100 / max )) > "$XDG_RUNTIME_DIR/wob.fifo" - ''}" - ]; + ''; + in + '' + -- Environment + hl.env("LD_PRELOAD", "") + hl.env("XCURSOR_THEME", "Bibata-Modern-Ice") + hl.env("XCURSOR_SIZE", "24") + hl.env("HYPRCURSOR_THEME", "Bibata-Modern-Ice") + hl.env("HYPRCURSOR_SIZE", "24") + hl.env("ELECTRON_OZONE_PLATFORM_HINT", "wayland") + hl.env("MOZ_ENABLE_WAYLAND", "1") + hl.env("QT_QPA_PLATFORM", "wayland;xcb") + hl.env("SDL_VIDEODRIVER", "wayland") + hl.env("_JAVA_AWT_WM_NONREPARENTING", "1") + ${lib.optionalString isGaming '' + -- GPU pinning — Navi 22 is card1 on the dual-GPU gaming box. + hl.env("AQ_DRM_DEVICES", "/dev/dri/card1") + hl.env("WLR_DRM_DEVICES", "/dev/dri/card1") + hl.env("DRI_PRIME", "pci-0000_03_00_0") + ''} - bindl = [ - ", XF86AudioPlay, exec, playerctl play-pause" - ", XF86AudioNext, exec, playerctl next" - ", XF86AudioPrev, exec, playerctl previous" - ]; - }; + -- Startup + hl.on("hyprland.start", function() + hl.exec_cmd("mako") + hl.exec_cmd("nm-applet --indicator") + hl.exec_cmd("wl-paste --type text --watch cliphist store") + hl.exec_cmd("wl-paste --type image --watch cliphist store") + hl.exec_cmd("hyprctl setcursor Bibata-Modern-Ice 24") + hl.exec_cmd("swayosd-server") + ${lib.optionalString isMacbook ''hl.exec_cmd("hypridle")''} + end) + -- Animation curve and definitions + hl.curve("snap", { type = "bezier", points = { {0.05, 0.9}, {0.1, 1.0} } }) + hl.animation({ leaf = "windows", enabled = true, speed = 1, bezier = "snap" }) + hl.animation({ leaf = "windowsOut", enabled = true, speed = 1, bezier = "snap", style = "popin 80%" }) + hl.animation({ leaf = "layers", enabled = true, speed = 1, bezier = "snap" }) + hl.animation({ leaf = "border", enabled = true, speed = 2, bezier = "default" }) + hl.animation({ leaf = "fade", enabled = true, speed = 1, bezier = "default" }) + hl.animation({ leaf = "workspaces", enabled = true, speed = 1, bezier = "snap" }) + + -- Window rules + -- Battle.net tray icon leaks as a tiny floating XWayland window. + hl.window_rule({ + match = { class = "steam_app_0", title = "^$", float = true }, + workspace = "special silent", + }) + + -- Binds + local mod = "SUPER" + + -- Apps + hl.bind(mod .. " + T", hl.dsp.exec_cmd("ghostty")) + hl.bind(mod .. " + E", hl.dsp.exec_cmd("nemo")) + hl.bind(mod .. " + R", hl.dsp.exec_cmd("killall anyrun || anyrun")) + hl.bind(mod .. " + Q", hl.dsp.window.close()) + hl.bind(mod .. " + SHIFT + E", hl.dsp.exit()) + + -- Floating / layout + hl.bind(mod .. " + V", hl.dsp.window.float({ action = "toggle" })) + hl.bind(mod .. " + F", hl.dsp.window.fullscreen({ state = 0 })) + hl.bind(mod .. " + P", hl.dsp.window.pseudo()) + hl.bind(mod .. " + S", hl.dsp.layout("togglesplit")) + + -- Focus + hl.bind(mod .. " + left", hl.dsp.focus({ direction = "left" })) + hl.bind(mod .. " + right", hl.dsp.focus({ direction = "right" })) + hl.bind(mod .. " + up", hl.dsp.focus({ direction = "up" })) + hl.bind(mod .. " + down", hl.dsp.focus({ direction = "down" })) + hl.bind(mod .. " + H", hl.dsp.focus({ direction = "left" })) + hl.bind(mod .. " + K", hl.dsp.focus({ direction = "up" })) + hl.bind(mod .. " + J", hl.dsp.focus({ direction = "down" })) + + -- Power menu — toggle: kills anyrun if already open + hl.bind(mod .. " + L", hl.dsp.exec_cmd("killall anyrun || ${powerMenu}")) + + -- Move windows + hl.bind(mod .. " + SHIFT + left", hl.dsp.window.move({ direction = "left" })) + hl.bind(mod .. " + SHIFT + right", hl.dsp.window.move({ direction = "right" })) + hl.bind(mod .. " + SHIFT + up", hl.dsp.window.move({ direction = "up" })) + hl.bind(mod .. " + SHIFT + down", hl.dsp.window.move({ direction = "down" })) + + -- Workspaces + hl.bind(mod .. " + 1", hl.dsp.workspace.change(1)) + hl.bind(mod .. " + 2", hl.dsp.workspace.change(2)) + hl.bind(mod .. " + 3", hl.dsp.workspace.change(3)) + hl.bind(mod .. " + 4", hl.dsp.workspace.change(4)) + hl.bind(mod .. " + 5", hl.dsp.workspace.change(5)) + hl.bind(mod .. " + 6", hl.dsp.workspace.change(6)) + hl.bind(mod .. " + 7", hl.dsp.workspace.change(7)) + hl.bind(mod .. " + 8", hl.dsp.workspace.change(8)) + hl.bind(mod .. " + 9", hl.dsp.workspace.change(9)) + hl.bind(mod .. " + 0", hl.dsp.workspace.change(10)) + + hl.bind(mod .. " + SHIFT + 1", hl.dsp.window.move_to_workspace({ id = 1, silent = true })) + hl.bind(mod .. " + SHIFT + 2", hl.dsp.window.move_to_workspace({ id = 2, silent = true })) + hl.bind(mod .. " + SHIFT + 3", hl.dsp.window.move_to_workspace({ id = 3, silent = true })) + hl.bind(mod .. " + SHIFT + 4", hl.dsp.window.move_to_workspace({ id = 4, silent = true })) + hl.bind(mod .. " + SHIFT + 5", hl.dsp.window.move_to_workspace({ id = 5, silent = true })) + hl.bind(mod .. " + SHIFT + 6", hl.dsp.window.move_to_workspace({ id = 6, silent = true })) + hl.bind(mod .. " + SHIFT + 7", hl.dsp.window.move_to_workspace({ id = 7, silent = true })) + hl.bind(mod .. " + SHIFT + 8", hl.dsp.window.move_to_workspace({ id = 8, silent = true })) + hl.bind(mod .. " + SHIFT + 9", hl.dsp.window.move_to_workspace({ id = 9, silent = true })) + hl.bind(mod .. " + SHIFT + 0", hl.dsp.window.move_to_workspace({ id = 10, silent = true })) + + -- Screenshots — Shift+Super+S matches GNOME binding + hl.bind(mod .. " + SHIFT + S", hl.dsp.exec_cmd("hyprshot -m region --clipboard-only")) + hl.bind("Print", hl.dsp.exec_cmd("hyprshot -m output --clipboard-only")) + + -- Settings shortcut — Super+I matches GNOME binding + hl.bind(mod .. " + I", hl.dsp.exec_cmd("pavucontrol")) + + -- Custom shortcuts + hl.bind(mod .. " + Z", hl.dsp.exec_cmd("zen-beta")) + + -- Mouse window manipulation + hl.bind(mod .. " + mouse:272", hl.dsp.window.drag(), { mouse = true }) + hl.bind(mod .. " + mouse:273", hl.dsp.window.resize(), { mouse = true }) + + -- Volume / brightness (repeating) + hl.bind("XF86AudioRaiseVolume", hl.dsp.exec_cmd("swayosd-client --output-volume raise"), { repeating = true }) + hl.bind("XF86AudioLowerVolume", hl.dsp.exec_cmd("swayosd-client --output-volume lower"), { repeating = true }) + hl.bind("XF86AudioMute", hl.dsp.exec_cmd("swayosd-client --output-volume mute-toggle"), { repeating = true }) + hl.bind("XF86MonBrightnessUp", hl.dsp.exec_cmd("swayosd-client --brightness raise"), { repeating = true }) + hl.bind("XF86MonBrightnessDown", hl.dsp.exec_cmd("swayosd-client --brightness lower"), { repeating = true }) + ${lib.optionalString isMacbook '' + hl.bind("XF86KbdBrightnessUp", hl.dsp.exec_cmd("${kbdBrightUp}"), { repeating = true }) + hl.bind("XF86KbdBrightnessDown", hl.dsp.exec_cmd("${kbdBrightDown}"), { repeating = true }) + ''} + + -- Media keys (locked — work through lockscreen) + hl.bind("XF86AudioPlay", hl.dsp.exec_cmd("playerctl play-pause"), { locked = true }) + hl.bind("XF86AudioNext", hl.dsp.exec_cmd("playerctl next"), { locked = true }) + hl.bind("XF86AudioPrev", hl.dsp.exec_cmd("playerctl previous"), { locked = true }) + ''; }; programs.anyrun = { @@ -408,7 +400,7 @@ in enable = true; settings = { general = { - lock_cmd = "pidof hyprlock || hyprlock"; + lock_cmd = "pidof hyprlock || hyprlock"; before_sleep_cmd = "loginctl lock-session"; after_sleep_cmd = "hyprctl dispatch dpms on"; }; @@ -469,7 +461,7 @@ in }; "custom/tray-handle" = { - format = ""; # Nerd Font arrow + format = ""; # Nerd Font arrow tooltip = false; };