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