niri-unstable.url = "github:YaLTeR/niri";
niri = {
  url = "github:sodiboo/niri-flake";
  inputs.niri-unstable.follows = "niri-unstable";
};
flake.nix:inputs
{ pkgs, ... }:
 
{
  imports = [ inputs.niri.nixosModules.niri ];
  nixpkgs.overlays = [ inputs.niri.overlays.niri ];
  programs.niri = {
    enable = true;
    package = pkgs.niri-unstable;
  };
}
nixos

Niri uses xdg-desktop-portal-gnome for screen sharing, and it looks like it nowdays implement the FileChooser call, which depends on nautilus.

{ pkgs, ... }:
 
{
  services.dbus.packages = [ pkgs.nautilus ];
}
nixos
{
  lib,
  pkgs,
  config,
  nixosConfig,
  ...
}:
 
{
  programs.niri.settings = {
    prefer-no-csd = true;
    layout.shadow.enable = true;
    input = {
      focus-follows-mouse = {
        enable = true;
        max-scroll-amount = "0%";
      };
      keyboard.xkb = with nixosConfig.services.xserver.xkb; {
        inherit variant layout options;
      };
    };
    window-rules = [
      {
        matches = [ { app-id = "authentication-agent-1|pwvucontrol"; } ];
        open-floating = true;
      }
    ];
    binds =
      with config.lib.niri.actions;
      let
        mod = if nixosConfig.virtualisation ? qemu then "Alt" else "Mod";
        set-volume = spawn "wpctl" "set-volume" "@DEFAULT_AUDIO_SINK@";
        brillo = spawn "${pkgs.brillo}/bin/brillo" "-q" "-u" "300000";
        playerctl = spawn "${pkgs.playerctl}/bin/playerctl";
      in
      {
        "${mod}+Shift+Slash".action = show-hotkey-overlay;
        "${mod}+D".action = spawn "fuzzel";
        "${mod}+Return".action = spawn "kitty";
        "Super+Alt+L".action =
          spawn "sh" "-c"
            "loginctl lock-session && sleep 5 && niri msg action power-off-monitors";
 
        XF86AudioRaiseVolume.action = set-volume "5%+";
        XF86AudioLowerVolume.action = set-volume "5%-";
        XF86AudioMute.action = spawn "wpctl" "set-mute" "@DEFAULT_AUDIO_SINK@" "toggle";
        XF86AudioMicMute.action = spawn "wpctl" "set-mute" "@DEFAULT_AUDIO_SOURCE@" "toggle";
 
        XF86AudioPlay.action = playerctl "play-pause";
        XF86AudioStop.action = playerctl "pause";
        XF86AudioPrev.action = playerctl "previous";
        XF86AudioNext.action = playerctl "next";
 
        XF86MonBrightnessUp.action = brillo "-A" "5";
        XF86MonBrightnessDown.action = brillo "-U" "5";
 
        # // Open/close the Overview: a zoomed-out view of workspaces and windows.
        # // You can also move the mouse into the top-left hot corner,
        # // or do a four-finger swipe up on a touchpad.
        "${mod}+O" = { action = toggle-overview; repeat = false; };
 
        "${mod}+Shift+Q".action = close-window;
        "${mod}+Left".action = focus-column-left;
        "${mod}+Down".action = focus-window-down;
        "${mod}+Up".action = focus-window-up;
        "${mod}+Right".action = focus-column-right;
        "${mod}+H".action = focus-column-left;
        "${mod}+J".action = focus-window-down;
        "${mod}+K".action = focus-window-up;
        "${mod}+L".action = focus-column-right;
 
        "${mod}+Ctrl+Left".action = move-column-left-or-to-monitor-left;
        "${mod}+Ctrl+Down".action = move-window-down-or-to-workspace-down;
        "${mod}+Ctrl+Up".action = move-window-up-or-to-workspace-up;
        "${mod}+Ctrl+Right".action = move-column-right-or-to-monitor-right;
        "${mod}+Ctrl+H".action = move-column-left-or-to-monitor-left;
        "${mod}+Ctrl+J".action = move-window-down-or-to-workspace-down;
        "${mod}+Ctrl+K".action = move-window-up-or-to-workspace-up;
        "${mod}+Ctrl+L".action = move-column-right-or-to-monitor-right;
 
        "${mod}+Home".action = focus-column-first;
        "${mod}+End".action = focus-column-last;
        "${mod}+Ctrl+Home".action = move-column-to-first;
        "${mod}+Ctrl+End".action = move-column-to-last;
 
        "${mod}+Shift+Left".action = focus-monitor-left;
        "${mod}+Shift+Down".action = focus-monitor-down;
        "${mod}+Shift+Up".action = focus-monitor-up;
        "${mod}+Shift+Right".action = focus-monitor-right;
        "${mod}+Shift+H".action = focus-monitor-left;
        "${mod}+Shift+J".action = focus-monitor-down;
        "${mod}+Shift+K".action = focus-monitor-up;
        "${mod}+Shift+L".action = focus-monitor-right;
 
        "${mod}+Shift+Ctrl+Left".action = move-column-to-monitor-left;
        "${mod}+Shift+Ctrl+Down".action = move-column-to-monitor-down;
        "${mod}+Shift+Ctrl+Up".action = move-column-to-monitor-up;
        "${mod}+Shift+Ctrl+Right".action = move-column-to-monitor-right;
        "${mod}+Shift+Ctrl+H".action = move-column-to-monitor-left;
        "${mod}+Shift+Ctrl+J".action = move-column-to-monitor-down;
        "${mod}+Shift+Ctrl+K".action = move-column-to-monitor-up;
        "${mod}+Shift+Ctrl+L".action = move-column-to-monitor-right;
 
        # // Alternatively, there are commands to move just a single window:
        # // ${mod}+Shift+Ctrl+Left  { move-window-to-monitor-left; }
        # // ...
 
        # // And you can also move a whole workspace to another monitor:
        # // ${mod}+Shift+Ctrl+Left  { move-workspace-to-monitor-left; }
        # // ...
 
        "${mod}+Page_Down".action = focus-workspace-down;
        "${mod}+Page_Up".action = focus-workspace-up;
        "${mod}+U".action = focus-workspace-down;
        "${mod}+I".action = focus-workspace-up;
        "${mod}+Ctrl+Page_Down".action = move-column-to-workspace-down;
        "${mod}+Ctrl+Page_Up".action = move-column-to-workspace-up;
        "${mod}+Ctrl+U".action = move-column-to-workspace-down;
        "${mod}+Ctrl+I".action = move-column-to-workspace-up;
        # // Alternatively, there are commands to move just a single window:
        # // ${mod}+Ctrl+Page_Down { move-window-to-workspace-down; }
        # // ...
 
        "${mod}+Shift+Page_Down".action = move-workspace-down;
        "${mod}+Shift+Page_Up".action = move-workspace-up;
        "${mod}+Shift+U".action = move-workspace-down;
        "${mod}+Shift+I".action = move-workspace-up;
 
        # // You can bind mouse wheel scroll ticks using the following syntax.
        # // These binds will change direction based on the natural-scroll setting.
        # //
        # // To avoid scrolling through workspaces really fast, you can use
        # // the cooldown-ms property. The bind will be rate-limited to this value.
        # // You can set a cooldown on any bind, but it's most useful for the wheel.
        "${mod}+WheelScrollDown" = {
          action = focus-workspace-down;
          cooldown-ms = 150;
        };
        "${mod}+WheelScrollUp" = {
          action = focus-workspace-up;
          cooldown-ms = 150;
        };
        "${mod}+Ctrl+WheelScrollDown" = {
          action = move-column-to-workspace-down;
          cooldown-ms = 150;
        };
        "${mod}+Ctrl+WheelScrollUp" = {
          action = move-column-to-workspace-up;
          cooldown-ms = 150;
        };
 
        "${mod}+WheelScrollRight".action = focus-column-right;
        "${mod}+WheelScrollLeft".action = focus-column-left;
        "${mod}+Ctrl+WheelScrollRight".action = move-column-right;
        "${mod}+Ctrl+WheelScrollLeft".action = move-column-left;
 
        # // Usually scrolling up and down with Shift in applications results in
        # // horizontal scrolling; these binds replicate that.
        "${mod}+Shift+WheelScrollDown".action = focus-column-right;
        "${mod}+Shift+WheelScrollUp".action = focus-column-left;
        "${mod}+Ctrl+Shift+WheelScrollDown".action = move-column-right;
        "${mod}+Ctrl+Shift+WheelScrollUp".action = move-column-left;
 
        # // Similarly, you can bind touchpad scroll "ticks".
        # // Touchpad scrolling is continuous, so for these binds it is split into
        # // discrete intervals.
        # // These binds are also affected by touchpad's natural-scroll, so these
        # // example binds are "inverted", since we have natural-scroll enabled for
        # // touchpads by default.
        # // ${mod}+TouchpadScrollDown { spawn "wpctl" "set-volume" "@DEFAULT_AUDIO_SINK@" "0.02+"; }
        # // ${mod}+TouchpadScrollUp   { spawn "wpctl" "set-volume" "@DEFAULT_AUDIO_SINK@" "0.02-"; }
 
        # // You can refer to workspaces by index. However, keep in mind that
        # // niri is a dynamic workspace system, so these commands are kind of
        # // "best effort". Trying to refer to a workspace index bigger than
        # // the current workspace count will instead refer to the bottommost
        # // (empty) workspace.
        # //
        # // For example, with 2 workspaces + 1 empty, indices 3, 4, 5 and so on
        # // will all refer to the 3rd workspace.
        "${mod}+1".action = focus-workspace 1;
        "${mod}+2".action = focus-workspace 2;
        "${mod}+3".action = focus-workspace 3;
        "${mod}+4".action = focus-workspace 4;
        "${mod}+5".action = focus-workspace 5;
        "${mod}+6".action = focus-workspace 6;
        "${mod}+7".action = focus-workspace 7;
        "${mod}+8".action = focus-workspace 8;
        "${mod}+9".action = focus-workspace 9;
 
        # The wonky format used here is to work-around https://github.com/sodiboo/niri-flake/issues/944
        "${mod}+Ctrl+1".action.move-column-to-workspace = [ 1 ];
        "${mod}+Ctrl+2".action.move-column-to-workspace = [ 2 ];
        "${mod}+Ctrl+3".action.move-column-to-workspace = [ 3 ];
        "${mod}+Ctrl+4".action.move-column-to-workspace = [ 4 ];
        "${mod}+Ctrl+5".action.move-column-to-workspace = [ 5 ];
        "${mod}+Ctrl+6".action.move-column-to-workspace = [ 6 ];
        "${mod}+Ctrl+7".action.move-column-to-workspace = [ 7 ];
        "${mod}+Ctrl+8".action.move-column-to-workspace = [ 8 ];
        "${mod}+Ctrl+9".action.move-column-to-workspace = [ 9 ];
 
        # // Alternatively, there are commands to move just a single window:
        # // ${mod}+Ctrl+1 { move-window-to-workspace 1; }
 
        # // Switches focus between the current and the previous workspace.
        # // ${mod}+Tab { focus-workspace-previous; }
 
        "${mod}+Comma".action = consume-window-into-column;
        "${mod}+Period".action = expel-window-from-column;
 
        # There are also commands that consume or expel a single window to the side.
        "${mod}+BracketLeft".action = consume-or-expel-window-left;
        "${mod}+BracketRight".action = consume-or-expel-window-right;
 
        "${mod}+R".action = switch-preset-column-width;
        "${mod}+Shift+R".action = reset-window-height;
        "${mod}+F".action = maximize-column;
        "${mod}+Shift+F".action = fullscreen-window;
        "${mod}+C".action = center-column;
 
        # // Finer width adjustments.
        # // This command can also:
        # // * set width in pixels: "1000"
        # // * adjust width in pixels: "-5" or "+5"
        # // * set width as a percentage of screen width: "25%"
        # // * adjust width as a percentage of screen width: "-10%" or "+10%"
        # // Pixel sizes use logical, or scaled, pixels. I.e. on an output with scale 2.0,
        # // set-column-width "100" will make the column occupy 200 physical screen pixels.
        "${mod}+Minus".action = set-column-width "-10%";
        "${mod}+Equal".action = set-column-width "+10%";
 
        # // Finer height adjustments when in column with other windows.
        "${mod}+Shift+Minus".action = set-window-height "-10%";
        "${mod}+Shift+Equal".action = set-window-height "+10%";
 
        # // Move the focused window between the floating and the tiling layout.
        "${mod}+V".action = toggle-window-floating;
        "${mod}+Shift+V".action = switch-focus-between-floating-and-tiling;
 
        # // Toggle tabbed column display mode.
        # // Windows in this column will appear as vertical tabs,
        # // rather than stacked on top of each other.
        "${mod}+W".action = toggle-column-tabbed-display;
 
        # // Actions to switch layouts.
        # // Note: if you uncomment these, make sure you do NOT have
        # // a matching layout switch hotkey configured in xkb options above.
        # // Having both at once on the same hotkey will break the switching,
        # // since it will switch twice upon pressing the hotkey (once by xkb, once by niri).
        # // ${mod}+Space       { switch-layout "next"; }
        # // ${mod}+Shift+Space { switch-layout "prev"; }
 
        "Print".action = screenshot;
        "Alt+Print".action = screenshot-window;
 
        # // The quit action will show a confirmation dialog to avoid accidental exits.
        "${mod}+Shift+E".action = quit;
 
        # // Powers off the monitors. To turn them back on, do any input like
        # // moving the mouse or pressing any other key.
        "${mod}+Shift+P".action = power-off-monitors;
      };
    spawn-at-startup = [
      { command = [ "waybar" ]; }
      { command = [ "${lib.getExe pkgs.networkmanagerapplet}" ]; }
      { command = [ "${lib.getExe pkgs.xwayland-satellite}" ]; }
    ];
    environment."DISPLAY" = ":0";
  };
}
home-manager
{ lib, pkgs, ... }:
 
