diff --git a/nixos/modules/services/audio/alsa.nix b/nixos/modules/services/audio/alsa.nix
index f632644af09ef4..69c11df2682184 100644
--- a/nixos/modules/services/audio/alsa.nix
+++ b/nixos/modules/services/audio/alsa.nix
@@ -4,10 +4,36 @@
with lib;
let
-
- inherit (pkgs) alsaUtils;
-
- pulseaudioEnabled = config.hardware.pulseaudio.enable;
+ inherit (pkgs) alsaUtils pulseaudio;
+
+ cfg = config.sound;
+ pulseCfg = config.hardware.pulseaudio;
+ pulseaudioEnabled = pulseCfg.enable;
+
+ volumeCmds =
+ let
+ inherit (cfg.mediaKeys) volumeStep;
+ amixer = "${alsaUtils}/bin/amixer";
+ pactl = "${pulseaudio}/bin/pactl";
+ in
+ {
+ pulseVolume =
+ {
+ up = "${pactl} set-sink-volume @DEFAULT_SINK@ +${volumeStep}";
+ down = "${pactl} set-sink-volume @DEFAULT_SINK@ -${volumeStep}";
+ mute = "${pactl} set-sink-mute @DEFAULT_SINK@ toggle";
+ micMute = "${pactl} set-source-mute @DEFAULT_SOURCE@ toggle";
+ userService = true;
+ };
+ alsaVolume =
+ {
+ up = "${amixer} -q set Master ${volumeStep}%+ unmute";
+ down = "${amixer} -q set Master ${volumeStep}%- unmute";
+ mute = "${amixer} -q set Master toggle";
+ micMute = "${amixer} -q set Capture toggle";
+ userService = false;
+ };
+ };
in
@@ -83,13 +109,14 @@ in
###### implementation
- config = mkIf config.sound.enable {
+ config = mkIf cfg.enable {
environment.systemPackages = [ alsaUtils ];
- environment.etc = mkIf (!pulseaudioEnabled && config.sound.extraConfig != "")
+ environment.etc = mkIf (!pulseaudioEnabled && cfg.extraConfig != "")
[
- { source = pkgs.writeText "asound.conf" config.sound.extraConfig;
+ {
+ source = pkgs.writeText "asound.conf" cfg.extraConfig;
target = "asound.conf";
}
];
@@ -97,10 +124,11 @@ in
# ALSA provides a udev rule for restoring volume settings.
services.udev.packages = [ alsaUtils ];
- boot.kernelModules = optional config.sound.enableOSSEmulation "snd_pcm_oss";
+ boot.kernelModules = optional cfg.enableOSSEmulation "snd_pcm_oss";
systemd.services.alsa-store =
- { description = "Store Sound Card State";
+ {
+ description = "Store Sound Card State";
wantedBy = [ "multi-user.target" ];
unitConfig.RequiresMountsFor = "/var/lib/alsa";
unitConfig.ConditionVirtualization = "!systemd-nspawn";
@@ -112,23 +140,49 @@ in
};
};
- services.actkbd = mkIf config.sound.mediaKeys.enable {
+ services.actkbd = mkIf cfg.mediaKeys.enable {
enable = true;
- bindings = [
- # "Mute" media key
- { keys = [ 113 ]; events = [ "key" ]; command = "${alsaUtils}/bin/amixer -q set Master toggle"; }
-
- # "Lower Volume" media key
- { keys = [ 114 ]; events = [ "key" "rep" ]; command = "${alsaUtils}/bin/amixer -q set Master ${config.sound.mediaKeys.volumeStep}- unmute"; }
-
- # "Raise Volume" media key
- { keys = [ 115 ]; events = [ "key" "rep" ]; command = "${alsaUtils}/bin/amixer -q set Master ${config.sound.mediaKeys.volumeStep}+ unmute"; }
-
- # "Mic Mute" media key
- { keys = [ 190 ]; events = [ "key" ]; command = "${alsaUtils}/bin/amixer -q set Capture toggle"; }
- ];
+ bindings =
+ let
+ volume =
+ if pulseaudioEnabled
+ then volumeCmds.pulseVolume
+ else volumeCmds.alsaVolume;
+ in
+ [
+ # "Mute" media key
+ {
+ userMode = volume.userService;
+ keys = [ 113 ];
+ events = [ "key" ];
+ command = "${volume.mute}";
+ }
+
+ # "Mic Mute" media key
+ {
+ userMode = volume.userService;
+ keys = [ 190 ];
+ events = [ "key" ];
+ command = "${volume.micMute}";
+ }
+
+ # "Lower Volume" media key
+ {
+ userMode = volume.userService;
+ keys = [ 114 ];
+ events = [ "key" "rep" ];
+ command = "${volume.down}";
+ }
+
+ # "Raise Volume" media key
+ {
+ userMode = volume.userService;
+ keys = [ 115 ];
+ events = [ "key" "rep" ];
+ command = "${volume.up}";
+ }
+ ];
};
-
};
}
diff --git a/nixos/modules/services/hardware/actkbd.nix b/nixos/modules/services/hardware/actkbd.nix
index 4168140b287a69..9861b82ba1fce3 100644
--- a/nixos/modules/services/hardware/actkbd.nix
+++ b/nixos/modules/services/hardware/actkbd.nix
@@ -6,18 +6,47 @@ let
cfg = config.services.actkbd;
- configFile = pkgs.writeText "actkbd.conf" ''
+ isUserBindings = bool:
+ builtins.filter (a: a.userMode == bool) cfg.bindings;
+
+ mkConfigFile = isUser:
+ pkgs.writeText "actkbd.conf" ''
${concatMapStringsSep "\n"
({ keys, events, attributes, command, ... }:
''${concatMapStringsSep "+" toString keys}:${concatStringsSep "," events}:${concatStringsSep "," attributes}:${command}''
)
- cfg.bindings}
- ${cfg.extraConfig}
- '';
+ ( isUserBindings isUser )}
+ ${cfg.extraConfig}'';
+
+ mkSystemdService = config:
+ {
+ enable = true;
+ restartIfChanged = true;
+ unitConfig = {
+ Description = "actkbd on %I";
+ ConditionPathExists = "%I";
+ };
+ serviceConfig = {
+ Type = "forking";
+ ExecStart = "${pkgs.actkbd}/bin/actkbd -D -c ${config} -d %I";
+ };
+ };
bindingCfg = { ... }: {
options = {
+ userMode = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ whether to deploy this binding as a systemd user service; useful for
+ some programs which require user privileges. This is rarely needed and
+ should only be enabled if it is absolutely required.
+
+ E.g. enables this when is set.
+ '';
+ };
+
keys = mkOption {
type = types.listOf types.int;
description = "List of keycodes to match.";
@@ -107,21 +136,36 @@ in
services.udev.packages = lib.singleton (pkgs.writeTextFile {
name = "actkbd-udev-rules";
destination = "/etc/udev/rules.d/61-actkbd.rules";
- text = ''
- ACTION=="add", SUBSYSTEM=="input", KERNEL=="event[0-9]*", ENV{ID_INPUT_KEY}=="1", TAG+="systemd", ENV{SYSTEMD_WANTS}+="actkbd@$env{DEVNAME}.service"
+ text = let actkbdVar = "actkbd@$env{DEVNAME}.service";
+ in ''
+ ACTION=="add" \
+ , SUBSYSTEM=="input" \
+ , KERNEL=="event[0-9]*" \
+ , ENV{ID_INPUT_KEY}=="1" \
+ , TAG+="systemd" \
+ , ENV{SYSTEMD_WANTS}+="${actkbdVar}" \
+ , ENV{SYSTEMD_USER_WANTS}+="${actkbdVar}"
'';
});
- systemd.services."actkbd@" = {
- enable = true;
- restartIfChanged = true;
- unitConfig = {
- Description = "actkbd on %I";
- ConditionPathExists = "%I";
- };
- serviceConfig = {
- Type = "forking";
- ExecStart = "${pkgs.actkbd}/bin/actkbd -D -c ${configFile} -d %I";
+ systemd.services = mkIf ( isUserBindings false != [] )
+ { "actkbd@" = ( mkSystemdService (mkConfigFile false) );
+ };
+
+ systemd.user = mkIf ( isUserBindings true != [] )
+ { services."actkbd@" = mkSystemdService (mkConfigFile true);
+
+ # this is needed because input devices initialize at boot and systemd
+ # only starts device dependant services on their initialization
+ services."start-actkbd" =
+ { enable = true;
+ unitConfig =
+ { Description = "user instances of actkbd";
+ Type = "idle";
+ };
+ script =
+ "${pkgs.systemd}/bin/systemctl --user start 'actkbd@*.service' --all";
+ wantedBy = [ "default.target" ];
};
};