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