{
  programs.waybar = {
    enable = true;
    settings = [
      {
        layer = "top";
        position = "top";
 
        modules-left = [ "niri/workspaces" ];
        modules-center = [ "niri/window" ];
        modules-right = [
          "idle_inhibitor"
          "niri/language"
          "pulseaudio"
          "disk"
          "battery"
          "custom/notification"
          "tray"
          "clock"
        ];
 
        "niri/workspaces" = {
          format = "{icon} {value}";
          format-icons = {
            active = "";
            default = "";
          };
        };
 
        "niri/window" = {
          icon = true;
        };
 
        idle_inhibitor = {
          format = "{icon}";
          format-icons = {
            activated = "";
            deactivated = "";
          };
        };
 
        "niri/language" = {
          format = "{short} <sup>{variant}</sup>";
        };
        "pulseaudio" = {
          format = "{icon}";
          format-bluetooth = "{icon} ";
          format-muted = "󰝟";
          format-icons = {
            headphone = "";
            default = [ "" "" ];
          };
          scroll-step = 1;
          on-click = "${lib.getExe pkgs.pwvucontrol}";
        };
 
        clock = {
          format = "{:%H:%M}  ";
          format-alt = "{:%A; %B %d, %Y (%R)}  ";
          tooltip-format = "<tt><small>{calendar}</small></tt>";
          calendar = {
            mode = "year";
            mode-mon-col = 3;
            weeks-pos = "right";
            on-scroll = 1;
            on-click-right = "mode";
            format = {
              months = "<span color='#ffead3'><b>{}</b></span>";
              days = "<span color='#ecc6d9'><b>{}</b></span>";
              weeks = "<span color='#99ffdd'><b>W{}</b></span>";
              weekdays = "<span color='#ffcc66'><b>{}</b></span>";
              today = "<span color='#ff6699'><b><u>{}</u></b></span>";
            };
          };
          actions = {
            on-click-right = "mode";
            on-click-forward = "tz_up";
            on-click-backward = "tz_down";
            on-scroll-up = "shift_up";
            on-scroll-down = "shift_down";
          };
        };
 
        battery = {
          format = "{icon}";
 
          format-icons = [ "󰁺" "󰁻" "󰁼" "󰁽" "󰁾" "󰁿" "󰂀" "󰂁" "󰂂" "󰁹" ];
          states = {
            battery-10 = 10;
            battery-20 = 20;
            battery-30 = 30;
            battery-40 = 40;
            battery-50 = 50;
            battery-60 = 60;
            battery-70 = 70;
            battery-80 = 80;
            battery-90 = 90;
            battery-100 = 100;
          };
 
          format-plugged = "󰚥";
          format-charging-battery-10 = "󰢜";
          format-charging-battery-20 = "󰂆";
          format-charging-battery-30 = "󰂇";
          format-charging-battery-40 = "󰂈";
          format-charging-battery-50 = "󰢝";
          format-charging-battery-60 = "󰂉";
          format-charging-battery-70 = "󰢞";
          format-charging-battery-80 = "󰂊";
          format-charging-battery-90 = "󰂋";
          format-charging-battery-100 = "󰂅";
          tooltip-format = "{capacity}% {timeTo}";
        };
 
        "custom/notification" = {
          format = "{icon}  {}  ";
          tooltip-format = "Left: Open Notification Center\nRight: Toggle Do not Disturb\nMiddle: Clear Notifications";
          format-icons = {
            notification = "<span foreground='red'><sup></sup></span>";
            none = "";
            dnd-notification = "<span foreground='red'><sup></sup></span>";
            dnd-none = "";
            inhibited-notification = "<span foreground='red'><sup></sup></span>";
            inhibited-none = "";
            dnd-inhibited-notification = "<span foreground='red'><sup></sup></span>";
            dnd-inhibited-none = "";
          };
          return-type = "json";
          exec-if = "which swaync-client";
          exec = "swaync-client -swb";
          on-click = "swaync-client -t -sw";
          on-click-right = "swaync-client -d -sw";
          on-click-middle = "swaync-client -C";
          escape = true;
        };
 
        tray = {
          icon-size = 21;
          spacing = 10;
        };
      }
    ];
    style = ''
      #workspaces button {
          color: @base05;
      }
    '';
  };
}
{
  _.persist.allUsers.directories = [ ".local/state/wireplumber" ];
}
{
  programs.fuzzel.enable = true;
}
{
  _.persist.users.vlaci.files = [ ".cache/fuzzel" ];
}
{
  services.swaync.enable = true;
}
{ pkgs, ... }:
 
