{ config, lib, pkgs, perSystem, ... }: with lib; let binds = { suffixes, prefixes, substitutions ? {}, }: let replacer = replaceStrings (attrNames substitutions) (attrValues substitutions); format = prefix: suffix: let actual-suffix = if isList suffix.action then { action = head suffix.action; args = tail suffix.action; } else { inherit (suffix) action; args = []; }; action = replacer "${prefix.action}-${actual-suffix.action}"; in { name = "${prefix.key}+${suffix.key}"; value.action.${action} = actual-suffix.args; }; pairs = attrs: fn: concatMap ( key: fn { inherit key; action = attrs.${key}; } ) (attrNames attrs); in listToAttrs (pairs prefixes (prefix: pairs suffixes (suffix: [(format prefix suffix)]))); in { programs.niri.settings = { environment = { DMS_SCREENSHOT_EDITOR = "satty"; ELECTRON_OZONE_PLATFORM_HINT = "auto"; QT_QPA_PLATFORM = "wayland"; QT_QPA_PLATFORMTHEME = "gtk3"; QT_QPA_PLATFORMTHEME_QT6 = "gtk3"; XDG_CURRENT_DESKTOP = "niri"; }; input = { keyboard = { xkb = { layout = "us,ua"; variant = "colemak_dh_ortho,"; }; repeat-delay = 200; repeat-rate = 50; track-layout = "window"; }; mouse = { accel-speed = 0.0; accel-profile = "flat"; }; touchpad = { tap = true; dwt = true; natural-scroll = true; click-method = "clickfinger"; }; tablet.map-to-output = "eDP-1"; touch.map-to-output = "eDP-1"; }; animations.enable = true; animations.slowdown = 0.4; clipboard.disable-primary = true; hotkey-overlay.skip-at-startup = true; prefer-no-csd = true; xwayland-satellite.path = lib.getExe perSystem.niri-flake.xwayland-satellite-unstable; overview.workspace-shadow.enable = false; # TODO: enable when implemented in github:sodiboo/niri-flake # recent-windows.highlight.corner-radius = 12; screenshot-path = "${config.xdg.userDirs.pictures}/screenshots/screenshot-%Y-%m-%d-%H-%M-%S.png"; layout = { gaps = 3; struts.left = 32; struts.right = 32; always-center-single-column = true; center-focused-column = "on-overflow"; # default-column-display = "tabbed"; empty-workspace-above-first = true; default-column-width.proportion = 2.0 / 3.0; preset-column-widths = [ {proportion = 1.0 / 3.0;} {proportion = 1.0 / 2.0;} {proportion = 2.0 / 3.0;} ]; background-color = "transparent"; border.enable = true; border.width = 1; focus-ring.enable = false; shadow.enable = false; tab-indicator = let i = 4; in { position = "bottom"; gaps-between-tabs = i; corner-radius = i; width = i; place-within-column = true; # hide-when-single-tab = true; # place-within-column = true; }; }; binds = with config.lib.niri.actions; let dms-ipc = spawn "dms" "ipc"; sh = spawn "sh" "-c"; focus = spawn "wlrctl" "window" "focus"; focus-meet = pkgs.writeShellApplication { name = "focus-zen-google-meet"; runtimeInputs = with pkgs; [wlrctl jq gnugrep config.programs.niri.package]; text = '' wlrctl window focus app_id:app.zen_browser.zen \ title:"$(niri msg --json windows \ | jq -r '.[].title' \ | grep -E 'Meet: "[a-zA-Z0-9-]+?"')" ''; }; focus-equicord = pkgs.writeShellApplication { name = "focus-equicord"; runtimeInputs = with pkgs; [wlrctl jq gnugrep config.programs.niri.package]; text = '' wlrctl window focus app_id:equibop \ title:"$(niri msg --json windows \ | jq -r '.[].title' \ | grep -E 'Meet: "[a-zA-Z0-9-]+?"')" ''; }; in lib.attrsets.mergeAttrsList [ { "Mod+Shift+Slash".action = show-hotkey-overlay; "Mod+Tab".action = focus-workspace-previous; "Mod+C".action = center-column; "Mod+Q".action = close-window; "Mod+M".action = maximize-column; "Mod+Ctrl+M".action = fullscreen-window; "Mod+Shift+M".action = maximize-window-to-edges; "Mod+Ctrl+Shift+M".action = toggle-windowed-fullscreen; "Mod+S".action = set-column-width "50%"; "Mod+Shift+S".action = set-column-width "33%"; "Mod+Ctrl+S".action = set-column-width "67%"; "Mod+T" = {action = toggle-column-tabbed-display;}; "Mod+F".action = switch-focus-between-floating-and-tiling; "Mod+Ctrl+F".action = toggle-window-floating; "Mod+Ctrl+Comma".action = consume-or-expel-window-left; "Mod+Ctrl+Period".action = consume-or-expel-window-right; "Mod+Minus".action = set-column-width "-10%"; "Mod+Equal".action = set-column-width "+10%"; "Mod+Plus".action = set-column-width "+10%"; "Mod+Shift+Minus".action = set-window-height "-10%"; "Mod+Shift+Plus".action = set-window-height "+10%"; "Print".action = dms-ipc "niri" "screenshot"; "Ctrl+Print".action = dms-ipc "niri" "screenshotScreen"; "Shift+Print".action = dms-ipc "niri" "screenshotWindow"; "Mod+Space" = {action = switch-layout "next";}; "Mod+Ctrl+Space" = { repeat = false; action = toggle-overview; }; "Mod+Return" = {action = sh "$TERMINAL";}; "Mod+R".action = sh "wl-kbptr -o modes=floating,click"; "Mod+Ctrl+R".action = sh "while wl-kbptr -o modes=split,click; do :; done"; "Mod+Shift+R".action = sh "wl-kbptr -o modes=tile,bisect,click"; "Super+Alt+G".action = spawn "${lib.getExe focus-meet}"; "Super+Alt+G".hotkey-overlay.title = "Focus Google Meet"; "Super+Alt+U".action = focus "app.zen_browser.zen" "state:inactive"; "Super+Alt+B".action = focus "io.github.ungoogled_software.ungoogled_chromium" "state:inactive"; "Super+Alt+D".action = focus "equibop"; "Super+Alt+T".action = focus "io.github.kukuruzka165.materialgram"; "Mod+D" = { action = spawn "vicinae" "toggle"; hotkey-overlay.title = "Toggle Application Launcher"; }; # "Mod+N" = { # action = dms-ipc "notifications" "toggle"; # hotkey-overlay.title = "Toggle Notification Center"; # }; # "Mod+Comma" = { # action = dms-ipc "settings" "toggle"; # hotkey-overlay.title = "Toggle Settings"; # }; # "Mod+P" = { # action = dms-ipc "notepad" "toggle"; # hotkey-overlay.title = "Toggle Notepad"; # }; "Mod+Ctrl+L" = { action = dms-ipc "lock" "lock"; hotkey-overlay.title = "Toggle Lock Screen"; }; "Mod+Ctrl+Q" = { action = dms-ipc "powermenu" "toggle"; hotkey-overlay.title = "Toggle Power Menu"; }; "XF86AudioRaiseVolume" = { allow-when-locked = true; action = dms-ipc "audio" "increment" "3"; }; "XF86AudioLowerVolume" = { allow-when-locked = true; action = dms-ipc "audio" "decrement" "3"; }; "XF86AudioMute" = { allow-when-locked = true; action = dms-ipc "audio" "mute"; }; "Shift+XF86AudioMute" = { allow-when-locked = true; action = dms-ipc "audio" "micmute"; }; "XF86AudioMicMute" = { allow-when-locked = true; action = dms-ipc "audio" "micmute"; }; "XF86AudioPlay".action = dms-ipc "mpris" "playPause"; "XF86AudioPause".action = dms-ipc "mpris" "pause"; "XF86AudioPrev".action = dms-ipc "mpris" "previous"; "XF86AudioNext".action = dms-ipc "mpris" "next"; "Mod+Alt+N" = { allow-when-locked = true; action = dms-ipc "night" "toggle"; hotkey-overlay.title = "Toggle Night Mode"; }; "Mod+V" = { action = spawn "vicinae" "vicinae://extensions/vicinae/clipboard/history"; hotkey-overlay.title = "Toggle Clipboard Manager"; }; "XF86MonBrightnessUp" = { allow-when-locked = true; action = dms-ipc "brightness" "increment" "5" ""; }; "XF86MonBrightnessDown" = { allow-when-locked = true; action = dms-ipc "brightness" "decrement" "5" ""; }; } (let left = "N"; down = "E"; up = "I"; right = "O"; in binds rec { suffixes = { "Left" = "column-left"; "Down" = "window-or-workspace-down"; "Up" = "window-or-workspace-up"; "Right" = "column-right"; ${left} = suffixes."Left"; ${down} = suffixes."Down"; ${up} = suffixes."Up"; ${right} = suffixes."Right"; }; prefixes = { "Mod" = "focus"; "Mod+Ctrl" = "move"; "Mod+Shift" = "focus-monitor"; "Mod+Shift+Ctrl" = "move-window-to-monitor"; }; substitutions = { "monitor-column" = "monitor"; "monitor-window-or-workspace" = "monitor"; "move-window-or-workspace" = "move-window"; }; }) # { # "Mod+V".action = switch-focus-between-floating-and-tiling; # "Mod+Shift+V".action = toggle-window-floating; # } (binds { suffixes."Home" = "first"; suffixes."End" = "last"; prefixes."Mod" = "focus-column"; prefixes."Mod+Ctrl" = "move-column-to"; }) (binds { # PageDown and PageUp suffixes."Prior" = "workspace-up"; suffixes."Next" = "workspace-down"; suffixes."WheelScrollUp" = "workspace-up"; suffixes."WheelScrollDown" = "workspace-down"; prefixes."Mod" = "focus"; prefixes."Mod+Ctrl" = "move-column-to"; prefixes."Mod+Shift" = "move"; }) (binds { suffixes = builtins.listToAttrs ( map (n: { name = toString n; value = [ "workspace" (n + 1) ]; # workspace 1 is empty; workspace 2 is the logical first. }) (range 1 9) ); prefixes."Mod" = "focus"; prefixes."Mod+Ctrl" = "move-window-to"; }) ]; window-rules = [ { clip-to-geometry = true; geometry-corner-radius = let r = 12.0; in { top-left = r; top-right = r; bottom-left = r; bottom-right = r; }; } # { # matches = [{is-active = false;}]; # draw-border-with-background = false; # opacity = 0.9; # } { matches = [ {title = "Picture-in-Picture";} {title = "Картинка в картинці";} { # telegram's picture-in-picture mode app-id = "io.github.kukuruzka165.materialgram"; title = "materialgram"; } ]; open-floating = true; } # { # matches = [ # { # # telegram's picture-in-picture mode # app-id = "io.github.kukuruzka165.materialgram"; # title = "materialgram"; # } # ]; # } { matches = [ {app-id = "re.sonny.Junction";} {app-id = "com.github.hluk.copyq";} {title = "Zen - Choose User Profile";} ]; open-floating = true; open-focused = true; } { matches = [ {app-id = "org.keepassxc.KeePassXC";} {app-id = "org.gnome.World.Secrets";} {app-id = "Bitwarden";} {app-id = "io.github.kukuruzka165.materialgram";} {app-id = "org.telegram.desktop";} {app-id = "org.signal.Signal";} {app-id = "WebCord";} {app-id = "equibop";} ]; block-out-from = "screencast"; } { matches = [ {app-id = "Alacritty";} {app-id = "com.mitchellh.ghostty";} {app-id = "org.wezfurlong.wezterm";} {app-id = "app.zen_browser.zen";} ]; # default-column-width = {proportion = 1.0;}; draw-border-with-background = false; } { matches = [{app-id = "org.quickshell";}]; open-floating = true; } ]; layer-rules = [ { matches = [{namespace = "^quickshell$";}]; place-within-backdrop = true; } ]; }; }