1{ config, lib, pkgs, pkgs_i686, ... }:
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 plainX = mkOption {
165 type = types.bool;
166 default = false;
167 description = ''
168 Whether the X11 session can be plain (without DM/WM) and
169 the Xsession script will be used as fallback or not.
170 '';
171 };
172
173 autorun = mkOption {
174 type = types.bool;
175 default = true;
176 description = ''
177 Whether to start the X server automatically.
178 '';
179 };
180
181 exportConfiguration = mkOption {
182 type = types.bool;
183 default = false;
184 description = ''
185 Whether to symlink the X server configuration under
186 <filename>/etc/X11/xorg.conf</filename>.
187 '';
188 };
189
190 enableTCP = mkOption {
191 type = types.bool;
192 default = false;
193 description = ''
194 Whether to allow the X server to accept TCP connections.
195 '';
196 };
197
198 autoRepeatDelay = mkOption {
199 type = types.nullOr types.int;
200 default = null;
201 description = ''
202 Sets the autorepeat delay (length of time in milliseconds that a key must be depressed before autorepeat starts).
203 '';
204 };
205
206 autoRepeatInterval = mkOption {
207 type = types.nullOr types.int;
208 default = null;
209 description = ''
210 Sets the autorepeat interval (length of time in milliseconds that should elapse between autorepeat-generated keystrokes).
211 '';
212 };
213
214 inputClassSections = mkOption {
215 type = types.listOf types.lines;
216 default = [];
217 example = literalExample ''
218 [ '''
219 Identifier "Trackpoint Wheel Emulation"
220 MatchProduct "ThinkPad USB Keyboard with TrackPoint"
221 Option "EmulateWheel" "true"
222 Option "EmulateWheelButton" "2"
223 Option "Emulate3Buttons" "false"
224 '''
225 ]
226 '';
227 description = "Content of additional InputClass sections of the X server configuration file.";
228 };
229
230 modules = mkOption {
231 type = types.listOf types.path;
232 default = [];
233 example = literalExample "[ pkgs.xf86_input_wacom ]";
234 description = "Packages to be added to the module search path of the X server.";
235 };
236
237 resolutions = mkOption {
238 type = types.listOf types.attrs;
239 default = [];
240 example = [ { x = 1600; y = 1200; } { x = 1024; y = 786; } ];
241 description = ''
242 The screen resolutions for the X server. The first element
243 is the default resolution. If this list is empty, the X
244 server will automatically configure the resolution.
245 '';
246 };
247
248 videoDrivers = mkOption {
249 type = types.listOf types.str;
250 # !!! We'd like "nv" here, but it segfaults the X server.
251 default = [ "ati" "cirrus" "intel" "vesa" "vmware" "modesetting" ];
252 example = [ "vesa" ];
253 description = ''
254 The names of the video drivers the configuration
255 supports. They will be tried in order until one that
256 supports your card is found.
257 '';
258 };
259
260 videoDriver = mkOption {
261 type = types.nullOr types.str;
262 default = null;
263 example = "i810";
264 description = ''
265 The name of the video driver for your graphics card. This
266 option is obsolete; please set the
267 <option>services.xserver.videoDrivers</option> instead.
268 '';
269 };
270
271 drivers = mkOption {
272 type = types.listOf types.attrs;
273 internal = true;
274 description = ''
275 A list of attribute sets specifying drivers to be loaded by
276 the X11 server.
277 '';
278 };
279
280 dpi = mkOption {
281 type = types.nullOr types.int;
282 default = null;
283 description = "DPI resolution to use for X server.";
284 };
285
286 startDbusSession = mkOption {
287 type = types.bool;
288 default = true;
289 description = ''
290 Whether to start a new DBus session when you log in with dbus-launch.
291 '';
292 };
293
294 updateDbusEnvironment = mkOption {
295 type = types.bool;
296 default = false;
297 description = ''
298 Whether to update the DBus activation environment after launching the
299 desktop manager.
300 '';
301 };
302
303 layout = mkOption {
304 type = types.str;
305 default = "us";
306 description = ''
307 Keyboard layout, or multiple keyboard layouts separated by commas.
308 '';
309 };
310
311 xkbModel = mkOption {
312 type = types.str;
313 default = "pc104";
314 example = "presario";
315 description = ''
316 Keyboard model.
317 '';
318 };
319
320 xkbOptions = mkOption {
321 type = types.str;
322 default = "terminate:ctrl_alt_bksp";
323 example = "grp:caps_toggle, grp_led:scroll";
324 description = ''
325 X keyboard options; layout switching goes here.
326 '';
327 };
328
329 xkbVariant = mkOption {
330 type = types.str;
331 default = "";
332 example = "colemak";
333 description = ''
334 X keyboard variant.
335 '';
336 };
337
338 xkbDir = mkOption {
339 type = types.path;
340 description = ''
341 Path used for -xkbdir xserver parameter.
342 '';
343 };
344
345 config = mkOption {
346 type = types.lines;
347 description = ''
348 The contents of the configuration file of the X server
349 (<filename>xorg.conf</filename>).
350 '';
351 };
352
353 deviceSection = mkOption {
354 type = types.lines;
355 default = "";
356 example = "VideoRAM 131072";
357 description = "Contents of the first Device section of the X server configuration file.";
358 };
359
360 screenSection = mkOption {
361 type = types.lines;
362 default = "";
363 example = ''
364 Option "RandRRotation" "on"
365 '';
366 description = "Contents of the first Screen section of the X server configuration file.";
367 };
368
369 monitorSection = mkOption {
370 type = types.lines;
371 default = "";
372 example = "HorizSync 28-49";
373 description = "Contents of the first Monitor section of the X server configuration file.";
374 };
375
376 xrandrHeads = mkOption {
377 default = [];
378 example = [
379 "HDMI-0"
380 { output = "DVI-0"; primary = true; }
381 { output = "DVI-1"; monitorConfig = "Option \"Rotate\" \"left\""; }
382 ];
383 type = with types; listOf (coercedTo str (output: {
384 inherit output;
385 }) (submodule { options = xrandrOptions; }));
386 # Set primary to true for the first head if no other has been set
387 # primary already.
388 apply = heads: let
389 hasPrimary = any (x: x.primary) heads;
390 firstPrimary = head heads // { primary = true; };
391 newHeads = singleton firstPrimary ++ tail heads;
392 in if heads != [] && !hasPrimary then newHeads else heads;
393 description = ''
394 Multiple monitor configuration, just specify a list of XRandR
395 outputs. The individual elements should be either simple strings or
396 an attribute set of output options.
397
398 If the element is a string, it is denoting the physical output for a
399 monitor, if it's an attribute set, you must at least provide the
400 <option>output</option> option.
401
402 The monitors will be mapped from left to right in the order of the
403 list.
404
405 By default, the first monitor will be set as the primary monitor if
406 none of the elements contain an option that has set
407 <option>primary</option> to <literal>true</literal>.
408
409 <note><para>Only one monitor is allowed to be primary.</para></note>
410
411 Be careful using this option with multiple graphic adapters or with
412 drivers that have poor support for XRandR, unexpected things might
413 happen with those.
414 '';
415 };
416
417 serverFlagsSection = mkOption {
418 default = "";
419 example =
420 ''
421 Option "BlankTime" "0"
422 Option "StandbyTime" "0"
423 Option "SuspendTime" "0"
424 Option "OffTime" "0"
425 '';
426 description = "Contents of the ServerFlags section of the X server configuration file.";
427 };
428
429 moduleSection = mkOption {
430 type = types.lines;
431 default = "";
432 example =
433 ''
434 SubSection "extmod"
435 EndSubsection
436 '';
437 description = "Contents of the Module section of the X server configuration file.";
438 };
439
440 serverLayoutSection = mkOption {
441 type = types.lines;
442 default = "";
443 example =
444 ''
445 Option "AIGLX" "true"
446 '';
447 description = "Contents of the ServerLayout section of the X server configuration file.";
448 };
449
450 extraDisplaySettings = mkOption {
451 type = types.lines;
452 default = "";
453 example = "Virtual 2048 2048";
454 description = "Lines to be added to every Display subsection of the Screen section.";
455 };
456
457 defaultDepth = mkOption {
458 type = types.int;
459 default = 0;
460 example = 8;
461 description = "Default colour depth.";
462 };
463
464 useXFS = mkOption {
465 # FIXME: what's the type of this option?
466 default = false;
467 example = "unix/:7100";
468 description = "Determines how to connect to the X Font Server.";
469 };
470
471 tty = mkOption {
472 type = types.nullOr types.int;
473 default = 7;
474 description = "Virtual console for the X server.";
475 };
476
477 display = mkOption {
478 type = types.nullOr types.int;
479 default = 0;
480 description = "Display number for the X server.";
481 };
482
483 virtualScreen = mkOption {
484 type = types.nullOr types.attrs;
485 default = null;
486 example = { x = 2048; y = 2048; };
487 description = ''
488 Virtual screen size for Xrandr.
489 '';
490 };
491
492 verbose = mkOption {
493 type = types.nullOr types.int;
494 default = 3;
495 example = 7;
496 description = ''
497 Controls verbosity of X logging.
498 '';
499 };
500
501 useGlamor = mkOption {
502 type = types.bool;
503 default = false;
504 description = ''
505 Whether to use the Glamor module for 2D acceleration,
506 if possible.
507 '';
508 };
509
510 enableCtrlAltBackspace = mkOption {
511 type = types.bool;
512 default = false;
513 description = ''
514 Whether to enable the DontZap option, which binds Ctrl+Alt+Backspace
515 to forcefully kill X. This can lead to data loss and is disabled
516 by default.
517 '';
518 };
519
520 terminateOnReset = mkOption {
521 type = types.bool;
522 default = true;
523 description = ''
524 Whether to terminate X upon server reset.
525 '';
526 };
527 };
528
529 };
530
531
532
533 ###### implementation
534
535 config = mkIf cfg.enable {
536
537 hardware.opengl.enable = mkDefault true;
538
539 services.xserver.videoDrivers = mkIf (cfg.videoDriver != null) [ cfg.videoDriver ];
540
541 # FIXME: somehow check for unknown driver names.
542 services.xserver.drivers = flip concatMap cfg.videoDrivers (name:
543 let driver =
544 attrByPath [name]
545 (if xorg ? ${"xf86video" + name}
546 then { modules = [xorg.${"xf86video" + name}]; }
547 else null)
548 knownVideoDrivers;
549 in optional (driver != null) ({ inherit name; modules = []; driverName = name; } // driver));
550
551 nixpkgs.config = optionalAttrs (elem "vboxvideo" cfg.videoDrivers) { xorg.abiCompat = "1.18"; };
552
553 assertions = [
554 { assertion = config.security.polkit.enable;
555 message = "X11 requires Polkit to be enabled (‘security.polkit.enable = true’).";
556 }
557 (let primaryHeads = filter (x: x.primary) cfg.xrandrHeads; in {
558 assertion = length primaryHeads < 2;
559 message = "Only one head is allowed to be primary in "
560 + "‘services.xserver.xrandrHeads’, but there are "
561 + "${toString (length primaryHeads)} heads set to primary: "
562 + concatMapStringsSep ", " (x: x.output) primaryHeads;
563 })
564 { assertion = cfg.desktopManager.default == "none" && cfg.windowManager.default == "none" -> cfg.plainX;
565 message = "Either the desktop manager or the window manager shouldn't be `none`! "
566 + "To explicitly allow this, you can also set `services.xserver.plainX` to `true`. "
567 + "The `default` value looks for enabled WMs/DMs and select the first one.";
568 }
569 ];
570
571 environment.etc =
572 (optionals cfg.exportConfiguration
573 [ { source = "${configFile}";
574 target = "X11/xorg.conf";
575 }
576 # -xkbdir command line option does not seems to be passed to xkbcomp.
577 { source = "${cfg.xkbDir}";
578 target = "X11/xkb";
579 }
580 ])
581 # localectl looks into 00-keyboard.conf
582 ++ [
583 {
584 text = ''
585 Section "InputClass"
586 Identifier "Keyboard catchall"
587 MatchIsKeyboard "on"
588 Option "XkbModel" "${cfg.xkbModel}"
589 Option "XkbLayout" "${cfg.layout}"
590 Option "XkbOptions" "${cfg.xkbOptions}"
591 Option "XkbVariant" "${cfg.xkbVariant}"
592 EndSection
593 '';
594 target = "X11/xorg.conf.d/00-keyboard.conf";
595 }
596 ]
597 # Needed since 1.18; see https://bugs.freedesktop.org/show_bug.cgi?id=89023#c5
598 ++ (let cfgPath = "/X11/xorg.conf.d/10-evdev.conf"; in
599 [{
600 source = xorg.xf86inputevdev.out + "/share" + cfgPath;
601 target = cfgPath;
602 }]
603 );
604
605 environment.systemPackages =
606 [ xorg.xorgserver.out
607 xorg.xrandr
608 xorg.xrdb
609 xorg.setxkbmap
610 xorg.iceauth # required for KDE applications (it's called by dcopserver)
611 xorg.xlsclients
612 xorg.xset
613 xorg.xsetroot
614 xorg.xinput
615 xorg.xprop
616 xorg.xauth
617 pkgs.xterm
618 pkgs.xdg_utils
619 xorg.xf86inputevdev.out # get evdev.4 man page
620 ]
621 ++ optional (elem "virtualbox" cfg.videoDrivers) xorg.xrefresh;
622
623 environment.pathsToLink =
624 [ "/etc/xdg" "/share/xdg" "/share/applications" "/share/icons" "/share/pixmaps" ];
625
626 # The default max inotify watches is 8192.
627 # Nowadays most apps require a good number of inotify watches,
628 # the value below is used by default on several other distros.
629 boot.kernel.sysctl."fs.inotify.max_user_watches" = mkDefault 524288;
630
631 systemd.defaultUnit = mkIf cfg.autorun "graphical.target";
632
633 systemd.services.display-manager =
634 { description = "X11 Server";
635
636 after = [ "systemd-udev-settle.service" "local-fs.target" "acpid.service" "systemd-logind.service" ];
637 wants = [ "systemd-udev-settle.service" ];
638
639 restartIfChanged = false;
640
641 environment =
642 {
643 XORG_DRI_DRIVER_PATH = "/run/opengl-driver/lib/dri"; # !!! Depends on the driver selected at runtime.
644 LD_LIBRARY_PATH = concatStringsSep ":" (
645 [ "${xorg.libX11.out}/lib" "${xorg.libXext.out}/lib" "/run/opengl-driver/lib" ]
646 ++ concatLists (catAttrs "libPath" cfg.drivers));
647 } // cfg.displayManager.job.environment;
648
649 preStart =
650 ''
651 ${cfg.displayManager.job.preStart}
652
653 rm -f /tmp/.X0-lock
654 '';
655
656 script = "${cfg.displayManager.job.execCmd}";
657
658 serviceConfig = {
659 Restart = "always";
660 RestartSec = "200ms";
661 SyslogIdentifier = "display-manager";
662 # Stop restarting if the display manager stops (crashes) 2 times
663 # in one minute. Starting X typically takes 3-4s.
664 StartLimitInterval = "30s";
665 StartLimitBurst = "3";
666 };
667 };
668
669 services.xserver.displayManager.xserverArgs =
670 [ "-config ${configFile}"
671 "-xkbdir" "${cfg.xkbDir}"
672 # Log at the default verbosity level to stderr rather than /var/log/X.*.log.
673 "-logfile" "/dev/null"
674 ] ++ optional (cfg.display != null) ":${toString cfg.display}"
675 ++ optional (cfg.tty != null) "vt${toString cfg.tty}"
676 ++ optional (cfg.dpi != null) "-dpi ${toString cfg.dpi}"
677 ++ optional (cfg.verbose != null) "-verbose ${toString cfg.verbose}"
678 ++ optional (!cfg.enableTCP) "-nolisten tcp"
679 ++ optional (cfg.autoRepeatDelay != null) "-ardelay ${toString cfg.autoRepeatDelay}"
680 ++ optional (cfg.autoRepeatInterval != null) "-arinterval ${toString cfg.autoRepeatInterval}"
681 ++ optional cfg.terminateOnReset "-terminate";
682
683 services.xserver.modules =
684 concatLists (catAttrs "modules" cfg.drivers) ++
685 [ xorg.xorgserver.out
686 xorg.xf86inputevdev.out
687 ];
688
689 services.xserver.xkbDir = mkDefault "${pkgs.xkeyboard_config}/etc/X11/xkb";
690
691 system.extraDependencies = singleton (pkgs.runCommand "xkb-validated" {
692 inherit (cfg) xkbModel layout xkbVariant xkbOptions;
693 nativeBuildInputs = [ pkgs.xkbvalidate ];
694 } ''
695 validate "$xkbModel" "$layout" "$xkbVariant" "$xkbOptions"
696 touch "$out"
697 '');
698
699 services.xserver.config =
700 ''
701 Section "ServerFlags"
702 Option "AllowMouseOpenFail" "on"
703 Option "DontZap" "${if cfg.enableCtrlAltBackspace then "off" else "on"}"
704 ${cfg.serverFlagsSection}
705 EndSection
706
707 Section "Module"
708 ${cfg.moduleSection}
709 EndSection
710
711 Section "Monitor"
712 Identifier "Monitor[0]"
713 ${cfg.monitorSection}
714 EndSection
715
716 # Additional "InputClass" sections
717 ${flip concatMapStrings cfg.inputClassSections (inputClassSection: ''
718 Section "InputClass"
719 ${inputClassSection}
720 EndSection
721 '')}
722
723
724 Section "ServerLayout"
725 Identifier "Layout[all]"
726 ${cfg.serverLayoutSection}
727 # Reference the Screen sections for each driver. This will
728 # cause the X server to try each in turn.
729 ${flip concatMapStrings cfg.drivers (d: ''
730 Screen "Screen-${d.name}[0]"
731 '')}
732 EndSection
733
734 ${if cfg.useGlamor then ''
735 Section "Module"
736 Load "dri2"
737 Load "glamoregl"
738 EndSection
739 '' else ""}
740
741 # For each supported driver, add a "Device" and "Screen"
742 # section.
743 ${flip concatMapStrings cfg.drivers (driver: ''
744
745 Section "Device"
746 Identifier "Device-${driver.name}[0]"
747 Driver "${driver.driverName or driver.name}"
748 ${if cfg.useGlamor then ''Option "AccelMethod" "glamor"'' else ""}
749 ${cfg.deviceSection}
750 ${xrandrDeviceSection}
751 EndSection
752
753 Section "Screen"
754 Identifier "Screen-${driver.name}[0]"
755 Device "Device-${driver.name}[0]"
756 ${optionalString (cfg.monitorSection != "") ''
757 Monitor "Monitor[0]"
758 ''}
759
760 ${cfg.screenSection}
761
762 ${optionalString (cfg.defaultDepth != 0) ''
763 DefaultDepth ${toString cfg.defaultDepth}
764 ''}
765
766 ${optionalString
767 (driver.name != "virtualbox" &&
768 (cfg.resolutions != [] ||
769 cfg.extraDisplaySettings != "" ||
770 cfg.virtualScreen != null))
771 (let
772 f = depth:
773 ''
774 SubSection "Display"
775 Depth ${toString depth}
776 ${optionalString (cfg.resolutions != [])
777 "Modes ${concatMapStrings (res: ''"${toString res.x}x${toString res.y}"'') cfg.resolutions}"}
778 ${cfg.extraDisplaySettings}
779 ${optionalString (cfg.virtualScreen != null)
780 "Virtual ${toString cfg.virtualScreen.x} ${toString cfg.virtualScreen.y}"}
781 EndSubSection
782 '';
783 in concatMapStrings f [8 16 24]
784 )}
785
786 EndSection
787 '')}
788
789 ${xrandrMonitorSections}
790 '';
791
792 fonts.enableDefaultFonts = mkDefault true;
793
794 };
795
796}