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