1{ config, lib, pkgs, ... }:
2
3with lib;
4
5let cfg = config.services.libinput;
6
7 xorgBool = v: if v then "on" else "off";
8
9 mkConfigForDevice = deviceType: {
10 dev = mkOption {
11 type = types.nullOr types.str;
12 default = null;
13 example = "/dev/input/event0";
14 description = ''
15 Path for ${deviceType} device. Set to `null` to apply to any
16 auto-detected ${deviceType}.
17 '';
18 };
19
20 accelProfile = mkOption {
21 type = types.enum [ "flat" "adaptive" ];
22 default = "adaptive";
23 example = "flat";
24 description = ''
25 Sets the pointer acceleration profile to the given profile.
26 Permitted values are `adaptive`, `flat`.
27 Not all devices support this option or all profiles.
28 If a profile is unsupported, the default profile for this is used.
29 `flat`: Pointer motion is accelerated by a constant
30 (device-specific) factor, depending on the current speed.
31 `adaptive`: Pointer acceleration depends on the input speed.
32 This is the default profile for most devices.
33 '';
34 };
35
36 accelSpeed = mkOption {
37 type = types.nullOr types.str;
38 default = null;
39 example = "-0.5";
40 description = "Cursor acceleration (how fast speed increases from minSpeed to maxSpeed).";
41 };
42
43 buttonMapping = mkOption {
44 type = types.nullOr types.str;
45 default = null;
46 example = "1 6 3 4 5 0 7";
47 description = ''
48 Sets the logical button mapping for this device, see XSetPointerMapping(3). The string must
49 be a space-separated list of button mappings in the order of the logical buttons on the
50 device, starting with button 1. The default mapping is "1 2 3 ... 32". A mapping of 0 deac‐
51 tivates the button. Multiple buttons can have the same mapping. Invalid mapping strings are
52 discarded and the default mapping is used for all buttons. Buttons not specified in the
53 user's mapping use the default mapping. See section BUTTON MAPPING for more details.
54 '';
55 };
56
57 calibrationMatrix = mkOption {
58 type = types.nullOr types.str;
59 default = null;
60 example = "0.5 0 0 0 0.8 0.1 0 0 1";
61 description = ''
62 A string of 9 space-separated floating point numbers. Sets the calibration matrix to the
63 3x3 matrix where the first row is (abc), the second row is (def) and the third row is (ghi).
64 '';
65 };
66
67 clickMethod = mkOption {
68 type = types.nullOr (types.enum [ "none" "buttonareas" "clickfinger" ]);
69 default = null;
70 example = "buttonareas";
71 description = ''
72 Enables a click method. Permitted values are `none`,
73 `buttonareas`, `clickfinger`.
74 Not all devices support all methods, if an option is unsupported,
75 the default click method for this device is used.
76 '';
77 };
78
79 leftHanded = mkOption {
80 type = types.bool;
81 default = false;
82 description = "Enables left-handed button orientation, i.e. swapping left and right buttons.";
83 };
84
85 middleEmulation = mkOption {
86 type = types.bool;
87 default = true;
88 description = ''
89 Enables middle button emulation. When enabled, pressing the left and right buttons
90 simultaneously produces a middle mouse button click.
91 '';
92 };
93
94 naturalScrolling = mkOption {
95 type = types.bool;
96 default = false;
97 description = "Enables or disables natural scrolling behavior.";
98 };
99
100 scrollButton = mkOption {
101 type = types.nullOr types.int;
102 default = null;
103 example = 1;
104 description = ''
105 Designates a button as scroll button. If the ScrollMethod is button and the button is logically
106 held down, x/y axis movement is converted into scroll events.
107 '';
108 };
109
110 scrollMethod = mkOption {
111 type = types.enum [ "twofinger" "edge" "button" "none" ];
112 default = "twofinger";
113 example = "edge";
114 description = ''
115 Specify the scrolling method: `twofinger`, `edge`,
116 `button`, or `none`
117 '';
118 };
119
120 horizontalScrolling = mkOption {
121 type = types.bool;
122 default = true;
123 description = ''
124 Enables or disables horizontal scrolling. When disabled, this driver will discard any
125 horizontal scroll events from libinput. This does not disable horizontal scroll events
126 from libinput; it merely discards the horizontal axis from any scroll events.
127 '';
128 };
129
130 sendEventsMode = mkOption {
131 type = types.enum [ "disabled" "enabled" "disabled-on-external-mouse" ];
132 default = "enabled";
133 example = "disabled";
134 description = ''
135 Sets the send events mode to `disabled`, `enabled`,
136 or `disabled-on-external-mouse`
137 '';
138 };
139
140 tapping = mkOption {
141 type = types.bool;
142 default = true;
143 description = ''
144 Enables or disables tap-to-click behavior.
145 '';
146 };
147
148 tappingButtonMap = mkOption {
149 type = types.nullOr (types.enum [ "lrm" "lmr" ]);
150 default = null;
151 description = ''
152 Set the button mapping for 1/2/3-finger taps to left/right/middle or left/middle/right, respectively.
153 '';
154 };
155
156 tappingDragLock = mkOption {
157 type = types.bool;
158 default = true;
159 description = ''
160 Enables or disables drag lock during tapping behavior. When enabled, a finger up during tap-
161 and-drag will not immediately release the button. If the finger is set down again within the
162 timeout, the dragging process continues.
163 '';
164 };
165
166 transformationMatrix = mkOption {
167 type = types.nullOr types.str;
168 default = null;
169 example = "0.5 0 0 0 0.8 0.1 0 0 1";
170 description = ''
171 A string of 9 space-separated floating point numbers. Sets the transformation matrix to
172 the 3x3 matrix where the first row is (abc), the second row is (def) and the third row is (ghi).
173 '';
174 };
175
176 disableWhileTyping = mkOption {
177 type = types.bool;
178 default = false;
179 description = ''
180 Disable input method while typing.
181 '';
182 };
183
184 additionalOptions = mkOption {
185 type = types.lines;
186 default = "";
187 example =
188 ''
189 Option "DragLockButtons" "L1 B1 L2 B2"
190 '';
191 description = ''
192 Additional options for libinput ${deviceType} driver. See
193 {manpage}`libinput(4)`
194 for available options.";
195 '';
196 };
197 };
198
199 mkX11ConfigForDevice = deviceType: matchIs: ''
200 Identifier "libinput ${deviceType} configuration"
201 MatchDriver "libinput"
202 MatchIs${matchIs} "${xorgBool true}"
203 ${optionalString (cfg.${deviceType}.dev != null) ''MatchDevicePath "${cfg.${deviceType}.dev}"''}
204 Option "AccelProfile" "${cfg.${deviceType}.accelProfile}"
205 ${optionalString (cfg.${deviceType}.accelSpeed != null) ''Option "AccelSpeed" "${cfg.${deviceType}.accelSpeed}"''}
206 ${optionalString (cfg.${deviceType}.buttonMapping != null) ''Option "ButtonMapping" "${cfg.${deviceType}.buttonMapping}"''}
207 ${optionalString (cfg.${deviceType}.calibrationMatrix != null) ''Option "CalibrationMatrix" "${cfg.${deviceType}.calibrationMatrix}"''}
208 ${optionalString (cfg.${deviceType}.transformationMatrix != null) ''Option "TransformationMatrix" "${cfg.${deviceType}.transformationMatrix}"''}
209 ${optionalString (cfg.${deviceType}.clickMethod != null) ''Option "ClickMethod" "${cfg.${deviceType}.clickMethod}"''}
210 Option "LeftHanded" "${xorgBool cfg.${deviceType}.leftHanded}"
211 Option "MiddleEmulation" "${xorgBool cfg.${deviceType}.middleEmulation}"
212 Option "NaturalScrolling" "${xorgBool cfg.${deviceType}.naturalScrolling}"
213 ${optionalString (cfg.${deviceType}.scrollButton != null) ''Option "ScrollButton" "${toString cfg.${deviceType}.scrollButton}"''}
214 Option "ScrollMethod" "${cfg.${deviceType}.scrollMethod}"
215 Option "HorizontalScrolling" "${xorgBool cfg.${deviceType}.horizontalScrolling}"
216 Option "SendEventsMode" "${cfg.${deviceType}.sendEventsMode}"
217 Option "Tapping" "${xorgBool cfg.${deviceType}.tapping}"
218 ${optionalString (cfg.${deviceType}.tappingButtonMap != null) ''Option "TappingButtonMap" "${cfg.${deviceType}.tappingButtonMap}"''}
219 Option "TappingDragLock" "${xorgBool cfg.${deviceType}.tappingDragLock}"
220 Option "DisableWhileTyping" "${xorgBool cfg.${deviceType}.disableWhileTyping}"
221 ${cfg.${deviceType}.additionalOptions}
222 '';
223in {
224
225 imports =
226 (map (option: mkRenamedOptionModule ([ "services" "xserver" "libinput" option ]) [ "services" "libinput" "touchpad" option ]) [
227 "accelProfile"
228 "accelSpeed"
229 "buttonMapping"
230 "calibrationMatrix"
231 "clickMethod"
232 "leftHanded"
233 "middleEmulation"
234 "naturalScrolling"
235 "scrollButton"
236 "scrollMethod"
237 "horizontalScrolling"
238 "sendEventsMode"
239 "tapping"
240 "tappingButtonMap"
241 "tappingDragLock"
242 "transformationMatrix"
243 "disableWhileTyping"
244 "additionalOptions"
245 ]) ++ [
246 (mkRenamedOptionModule [ "services" "xserver" "libinput" "enable" ] [ "services" "libinput" "enable" ])
247 (mkRenamedOptionModule [ "services" "xserver" "libinput" "mouse" ] [ "services" "libinput" "mouse" ])
248 (mkRenamedOptionModule [ "services" "xserver" "libinput" "touchpad" ] [ "services" "libinput" "touchpad" ])
249 ];
250
251 options = {
252
253 services.libinput = {
254 enable = mkEnableOption "libinput" // {
255 default = config.services.xserver.enable;
256 defaultText = lib.literalExpression "config.services.xserver.enable";
257 };
258 mouse = mkConfigForDevice "mouse";
259 touchpad = mkConfigForDevice "touchpad";
260 };
261 };
262
263
264 config = mkIf cfg.enable {
265
266 services.xserver.modules = [ pkgs.xorg.xf86inputlibinput ];
267
268 environment.systemPackages = [ pkgs.xorg.xf86inputlibinput ];
269
270 environment.etc =
271 let cfgPath = "X11/xorg.conf.d/40-libinput.conf";
272 in {
273 ${cfgPath} = {
274 source = pkgs.xorg.xf86inputlibinput.out + "/share/" + cfgPath;
275 };
276 };
277
278 services.udev.packages = [ pkgs.libinput.out ];
279
280 services.xserver.inputClassSections = [
281 (mkX11ConfigForDevice "mouse" "Pointer")
282 (mkX11ConfigForDevice "touchpad" "Touchpad")
283 ];
284
285 assertions = [
286 # already present in synaptics.nix
287 /* {
288 assertion = !config.services.xserver.synaptics.enable;
289 message = "Synaptics and libinput are incompatible, you cannot enable both (in services.xserver).";
290 } */
291 ];
292
293 };
294
295}