From bb6a5b079fe8c88a6e42c92179711b2d0a9a9842 Mon Sep 17 00:00:00 2001 From: Roger Qiu Date: Tue, 15 Mar 2016 00:00:55 +1100 Subject: [PATCH 1/4] nixos/xserver: Changed xrandrHeads to support corresponding monitor section configuration in Xorg --- nixos/doc/manual/release-notes/rl-1709.xml | 11 ++++- nixos/modules/services/x11/xserver.nix | 57 ++++++++++++++++++---- 2 files changed, 57 insertions(+), 11 deletions(-) diff --git a/nixos/doc/manual/release-notes/rl-1709.xml b/nixos/doc/manual/release-notes/rl-1709.xml index 5fba4c34ec82..46469a4da782 100644 --- a/nixos/doc/manual/release-notes/rl-1709.xml +++ b/nixos/doc/manual/release-notes/rl-1709.xml @@ -17,7 +17,16 @@ has the following highlights: A consequence is that UIDs and GIDs are no longer reused. - + + + Xserver services module now allows one to specify configuration for each monitor. + This is done via the xrandrHeads property. It is backwards compatible, so your + existing configuration still works. You can replace each monitor designation with + an attribute set containing the monitor designation, whether the monitor is the + primary monitor, and extra configuration for that specific monitor. Only one + monitor can be the primary monitor. + + The following new services were added since the last release: diff --git a/nixos/modules/services/x11/xserver.nix b/nixos/modules/services/x11/xserver.nix index 8438e6dcc702..b5fece488205 100644 --- a/nixos/modules/services/x11/xserver.nix +++ b/nixos/modules/services/x11/xserver.nix @@ -33,12 +33,30 @@ let # Just enumerate all heads without discarding XRandR output information. - xrandrHeads = let - mkHead = num: output: { - name = "multihead${toString num}"; - inherit output; - }; - in imap mkHead cfg.xrandrHeads; + 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; xrandrDeviceSection = let monitors = flip map xrandrHeads (h: '' @@ -62,9 +80,13 @@ let value = '' Section "Monitor" Identifier "${current.name}" + ${optionalString (current.primary) '' + Option "Primary" "true" + ''} ${optionalString (previous != []) '' Option "RightOf" "${(head previous).name}" ''} + ${current.monitorConfig} EndSection ''; } ++ previous; @@ -329,12 +351,27 @@ in xrandrHeads = mkOption { default = []; - example = [ "HDMI-0" "DVI-0" ]; - type = with types; listOf string; + 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; + }) + ); description = '' Simple multiple monitor configuration, just specify a list of XRandR - outputs which will be mapped from left to right in the order of the - list. + outputs as the values of the list. 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. + 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. + + Only one monitor is allowed to be primary. If multiple monitors are + specified as primary, only the last monitor will be primary. Be careful using this option with multiple graphic adapters or with drivers that have poor support for XRandR, unexpected things might From 8266c89b55e3ae97ff635a0f9f8aee556ae7b384 Mon Sep 17 00:00:00 2001 From: aszlig Date: Mon, 24 Apr 2017 11:08:33 +0200 Subject: [PATCH 2/4] 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 --- nixos/modules/services/x11/xserver.nix | 131 +++++++++++++++---------- 1 file changed, 81 insertions(+), 50 deletions(-) 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 From d7a8876c13f75e7a55e2f3cbc16346406e9abdf8 Mon Sep 17 00:00:00 2001 From: aszlig Date: Mon, 24 Apr 2017 11:54:38 +0200 Subject: [PATCH 3/4] nixos/doc: Improve release notes for xrandrHeads The xrandrHeads option has been there since a long time, so there is no need to advertise it as a new feature. Instead, let's focus on just what has changed, which is that we now assign one head to be primary. Signed-off-by: aszlig --- nixos/doc/manual/release-notes/rl-1709.xml | 25 ++++++++++++++++------ 1 file changed, 19 insertions(+), 6 deletions(-) diff --git a/nixos/doc/manual/release-notes/rl-1709.xml b/nixos/doc/manual/release-notes/rl-1709.xml index 46469a4da782..988562366471 100644 --- a/nixos/doc/manual/release-notes/rl-1709.xml +++ b/nixos/doc/manual/release-notes/rl-1709.xml @@ -19,12 +19,25 @@ has the following highlights: - Xserver services module now allows one to specify configuration for each monitor. - This is done via the xrandrHeads property. It is backwards compatible, so your - existing configuration still works. You can replace each monitor designation with - an attribute set containing the monitor designation, whether the monitor is the - primary monitor, and extra configuration for that specific monitor. Only one - monitor can be the primary monitor. + The module option now + causes the first head specified in this list to be set as the primary + head. Apart from that, it's now possible to also set additional options + by using an attribute set, for example: + +{ services.xserver.xrandrHeads = [ + "HDMI-0" + { + output = "DVI-0"; + primary = true; + monitorConfig = '' + Option "Rotate" "right" + ''; + } + ]; +} + + This will set the DVI-0 output to be the primary head, + even though HDMI-0 is the first head in the list. From 79e712822fcfe797c545e2db5bcef9a07b00b2e5 Mon Sep 17 00:00:00 2001 From: aszlig Date: Mon, 24 Apr 2017 12:02:10 +0200 Subject: [PATCH 4/4] nixos/xserver: Document xrandrHeads.apply It was asked by @CMCDragonkai to elaborate on that, so let's just do this by actually providing a code comment. Signed-off-by: aszlig --- nixos/modules/services/x11/xserver.nix | 2 ++ 1 file changed, 2 insertions(+) diff --git a/nixos/modules/services/x11/xserver.nix b/nixos/modules/services/x11/xserver.nix index 9ac69195a679..20028d7eb6a8 100644 --- a/nixos/modules/services/x11/xserver.nix +++ b/nixos/modules/services/x11/xserver.nix @@ -374,6 +374,8 @@ in type = with types; listOf (coercedTo str (output: { inherit output; }) (submodule { options = xrandrOptions; })); + # Set primary to true for the first head if no other has been set + # primary already. apply = heads: let hasPrimary = any (x: x.primary) heads; firstPrimary = head heads // { primary = true; };