1{
2 config,
3 lib,
4 pkgs,
5 ...
6}:
7let
8 cfg = config.services.libinput;
9
10 xorgBool = v: if v then "on" else "off";
11
12 mkConfigForDevice = deviceType: {
13 dev = lib.mkOption {
14 type = lib.types.nullOr lib.types.str;
15 default = null;
16 example = "/dev/input/event0";
17 description = ''
18 Path for ${deviceType} device. Set to `null` to apply to any
19 auto-detected ${deviceType}.
20 '';
21 };
22
23 accelProfile = lib.mkOption {
24 type = lib.types.enum [
25 "flat"
26 "adaptive"
27 "custom"
28 ];
29 default = "adaptive";
30 example = "flat";
31 description = ''
32 Sets the pointer acceleration profile to the given profile.
33 Permitted values are `adaptive`, `flat`, `custom`.
34 Not all devices support this option or all profiles.
35 If a profile is unsupported, the default profile for this is used.
36 `flat`: Pointer motion is accelerated by a constant
37 (device-specific) factor, depending on the current speed.
38 `adaptive`: Pointer acceleration depends on the input speed.
39 This is the default profile for most devices.
40 `custom`: Allows the user to define a custom acceleration function.
41 To define custom functions use the accelPoints<Fallback/Motion/Scroll>
42 and accelStep<Fallback/Motion/Scroll> options.
43 '';
44 };
45
46 accelSpeed = lib.mkOption {
47 type = lib.types.nullOr lib.types.str;
48 default = null;
49 example = "-0.5";
50 description = ''
51 Cursor acceleration (how fast speed increases from minSpeed to maxSpeed).
52 This only applies to the flat or adaptive profile.
53 '';
54 };
55
56 accelPointsFallback = lib.mkOption {
57 type = lib.types.nullOr (lib.types.listOf lib.types.number);
58 default = null;
59 example = [
60 0.0
61 1.0
62 2.4
63 2.5
64 ];
65 description = ''
66 Sets the points of the fallback acceleration function. The value must be a list of
67 floating point non-negative numbers. This only applies to the custom profile.
68 '';
69 };
70
71 accelPointsMotion = lib.mkOption {
72 type = lib.types.nullOr (lib.types.listOf lib.types.number);
73 default = null;
74 example = [
75 0.0
76 1.0
77 2.4
78 2.5
79 ];
80 description = ''
81 Sets the points of the (pointer) motion acceleration function. The value must be a
82 list of floating point non-negative numbers. This only applies to the custom profile.
83 '';
84 };
85
86 accelPointsScroll = lib.mkOption {
87 type = lib.types.nullOr (lib.types.listOf lib.types.number);
88 default = null;
89 example = [
90 0.0
91 1.0
92 2.4
93 2.5
94 ];
95 description = ''
96 Sets the points of the scroll acceleration function. The value must be a list of
97 floating point non-negative numbers. This only applies to the custom profile.
98 '';
99 };
100
101 accelStepFallback = lib.mkOption {
102 type = lib.types.nullOr lib.types.number;
103 default = null;
104 example = 0.1;
105 description = ''
106 Sets the step between the points of the fallback acceleration function. When a step of
107 0.0 is provided, libinput's Fallback acceleration function is used. This only applies
108 to the custom profile.
109 '';
110 };
111
112 accelStepMotion = lib.mkOption {
113 type = lib.types.nullOr lib.types.number;
114 default = null;
115 example = 0.1;
116 description = ''
117 Sets the step between the points of the (pointer) motion acceleration function. When a
118 step of 0.0 is provided, libinput's Fallback acceleration function is used. This only
119 applies to the custom profile.
120 '';
121 };
122
123 accelStepScroll = lib.mkOption {
124 type = lib.types.nullOr lib.types.number;
125 default = null;
126 example = 0.1;
127 description = ''
128 Sets the step between the points of the scroll acceleration function. When a step of
129 0.0 is provided, libinput's Fallback acceleration function is used. This only applies
130 to the custom profile.
131 '';
132 };
133
134 buttonMapping = lib.mkOption {
135 type = lib.types.nullOr lib.types.str;
136 default = null;
137 example = "1 6 3 4 5 0 7";
138 description = ''
139 Sets the logical button mapping for this device, see {manpage}`XSetPointerMapping(3)`. The string must
140 be a space-separated list of button mappings in the order of the logical buttons on the
141 device, starting with button 1. The default mapping is "1 2 3 ... 32". A mapping of 0 deac‐
142 tivates the button. Multiple buttons can have the same mapping. Invalid mapping strings are
143 discarded and the default mapping is used for all buttons. Buttons not specified in the
144 user's mapping use the default mapping. See section BUTTON MAPPING for more details.
145 '';
146 };
147
148 calibrationMatrix = lib.mkOption {
149 type = lib.types.nullOr lib.types.str;
150 default = null;
151 example = "0.5 0 0 0 0.8 0.1 0 0 1";
152 description = ''
153 A string of 9 space-separated floating point numbers. Sets the calibration matrix to the
154 3x3 matrix where the first row is (abc), the second row is (def) and the third row is (ghi).
155 '';
156 };
157
158 clickMethod = lib.mkOption {
159 type = lib.types.nullOr (
160 lib.types.enum [
161 "none"
162 "buttonareas"
163 "clickfinger"
164 ]
165 );
166 default = null;
167 example = "buttonareas";
168 description = ''
169 Enables a click method. Permitted values are `none`,
170 `buttonareas`, `clickfinger`.
171 Not all devices support all methods, if an option is unsupported,
172 the default click method for this device is used.
173 '';
174 };
175
176 leftHanded = lib.mkOption {
177 type = lib.types.bool;
178 default = false;
179 description = "Enables left-handed button orientation, i.e. swapping left and right buttons.";
180 };
181
182 middleEmulation = lib.mkOption {
183 type = lib.types.bool;
184 default = true;
185 description = ''
186 Enables middle button emulation. When enabled, pressing the left and right buttons
187 simultaneously produces a middle mouse button click.
188 '';
189 };
190
191 naturalScrolling = lib.mkOption {
192 type = lib.types.bool;
193 default = false;
194 description = "Enables or disables natural scrolling behavior.";
195 };
196
197 scrollButton = lib.mkOption {
198 type = lib.types.nullOr lib.types.int;
199 default = null;
200 example = 1;
201 description = ''
202 Designates a button as scroll button. If the ScrollMethod is button and the button is logically
203 held down, x/y axis movement is converted into scroll events.
204 '';
205 };
206
207 scrollMethod = lib.mkOption {
208 type = lib.types.enum [
209 "twofinger"
210 "edge"
211 "button"
212 "none"
213 ];
214 default = "twofinger";
215 example = "edge";
216 description = ''
217 Specify the scrolling method: `twofinger`, `edge`,
218 `button`, or `none`
219 '';
220 };
221
222 horizontalScrolling = lib.mkOption {
223 type = lib.types.bool;
224 default = true;
225 description = ''
226 Enables or disables horizontal scrolling. When disabled, this driver will discard any
227 horizontal scroll events from libinput. This does not disable horizontal scroll events
228 from libinput; it merely discards the horizontal axis from any scroll events.
229 '';
230 };
231
232 sendEventsMode = lib.mkOption {
233 type = lib.types.enum [
234 "disabled"
235 "enabled"
236 "disabled-on-external-mouse"
237 ];
238 default = "enabled";
239 example = "disabled";
240 description = ''
241 Sets the send events mode to `disabled`, `enabled`,
242 or `disabled-on-external-mouse`
243 '';
244 };
245
246 tapping = lib.mkOption {
247 type = lib.types.bool;
248 default = true;
249 description = ''
250 Enables or disables tap-to-click behavior.
251 '';
252 };
253
254 tappingButtonMap = lib.mkOption {
255 type = lib.types.nullOr (
256 lib.types.enum [
257 "lrm"
258 "lmr"
259 ]
260 );
261 default = null;
262 description = ''
263 Set the button mapping for 1/2/3-finger taps to left/right/middle or left/middle/right, respectively.
264 '';
265 };
266
267 tappingDragLock = lib.mkOption {
268 type = lib.types.bool;
269 default = true;
270 description = ''
271 Enables or disables drag lock during tapping behavior. When enabled, a finger up during tap-
272 and-drag will not immediately release the button. If the finger is set down again within the
273 timeout, the dragging process continues.
274 '';
275 };
276
277 transformationMatrix = lib.mkOption {
278 type = lib.types.nullOr lib.types.str;
279 default = null;
280 example = "0.5 0 0 0 0.8 0.1 0 0 1";
281 description = ''
282 A string of 9 space-separated floating point numbers. Sets the transformation matrix to
283 the 3x3 matrix where the first row is (abc), the second row is (def) and the third row is (ghi).
284 '';
285 };
286
287 disableWhileTyping = lib.mkOption {
288 type = lib.types.bool;
289 default = false;
290 description = ''
291 Disable input method while typing.
292 '';
293 };
294
295 additionalOptions = lib.mkOption {
296 type = lib.types.lines;
297 default = "";
298 example = ''
299 Option "DragLockButtons" "L1 B1 L2 B2"
300 '';
301 description = ''
302 Additional options for libinput ${deviceType} driver. See
303 {manpage}`libinput(4)`
304 for available options.";
305 '';
306 };
307 };
308
309 mkX11ConfigForDevice = deviceType: matchIs: ''
310 Identifier "libinput ${deviceType} configuration"
311 MatchDriver "libinput"
312 MatchIs${matchIs} "${xorgBool true}"
313 ${lib.optionalString (cfg.${deviceType}.dev != null) ''MatchDevicePath "${cfg.${deviceType}.dev}"''}
314 Option "AccelProfile" "${cfg.${deviceType}.accelProfile}"
315 ${lib.optionalString (
316 cfg.${deviceType}.accelSpeed != null
317 ) ''Option "AccelSpeed" "${cfg.${deviceType}.accelSpeed}"''}
318 ${lib.optionalString (
319 cfg.${deviceType}.accelPointsFallback != null
320 ) ''Option "AccelPointsFallback" "${toString cfg.${deviceType}.accelPointsFallback}"''}
321 ${lib.optionalString (cfg.${deviceType}.accelPointsMotion != null)
322 ''Option "AccelPointsMotion" "${toString cfg.${deviceType}.accelPointsMotion}"''
323 }
324 ${lib.optionalString (cfg.${deviceType}.accelPointsScroll != null)
325 ''Option "AccelPointsScroll" "${toString cfg.${deviceType}.accelPointsScroll}"''
326 }
327 ${lib.optionalString (cfg.${deviceType}.accelStepFallback != null)
328 ''Option "AccelStepFallback" "${toString cfg.${deviceType}.accelStepFallback}"''
329 }
330 ${lib.optionalString (cfg.${deviceType}.accelStepMotion != null)
331 ''Option "AccelStepMotion" "${toString cfg.${deviceType}.accelStepMotion}"''
332 }
333 ${lib.optionalString (cfg.${deviceType}.accelStepScroll != null)
334 ''Option "AccelStepScroll" "${toString cfg.${deviceType}.accelStepScroll}"''
335 }
336 ${lib.optionalString (cfg.${deviceType}.buttonMapping != null)
337 ''Option "ButtonMapping" "${cfg.${deviceType}.buttonMapping}"''
338 }
339 ${lib.optionalString (cfg.${deviceType}.calibrationMatrix != null)
340 ''Option "CalibrationMatrix" "${cfg.${deviceType}.calibrationMatrix}"''
341 }
342 ${lib.optionalString (
343 cfg.${deviceType}.transformationMatrix != null
344 ) ''Option "TransformationMatrix" "${cfg.${deviceType}.transformationMatrix}"''}
345 ${lib.optionalString (
346 cfg.${deviceType}.clickMethod != null
347 ) ''Option "ClickMethod" "${cfg.${deviceType}.clickMethod}"''}
348 Option "LeftHanded" "${xorgBool cfg.${deviceType}.leftHanded}"
349 Option "MiddleEmulation" "${xorgBool cfg.${deviceType}.middleEmulation}"
350 Option "NaturalScrolling" "${xorgBool cfg.${deviceType}.naturalScrolling}"
351 ${lib.optionalString (cfg.${deviceType}.scrollButton != null)
352 ''Option "ScrollButton" "${toString cfg.${deviceType}.scrollButton}"''
353 }
354 Option "ScrollMethod" "${cfg.${deviceType}.scrollMethod}"
355 Option "HorizontalScrolling" "${xorgBool cfg.${deviceType}.horizontalScrolling}"
356 Option "SendEventsMode" "${cfg.${deviceType}.sendEventsMode}"
357 Option "Tapping" "${xorgBool cfg.${deviceType}.tapping}"
358 ${lib.optionalString (cfg.${deviceType}.tappingButtonMap != null)
359 ''Option "TappingButtonMap" "${cfg.${deviceType}.tappingButtonMap}"''
360 }
361 Option "TappingDragLock" "${xorgBool cfg.${deviceType}.tappingDragLock}"
362 Option "DisableWhileTyping" "${xorgBool cfg.${deviceType}.disableWhileTyping}"
363 ${cfg.${deviceType}.additionalOptions}
364 '';
365in
366{
367
368 imports =
369 (map
370 (
371 option:
372 lib.mkRenamedOptionModule
373 ([
374 "services"
375 "xserver"
376 "libinput"
377 option
378 ])
379 [
380 "services"
381 "libinput"
382 "touchpad"
383 option
384 ]
385 )
386 [
387 "accelProfile"
388 "accelSpeed"
389 "buttonMapping"
390 "calibrationMatrix"
391 "clickMethod"
392 "leftHanded"
393 "middleEmulation"
394 "naturalScrolling"
395 "scrollButton"
396 "scrollMethod"
397 "horizontalScrolling"
398 "sendEventsMode"
399 "tapping"
400 "tappingButtonMap"
401 "tappingDragLock"
402 "transformationMatrix"
403 "disableWhileTyping"
404 "additionalOptions"
405 ]
406 )
407 ++ [
408 (lib.mkRenamedOptionModule
409 [ "services" "xserver" "libinput" "enable" ]
410 [ "services" "libinput" "enable" ]
411 )
412 (lib.mkRenamedOptionModule
413 [ "services" "xserver" "libinput" "mouse" ]
414 [ "services" "libinput" "mouse" ]
415 )
416 (lib.mkRenamedOptionModule
417 [ "services" "xserver" "libinput" "touchpad" ]
418 [ "services" "libinput" "touchpad" ]
419 )
420 ];
421
422 options = {
423
424 services.libinput = {
425 enable = lib.mkEnableOption "libinput" // {
426 default = config.services.xserver.enable;
427 defaultText = lib.literalExpression "config.services.xserver.enable";
428 };
429 mouse = mkConfigForDevice "mouse";
430 touchpad = mkConfigForDevice "touchpad";
431 };
432 };
433
434 config = lib.mkIf cfg.enable {
435
436 services.xserver.modules = [ pkgs.xorg.xf86inputlibinput ];
437
438 environment.systemPackages = [ pkgs.xorg.xf86inputlibinput ];
439
440 environment.etc =
441 let
442 cfgPath = "X11/xorg.conf.d/40-libinput.conf";
443 in
444 {
445 ${cfgPath} = {
446 source = pkgs.xorg.xf86inputlibinput.out + "/share/" + cfgPath;
447 };
448 };
449
450 services.udev.packages = [ pkgs.libinput.out ];
451
452 services.xserver.inputClassSections = [
453 (mkX11ConfigForDevice "mouse" "Pointer")
454 (mkX11ConfigForDevice "touchpad" "Touchpad")
455 ];
456
457 assertions = [
458 # already present in synaptics.nix
459 /*
460 {
461 assertion = !config.services.xserver.synaptics.enable;
462 message = "Synaptics and libinput are incompatible, you cannot enable both (in services.xserver).";
463 }
464 */
465 ];
466
467 };
468
469}