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