{ config, inputs, lib, pkgs, ... }: let cfg = config.poz.programs.hypr.land; inherit (config.poz.other.system) username; hmCfg = config.home-manager.users.${username}; inherit (builtins) toString; inherit (lib.attrsets) attrNames attrValues mapAttrsToList; inherit (lib.lists) elem flatten genList imap0 map optionals; inherit (lib.meta) getExe getExe'; inherit (lib.modules) mkIf; inherit (lib.options) mkEnableOption mkOption; inherit (lib.strings) optionalString removeSuffix; inherit (lib.types) bool listOf nullOr package path str submodule; isAmdGpuPresent = elem "amdgpu" config.boot.initrd.kernelModules; smwPresent = elem inputs.split-monitor-workspaces.packages.${pkgs.system}.split-monitor-workspaces cfg.extraPlugins; foot = getExe' pkgs.foot "foot"; footclient = getExe' pkgs.foot "footclient"; wl-copy = getExe' pkgs.wl-clipboard "wl-copy"; wl-paste = getExe' pkgs.wl-clipboard "wl-paste"; cliphist = getExe pkgs.cliphist; swww-daemon = getExe' pkgs.swww "swww-daemon"; wlsunset = getExe pkgs.wlsunset; anyrun = getExe inputs.anyrun.packages.${pkgs.system}.anyrun; hyprcontrib = inputs.hyprcontrib.packages.${pkgs.system}; grim = getExe pkgs.grim; slurp = getExe pkgs.slurp; grimblast = getExe hyprcontrib.grimblast; tesseract = getExe pkgs.tesseract; shadower = getExe inputs.shadower.packages.${pkgs.system}.shadower; inherit (inputs.hyprland.packages.${pkgs.system}) hyprland xdg-desktop-portal-hyprland; hyprctl = getExe' hyprland "hyprctl"; waybar = getExe inputs.waybar.packages.${pkgs.system}.waybar; woomer = getExe inputs.woomer.packages.${pkgs.system}.default; pkill = getExe' pkgs.procps "pkill"; tee = getExe' pkgs.coreutils-full "tee"; sleep = getExe' pkgs.coreutils-full "sleep"; xdg-user-dir = getExe' pkgs.xdg-user-dirs "xdg-user-dir"; xdg-open = getExe' pkgs.xdg-utils "xdg-open"; notify-send = getExe pkgs.libnotify; satty = getExe pkgs.satty; asusctl = getExe' config.services.asusd.package "asusctl"; rog-control-center = getExe' config.services.asusd.package "rog-control-center"; systemctl = getExe' pkgs.systemd "systemctl"; loginctl = getExe' pkgs.systemd "loginctl"; dbus-update-activation-environment = getExe' pkgs.dbus "dbus-update-activation-environment"; btop = getExe pkgs.btop; amdgpu_top = getExe pkgs.amdgpu_top; helvum = getExe pkgs.helvum; pwvucontrol = getExe pkgs.pwvucontrol; nm-applet = getExe pkgs.networkmanagerapplet; blueman-applet = getExe' pkgs.blueman "blueman-applet"; keepassxc = getExe' pkgs.keepassxc "keepassxc"; playerctl = getExe pkgs.playerctl; pamixer = getExe pkgs.pamixer; brightnessctl = getExe pkgs.brightnessctl; rg = getExe pkgs.ripgrep; awk = getExe pkgs.gawk; mpv = getExe pkgs.mpv; socat = getExe pkgs.socat; in { options.poz.programs.hypr.land = { enable = mkEnableOption "hiper ląd"; startupSound = mkOption { type = nullOr path; description = "sound to play on hyprland startup"; default = null; }; tearing = mkOption { type = bool; description = "enable tearing"; default = false; }; # there's unfortunately no recursive merge function that I know of # which means just `//`ing cfg.extraSettings with settings will # overwrite the default settings from this module # my workaround for that right now is to manually `//` or `++` each # list or attrs in cfg.extraSettings - that's why I need these types # whenever I wanna add something else I'll need to add that here extraSettings = mkOption { type = submodule { options = { exec-once = mkOption { description = "commands ran on hyprland launch"; default = []; type = listOf str; }; exec = mkOption { description = "commands ran on hyprland reload"; default = []; type = listOf str; }; }; }; description = "extra per host hyprland settings"; default = {}; }; extraPlugins = mkOption { type = listOf package; description = "extra per host hyprland plugins"; default = []; }; }; disabledModules = [ "programs/hyprland.nix" ]; config = mkIf cfg.enable { nix.settings = { substituters = [ "https://hyprland.cachix.org" ]; trusted-public-keys = [ "hyprland.cachix.org-1:a7pgxzMz7+chwVL3/pzj6jIBMioiJM7ypFP8PwtkuGc=" ]; }; environment.sessionVariables = { XDG_CURRENT_DESKTOP = "Hyprland"; XDG_SESSION_TYPE = "wayland"; XDG_SESSION_DESKTOP = "Hyprland"; SDL_VIDEODRIVER = "wayland"; _JAVA_AWT_WM_NONEREPARENTING = "1"; CLUTTER_BACKEND = "wayland"; GDK_BACKEND = "wayland"; QT_QPA_PLATFORM = "wayland"; LIBSEAT_BACKEND = "logind"; }; xdg.portal = { enable = true; extraPortals = [ pkgs.xdg-desktop-portal-gtk xdg-desktop-portal-hyprland ]; config.common = { default = [ "gtk" ]; "org.freedesktop.impl.portal.Screencast" = [ hyprland ]; }; }; programs.hyprland = { enable = true; package = hyprland; portalPackage = xdg-desktop-portal-hyprland; }; home-manager.users.${username} = { xdg.dataFile = mkIf (cfg.startupSound != null) { "hypr/startup_sound".source = cfg.startupSound; }; home.packages = attrValues { inherit (pkgs) cliphist wl-clipboard hyprpicker ydotool wev ; inherit (inputs.hyprcontrib.packages.${pkgs.system}) hyprprop grimblast ; }; wayland.windowManager.hyprland = { enable = true; package = hyprland; plugins = cfg.extraPlugins; settings = let mod = "SUPER"; in { monitor = mapAttrsToList (name: m: let w = toString m.resolution.w; h = toString m.resolution.h; refresh = toString m.refreshRate; x = toString m.position.x; y = toString m.position.y; scale = toString m.scale; in "${name},${w}x${h}@${refresh},${x}x${y},${scale}" ) config.poz.other.hardware.monitors; # I'm so sorry workspace = (flatten (imap0 (monitor_index: monitor_name: ( map (i: let # a % b mod = a: b: a - (b * (a / b)); workspace = toString i; isDefault = (mod i 10) == 1; # 11, 21, 31, ... in "${workspace}, monitor:${monitor_name}${optionalString isDefault ", default:true"}" ) (genList (i: i + 1 + (10 * monitor_index)) 10) )) (attrNames config.poz.other.hardware.monitors))) ++ [ # scratchpads "special:btop, decorate:false" (mkIf isAmdGpuPresent "special:amdgpu_top, decorate:false") "special:pipewire, decorate:false" "special:nixos, decorate:false" (mkIf config.services.asusd.enable "special:rog, decorate:false") "special:keepassxc, decorate:false" ]; input = { kb_layout = "pl,ru"; kb_variant = ""; kb_model = ""; kb_options = ""; kb_rules = ""; follow_mouse = true; touchpad = { disable_while_typing = false; }; repeat_rate = 50; repeat_delay = 250; touchdevice = { output = "eDP-1"; }; tablet = { output = "eDP-1"; }; }; general = { gaps_in = 5; gaps_out = 5; border_size = 2; allow_tearing = mkIf cfg.tearing true; "col.active_border" = "0xFFF5C2E7"; "col.inactive_border" = "0xFF45475A"; #col.group_border_active = "0xFFA6E3A1"; #col.group_border = "0xFF45475A"; }; env = optionals cfg.tearing [ "WLR_DRM_NO_ATOMIC,1" ]; decoration = { rounding = 10; blur = { enabled = true; size = 3; passes = 2; }; drop_shadow = 1; shadow_range = 15; shadow_render_power = 2; shadow_ignore_window = 1; shadow_offset = "2 4"; shadow_scale = 1; "col.shadow" = "0xAF1E1E2E"; }; gestures = { workspace_swipe = 1; workspace_swipe_distance = 200; workspace_swipe_invert = 1; workspace_swipe_min_speed_to_force = 20; workspace_swipe_cancel_ratio = 0.5; }; bezier = [ "dupa, 0.1, 0.9, 0.1, 1.05" ]; animations = { enabled = true; animation = [ "windows, 1, 4, dupa, popin" "windowsOut, 1, 7, dupa, slide" "border, 1, 15, default" "fade, 1, 10, default" "workspaces, 1, 5, dupa, slidevert" ]; }; dwindle = { no_gaps_when_only = true; }; misc = { enable_swallow = false; swallow_regex = "foot"; focus_on_activate = true; vrr = 1; vfr = true; animate_manual_resizes = false; animate_mouse_windowdragging = false; force_default_wallpaper = 0; }; windowrulev2 = [ "float, class:^(Tor Browser)$" "float, class:^(Mullvad Browser)$" "float, class:^(foot)$" "float, class:^(mpv)$" "float, class:^(imv)$" "float, class:^(Vieb)$" "float, title:^(Picture-in-Picture)$" "float, title:^(.*)(Choose User Profile)(.*)$" "float, title:^(blob:null/)(.*)$" "float, class:^(xdg-desktop-portal-gtk)$" "float, class:^(code), title: ^(Open*)" "size 70% 70%, class:^(code), title: ^(Open*)" "center, class: ^(code), title: ^(Open*)" "float, class:^(org.keepassxc.KeePassXC)$" (mkIf config.services.asusd.enable "size 960 670, title:^(ROG Control Center)$") (mkIf cfg.tearing "immediate, class:^(osu!)$") ]; bind = ( # workspace map (i: let key = if i == "10" then "0" else i; dispatcher = "${optionalString smwPresent "split-"}workspace"; in "${mod}, ${key}, ${dispatcher}, ${i}" ) (genList (i: toString (i + 1)) 10) ) ++ ( # movetoworkspacesilent map (i: let key = if i == "10" then "0" else i; dispatcher = "${optionalString smwPresent "split-"}movetoworkspacesilent"; in "${mod} SHIFT, ${key}, ${dispatcher}, ${i}" ) (genList (i: toString (i + 1)) 10) ) ++ [ "${mod}, RETURN, exec, ${if config.poz.programs.foot.server then footclient else foot}" "${mod}, Q, killactive" "${mod}, F, fullscreen, 0" "${mod}, M, fullscreen, 1" "${mod}, D, exec, ${pkill} anyrun || ${anyrun}" "${mod}, SPACE, togglefloating, active" "${mod} SHIFT, C, centerwindow" "${mod} CONTROL, R, bringactivetotop" "${mod} SHIFT, P, pin" "${mod}, W, exec, ${removeSuffix " %U" hmCfg.xdg.desktopEntries.firefox-schizo.exec}" "${mod} SHIFT, W, exec, ${removeSuffix " %U" hmCfg.xdg.desktopEntries.firefox-unschizo.exec}" # screenshots "${mod}, S, exec, ${grimblast} save area - | ${tee} \"$(${xdg-user-dir} PICTURES)/Screenshots/$(date +'screenshot-%Y%m%d%H%M%S.png')\" | ${wl-copy} && ${notify-send} \"Screenshot taken\"" "${mod} SHIFT, S, exec, ${grimblast} save area - | ${shadower} | ${tee} \"$(${xdg-user-dir} PICTURES)/Screenshots/$(date +'screenshot-%Y%m%d%H%M%S.png')\" | ${wl-copy} && ${notify-send} \"Screenshot taken (shadower)\"" "${mod} CONTROL, S, exec, ${grimblast} save area - | ${satty} --early-exit --initial-tool brush --save-after-copy -f - --output-filename \"$(${xdg-user-dir} PICTURES)/Screenshots/$(date +'screenshot-%Y%m%d%H%M%S.png')\" && ${notify-send} \"Screenshot taken\"" "${mod} SHIFT CONTROL, S, exec, ${grimblast} save area - | ${shadower} | ${satty} --early-exit --initial-tool brush --save-after-copy -f - --output-filename \"$(${xdg-user-dir} PICTURES)/Screenshots/$(date +'screenshot-%Y%m%d%H%M%S.png')\" && ${notify-send} \"Screenshot taken (shadower)\"" "${mod} SHIFT, R, exec, ${hyprctl} reload" (mkIf config.services.asusd.enable ", XF86Launch3, exec, ${asusctl} led-mode -n") (mkIf config.services.asusd.enable ", XF86Launch4, exec, ${asusctl} profile -n") # TODO implement touchpad toggle some day maybe ", XF86TouchpadToggle, exec, " "${mod}, T, exec, ${grim} -g \"$(${slurp})\" -t ppm - | ${tesseract} -l pol - - | ${wl-copy} && ${notify-send} \"Copied text: \" \"\\\"$(${wl-paste})\\\"\"" "${mod}, SEMICOLON, exec, quickshell -c powermenu" "${mod}, G, togglegroup" # this sometimes fixes the portal "${mod} CONTROL, K, exec, ${systemctl} --user restart xdg-desktop-portal-hyprland" "${mod} SHIFT, K, exec, ${hyprctl} kill" # scratchpad binds "${mod}, B, togglespecialworkspace, btop" (mkIf isAmdGpuPresent "${mod}, A, togglespecialworkspace, amdgpu_top") "${mod}, V, togglespecialworkspace, pipewire" "${mod}, N, togglespecialworkspace, nixos" # rog button on laptop keyboard (mkIf config.services.asusd.enable ", XF86Launch1, togglespecialworkspace, rog") (mkIf config.services.asusd.enable "${mod}, R, togglespecialworkspace, rog") "${mod}, X, togglespecialworkspace, keepassxc" "${mod}, C, exec, ${loginctl} lock-session" # toggle waybar "${mod} CONTROL, B, exec, ${pkill} waybar || ${waybar}" "${mod} CONTROL, minus, exec, ${hyprctl} keyword misc:cursor_zoom_factor 2" "${mod} CONTROL, apostrophe, exec, ${hyprctl} keyword misc:cursor_zoom_factor 1" "${mod}, Z, exec, ${woomer}" # I love Microsoft !!!!!! "${mod} ALT CONTROL SHIFT, L, exec, ${xdg-open} https://www.linkedin.com/" ]; # repeat binde = [ # window focus "${mod}, J, movefocus, d" "${mod}, K, movefocus, u" # monitor focus "${mod}, H, focusmonitor, l" "${mod}, L, focusmonitor, r" # keyboard brightness (mkIf config.services.asusd.enable ", XF86KbdBrightnessUp, exec, ${asusctl} -n") (mkIf config.services.asusd.enable ", XF86KbdBrightnessDown, exec, ${asusctl} -p") "${mod} SHIFT, G, changegroupactive, f" "${mod} CONTROL, G, changegroupactive, b" "${mod}, minus, exec, ${hyprctl} keyword misc:cursor_zoom_factor \"$(${hyprctl} getoption misc:cursor_zoom_factor | ${rg} float | ${awk} '{print $2 + 0.05}')\"" "${mod}, apostrophe, exec, ${hyprctl} keyword misc:cursor_zoom_factor \"$(${hyprctl} getoption misc:cursor_zoom_factor | ${rg} float | ${awk} '{if($2 != 1) {print $2 - 0.05}}')\"" ]; # locked bindl = let play-pause = "${playerctl} play-pause"; stop = "${playerctl} stop"; prev = "${playerctl} previous"; next = "${playerctl} next"; toggle-mute = "${pamixer} --toggle-mute"; in [ ", XF86AudioMedia, exec, ${play-pause}" ", XF86AudioPlay, exec, ${play-pause}" "${mod}, Y, exec, ${play-pause}" ", XF86AudioStop, exec, ${stop}" "${mod}, U, exec, ${stop}" ", XF86AudioPrev, exec, ${prev}" "${mod}, I, exec, ${prev}" ", XF86AudioNext, exec, ${next}" "${mod}, O, exec, ${next}" ", XF86AudioMute, exec, ${toggle-mute}" "${mod}, P, exec, ${toggle-mute}" ]; # locked + repeat bindle = let brightness_up = "${brightnessctl} set +5%"; brightness_down = "${brightnessctl} set 5%-"; volume_up = "${pamixer} -ui 5"; volume_down = "${pamixer} -ud 5"; in [ # TODO integrate the scripts from https://codeberg.org/jacekpoz/dotconfig/src/branch/master/Scripts ", XF86MonBrightnessUp, exec, ${brightness_up}" "${mod} SHIFT, Z, exec, ${brightness_up}" ", XF86MonBrightnessDown, exec, ${brightness_down}" "${mod} SHIFT, X, exec, ${brightness_down}" ", XF86AudioRaiseVolume, exec, ${volume_up}" "${mod} SHIFT, C, exec, ${volume_up}" ", XF86AudioLowerVolume, exec, ${volume_down}" "${mod} SHIFT, V, exec, ${volume_down}" ]; # mouse bindm = [ "${mod}, mouse:272, movewindow" "${mod}, mouse:273, resizewindow" ]; binds = { pass_mouse_when_bound = false; movefocus_cycles_fullscreen = false; }; exec-once = let # https://wiki.hyprland.org/IPC/#how-to-use-socket2-with-bash handle_hyprland_events = pkgs.writeShellScript "handle_hyprland_events" '' #!/bin/sh handle() { case $1 in configreloaded*) ${hyprctl} notify 1 2500 "" " Reloading Hyprland..." ;; esac } ${socat} -U - UNIX-CONNECT:$XDG_RUNTIME_DIR/hypr/$HYPRLAND_INSTANCE_SIGNATURE/.socket2.sock | while read -r line; do handle "$line" done ''; in [ waybar # run persistent special workspace windows "[workspace special:nixos silent;tile] cd ~/niksos; ${foot}" (mkIf config.services.asusd.enable "[workspace special:rog silent;tile] ${rog-control-center}") "[workspace special:keepassxc silent;tile] ${keepassxc}" (mkIf config.poz.programs.foot.server "${sleep} 0.5 && ${systemctl} --user restart foot.service") "${hyprctl} setcursor Bibata-Modern-Classic 24" "${wl-paste} --watch ${cliphist} store" "${swww-daemon}" "${wlsunset} -S 06:00 -s 20:00" # TODO do these do anything anymore "${systemctl} --user import-environment WAYLAND_DISPLAY XDG_CURRENT_DESKTOP" "${dbus-update-activation-environment} --systemd WAYLAND_DISPLAY XDG_CURRENT_DESKTOP=$XDG_CURRENT_DESKTOP" "${handle_hyprland_events}" (mkIf (cfg.startupSound != null) "${mpv} --no-video --volume=100 ${hmCfg.xdg.dataHome}/hypr/startup_sound") ] ++ cfg.extraSettings.exec-once; exec = [ # kill (almost) everything on special workspaces "${pkill} btop" (mkIf isAmdGpuPresent "${pkill} amdgpu_top") "${pkill} helvum" "${pkill} pwvucontrol" # and run it all again "[workspace special:btop silent;tile] ${foot} -e ${btop}" (mkIf isAmdGpuPresent "[workspace special:amdgpu_top silent;tile] ${foot} -e ${amdgpu_top} -i 0") (mkIf isAmdGpuPresent "[workspace special:amdgpu_top silent;tile] ${foot} -e ${amdgpu_top} -i 1") "[workspace special:pipewire silent;tile] ${helvum}" "[workspace special:pipewire silent;tile] ${pwvucontrol}" "${nm-applet} --indicator" "${blueman-applet}" ] ++ cfg.extraSettings.exec; layerrule = [ # no black border on grimblast screenshots "noanim, ^(selection)$" "blur, ^(waybar)$" "blur, ^(anyrun)$" ]; plugin = { csgo-vulkan-fix = { res_w = 2560; res_h = 1440; }; split-monitor-workspaces = { count = 10; keep_focused = true; }; }; }; }; }; }; }