1{
2 config,
3 lib,
4 pkgs,
5 ...
6}:
7let
8
9 cfg = config.services.actkbd;
10
11 configFile = pkgs.writeText "actkbd.conf" ''
12 ${lib.concatMapStringsSep "\n" (
13 {
14 keys,
15 events,
16 attributes,
17 command,
18 ...
19 }:
20 ''${
21 lib.concatMapStringsSep "+" toString keys
22 }:${lib.concatStringsSep "," events}:${lib.concatStringsSep "," attributes}:${command}''
23 ) cfg.bindings}
24 ${cfg.extraConfig}
25 '';
26
27 bindingCfg =
28 { ... }:
29 {
30 options = {
31
32 keys = lib.mkOption {
33 type = lib.types.listOf lib.types.int;
34 description = "List of keycodes to match.";
35 };
36
37 events = lib.mkOption {
38 type = lib.types.listOf (
39 lib.types.enum [
40 "key"
41 "rep"
42 "rel"
43 ]
44 );
45 default = [ "key" ];
46 description = "List of events to match.";
47 };
48
49 attributes = lib.mkOption {
50 type = lib.types.listOf lib.types.str;
51 default = [ "exec" ];
52 description = "List of attributes.";
53 };
54
55 command = lib.mkOption {
56 type = lib.types.str;
57 default = "";
58 description = "What to run.";
59 };
60
61 };
62 };
63
64in
65
66{
67
68 ###### interface
69
70 options = {
71
72 services.actkbd = {
73
74 enable = lib.mkOption {
75 type = lib.types.bool;
76 default = false;
77 description = ''
78 Whether to enable the {command}`actkbd` key mapping daemon.
79
80 Turning this on will start an {command}`actkbd`
81 instance for every evdev input that has at least one key
82 (which is okay even for systems with tiny memory footprint,
83 since actkbd normally uses \<100 bytes of memory per
84 instance).
85
86 This allows binding keys globally without the need for e.g.
87 X11.
88 '';
89 };
90
91 bindings = lib.mkOption {
92 type = lib.types.listOf (lib.types.submodule bindingCfg);
93 default = [ ];
94 example = lib.literalExpression ''
95 [ { keys = [ 113 ]; events = [ "key" ]; command = "''${pkgs.alsa-utils}/bin/amixer -q set Master toggle"; }
96 ]
97 '';
98 description = ''
99 Key bindings for {command}`actkbd`.
100
101 See {command}`actkbd` {file}`README` for documentation.
102
103 The example shows a piece of what {option}`sound.mediaKeys.enable` does when enabled.
104 '';
105 };
106
107 extraConfig = lib.mkOption {
108 type = lib.types.lines;
109 default = "";
110 description = ''
111 Literal contents to append to the end of actkbd configuration file.
112 '';
113 };
114
115 };
116
117 };
118
119 ###### implementation
120
121 config = lib.mkIf cfg.enable {
122
123 services.udev.packages = lib.singleton (
124 pkgs.writeTextFile {
125 name = "actkbd-udev-rules";
126 destination = "/etc/udev/rules.d/61-actkbd.rules";
127 text = ''
128 ACTION=="add", SUBSYSTEM=="input", KERNEL=="event[0-9]*", ENV{ID_INPUT_KEY}=="1", TAG+="systemd", ENV{SYSTEMD_WANTS}+="actkbd@$env{DEVNAME}.service"
129 '';
130 }
131 );
132
133 systemd.services."actkbd@" = {
134 enable = true;
135 restartIfChanged = true;
136 unitConfig = {
137 Description = "actkbd on %I";
138 ConditionPathExists = "%I";
139 };
140 serviceConfig = {
141 Type = "forking";
142 ExecStart = "${pkgs.actkbd}/bin/actkbd -D -c ${configFile} -d %I";
143 };
144 };
145
146 # For testing
147 environment.systemPackages = [ pkgs.actkbd ];
148
149 };
150
151}