From ccea5f6e2261b05b94046d176c2fff4a468b2087 Mon Sep 17 00:00:00 2001 From: Skyler Grey Date: Tue, 30 Sep 2025 20:43:50 +0000 Subject: [PATCH] fix(swayidle): move startup to systemd Change-Id: unpoyppnxnzkktwqtsumpmtttsmqnzwm Previously we were starting swayidle on niri. Unfortunately, this caused a race condition where niri idle inhibitions were not respected. As niri gets idle inhibitions from, say, browsers when playing video, this meant we had to do nasty hacks such as manually systemd-inhibiting via a shell By moving startup to systemd, we can start swayidle later - and in much the same way as starting our SSH agent later avoids its race conditions, starting swayidle later fixes this issue... --- packetmix/homes/coded/niri.nix | 2 +- packetmix/homes/niri/niri.nix | 495 ++++++++++++++---------------- packetmix/homes/niri/swayidle.nix | 46 +++ 3 files changed, 281 insertions(+), 262 deletions(-) create mode 100644 packetmix/homes/niri/swayidle.nix diff --git a/packetmix/homes/coded/niri.nix b/packetmix/homes/coded/niri.nix index 1c955e79..ddccb138 100644 --- a/packetmix/homes/coded/niri.nix +++ b/packetmix/homes/coded/niri.nix @@ -5,7 +5,7 @@ { ingredient.niri.enable = true; - ingredient.niri.niri.timers = { + ingredient.niri.swayidle.timers = { lock = 900; sleep = 1800; }; diff --git a/packetmix/homes/niri/niri.nix b/packetmix/homes/niri/niri.nix index 9c328ad8..1658d0b9 100644 --- a/packetmix/homes/niri/niri.nix +++ b/packetmix/homes/niri/niri.nix @@ -33,17 +33,10 @@ ''; }; }; - timers = { - lock = lib.mkOption { - type = lib.types.int; - description = "How long while idling before locking the device (in seconds)"; - default = 300; - }; - sleep = lib.mkOption { - type = lib.types.addCheck lib.types.int (x: x >= config.ingredient.niri.niri.timers.lock); - description = "How long while idling before sleeping the device (in seconds)"; - default = 450; - }; + lockCommand = lib.mkOption { + type = lib.types.str; + description = "The command run when you lock your computer, or when it is locked automatically"; + default = ''${config.programs.niri.package}/bin/niri msg action do-screen-transition && ${pkgs.swaylock}/bin/swaylock -i ${config.ingredient.niri.niri.lockscreen} -s fill -f''; }; overviewBackground = lib.mkOption { type = lib.types.path; @@ -62,272 +55,252 @@ }; config = { - programs.niri = - let - lock = ''${config.programs.niri.package}/bin/niri msg action do-screen-transition && ${pkgs.swaylock}/bin/swaylock -i ${config.ingredient.niri.niri.lockscreen} -s fill -f''; - in - { - enable = true; - - package = pkgs.niri; - - settings = { - xwayland-satellite = { - enable = true; - path = "${pkgs.xwayland-satellite}/bin/xwayland-satellite"; - }; - environment.NIXOS_OZONE_WL = "1"; - - input.keyboard = { - track-layout = "window"; - repeat-delay = 200; - repeat-rate = 25; - - xkb = lib.mkIf (config.home.keyboard != null) { - layout = if config.home.keyboard.layout == null then "" else config.home.keyboard.layout; - model = if config.home.keyboard.model == null then "" else config.home.keyboard.model; - options = builtins.concatStringsSep "," config.home.keyboard.options; - variant = if config.home.keyboard.variant == null then "" else config.home.keyboard.variant; - }; - }; + programs.niri = { + enable = true; - input.touchpad.natural-scroll = true; - input.touchpad.click-method = "clickfinger"; + package = pkgs.niri; - input.warp-mouse-to-focus.enable = true; - input.focus-follows-mouse = { - enable = true; - max-scroll-amount = "0%"; + settings = { + xwayland-satellite = { + enable = true; + path = "${pkgs.xwayland-satellite}/bin/xwayland-satellite"; + }; + environment.NIXOS_OZONE_WL = "1"; + + input.keyboard = { + track-layout = "window"; + repeat-delay = 200; + repeat-rate = 25; + + xkb = lib.mkIf (config.home.keyboard != null) { + layout = if config.home.keyboard.layout == null then "" else config.home.keyboard.layout; + model = if config.home.keyboard.model == null then "" else config.home.keyboard.model; + options = builtins.concatStringsSep "," config.home.keyboard.options; + variant = if config.home.keyboard.variant == null then "" else config.home.keyboard.variant; }; + }; - input.power-key-handling.enable = false; - - binds = - let - mod = "Super"; - mod1 = "Alt"; - - generateWorkspaceBindings = workspaceNumber: { - "${mod}+${builtins.toString (lib.mod workspaceNumber 10)}".action.focus-workspace = [ - workspaceNumber - ]; - "${mod}+Shift+${builtins.toString (lib.mod workspaceNumber 10)}".action.move-column-to-workspace = [ - workspaceNumber - ]; - }; - joinAttrsetList = listOfAttrsets: lib.fold (a: b: a // b) { } listOfAttrsets; - in - { - # General Keybinds - "${mod}+Q".action.close-window = [ ]; - "${mod}+Shift+Q".action.quit = [ ]; - "${mod}+Return".action.spawn = "${pkgs.ghostty}/bin/ghostty"; - "${mod}+L".action.spawn = [ - "sh" - "-c" - lock - ]; - "${mod}+P".action.power-off-monitors = [ ]; - - "${mod}+R".action.screenshot = [ ]; - "${mod}+Ctrl+R".action.screenshot-screen = [ ]; - "${mod}+Shift+R".action.screenshot-window = [ ]; - "Print".action.screenshot = [ ]; - "Ctrl+Print".action.screenshot-screen = [ ]; - "Shift+Print".action.screenshot-window = [ ]; + input.touchpad.natural-scroll = true; + input.touchpad.click-method = "clickfinger"; - "${mod}+Space".action.switch-layout = [ "next" ]; - "${mod}+Shift+Space".action.switch-layout = [ "prev" ]; + input.warp-mouse-to-focus.enable = true; + input.focus-follows-mouse = { + enable = true; + max-scroll-amount = "0%"; + }; - "${mod}+Shift+Slash".action.show-hotkey-overlay = [ ]; + input.power-key-handling.enable = false; - "${mod}+V".action.set-dynamic-cast-monitor = [ ]; - "${mod}+W".action.set-dynamic-cast-window = [ ]; - "${mod}+Shift+V".action.clear-dynamic-cast-target = [ ]; - "${mod}+Shift+W".action.clear-dynamic-cast-target = [ ]; + binds = + let + mod = "Super"; + mod1 = "Alt"; - "${mod}+N".action.spawn = [ - "sh" - "-c" - "${pkgs.systemd}/bin/systemctl --user start swaync && ${pkgs.swaynotificationcenter}/bin/swaync-client -t" + generateWorkspaceBindings = workspaceNumber: { + "${mod}+${builtins.toString (lib.mod workspaceNumber 10)}".action.focus-workspace = [ + workspaceNumber ]; - # We need to ensure swaync is started, since as it isn't usually until we get a notification - } - // - # Workspace Keybinds - (lib.pipe (lib.range 1 10) [ - (map generateWorkspaceBindings) - joinAttrsetList - ]) - // - # Window Manipulation Bindings - ({ - "${mod}+BracketLeft".action.consume-or-expel-window-left = [ ]; - "${mod}+BracketRight".action.consume-or-expel-window-right = [ ]; - "${mod}+Shift+BracketLeft".action.consume-window-into-column = [ ]; - "${mod}+Shift+BracketRight".action.expel-window-from-column = [ ]; - "${mod}+Slash".action.switch-preset-column-width = [ ]; - "${mod}+${mod1}+F".action.fullscreen-window = [ ]; - "${mod}+${mod1}+Shift+F".action.toggle-windowed-fullscreen = [ ]; - - # Focus - "${mod}+Up".action.focus-window-or-workspace-up = [ ]; - "${mod}+Down".action.focus-window-or-workspace-down = [ ]; - - # Non Jump Movement - "${mod}+Shift+Up".action.move-window-up-or-to-workspace-up = [ ]; - "${mod}+Shift+Down".action.move-window-down-or-to-workspace-down = [ ]; - "${mod}+Shift+Left".action.consume-or-expel-window-left = [ ]; - "${mod}+Shift+Right".action.consume-or-expel-window-right = [ ]; - - # To Monitor - "${mod}+Shift+Ctrl+Up".action.move-window-to-monitor-up = [ ]; - "${mod}+Shift+Ctrl+Down".action.move-window-to-monitor-down = [ ]; - "${mod}+Shift+Ctrl+Left".action.move-window-to-monitor-left = [ ]; - "${mod}+Shift+Ctrl+Right".action.move-window-to-monitor-right = [ ]; - - # To Workspace - "${mod}+Ctrl+Up".action.move-window-to-workspace-up = [ ]; - "${mod}+Ctrl+Down".action.move-window-to-workspace-down = [ ]; - - # Sizing - "${mod}+Equal".action.set-window-height = [ "+5%" ]; - "${mod}+Minus".action.set-window-height = [ "-5%" ]; - }) - // - # Column Manipulation Bindings - ({ - # Focus - "${mod}+Left".action.focus-column-left = [ ]; - "${mod}+Right".action.focus-column-right = [ ]; - "${mod}+${mod1}+C".action.center-column = [ ]; - "${mod}+F".action.maximize-column = [ ]; - - # Non Monitor Movement - "${mod}+${mod1}+Shift+Up".action.move-column-to-workspace-up = [ ]; - "${mod}+${mod1}+Shift+Down".action.move-column-to-workspace-down = [ ]; - "${mod}+${mod1}+Shift+Left".action.move-column-left = [ ]; - "${mod}+${mod1}+Shift+Right".action.move-column-right = [ ]; - - # To Monitor - "${mod}+${mod1}+Shift+Ctrl+Up".action.move-column-to-monitor-up = [ ]; - "${mod}+${mod1}+Shift+Ctrl+Down".action.move-column-to-monitor-down = [ ]; - "${mod}+${mod1}+Shift+Ctrl+Left".action.move-column-to-monitor-left = [ ]; - "${mod}+${mod1}+Shift+Ctrl+Right".action.move-column-to-monitor-right = [ ]; - - # Sizing - "${mod}+${mod1}+Equal".action.set-column-width = [ "+5%" ]; - "${mod}+${mod1}+Minus".action.set-column-width = [ "-5%" ]; - }) - // - # Workspace Manipulation Bindings - ({ - # Focus - "${mod}+Page_Up".action.focus-workspace-up = [ ]; - "${mod}+Page_Down".action.focus-workspace-down = [ ]; - - # Within Itself - "${mod}+Shift+Page_Up".action.move-workspace-up = [ ]; - "${mod}+Shift+Page_Down".action.move-workspace-down = [ ]; - - # To Monitor - "${mod}+Shift+Ctrl+Page_Up".action.move-workspace-to-monitor-up = [ ]; - "${mod}+Shift+Ctrl+Page_Down".action.move-workspace-to-monitor-down = [ ]; - "${mod}+Shift+Ctrl+Home".action.move-workspace-to-monitor-left = [ ]; - "${mod}+Shift+Ctrl+End".action.move-workspace-to-monitor-right = [ ]; - - "${mod}+Space" = { - action.toggle-overview = [ ]; - repeat = false; - }; - }) - // { - # Audio - "XF86AudioRaiseVolume" = { - allow-when-locked = true; - action.spawn = [ - "${pkgs.wireplumber}/bin/wpctl" - "set-volume" - "@DEFAULT_AUDIO_SINK@" - "0.05+" - ]; - }; - "XF86AudioLowerVolume" = { - allow-when-locked = true; - action.spawn = [ - "${pkgs.wireplumber}/bin/wpctl" - "set-volume" - "@DEFAULT_AUDIO_SINK@" - "0.05-" - ]; - }; - "XF86AudioMute" = { - allow-when-locked = true; - action.spawn = [ - "${pkgs.wireplumber}/bin/wpctl" - "set-mute" - "@DEFAULT_AUDIO_SINK@" - "toggle" - ]; - }; - "XF86AudioMicMute" = { - allow-when-locked = true; - action.spawn = [ - "${pkgs.wireplumber}/bin/wpctl" - "set-mute" - "@DEFAULT_AUDIO_SOURCE@" - "toggle" - ]; + "${mod}+Shift+${builtins.toString (lib.mod workspaceNumber 10)}".action.move-column-to-workspace = [ + workspaceNumber + ]; + }; + joinAttrsetList = listOfAttrsets: lib.fold (a: b: a // b) { } listOfAttrsets; + in + { + # General Keybinds + "${mod}+Q".action.close-window = [ ]; + "${mod}+Shift+Q".action.quit = [ ]; + "${mod}+Return".action.spawn = "${pkgs.ghostty}/bin/ghostty"; + "${mod}+L".action.spawn = [ + "sh" + "-c" + config.ingredient.niri.niri.lockCommand + ]; + "${mod}+P".action.power-off-monitors = [ ]; + + "${mod}+R".action.screenshot = [ ]; + "${mod}+Ctrl+R".action.screenshot-screen = [ ]; + "${mod}+Shift+R".action.screenshot-window = [ ]; + "Print".action.screenshot = [ ]; + "Ctrl+Print".action.screenshot-screen = [ ]; + "Shift+Print".action.screenshot-window = [ ]; + + "${mod}+Space".action.switch-layout = [ "next" ]; + "${mod}+Shift+Space".action.switch-layout = [ "prev" ]; + + "${mod}+Shift+Slash".action.show-hotkey-overlay = [ ]; + + "${mod}+V".action.set-dynamic-cast-monitor = [ ]; + "${mod}+W".action.set-dynamic-cast-window = [ ]; + "${mod}+Shift+V".action.clear-dynamic-cast-target = [ ]; + "${mod}+Shift+W".action.clear-dynamic-cast-target = [ ]; + + "${mod}+N".action.spawn = [ + "sh" + "-c" + "${pkgs.systemd}/bin/systemctl --user start swaync && ${pkgs.swaynotificationcenter}/bin/swaync-client -t" + ]; + # We need to ensure swaync is started, since as it isn't usually until we get a notification + } + // + # Workspace Keybinds + (lib.pipe (lib.range 1 10) [ + (map generateWorkspaceBindings) + joinAttrsetList + ]) + // + # Window Manipulation Bindings + ({ + "${mod}+BracketLeft".action.consume-or-expel-window-left = [ ]; + "${mod}+BracketRight".action.consume-or-expel-window-right = [ ]; + "${mod}+Shift+BracketLeft".action.consume-window-into-column = [ ]; + "${mod}+Shift+BracketRight".action.expel-window-from-column = [ ]; + "${mod}+Slash".action.switch-preset-column-width = [ ]; + "${mod}+${mod1}+F".action.fullscreen-window = [ ]; + "${mod}+${mod1}+Shift+F".action.toggle-windowed-fullscreen = [ ]; + + # Focus + "${mod}+Up".action.focus-window-or-workspace-up = [ ]; + "${mod}+Down".action.focus-window-or-workspace-down = [ ]; + + # Non Jump Movement + "${mod}+Shift+Up".action.move-window-up-or-to-workspace-up = [ ]; + "${mod}+Shift+Down".action.move-window-down-or-to-workspace-down = [ ]; + "${mod}+Shift+Left".action.consume-or-expel-window-left = [ ]; + "${mod}+Shift+Right".action.consume-or-expel-window-right = [ ]; + + # To Monitor + "${mod}+Shift+Ctrl+Up".action.move-window-to-monitor-up = [ ]; + "${mod}+Shift+Ctrl+Down".action.move-window-to-monitor-down = [ ]; + "${mod}+Shift+Ctrl+Left".action.move-window-to-monitor-left = [ ]; + "${mod}+Shift+Ctrl+Right".action.move-window-to-monitor-right = [ ]; + + # To Workspace + "${mod}+Ctrl+Up".action.move-window-to-workspace-up = [ ]; + "${mod}+Ctrl+Down".action.move-window-to-workspace-down = [ ]; + + # Sizing + "${mod}+Equal".action.set-window-height = [ "+5%" ]; + "${mod}+Minus".action.set-window-height = [ "-5%" ]; + }) + // + # Column Manipulation Bindings + ({ + # Focus + "${mod}+Left".action.focus-column-left = [ ]; + "${mod}+Right".action.focus-column-right = [ ]; + "${mod}+${mod1}+C".action.center-column = [ ]; + "${mod}+F".action.maximize-column = [ ]; + + # Non Monitor Movement + "${mod}+${mod1}+Shift+Up".action.move-column-to-workspace-up = [ ]; + "${mod}+${mod1}+Shift+Down".action.move-column-to-workspace-down = [ ]; + "${mod}+${mod1}+Shift+Left".action.move-column-left = [ ]; + "${mod}+${mod1}+Shift+Right".action.move-column-right = [ ]; + + # To Monitor + "${mod}+${mod1}+Shift+Ctrl+Up".action.move-column-to-monitor-up = [ ]; + "${mod}+${mod1}+Shift+Ctrl+Down".action.move-column-to-monitor-down = [ ]; + "${mod}+${mod1}+Shift+Ctrl+Left".action.move-column-to-monitor-left = [ ]; + "${mod}+${mod1}+Shift+Ctrl+Right".action.move-column-to-monitor-right = [ ]; + + # Sizing + "${mod}+${mod1}+Equal".action.set-column-width = [ "+5%" ]; + "${mod}+${mod1}+Minus".action.set-column-width = [ "-5%" ]; + }) + // + # Workspace Manipulation Bindings + ({ + # Focus + "${mod}+Page_Up".action.focus-workspace-up = [ ]; + "${mod}+Page_Down".action.focus-workspace-down = [ ]; + + # Within Itself + "${mod}+Shift+Page_Up".action.move-workspace-up = [ ]; + "${mod}+Shift+Page_Down".action.move-workspace-down = [ ]; + + # To Monitor + "${mod}+Shift+Ctrl+Page_Up".action.move-workspace-to-monitor-up = [ ]; + "${mod}+Shift+Ctrl+Page_Down".action.move-workspace-to-monitor-down = [ ]; + "${mod}+Shift+Ctrl+Home".action.move-workspace-to-monitor-left = [ ]; + "${mod}+Shift+Ctrl+End".action.move-workspace-to-monitor-right = [ ]; + + "${mod}+Space" = { + action.toggle-overview = [ ]; + repeat = false; }; + }) + // { + # Audio + "XF86AudioRaiseVolume" = { + allow-when-locked = true; + action.spawn = [ + "${pkgs.wireplumber}/bin/wpctl" + "set-volume" + "@DEFAULT_AUDIO_SINK@" + "0.05+" + ]; }; + "XF86AudioLowerVolume" = { + allow-when-locked = true; + action.spawn = [ + "${pkgs.wireplumber}/bin/wpctl" + "set-volume" + "@DEFAULT_AUDIO_SINK@" + "0.05-" + ]; + }; + "XF86AudioMute" = { + allow-when-locked = true; + action.spawn = [ + "${pkgs.wireplumber}/bin/wpctl" + "set-mute" + "@DEFAULT_AUDIO_SINK@" + "toggle" + ]; + }; + "XF86AudioMicMute" = { + allow-when-locked = true; + action.spawn = [ + "${pkgs.wireplumber}/bin/wpctl" + "set-mute" + "@DEFAULT_AUDIO_SOURCE@" + "toggle" + ]; + }; + }; - layout = { - gaps = 16; - - center-focused-column = "on-overflow"; + layout = { + gaps = 16; - preset-column-widths = [ - { proportion = 1. / 4.; } - { proportion = 1. / 3.; } - { proportion = 1. / 2.; } - { proportion = 2. / 3.; } - { proportion = 9. / 10.; } - ]; # TODO: clicks to PR a docs update for niri-flake - }; + center-focused-column = "on-overflow"; - prefer-no-csd = true; # No "client-side-decorations" (i.e. client-side window open/close buttons) - hotkey-overlay.skip-at-startup = true; - screenshot-path = null; - - spawn-at-startup = [ - { - command = [ - "${pkgs.swaybg}/bin/swaybg" - "-i" - "${config.ingredient.niri.niri.wallpaper}" - "-m" - "fill" - ]; - } - { - command = [ - "${pkgs.swayidle}/bin/swayidle" - "-w" - "timeout" - (toString config.ingredient.niri.niri.timers.lock) - lock - "timeout" - (toString config.ingredient.niri.niri.timers.sleep) - "niri msg action power-off-monitors" - "resume" - "niri msg action power-on-monitors" # Not sure if this is really needed - niri normally powers on monitors on a movement action anyway, but maybe this can affect resuming in different ways? - "before-sleep" - lock - ]; - } - ]; + preset-column-widths = [ + { proportion = 1. / 4.; } + { proportion = 1. / 3.; } + { proportion = 1. / 2.; } + { proportion = 2. / 3.; } + { proportion = 9. / 10.; } + ]; # TODO: clicks to PR a docs update for niri-flake }; + + prefer-no-csd = true; # No "client-side-decorations" (i.e. client-side window open/close buttons) + hotkey-overlay.skip-at-startup = true; + screenshot-path = null; + + spawn-at-startup = [ + { + command = [ + "${pkgs.swaybg}/bin/swaybg" + "-i" + "${config.ingredient.niri.niri.wallpaper}" + "-m" + "fill" + ]; + } + ]; }; + }; programs.bash.profileExtra = lib.mkBefore '' if [ -z $WAYLAND_DISPLAY ] && [ "$(tty)" = "/dev/tty1" ]; then diff --git a/packetmix/homes/niri/swayidle.nix b/packetmix/homes/niri/swayidle.nix new file mode 100644 index 00000000..47d4b8bf --- /dev/null +++ b/packetmix/homes/niri/swayidle.nix @@ -0,0 +1,46 @@ +# SPDX-FileCopyrightText: 2025 FreshlyBakedCake +# +# SPDX-License-Identifier: MIT + +{ + pkgs, + lib, + config, + ... +}: +{ + options.ingredient.niri.swayidle.timers = { + lock = lib.mkOption { + type = lib.types.int; + description = "How long while idling before locking the device (in seconds)"; + default = 300; + }; + sleep = lib.mkOption { + type = lib.types.addCheck lib.types.int (x: x >= config.ingredient.niri.swayidle.timers.lock); + description = "How long while idling before sleeping the device (in seconds)"; + default = 450; + }; + }; + + config.systemd.user.services.swayidle = { + Unit.After = [ "niri.service" ]; + Install.WantedBy = [ "niri.service" ]; + + Service.ExecStart = builtins.concatStringsSep " " ( + map (arg: "'${arg}'") [ + "${pkgs.swayidle}/bin/swayidle" + "-w" + "timeout" + (toString config.ingredient.niri.swayidle.timers.lock) + config.ingredient.niri.niri.lockCommand + "timeout" + (toString config.ingredient.niri.swayidle.timers.sleep) + "niri msg action power-off-monitors" + "resume" + "niri msg action power-on-monitors" # Not sure if this is really needed - niri normally powers on monitors on a movement action anyway, but maybe this can affect resuming in different ways? + "before-sleep" + config.ingredient.niri.niri.lockCommand + ] + ); # There's some nastiness here around "what happens if your commands contain single quotes"... at the moment, don't :) + }; +} -- 2.43.0