|
|
|
@ -5,6 +5,243 @@ with lib; |
|
|
|
|
let |
|
|
|
|
|
|
|
|
|
cfg = config.services.autorandr; |
|
|
|
|
hookType = types.lines; |
|
|
|
|
|
|
|
|
|
matrixOf = n: m: elemType: |
|
|
|
|
mkOptionType rec { |
|
|
|
|
name = "matrixOf"; |
|
|
|
|
description = |
|
|
|
|
"${toString n}×${toString m} matrix of ${elemType.description}s"; |
|
|
|
|
check = xss: |
|
|
|
|
let listOfSize = l: xs: isList xs && length xs == l; |
|
|
|
|
in listOfSize n xss |
|
|
|
|
&& all (xs: listOfSize m xs && all elemType.check xs) xss; |
|
|
|
|
merge = mergeOneOption; |
|
|
|
|
getSubOptions = prefix: elemType.getSubOptions (prefix ++ [ "*" "*" ]); |
|
|
|
|
getSubModules = elemType.getSubModules; |
|
|
|
|
substSubModules = mod: matrixOf n m (elemType.substSubModules mod); |
|
|
|
|
functor = (defaultFunctor name) // { wrapped = elemType; }; |
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
profileModule = types.submodule { |
|
|
|
|
options = { |
|
|
|
|
fingerprint = mkOption { |
|
|
|
|
type = types.attrsOf types.str; |
|
|
|
|
description = '' |
|
|
|
|
Output name to EDID mapping. |
|
|
|
|
Use <code>autorandr --fingerprint</code> to get current setup values. |
|
|
|
|
''; |
|
|
|
|
default = { }; |
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
config = mkOption { |
|
|
|
|
type = types.attrsOf configModule; |
|
|
|
|
description = "Per output profile configuration."; |
|
|
|
|
default = { }; |
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
hooks = mkOption { |
|
|
|
|
type = hooksModule; |
|
|
|
|
description = "Profile hook scripts."; |
|
|
|
|
default = { }; |
|
|
|
|
}; |
|
|
|
|
}; |
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
configModule = types.submodule { |
|
|
|
|
options = { |
|
|
|
|
enable = mkOption { |
|
|
|
|
type = types.bool; |
|
|
|
|
description = "Whether to enable the output."; |
|
|
|
|
default = true; |
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
crtc = mkOption { |
|
|
|
|
type = types.nullOr types.ints.unsigned; |
|
|
|
|
description = "Output video display controller."; |
|
|
|
|
default = null; |
|
|
|
|
example = 0; |
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
primary = mkOption { |
|
|
|
|
type = types.bool; |
|
|
|
|
description = "Whether output should be marked as primary"; |
|
|
|
|
default = false; |
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
position = mkOption { |
|
|
|
|
type = types.str; |
|
|
|
|
description = "Output position"; |
|
|
|
|
default = ""; |
|
|
|
|
example = "5760x0"; |
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
mode = mkOption { |
|
|
|
|
type = types.str; |
|
|
|
|
description = "Output resolution."; |
|
|
|
|
default = ""; |
|
|
|
|
example = "3840x2160"; |
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
rate = mkOption { |
|
|
|
|
type = types.str; |
|
|
|
|
description = "Output framerate."; |
|
|
|
|
default = ""; |
|
|
|
|
example = "60.00"; |
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
gamma = mkOption { |
|
|
|
|
type = types.str; |
|
|
|
|
description = "Output gamma configuration."; |
|
|
|
|
default = ""; |
|
|
|
|
example = "1.0:0.909:0.833"; |
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
rotate = mkOption { |
|
|
|
|
type = types.nullOr (types.enum [ "normal" "left" "right" "inverted" ]); |
|
|
|
|
description = "Output rotate configuration."; |
|
|
|
|
default = null; |
|
|
|
|
example = "left"; |
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
transform = mkOption { |
|
|
|
|
type = types.nullOr (matrixOf 3 3 types.float); |
|
|
|
|
default = null; |
|
|
|
|
example = literalExpression '' |
|
|
|
|
[ |
|
|
|
|
[ 0.6 0.0 0.0 ] |
|
|
|
|
[ 0.0 0.6 0.0 ] |
|
|
|
|
[ 0.0 0.0 1.0 ] |
|
|
|
|
] |
|
|
|
|
''; |
|
|
|
|
description = '' |
|
|
|
|
Refer to |
|
|
|
|
<citerefentry> |
|
|
|
|
<refentrytitle>xrandr</refentrytitle> |
|
|
|
|
<manvolnum>1</manvolnum> |
|
|
|
|
</citerefentry> |
|
|
|
|
for the documentation of the transform matrix. |
|
|
|
|
''; |
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
dpi = mkOption { |
|
|
|
|
type = types.nullOr types.ints.positive; |
|
|
|
|
description = "Output DPI configuration."; |
|
|
|
|
default = null; |
|
|
|
|
example = 96; |
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
scale = mkOption { |
|
|
|
|
type = types.nullOr (types.submodule { |
|
|
|
|
options = { |
|
|
|
|
method = mkOption { |
|
|
|
|
type = types.enum [ "factor" "pixel" ]; |
|
|
|
|
description = "Output scaling method."; |
|
|
|
|
default = "factor"; |
|
|
|
|
example = "pixel"; |
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
x = mkOption { |
|
|
|
|
type = types.either types.float types.ints.positive; |
|
|
|
|
description = "Horizontal scaling factor/pixels."; |
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
y = mkOption { |
|
|
|
|
type = types.either types.float types.ints.positive; |
|
|
|
|
description = "Vertical scaling factor/pixels."; |
|
|
|
|
}; |
|
|
|
|
}; |
|
|
|
|
}); |
|
|
|
|
description = '' |
|
|
|
|
Output scale configuration. |
|
|
|
|
</para><para> |
|
|
|
|
Either configure by pixels or a scaling factor. When using pixel method the |
|
|
|
|
<citerefentry> |
|
|
|
|
<refentrytitle>xrandr</refentrytitle> |
|
|
|
|
<manvolnum>1</manvolnum> |
|
|
|
|
</citerefentry> |
|
|
|
|
option |
|
|
|
|
<parameter class="command">--scale-from</parameter> |
|
|
|
|
will be used; when using factor method the option |
|
|
|
|
<parameter class="command">--scale</parameter> |
|
|
|
|
will be used. |
|
|
|
|
</para><para> |
|
|
|
|
This option is a shortcut version of the transform option and they are mutually |
|
|
|
|
exclusive. |
|
|
|
|
''; |
|
|
|
|
default = null; |
|
|
|
|
example = literalExpression '' |
|
|
|
|
{ |
|
|
|
|
x = 1.25; |
|
|
|
|
y = 1.25; |
|
|
|
|
} |
|
|
|
|
''; |
|
|
|
|
}; |
|
|
|
|
}; |
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
hooksModule = types.submodule { |
|
|
|
|
options = { |
|
|
|
|
postswitch = mkOption { |
|
|
|
|
type = types.attrsOf hookType; |
|
|
|
|
description = "Postswitch hook executed after mode switch."; |
|
|
|
|
default = { }; |
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
preswitch = mkOption { |
|
|
|
|
type = types.attrsOf hookType; |
|
|
|
|
description = "Preswitch hook executed before mode switch."; |
|
|
|
|
default = { }; |
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
predetect = mkOption { |
|
|
|
|
type = types.attrsOf hookType; |
|
|
|
|
description = '' |
|
|
|
|
Predetect hook executed before autorandr attempts to run xrandr. |
|
|
|
|
''; |
|
|
|
|
default = { }; |
|
|
|
|
}; |
|
|
|
|
}; |
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
hookToFile = folder: name: hook: |
|
|
|
|
nameValuePair "xdg/autorandr/${folder}/${name}" { |
|
|
|
|
source = "${pkgs.writeShellScriptBin "hook" hook}/bin/hook"; |
|
|
|
|
}; |
|
|
|
|
profileToFiles = name: profile: |
|
|
|
|
with profile; |
|
|
|
|
mkMerge ([ |
|
|
|
|
{ |
|
|
|
|
"xdg/autorandr/${name}/setup".text = concatStringsSep "\n" |
|
|
|
|
(mapAttrsToList fingerprintToString fingerprint); |
|
|
|
|
"xdg/autorandr/${name}/config".text = |
|
|
|
|
concatStringsSep "\n" (mapAttrsToList configToString profile.config); |
|
|
|
|
} |
|
|
|
|
(mapAttrs' (hookToFile "${name}/postswitch.d") hooks.postswitch) |
|
|
|
|
(mapAttrs' (hookToFile "${name}/preswitch.d") hooks.preswitch) |
|
|
|
|
(mapAttrs' (hookToFile "${name}/predetect.d") hooks.predetect) |
|
|
|
|
]); |
|
|
|
|
fingerprintToString = name: edid: "${name} ${edid}"; |
|
|
|
|
configToString = name: config: |
|
|
|
|
if config.enable then |
|
|
|
|
concatStringsSep "\n" ([ "output ${name}" ] |
|
|
|
|
++ optional (config.position != "") "pos ${config.position}" |
|
|
|
|
++ optional (config.crtc != null) "crtc ${toString config.crtc}" |
|
|
|
|
++ optional config.primary "primary" |
|
|
|
|
++ optional (config.dpi != null) "dpi ${toString config.dpi}" |
|
|
|
|
++ optional (config.gamma != "") "gamma ${config.gamma}" |
|
|
|
|
++ optional (config.mode != "") "mode ${config.mode}" |
|
|
|
|
++ optional (config.rate != "") "rate ${config.rate}" |
|
|
|
|
++ optional (config.rotate != null) "rotate ${config.rotate}" |
|
|
|
|
++ optional (config.transform != null) ("transform " |
|
|
|
|
+ concatMapStringsSep "," toString (flatten config.transform)) |
|
|
|
|
++ optional (config.scale != null) |
|
|
|
|
((if config.scale.method == "factor" then "scale" else "scale-from") |
|
|
|
|
+ " ${toString config.scale.x}x${toString config.scale.y}")) |
|
|
|
|
else '' |
|
|
|
|
output ${name} |
|
|
|
|
off |
|
|
|
|
''; |
|
|
|
|
|
|
|
|
|
in { |
|
|
|
|
|
|
|
|
@ -22,6 +259,67 @@ in { |
|
|
|
|
for further reference. |
|
|
|
|
''; |
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
hooks = mkOption { |
|
|
|
|
type = hooksModule; |
|
|
|
|
description = "Global hook scripts"; |
|
|
|
|
default = { }; |
|
|
|
|
example = '' |
|
|
|
|
{ |
|
|
|
|
postswitch = { |
|
|
|
|
"notify-i3" = "''${pkgs.i3}/bin/i3-msg restart"; |
|
|
|
|
"change-background" = readFile ./change-background.sh; |
|
|
|
|
"change-dpi" = ''' |
|
|
|
|
case "$AUTORANDR_CURRENT_PROFILE" in |
|
|
|
|
default) |
|
|
|
|
DPI=120 |
|
|
|
|
;; |
|
|
|
|
home) |
|
|
|
|
DPI=192 |
|
|
|
|
;; |
|
|
|
|
work) |
|
|
|
|
DPI=144 |
|
|
|
|
;; |
|
|
|
|
*) |
|
|
|
|
echo "Unknown profle: $AUTORANDR_CURRENT_PROFILE" |
|
|
|
|
exit 1 |
|
|
|
|
esac |
|
|
|
|
echo "Xft.dpi: $DPI" | ''${pkgs.xorg.xrdb}/bin/xrdb -merge |
|
|
|
|
''' |
|
|
|
|
}; |
|
|
|
|
} |
|
|
|
|
''; |
|
|
|
|
}; |
|
|
|
|
profiles = mkOption { |
|
|
|
|
type = types.attrsOf profileModule; |
|
|
|
|
description = "Autorandr profiles specification."; |
|
|
|
|
default = { }; |
|
|
|
|
example = literalExpression '' |
|
|
|
|
{ |
|
|
|
|
"work" = { |
|
|
|
|
fingerprint = { |
|
|
|
|
eDP1 = "<EDID>"; |
|
|
|
|
DP1 = "<EDID>"; |
|
|
|
|
}; |
|
|
|
|
config = { |
|
|
|
|
eDP1.enable = false; |
|
|
|
|
DP1 = { |
|
|
|
|
enable = true; |
|
|
|
|
crtc = 0; |
|
|
|
|
primary = true; |
|
|
|
|
position = "0x0"; |
|
|
|
|
mode = "3840x2160"; |
|
|
|
|
gamma = "1.0:0.909:0.833"; |
|
|
|
|
rate = "60.00"; |
|
|
|
|
rotate = "left"; |
|
|
|
|
}; |
|
|
|
|
}; |
|
|
|
|
hooks.postswitch = readFile ./work-postswitch.sh; |
|
|
|
|
}; |
|
|
|
|
} |
|
|
|
|
''; |
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
}; |
|
|
|
@ -30,7 +328,15 @@ in { |
|
|
|
|
|
|
|
|
|
services.udev.packages = [ pkgs.autorandr ]; |
|
|
|
|
|
|
|
|
|
environment.systemPackages = [ pkgs.autorandr ]; |
|
|
|
|
environment = { |
|
|
|
|
systemPackages = [ pkgs.autorandr ]; |
|
|
|
|
etc = mkMerge ([ |
|
|
|
|
(mapAttrs' (hookToFile "postswitch.d") cfg.hooks.postswitch) |
|
|
|
|
(mapAttrs' (hookToFile "preswitch.d") cfg.hooks.preswitch) |
|
|
|
|
(mapAttrs' (hookToFile "predetect.d") cfg.hooks.predetect) |
|
|
|
|
(mkMerge (mapAttrsToList profileToFiles cfg.profiles)) |
|
|
|
|
]); |
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
systemd.services.autorandr = { |
|
|
|
|
wantedBy = [ "sleep.target" ]; |
|
|
|
@ -48,5 +354,5 @@ in { |
|
|
|
|
|
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
meta.maintainers = with maintainers; [ ]; |
|
|
|
|
meta.maintainers = with maintainers; [ alexnortung ]; |
|
|
|
|
} |
|
|
|
|