git.iko.soy/typeable/nixpkgs
on master help sign in
nixos/autorandr: refactor

The autorandr module now provides options to set hooks declaratively It also provides options to set profiles declaratively.

commit41dd1d4d8b0a8b6dc2a7d71ab28adb0db111bf97 authorAlexander Nortung <alex_nortung@live.dk> dateTue 25 Jan 2022, 10:43 (5 years ago) parent8a5e9ef
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
@@ -699,6 +699,12 @@
699699 warning.
700700 </para>
701701 </listitem>
702+ <listitem>
703+ <para>
704+ <literal>services.autorandr</literal> now allows for adding
705+ hooks and profiles declaratively.
706+ </para>
707+ </listitem>
702708 </itemizedlist>
703709 </section>
704710</section>
nixos/doc/manual/release-notes/rl-2205.section.md+2
@@ -237,4 +237,6 @@ In addition to numerous new and upgraded packages, this release has the followin
237237 Reason is that the old name has been deprecated upstream.
238238 Using the old option name will still work, but produce a warning.
239239
240+- `services.autorandr` now allows for adding hooks and profiles declaratively.
241+
240242<!-- To avoid merge conflicts, consider adding your item at an arbitrary place in the list instead. -->
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" ];
@@ -48,5 +354,5 @@ in {
48354
49355 };
50356
51− meta.maintainers = with maintainers; [ ];
357+ meta.maintainers = with maintainers; [ alexnortung ];
52358}