nixos/xserver: Fix up/refactor xrandrHeads option

Using invalid module options in the submodule isn't very nice, because
it doesn't give very useful errors in case of type mismatch, also we
don't get descriptions of these options as they're effecively
nonexistent to the module system. Another downside of this is that
merging of these options isn't done correctly as well (eg. for
types.lines).

So we now have proper submodules for each xrandrHead and we also use
corcedTo in the type of xrandrHeads so that we can populate the
submodule's "output" option in case a plain string is defined for a list
item.

Instead of silently skipping multiple primary heads, we now have an
assertion, which displays a message and aborts configuration evaluation
appropriately.

Signed-off-by: aszlig <aszlig@redmoonstudios.org>
This commit is contained in:
aszlig 2017-04-24 11:08:33 +02:00
parent bb6a5b079f
commit 8266c89b55
No known key found for this signature in database
GPG Key ID: 1DE8E48E57DB5436

View File

@ -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 <citerefentry>
<refentrytitle>xrandr</refentrytitle>
<manvolnum>1</manvolnum>
</citerefentry> 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 <literal>Monitor</literal> 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>output</option> 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
<option>primary</option> to <literal>true</literal>.
<note><para>Only one monitor is allowed to be primary.</para></note>
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