1
0
Fork 0
mirror of https://github.com/nix-community/home-manager.git synced 2025-12-06 17:11:03 +01:00

swayidle: improve confusing "events" configuration

With swayidle one can configure two different kinds of hooks:

  - Idle timeouts are executed after the session has been idle for a
    specific amount of time.

  - Events are executed when systemd notifies us that for example the
    user session is locked or that the device is about to suspend.

While not obvious, there is a significant difference between how these
two kinds are configured: there can be several timeouts with separate
commands to be executed, but each event can only be specified once. If
an event is specified multiple times, then the last command wins.

This can be very easy to miss in swayidle's documentation. Furthermore,
because the config is a list of `{ event = "..."; command = "..."; }`
attrset, we double down on this confusion and make it seem like having
multiple handlers for an event was actually supported.

Fix this by converting from a list of "event" submodules to an attrset
where the key is the event name and the value is the command to be
executed. This makes it impossible to specify multiple commands for a
single event by accident.

If a user _does_ want to have multiple commands executed on any event
they can for example use `pkgs.writeShellScript` and manually chain the
commands in that script.
This commit is contained in:
Patrick Steinhardt 2025-11-17 22:04:08 +01:00 committed by Austin Horstman
parent 93d907a205
commit 281e9398cc
4 changed files with 108 additions and 31 deletions

View file

@ -42,21 +42,30 @@ in
}; };
}; };
eventModule = { eventsModule = {
options = { options = {
event = mkOption { before-sleep = mkOption {
type = types.enum [ type = types.nullOr types.str;
"before-sleep" default = null;
"after-resume" description = "Command to run before suspending.";
"lock"
"unlock"
];
description = "Event name.";
}; };
command = mkOption { after-resume = mkOption {
type = types.str; type = types.nullOr types.str;
description = "Command to run when event occurs."; default = null;
description = "Command to run after resuming.";
};
lock = mkOption {
type = types.nullOr types.str;
default = null;
description = "Command to run when the logind session is locked.";
};
unlock = mkOption {
type = types.nullOr types.str;
default = null;
description = "Command to run when the logind session is unlocked.";
}; };
}; };
}; };
@ -80,13 +89,31 @@ in
}; };
events = mkOption { events = mkOption {
type = with types; listOf (submodule eventModule); type =
with types;
(coercedTo (listOf attrs)) (
events:
lib.warn
''
The syntax of services.swayidle.events has changed. While it
previously accepted a list of events, it now accepts an attrset
keyed by the event name.
''
(
lib.listToAttrs (
map (e: {
name = e.event;
value = e.command;
}) events
)
)
) (submodule eventsModule);
default = [ ]; default = [ ];
example = literalExpression '' example = literalExpression ''
[ {
{ event = "before-sleep"; command = "''${pkgs.swaylock}/bin/swaylock -fF"; } "before-sleep" = "''${pkgs.swaylock}/bin/swaylock -fF";
{ event = "lock"; command = "lock"; } "lock" = "lock";
] }
''; '';
description = "Run command on occurrence of a event."; description = "Run command on occurrence of a event.";
}; };
@ -144,13 +171,17 @@ in
t.resumeCommand t.resumeCommand
]; ];
mkEvent = e: [ mkEvent = event: command: [
e.event event
e.command command
]; ];
nonemptyEvents = lib.filterAttrs (event: command: command != null) cfg.events;
args = args =
cfg.extraArgs ++ (lib.concatMap mkTimeout cfg.timeouts) ++ (lib.concatMap mkEvent cfg.events); cfg.extraArgs
++ (lib.concatMap mkTimeout cfg.timeouts)
++ (lib.flatten (lib.mapAttrsToList mkEvent nonemptyEvents));
in in
"${lib.getExe cfg.package} ${lib.escapeShellArgs args}"; "${lib.getExe cfg.package} ${lib.escapeShellArgs args}";
}; };

View file

@ -19,16 +19,10 @@
resumeCommand = ''swaymsg "output * dpms on"''; resumeCommand = ''swaymsg "output * dpms on"'';
} }
]; ];
events = [ events = {
{ before-sleep = "swaylock -fF";
event = "before-sleep"; lock = "swaylock -fF";
command = "swaylock -fF"; };
}
{
event = "lock";
command = "swaylock -fF";
}
];
}; };
nmt.script = '' nmt.script = ''

View file

@ -2,4 +2,5 @@
lib.optionalAttrs pkgs.stdenv.hostPlatform.isLinux { lib.optionalAttrs pkgs.stdenv.hostPlatform.isLinux {
swayidle-basic-configuration = ./basic-configuration.nix; swayidle-basic-configuration = ./basic-configuration.nix;
swayidle-legacy-configuration = ./legacy-configuration.nix;
} }

View file

@ -0,0 +1,51 @@
{ config, ... }:
{
services.swayidle = {
enable = true;
package = config.lib.test.mkStubPackage { outPath = "@swayidle@"; };
events = [
{
event = "lock";
command = "swaylock -fF";
}
{
event = "before-sleep";
command = "swaylock -fF";
}
];
};
test.asserts.evalWarnings.expected = [
''
The syntax of services.swayidle.events has changed. While it
previously accepted a list of events, it now accepts an attrset
keyed by the event name.
''
];
nmt.script = ''
serviceFile=home-files/.config/systemd/user/swayidle.service
assertFileExists "$serviceFile"
serviceFileNormalized="$(normalizeStorePaths "$serviceFile")"
assertFileContent "$serviceFileNormalized" ${builtins.toFile "expected.service" ''
[Install]
WantedBy=graphical-session.target
[Service]
Environment=PATH=@bash-interactive@/bin
ExecStart=@swayidle@/bin/dummy -w before-sleep 'swaylock -fF' lock 'swaylock -fF'
Restart=always
Type=simple
[Unit]
After=graphical-session.target
ConditionEnvironment=WAYLAND_DISPLAY
Description=Idle manager for Wayland
Documentation=man:swayidle(1)
PartOf=graphical-session.target
''}
'';
}