git.iko.soy/typeable/nixpkgs
on master help sign in
Merge pull request #156678 from Alexnortung/autorandr-module

nixos/autorandr: refactor

commitde698b740bbebbfb120460ea0d0c7070e7d7f7c8 authorJörg Thalheim <Mic92@users.noreply.github.com> dateSun 27 Mar 2022, 07:46 (5 years ago) parent12417777844c87
3 files changed+316−2
nixos/doc/manual/from_md/release-notes/rl-2205.section.xml+6 nixos/doc/manual/release-notes/rl-2205.section.md+2 nixos/modules/services/misc/autorandr.nix+308−2
nixos/doc/manual/from_md/release-notes/rl-2205.section.xml+6
@@ -1602,6 +1602,12 @@
16021602 warning.
16031603 </para>
16041604 </listitem>
1605+ <listitem>
1606+ <para>
1607+ <literal>services.autorandr</literal> now allows for adding
1608+ hooks and profiles declaratively.
1609+ </para>
1610+ </listitem>
16051611 <listitem>
16061612 <para>
16071613 The <literal>pomerium-cli</literal> command has been moved out
nixos/doc/manual/release-notes/rl-2205.section.md+2
@@ -567,6 +567,8 @@ In addition to numerous new and upgraded packages, this release has the followin
567567 Reason is that the old name has been deprecated upstream.
568568 Using the old option name will still work, but produce a warning.
569569
570+- `services.autorandr` now allows for adding hooks and profiles declaratively.
571+
570572- The `pomerium-cli` command has been moved out of the `pomerium` package into
571573 the `pomerium-cli` package, following upstream's repository split. If you are
572574 using the `pomerium-cli` command, you should now install the `pomerium-cli`
nixos/modules/services/misc/autorandr.nix+308−2
@@ -5,6 +5,243 @@ with lib;
55let
66
77 cfg = config.services.autorandr;
8+ hookType = types.lines;
9+
10+ matrixOf = n: m: elemType:
11+ mkOptionType rec {
12+ name = "matrixOf";
13+ description =
14+ "${toString n}×${toString m} matrix of ${elemType.description}s";
15+ check = xss:
16+ let listOfSize = l: xs: isList xs && length xs == l;
17+ in listOfSize n xss
18+ && all (xs: listOfSize m xs && all elemType.check xs) xss;
19+ merge = mergeOneOption;
20+ getSubOptions = prefix: elemType.getSubOptions (prefix ++ [ "*" "*" ]);
21+ getSubModules = elemType.getSubModules;
22+ substSubModules = mod: matrixOf n m (elemType.substSubModules mod);
23+ functor = (defaultFunctor name) // { wrapped = elemType; };
24+ };
25+
26+ profileModule = types.submodule {
27+ options = {
28+ fingerprint = mkOption {
29+ type = types.attrsOf types.str;
30+ description = ''
31+ Output name to EDID mapping.
32+ Use <code>autorandr --fingerprint</code> to get current setup values.
33+ '';
34+ default = { };
35+ };
36+
37+ config = mkOption {
38+ type = types.attrsOf configModule;
39+ description = "Per output profile configuration.";
40+ default = { };
41+ };
42+
43+ hooks = mkOption {
44+ type = hooksModule;
45+ description = "Profile hook scripts.";
46+ default = { };
47+ };
48+ };
49+ };
50+
51+ configModule = types.submodule {
52+ options = {
53+ enable = mkOption {
54+ type = types.bool;
55+ description = "Whether to enable the output.";
56+ default = true;
57+ };
58+
59+ crtc = mkOption {
60+ type = types.nullOr types.ints.unsigned;
61+ description = "Output video display controller.";
62+ default = null;
63+ example = 0;
64+ };
65+
66+ primary = mkOption {
67+ type = types.bool;
68+ description = "Whether output should be marked as primary";
69+ default = false;
70+ };
71+
72+ position = mkOption {
73+ type = types.str;
74+ description = "Output position";
75+ default = "";
76+ example = "5760x0";
77+ };
78+
79+ mode = mkOption {
80+ type = types.str;
81+ description = "Output resolution.";
82+ default = "";
83+ example = "3840x2160";
84+ };
85+
86+ rate = mkOption {
87+ type = types.str;
88+ description = "Output framerate.";
89+ default = "";
90+ example = "60.00";
91+ };
92+
93+ gamma = mkOption {
94+ type = types.str;
95+ description = "Output gamma configuration.";
96+ default = "";
97+ example = "1.0:0.909:0.833";
98+ };
99+
100+ rotate = mkOption {
101+ type = types.nullOr (types.enum [ "normal" "left" "right" "inverted" ]);
102+ description = "Output rotate configuration.";
103+ default = null;
104+ example = "left";
105+ };
106+
107+ transform = mkOption {
108+ type = types.nullOr (matrixOf 3 3 types.float);
109+ default = null;
110+ example = literalExpression ''
111+ [
112+ [ 0.6 0.0 0.0 ]
113+ [ 0.0 0.6 0.0 ]
114+ [ 0.0 0.0 1.0 ]
115+ ]
116+ '';
117+ description = ''
118+ Refer to
119+ <citerefentry>
120+ <refentrytitle>xrandr</refentrytitle>
121+ <manvolnum>1</manvolnum>
122+ </citerefentry>
123+ for the documentation of the transform matrix.
124+ '';
125+ };
126+
127+ dpi = mkOption {
128+ type = types.nullOr types.ints.positive;
129+ description = "Output DPI configuration.";
130+ default = null;
131+ example = 96;
132+ };
133+
134+ scale = mkOption {
135+ type = types.nullOr (types.submodule {
136+ options = {
137+ method = mkOption {
138+ type = types.enum [ "factor" "pixel" ];
139+ description = "Output scaling method.";
140+ default = "factor";
141+ example = "pixel";
142+ };
143+
144+ x = mkOption {
145+ type = types.either types.float types.ints.positive;
146+ description = "Horizontal scaling factor/pixels.";
147+ };
148+
149+ y = mkOption {
150+ type = types.either types.float types.ints.positive;
151+ description = "Vertical scaling factor/pixels.";
152+ };
153+ };
154+ });
155+ description = ''
156+ Output scale configuration.
157+ </para><para>
158+ Either configure by pixels or a scaling factor. When using pixel method the
159+ <citerefentry>
160+ <refentrytitle>xrandr</refentrytitle>
161+ <manvolnum>1</manvolnum>
162+ </citerefentry>
163+ option
164+ <parameter class="command">--scale-from</parameter>
165+ will be used; when using factor method the option
166+ <parameter class="command">--scale</parameter>
167+ will be used.
168+ </para><para>
169+ This option is a shortcut version of the transform option and they are mutually
170+ exclusive.
171+ '';
172+ default = null;
173+ example = literalExpression ''
174+ {
175+ x = 1.25;
176+ y = 1.25;
177+ }
178+ '';
179+ };
180+ };
181+ };
182+
183+ hooksModule = types.submodule {
184+ options = {
185+ postswitch = mkOption {
186+ type = types.attrsOf hookType;
187+ description = "Postswitch hook executed after mode switch.";
188+ default = { };
189+ };
190+
191+ preswitch = mkOption {
192+ type = types.attrsOf hookType;
193+ description = "Preswitch hook executed before mode switch.";
194+ default = { };
195+ };
196+
197+ predetect = mkOption {
198+ type = types.attrsOf hookType;
199+ description = ''
200+ Predetect hook executed before autorandr attempts to run xrandr.
201+ '';
202+ default = { };
203+ };
204+ };
205+ };
206+
207+ hookToFile = folder: name: hook:
208+ nameValuePair "xdg/autorandr/${folder}/${name}" {
209+ source = "${pkgs.writeShellScriptBin "hook" hook}/bin/hook";
210+ };
211+ profileToFiles = name: profile:
212+ with profile;
213+ mkMerge ([
214+ {
215+ "xdg/autorandr/${name}/setup".text = concatStringsSep "\n"
216+ (mapAttrsToList fingerprintToString fingerprint);
217+ "xdg/autorandr/${name}/config".text =
218+ concatStringsSep "\n" (mapAttrsToList configToString profile.config);
219+ }
220+ (mapAttrs' (hookToFile "${name}/postswitch.d") hooks.postswitch)
221+ (mapAttrs' (hookToFile "${name}/preswitch.d") hooks.preswitch)
222+ (mapAttrs' (hookToFile "${name}/predetect.d") hooks.predetect)
223+ ]);
224+ fingerprintToString = name: edid: "${name} ${edid}";
225+ configToString = name: config:
226+ if config.enable then
227+ concatStringsSep "\n" ([ "output ${name}" ]
228+ ++ optional (config.position != "") "pos ${config.position}"
229+ ++ optional (config.crtc != null) "crtc ${toString config.crtc}"
230+ ++ optional config.primary "primary"
231+ ++ optional (config.dpi != null) "dpi ${toString config.dpi}"
232+ ++ optional (config.gamma != "") "gamma ${config.gamma}"
233+ ++ optional (config.mode != "") "mode ${config.mode}"
234+ ++ optional (config.rate != "") "rate ${config.rate}"
235+ ++ optional (config.rotate != null) "rotate ${config.rotate}"
236+ ++ optional (config.transform != null) ("transform "
237+ + concatMapStringsSep "," toString (flatten config.transform))
238+ ++ optional (config.scale != null)
239+ ((if config.scale.method == "factor" then "scale" else "scale-from")
240+ + " ${toString config.scale.x}x${toString config.scale.y}"))
241+ else ''
242+ output ${name}
243+ off
244+ '';
8245
9246in {
10247
@@ -22,6 +259,67 @@ in {
22259 for further reference.
23260 '';
24261 };
262+
263+ hooks = mkOption {
264+ type = hooksModule;
265+ description = "Global hook scripts";
266+ default = { };
267+ example = ''
268+ {
269+ postswitch = {
270+ "notify-i3" = "''${pkgs.i3}/bin/i3-msg restart";
271+ "change-background" = readFile ./change-background.sh;
272+ "change-dpi" = '''
273+ case "$AUTORANDR_CURRENT_PROFILE" in
274+ default)
275+ DPI=120
276+ ;;
277+ home)
278+ DPI=192
279+ ;;
280+ work)
281+ DPI=144
282+ ;;
283+ *)
284+ echo "Unknown profle: $AUTORANDR_CURRENT_PROFILE"
285+ exit 1
286+ esac
287+ echo "Xft.dpi: $DPI" | ''${pkgs.xorg.xrdb}/bin/xrdb -merge
288+ '''
289+ };
290+ }
291+ '';
292+ };
293+ profiles = mkOption {
294+ type = types.attrsOf profileModule;
295+ description = "Autorandr profiles specification.";
296+ default = { };
297+ example = literalExpression ''
298+ {
299+ "work" = {
300+ fingerprint = {
301+ eDP1 = "<EDID>";
302+ DP1 = "<EDID>";
303+ };
304+ config = {
305+ eDP1.enable = false;
306+ DP1 = {
307+ enable = true;
308+ crtc = 0;
309+ primary = true;
310+ position = "0x0";
311+ mode = "3840x2160";
312+ gamma = "1.0:0.909:0.833";
313+ rate = "60.00";
314+ rotate = "left";
315+ };
316+ };
317+ hooks.postswitch = readFile ./work-postswitch.sh;
318+ };
319+ }
320+ '';
321+ };
322+
25323 };
26324
27325 };
@@ -30,7 +328,15 @@ in {
30328
31329 services.udev.packages = [ pkgs.autorandr ];
32330
33− environment.systemPackages = [ pkgs.autorandr ];
331+ environment = {
332+ systemPackages = [ pkgs.autorandr ];
333+ etc = mkMerge ([
334+ (mapAttrs' (hookToFile "postswitch.d") cfg.hooks.postswitch)
335+ (mapAttrs' (hookToFile "preswitch.d") cfg.hooks.preswitch)
336+ (mapAttrs' (hookToFile "predetect.d") cfg.hooks.predetect)
337+ (mkMerge (mapAttrsToList profileToFiles cfg.profiles))
338+ ]);
339+ };
34340
35341 systemd.services.autorandr = {
36342 wantedBy = [ "sleep.target" ];
@@ -49,5 +355,5 @@ in {
49355
50356 };
51357
52− meta.maintainers = with maintainers; [ ];
358+ meta.maintainers = with maintainers; [ alexnortung ];
53359}