1{ config, lib, pkgs, ... }:
2
3with lib;
4
5let
6
7 kernelPackages = config.boot.kernelPackages;
8
9 # Abbreviations.
10 cfg = config.services.xserver;
11 xorg = pkgs.xorg;
12
13
14 # Map video driver names to driver packages. FIXME: move into card-specific modules.
15 knownVideoDrivers = {
16 virtualbox = { modules = [ kernelPackages.virtualboxGuestAdditions ]; driverName = "vboxvideo"; };
17
18 # modesetting does not have a xf86videomodesetting package as it is included in xorgserver
19 modesetting = {};
20 };
21
22 fontsForXServer =
23 config.fonts.fonts ++
24 # We don't want these fonts in fonts.conf, because then modern,
25 # fontconfig-based applications will get horrible bitmapped
26 # Helvetica fonts. It's better to get a substitution (like Nimbus
27 # Sans) than that horror. But we do need the Adobe fonts for some
28 # old non-fontconfig applications. (Possibly this could be done
29 # better using a fontconfig rule.)
30 [ pkgs.xorg.fontadobe100dpi
31 pkgs.xorg.fontadobe75dpi
32 ];
33
34 xrandrOptions = {
35 output = mkOption {
36 type = types.str;
37 example = "DVI-0";
38 description = ''
39 The output name of the monitor, as shown by <citerefentry>
40 <refentrytitle>xrandr</refentrytitle>
41 <manvolnum>1</manvolnum>
42 </citerefentry> invoked without arguments.
43 '';
44 };
45
46 primary = mkOption {
47 type = types.bool;
48 default = false;
49 description = ''
50 Whether this head is treated as the primary monitor,
51 '';
52 };
53
54 monitorConfig = mkOption {
55 type = types.lines;
56 default = "";
57 example = ''
58 DisplaySize 408 306
59 Option "DPMS" "false"
60 '';
61 description = ''
62 Extra lines to append to the <literal>Monitor</literal> section
63 verbatim.
64 '';
65 };
66 };
67
68 # Just enumerate all heads without discarding XRandR output information.
69 xrandrHeads = let
70 mkHead = num: config: {
71 name = "multihead${toString num}";
72 inherit config;
73 };
74 in imap1 mkHead cfg.xrandrHeads;
75
76 xrandrDeviceSection = let
77 monitors = flip map xrandrHeads (h: ''
78 Option "monitor-${h.config.output}" "${h.name}"
79 '');
80 # First option is indented through the space in the config but any
81 # subsequent options aren't so we need to apply indentation to
82 # them here
83 monitorsIndented = if length monitors > 1
84 then singleton (head monitors) ++ map (m: " " + m) (tail monitors)
85 else monitors;
86 in concatStrings monitorsIndented;
87
88 # Here we chain every monitor from the left to right, so we have:
89 # m4 right of m3 right of m2 right of m1 .----.----.----.----.
90 # Which will end up in reverse ----------> | m1 | m2 | m3 | m4 |
91 # `----^----^----^----'
92 xrandrMonitorSections = let
93 mkMonitor = previous: current: singleton {
94 inherit (current) name;
95 value = ''
96 Section "Monitor"
97 Identifier "${current.name}"
98 ${optionalString (current.config.primary) ''
99 Option "Primary" "true"
100 ''}
101 ${optionalString (previous != []) ''
102 Option "RightOf" "${(head previous).name}"
103 ''}
104 ${current.config.monitorConfig}
105 EndSection
106 '';
107 } ++ previous;
108 monitors = reverseList (foldl mkMonitor [] xrandrHeads);
109 in concatMapStrings (getAttr "value") monitors;
110
111 configFile = pkgs.runCommand "xserver.conf"
112 { xfs = optionalString (cfg.useXFS != false)
113 ''FontPath "${toString cfg.useXFS}"'';
114 inherit (cfg) config;
115 }
116 ''
117 echo 'Section "Files"' >> $out
118 echo $xfs >> $out
119
120 for i in ${toString fontsForXServer}; do
121 if test "''${i:0:''${#NIX_STORE}}" == "$NIX_STORE"; then
122 for j in $(find $i -name fonts.dir); do
123 echo " FontPath \"$(dirname $j)\"" >> $out
124 done
125 fi
126 done
127
128 for i in $(find ${toString cfg.modules} -type d); do
129 if test $(echo $i/*.so* | wc -w) -ne 0; then
130 echo " ModulePath \"$i\"" >> $out
131 fi
132 done
133
134 echo 'EndSection' >> $out
135
136 echo "$config" >> $out
137 ''; # */
138
139in
140
141{
142
143 imports =
144 [ ./display-managers/default.nix
145 ./window-managers/default.nix
146 ./desktop-managers/default.nix
147 ];
148
149
150 ###### interface
151
152 options = {
153
154 services.xserver = {
155
156 enable = mkOption {
157 type = types.bool;
158 default = false;
159 description = ''
160 Whether to enable the X server.
161 '';
162 };
163
164 autorun = mkOption {
165 type = types.bool;
166 default = true;
167 description = ''
168 Whether to start the X server automatically.
169 '';
170 };
171
172 exportConfiguration = mkOption {
173 type = types.bool;
174 default = false;
175 description = ''
176 Whether to symlink the X server configuration under
177 <filename>/etc/X11/xorg.conf</filename>.
178 '';
179 };
180
181 enableTCP = mkOption {
182 type = types.bool;
183 default = false;
184 description = ''
185 Whether to allow the X server to accept TCP connections.
186 '';
187 };
188
189 autoRepeatDelay = mkOption {
190 type = types.nullOr types.int;
191 default = null;
192 description = ''
193 Sets the autorepeat delay (length of time in milliseconds that a key must be depressed before autorepeat starts).
194 '';
195 };
196
197 autoRepeatInterval = mkOption {
198 type = types.nullOr types.int;
199 default = null;
200 description = ''
201 Sets the autorepeat interval (length of time in milliseconds that should elapse between autorepeat-generated keystrokes).
202 '';
203 };
204
205 inputClassSections = mkOption {
206 type = types.listOf types.lines;
207 default = [];
208 example = literalExample ''
209 [ '''
210 Identifier "Trackpoint Wheel Emulation"
211 MatchProduct "ThinkPad USB Keyboard with TrackPoint"
212 Option "EmulateWheel" "true"
213 Option "EmulateWheelButton" "2"
214 Option "Emulate3Buttons" "false"
215 '''
216 ]
217 '';
218 description = "Content of additional InputClass sections of the X server configuration file.";
219 };
220
221 modules = mkOption {
222 type = types.listOf types.path;
223 default = [];
224 example = literalExample "[ pkgs.xf86_input_wacom ]";
225 description = "Packages to be added to the module search path of the X server.";
226 };
227
228 resolutions = mkOption {
229 type = types.listOf types.attrs;
230 default = [];
231 example = [ { x = 1600; y = 1200; } { x = 1024; y = 786; } ];
232 description = ''
233 The screen resolutions for the X server. The first element
234 is the default resolution. If this list is empty, the X
235 server will automatically configure the resolution.
236 '';
237 };
238
239 videoDrivers = mkOption {
240 type = types.listOf types.str;
241 # !!! We'd like "nv" here, but it segfaults the X server.
242 default = [ "ati" "cirrus" "intel" "vesa" "vmware" "modesetting" ];
243 example = [
244 "ati_unfree" "amdgpu" "amdgpu-pro"
245 "nv" "nvidia" "nvidiaLegacy340" "nvidiaLegacy304"
246 ];
247 # TODO(@oxij): think how to easily add the rest, like those nvidia things
248 relatedPackages = concatLists
249 (mapAttrsToList (n: v:
250 optional (hasPrefix "xf86video" n) {
251 path = [ "xorg" n ];
252 title = removePrefix "xf86video" n;
253 }) pkgs.xorg);
254 description = ''
255 The names of the video drivers the configuration
256 supports. They will be tried in order until one that
257 supports your card is found.
258 '';
259 };
260
261 videoDriver = mkOption {
262 type = types.nullOr types.str;
263 default = null;
264 example = "i810";
265 description = ''
266 The name of the video driver for your graphics card. This
267 option is obsolete; please set the
268 <option>services.xserver.videoDrivers</option> instead.
269 '';
270 };
271
272 drivers = mkOption {
273 type = types.listOf types.attrs;
274 internal = true;
275 description = ''
276 A list of attribute sets specifying drivers to be loaded by
277 the X11 server.
278 '';
279 };
280
281 dpi = mkOption {
282 type = types.nullOr types.int;
283 default = null;
284 description = "DPI resolution to use for X server.";
285 };
286
287 startDbusSession = mkOption {
288 type = types.bool;
289 default = true;
290 description = ''
291 Whether to start a new DBus session when you log in with dbus-launch.
292 '';
293 };
294
295 updateDbusEnvironment = mkOption {
296 type = types.bool;
297 default = false;
298 description = ''
299 Whether to update the DBus activation environment after launching the
300 desktop manager.
301 '';
302 };
303
304 layout = mkOption {
305 type = types.str;
306 default = "us";
307 description = ''
308 Keyboard layout, or multiple keyboard layouts separated by commas.
309 '';
310 };
311
312 xkbModel = mkOption {
313 type = types.str;
314 default = "pc104";
315 example = "presario";
316 description = ''
317 Keyboard model.
318 '';
319 };
320
321 xkbOptions = mkOption {
322 type = types.str;
323 default = "terminate:ctrl_alt_bksp";
324 example = "grp:caps_toggle, grp_led:scroll";
325 description = ''
326 X keyboard options; layout switching goes here.
327 '';
328 };
329
330 xkbVariant = mkOption {
331 type = types.str;
332 default = "";
333 example = "colemak";
334 description = ''
335 X keyboard variant.
336 '';
337 };
338
339 xkbDir = mkOption {
340 type = types.path;
341 description = ''
342 Path used for -xkbdir xserver parameter.
343 '';
344 };
345
346 config = mkOption {
347 type = types.lines;
348 description = ''
349 The contents of the configuration file of the X server
350 (<filename>xorg.conf</filename>).
351 '';
352 };
353
354 deviceSection = mkOption {
355 type = types.lines;
356 default = "";
357 example = "VideoRAM 131072";
358 description = "Contents of the first Device section of the X server configuration file.";
359 };
360
361 screenSection = mkOption {
362 type = types.lines;
363 default = "";
364 example = ''
365 Option "RandRRotation" "on"
366 '';
367 description = "Contents of the first Screen section of the X server configuration file.";
368 };
369
370 monitorSection = mkOption {
371 type = types.lines;
372 default = "";
373 example = "HorizSync 28-49";
374 description = "Contents of the first Monitor section of the X server configuration file.";
375 };
376
377 xrandrHeads = mkOption {
378 default = [];
379 example = [
380 "HDMI-0"
381 { output = "DVI-0"; primary = true; }
382 { output = "DVI-1"; monitorConfig = "Option \"Rotate\" \"left\""; }
383 ];
384 type = with types; listOf (coercedTo str (output: {
385 inherit output;
386 }) (submodule { options = xrandrOptions; }));
387 # Set primary to true for the first head if no other has been set
388 # primary already.
389 apply = heads: let
390 hasPrimary = any (x: x.primary) heads;
391 firstPrimary = head heads // { primary = true; };
392 newHeads = singleton firstPrimary ++ tail heads;
393 in if heads != [] && !hasPrimary then newHeads else heads;
394 description = ''
395 Multiple monitor configuration, just specify a list of XRandR
396 outputs. The individual elements should be either simple strings or
397 an attribute set of output options.
398
399 If the element is a string, it is denoting the physical output for a
400 monitor, if it's an attribute set, you must at least provide the
401 <option>output</option> option.
402
403 The monitors will be mapped from left to right in the order of the
404 list.
405
406 By default, the first monitor will be set as the primary monitor if
407 none of the elements contain an option that has set
408 <option>primary</option> to <literal>true</literal>.
409
410 <note><para>Only one monitor is allowed to be primary.</para></note>
411
412 Be careful using this option with multiple graphic adapters or with
413 drivers that have poor support for XRandR, unexpected things might
414 happen with those.
415 '';
416 };
417
418 serverFlagsSection = mkOption {
419 default = "";
420 example =
421 ''
422 Option "BlankTime" "0"
423 Option "StandbyTime" "0"
424 Option "SuspendTime" "0"
425 Option "OffTime" "0"
426 '';
427 description = "Contents of the ServerFlags section of the X server configuration file.";
428 };
429
430 moduleSection = mkOption {
431 type = types.lines;
432 default = "";
433 example =
434 ''
435 SubSection "extmod"
436 EndSubsection
437 '';
438 description = "Contents of the Module section of the X server configuration file.";
439 };
440
441 serverLayoutSection = mkOption {
442 type = types.lines;
443 default = "";
444 example =
445 ''
446 Option "AIGLX" "true"
447 '';
448 description = "Contents of the ServerLayout section of the X server configuration file.";
449 };
450
451 extraDisplaySettings = mkOption {
452 type = types.lines;
453 default = "";
454 example = "Virtual 2048 2048";
455 description = "Lines to be added to every Display subsection of the Screen section.";
456 };
457
458 defaultDepth = mkOption {
459 type = types.int;
460 default = 0;
461 example = 8;
462 description = "Default colour depth.";
463 };
464
465 useXFS = mkOption {
466 # FIXME: what's the type of this option?
467 default = false;
468 example = "unix/:7100";
469 description = "Determines how to connect to the X Font Server.";
470 };
471
472 tty = mkOption {
473 type = types.nullOr types.int;
474 default = 7;
475 description = "Virtual console for the X server.";
476 };
477
478 display = mkOption {
479 type = types.nullOr types.int;
480 default = 0;
481 description = "Display number for the X server.";
482 };
483
484 virtualScreen = mkOption {
485 type = types.nullOr types.attrs;
486 default = null;
487 example = { x = 2048; y = 2048; };
488 description = ''
489 Virtual screen size for Xrandr.
490 '';
491 };
492
493 verbose = mkOption {
494 type = types.nullOr types.int;
495 default = 3;
496 example = 7;
497 description = ''
498 Controls verbosity of X logging.
499 '';
500 };
501
502 useGlamor = mkOption {
503 type = types.bool;
504 default = false;
505 description = ''
506 Whether to use the Glamor module for 2D acceleration,
507 if possible.
508 '';
509 };
510
511 enableCtrlAltBackspace = mkOption {
512 type = types.bool;
513 default = false;
514 description = ''
515 Whether to enable the DontZap option, which binds Ctrl+Alt+Backspace
516 to forcefully kill X. This can lead to data loss and is disabled
517 by default.
518 '';
519 };
520
521 terminateOnReset = mkOption {
522 type = types.bool;
523 default = true;
524 description = ''
525 Whether to terminate X upon server reset.
526 '';
527 };
528 };
529
530 };
531
532
533
534 ###### implementation
535
536 config = mkIf cfg.enable {
537
538 services.xserver.displayManager.lightdm.enable =
539 let dmconf = cfg.displayManager;
540 default = !( dmconf.auto.enable
541 || dmconf.gdm.enable
542 || dmconf.sddm.enable
543 || dmconf.slim.enable
544 || dmconf.xpra.enable );
545 in mkIf (default) true;
546
547 hardware.opengl.enable = mkDefault true;
548
549 services.xserver.videoDrivers = mkIf (cfg.videoDriver != null) [ cfg.videoDriver ];
550
551 # FIXME: somehow check for unknown driver names.
552 services.xserver.drivers = flip concatMap cfg.videoDrivers (name:
553 let driver =
554 attrByPath [name]
555 (if xorg ? ${"xf86video" + name}
556 then { modules = [xorg.${"xf86video" + name}]; }
557 else null)
558 knownVideoDrivers;
559 in optional (driver != null) ({ inherit name; modules = []; driverName = name; } // driver));
560
561 nixpkgs.config = optionalAttrs (elem "vboxvideo" cfg.videoDrivers) { xorg.abiCompat = "1.18"; };
562
563 assertions = [
564 { assertion = config.security.polkit.enable;
565 message = "X11 requires Polkit to be enabled (‘security.polkit.enable = true’).";
566 }
567 (let primaryHeads = filter (x: x.primary) cfg.xrandrHeads; in {
568 assertion = length primaryHeads < 2;
569 message = "Only one head is allowed to be primary in "
570 + "‘services.xserver.xrandrHeads’, but there are "
571 + "${toString (length primaryHeads)} heads set to primary: "
572 + concatMapStringsSep ", " (x: x.output) primaryHeads;
573 })
574 ];
575
576 environment.etc =
577 (optionals cfg.exportConfiguration
578 [ { source = "${configFile}";
579 target = "X11/xorg.conf";
580 }
581 # -xkbdir command line option does not seems to be passed to xkbcomp.
582 { source = "${cfg.xkbDir}";
583 target = "X11/xkb";
584 }
585 ])
586 # localectl looks into 00-keyboard.conf
587 ++ [
588 {
589 text = ''
590 Section "InputClass"
591 Identifier "Keyboard catchall"
592 MatchIsKeyboard "on"
593 Option "XkbModel" "${cfg.xkbModel}"
594 Option "XkbLayout" "${cfg.layout}"
595 Option "XkbOptions" "${cfg.xkbOptions}"
596 Option "XkbVariant" "${cfg.xkbVariant}"
597 EndSection
598 '';
599 target = "X11/xorg.conf.d/00-keyboard.conf";
600 }
601 ]
602 # Needed since 1.18; see https://bugs.freedesktop.org/show_bug.cgi?id=89023#c5
603 ++ (let cfgPath = "/X11/xorg.conf.d/10-evdev.conf"; in
604 [{
605 source = xorg.xf86inputevdev.out + "/share" + cfgPath;
606 target = cfgPath;
607 }]
608 );
609
610 environment.systemPackages =
611 [ xorg.xorgserver.out
612 xorg.xrandr
613 xorg.xrdb
614 xorg.setxkbmap
615 xorg.iceauth # required for KDE applications (it's called by dcopserver)
616 xorg.xlsclients
617 xorg.xset
618 xorg.xsetroot
619 xorg.xinput
620 xorg.xprop
621 xorg.xauth
622 pkgs.xterm
623 pkgs.xdg_utils
624 xorg.xf86inputevdev.out # get evdev.4 man page
625 ]
626 ++ optional (elem "virtualbox" cfg.videoDrivers) xorg.xrefresh;
627
628 xdg = {
629 autostart.enable = true;
630 menus.enable = true;
631 mime.enable = true;
632 icons.enable = true;
633 };
634
635 # The default max inotify watches is 8192.
636 # Nowadays most apps require a good number of inotify watches,
637 # the value below is used by default on several other distros.
638 boot.kernel.sysctl."fs.inotify.max_user_watches" = mkDefault 524288;
639
640 systemd.defaultUnit = mkIf cfg.autorun "graphical.target";
641
642 systemd.services.display-manager =
643 { description = "X11 Server";
644
645 after = [ "systemd-udev-settle.service" "local-fs.target" "acpid.service" "systemd-logind.service" ];
646 wants = [ "systemd-udev-settle.service" ];
647
648 restartIfChanged = false;
649
650 environment =
651 {
652 LD_LIBRARY_PATH = concatStringsSep ":" ([ "/run/opengl-driver/lib" ]
653 ++ concatLists (catAttrs "libPath" cfg.drivers));
654 } // cfg.displayManager.job.environment;
655
656 preStart =
657 ''
658 ${cfg.displayManager.job.preStart}
659
660 rm -f /tmp/.X0-lock
661 '';
662
663 script = "${cfg.displayManager.job.execCmd}";
664
665 serviceConfig = {
666 Restart = "always";
667 RestartSec = "200ms";
668 SyslogIdentifier = "display-manager";
669 # Stop restarting if the display manager stops (crashes) 2 times
670 # in one minute. Starting X typically takes 3-4s.
671 StartLimitInterval = "30s";
672 StartLimitBurst = "3";
673 };
674 };
675
676 services.xserver.displayManager.xserverArgs =
677 [ "-config ${configFile}"
678 "-xkbdir" "${cfg.xkbDir}"
679 # Log at the default verbosity level to stderr rather than /var/log/X.*.log.
680 "-logfile" "/dev/null"
681 ] ++ optional (cfg.display != null) ":${toString cfg.display}"
682 ++ optional (cfg.tty != null) "vt${toString cfg.tty}"
683 ++ optional (cfg.dpi != null) "-dpi ${toString cfg.dpi}"
684 ++ optional (cfg.verbose != null) "-verbose ${toString cfg.verbose}"
685 ++ optional (!cfg.enableTCP) "-nolisten tcp"
686 ++ optional (cfg.autoRepeatDelay != null) "-ardelay ${toString cfg.autoRepeatDelay}"
687 ++ optional (cfg.autoRepeatInterval != null) "-arinterval ${toString cfg.autoRepeatInterval}"
688 ++ optional cfg.terminateOnReset "-terminate";
689
690 services.xserver.modules =
691 concatLists (catAttrs "modules" cfg.drivers) ++
692 [ xorg.xorgserver.out
693 xorg.xf86inputevdev.out
694 ];
695
696 services.xserver.xkbDir = mkDefault "${pkgs.xkeyboard_config}/etc/X11/xkb";
697
698 system.extraDependencies = singleton (pkgs.runCommand "xkb-validated" {
699 inherit (cfg) xkbModel layout xkbVariant xkbOptions;
700 nativeBuildInputs = [ pkgs.xkbvalidate ];
701 } ''
702 validate "$xkbModel" "$layout" "$xkbVariant" "$xkbOptions"
703 touch "$out"
704 '');
705
706 services.xserver.config =
707 ''
708 Section "ServerFlags"
709 Option "AllowMouseOpenFail" "on"
710 Option "DontZap" "${if cfg.enableCtrlAltBackspace then "off" else "on"}"
711 ${cfg.serverFlagsSection}
712 EndSection
713
714 Section "Module"
715 ${cfg.moduleSection}
716 EndSection
717
718 Section "Monitor"
719 Identifier "Monitor[0]"
720 ${cfg.monitorSection}
721 EndSection
722
723 # Additional "InputClass" sections
724 ${flip concatMapStrings cfg.inputClassSections (inputClassSection: ''
725 Section "InputClass"
726 ${inputClassSection}
727 EndSection
728 '')}
729
730
731 Section "ServerLayout"
732 Identifier "Layout[all]"
733 ${cfg.serverLayoutSection}
734 # Reference the Screen sections for each driver. This will
735 # cause the X server to try each in turn.
736 ${flip concatMapStrings cfg.drivers (d: ''
737 Screen "Screen-${d.name}[0]"
738 '')}
739 EndSection
740
741 ${if cfg.useGlamor then ''
742 Section "Module"
743 Load "dri2"
744 Load "glamoregl"
745 EndSection
746 '' else ""}
747
748 # For each supported driver, add a "Device" and "Screen"
749 # section.
750 ${flip concatMapStrings cfg.drivers (driver: ''
751
752 Section "Device"
753 Identifier "Device-${driver.name}[0]"
754 Driver "${driver.driverName or driver.name}"
755 ${if cfg.useGlamor then ''Option "AccelMethod" "glamor"'' else ""}
756 ${cfg.deviceSection}
757 ${xrandrDeviceSection}
758 EndSection
759
760 Section "Screen"
761 Identifier "Screen-${driver.name}[0]"
762 Device "Device-${driver.name}[0]"
763 ${optionalString (cfg.monitorSection != "") ''
764 Monitor "Monitor[0]"
765 ''}
766
767 ${cfg.screenSection}
768
769 ${optionalString (cfg.defaultDepth != 0) ''
770 DefaultDepth ${toString cfg.defaultDepth}
771 ''}
772
773 ${optionalString
774 (driver.name != "virtualbox" &&
775 (cfg.resolutions != [] ||
776 cfg.extraDisplaySettings != "" ||
777 cfg.virtualScreen != null))
778 (let
779 f = depth:
780 ''
781 SubSection "Display"
782 Depth ${toString depth}
783 ${optionalString (cfg.resolutions != [])
784 "Modes ${concatMapStrings (res: ''"${toString res.x}x${toString res.y}"'') cfg.resolutions}"}
785 ${cfg.extraDisplaySettings}
786 ${optionalString (cfg.virtualScreen != null)
787 "Virtual ${toString cfg.virtualScreen.x} ${toString cfg.virtualScreen.y}"}
788 EndSubSection
789 '';
790 in concatMapStrings f [8 16 24]
791 )}
792
793 EndSection
794 '')}
795
796 ${xrandrMonitorSections}
797 '';
798
799 fonts.enableDefaultFonts = mkDefault true;
800
801 };
802
803}