{
  home.packages = with pkgs; [
    wl-clipboard
  ];
}

Working around the issue1 of waybar panels are duplicating after DPMS standby

{ lib, ...}:
 
{
  programs.waybar.systemd.enable = true;
  systemd.user.services."waybar".Service.ExecReload = lib.mkForce "";
}
{
  pkgs,
  lib,
  ...
}:
 
let
  bgImageSection = name: ''
    #${name} {
      background-image: image(url("${pkgs.wlogout}/share/wlogout/icons/${name}.png"));
    }
  '';
in
{
  programs.wlogout = {
    enable = true;
 
    style = ''
      * {
        background: none;
      }
 
      window {
        background-color: rgba(0, 0, 0, .5);
      }
 
      button {
        background: rgba(0, 0, 0, .05);
        border-radius: 8px;
        box-shadow: inset 0 0 0 1px rgba(255, 255, 255, .1), 0 0 rgba(0, 0, 0, .5);
        margin: 1rem;
        background-repeat: no-repeat;
        background-position: center;
        background-size: 25%;
      }
 
      button:focus, button:active, button:hover {
        background-color: rgba(255, 255, 255, 0.2);
        outline-style: none;
      }
 
      ${lib.concatMapStringsSep "\n" bgImageSection [
        "lock"
        "logout"
        "suspend"
        "hibernate"
        "shutdown"
        "reboot"
      ]}
    '';
  };
}

Footnotes

  1. https://github.com/Alexays/Waybar/issues/3344