diff --git a/nixos/modules/services/x11/xserver.nix b/nixos/modules/services/x11/xserver.nix
index b5fece488205..9ac69195a679 100644
--- a/nixos/modules/services/x11/xserver.nix
+++ b/nixos/modules/services/x11/xserver.nix
@@ -31,36 +31,51 @@ let
pkgs.xorg.fontadobe75dpi
];
+ xrandrOptions = {
+ output = mkOption {
+ type = types.str;
+ example = "DVI-0";
+ description = ''
+ The output name of the monitor, as shown by
+ xrandr
+ 1
+ invoked without arguments.
+ '';
+ };
+
+ primary = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ Whether this head is treated as the primary monitor,
+ '';
+ };
+
+ monitorConfig = mkOption {
+ type = types.lines;
+ default = "";
+ example = ''
+ DisplaySize 408 306
+ Option "DPMS" "false"
+ '';
+ description = ''
+ Extra lines to append to the Monitor section
+ verbatim.
+ '';
+ };
+ };
# Just enumerate all heads without discarding XRandR output information.
- xrandrHeads = (
- fold
- (nextHead: { index, alreadyPrimary, processedHeads }:
- let
- processedHead = { name = "multihead${toString index}"; } //
- (if isAttrs nextHead then
- {
- output = nextHead.output;
- primary = if alreadyPrimary then false else nextHead.primary || index == 0;
- monitorConfig = nextHead.monitorConfig;
- }
- else
- {
- output = nextHead;
- primary = if alreadyPrimary then false else index == 0;
- monitorConfig = "";
- }
- );
- primariness = if alreadyPrimary then true else processedHead.primary;
- in
- { index = index - 1; alreadyPrimary = primariness; processedHeads = ([ processedHead ] ++ processedHeads); })
- { index = (length cfg.xrandrHeads) - 1; alreadyPrimary = false; processedHeads = []; }
- cfg.xrandrHeads
- ).processedHeads;
+ xrandrHeads = let
+ mkHead = num: config: {
+ name = "multihead${toString num}";
+ inherit config;
+ };
+ in imap mkHead cfg.xrandrHeads;
xrandrDeviceSection = let
monitors = flip map xrandrHeads (h: ''
- Option "monitor-${h.output}" "${h.name}"
+ Option "monitor-${h.config.output}" "${h.name}"
'');
# First option is indented through the space in the config but any
# subsequent options aren't so we need to apply indentation to
@@ -80,13 +95,13 @@ let
value = ''
Section "Monitor"
Identifier "${current.name}"
- ${optionalString (current.primary) ''
+ ${optionalString (current.config.primary) ''
Option "Primary" "true"
''}
${optionalString (previous != []) ''
Option "RightOf" "${(head previous).name}"
''}
- ${current.monitorConfig}
+ ${current.config.monitorConfig}
EndSection
'';
} ++ previous;
@@ -351,27 +366,36 @@ in
xrandrHeads = mkOption {
default = [];
- example = [ "HDMI-0" { output = "DVI-0"; primary = true; monitorConfig = ""; } ];
- type = with types; listOf (either
- str
- (submodule {
- options.output = str;
- options.primary = bool;
- options.monitorConfig = lines;
- })
- );
+ example = [
+ "HDMI-0"
+ { output = "DVI-0"; primary = true; }
+ { output = "DVI-1"; monitorConfig = "Option \"Rotate\" \"left\""; }
+ ];
+ type = with types; listOf (coercedTo str (output: {
+ inherit output;
+ }) (submodule { options = xrandrOptions; }));
+ apply = heads: let
+ hasPrimary = any (x: x.primary) heads;
+ firstPrimary = head heads // { primary = true; };
+ newHeads = singleton firstPrimary ++ tail heads;
+ in if heads != [] && !hasPrimary then newHeads else heads;
description = ''
- Simple multiple monitor configuration, just specify a list of XRandR
- outputs as the values of the list. The monitors will be mapped from
- left to right in the order of the list.
+ Multiple monitor configuration, just specify a list of XRandR
+ outputs. The individual elements should be either simple strings or
+ an attribute set of output options.
- By default, the first monitor will be set as the primary monitor.
- However instead of a list, you can give an attribute set. That set
- can contain a primary monitor specification and a custom monitor
- configuration section.
+ If the element is a string, it is denoting the physical output for a
+ monitor, if it's an attribute set, you must at least provide the
+ option.
- Only one monitor is allowed to be primary. If multiple monitors are
- specified as primary, only the last monitor will be primary.
+ The monitors will be mapped from left to right in the order of the
+ list.
+
+ By default, the first monitor will be set as the primary monitor if
+ none of the elements contain an option that has set
+ to true.
+
+ Only one monitor is allowed to be primary.
Be careful using this option with multiple graphic adapters or with
drivers that have poor support for XRandR, unexpected things might
@@ -506,11 +530,18 @@ in
nixpkgs.config.xorg = optionalAttrs (elem "vboxvideo" cfg.videoDrivers) { abiCompat = "1.18"; };
- assertions =
- [ { assertion = config.security.polkit.enable;
- message = "X11 requires Polkit to be enabled (‘security.polkit.enable = true’).";
- }
- ];
+ assertions = [
+ { assertion = config.security.polkit.enable;
+ message = "X11 requires Polkit to be enabled (‘security.polkit.enable = true’).";
+ }
+ (let primaryHeads = filter (x: x.primary) cfg.xrandrHeads; in {
+ assertion = length primaryHeads < 2;
+ message = "Only one head is allowed to be primary in "
+ + "‘services.xserver.xrandrHeads’, but there are "
+ + "${toString (length primaryHeads)} heads set to primary: "
+ + concatMapStringsSep ", " (x: x.output) primaryHeads;
+ })
+ ];
environment.etc =
(optionals cfg.exportConfiguration