From 25559ac82a06efc420b0d20571b904dddceb2bd4 Mon Sep 17 00:00:00 2001 From: Timothy DeHerrera Date: Fri, 23 Aug 2019 18:39:27 -0600 Subject: [PATCH 1/2] actkbd: add ability to deploy as user service Some processes require user level permissions to interact with them, E.g. pulseaudio. Allow users to make keybindings for them. * add new 'userMode' toggle to bindings; default to 'false' * add 'ENV{SYSTEMD_USER_WANTS}' to udev rule * only launch user service instances if there are relevant bindings * add 'start-actkbd' user service to initialize actkbd instances --- nixos/modules/services/hardware/actkbd.nix | 76 +++++++++++++++++----- 1 file changed, 60 insertions(+), 16 deletions(-) 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" ]; }; }; From abf5c8f1b2ce34ef40eaa1f5b647e0fa9a90f460 Mon Sep 17 00:00:00 2001 From: Timothy DeHerrera Date: Mon, 23 Sep 2019 18:04:09 -0600 Subject: [PATCH 2/2] alsa: import 'mediaKeys' to work with pulseaudio Resolves https://github.com/NixOS/nixpkgs/issues/66854 Media keys are audio server agnostic and 'just work' * declare new keybinds for pulseaudio using pamixer * set new keybinds if pulseaudio is enabled otherwise use original binds --- nixos/modules/services/audio/alsa.nix | 102 ++++++++++++++++++++------ 1 file changed, 78 insertions(+), 24 deletions(-) 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}"; + } + ]; }; - }; }