2018-04-09 15:04:43 +02:00
|
|
|
|
{ config, lib, pkgs, ... }:
|
|
|
|
|
|
|
|
|
|
with lib;
|
|
|
|
|
|
|
|
|
|
let
|
|
|
|
|
|
|
|
|
|
cfg = config.programs.autorandr;
|
|
|
|
|
|
2020-02-02 00:39:17 +01:00
|
|
|
|
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; };
|
|
|
|
|
};
|
2019-01-07 17:38:41 +01:00
|
|
|
|
|
2018-04-09 15:04:43 +02:00
|
|
|
|
profileModule = types.submodule {
|
|
|
|
|
options = {
|
|
|
|
|
fingerprint = mkOption {
|
2018-12-04 23:41:42 +01:00
|
|
|
|
type = types.attrsOf types.str;
|
2018-04-09 15:04:43 +02:00
|
|
|
|
description = ''
|
|
|
|
|
Output name to EDID mapping.
|
|
|
|
|
Use <code>autorandr --fingerprint</code> to get current setup values.
|
|
|
|
|
'';
|
2020-02-02 00:39:17 +01:00
|
|
|
|
default = { };
|
2018-04-09 15:04:43 +02:00
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
config = mkOption {
|
|
|
|
|
type = types.attrsOf configModule;
|
|
|
|
|
description = "Per output profile configuration.";
|
2020-02-02 00:39:17 +01:00
|
|
|
|
default = { };
|
2018-04-09 15:04:43 +02:00
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
hooks = mkOption {
|
|
|
|
|
type = profileHooksModule;
|
|
|
|
|
description = "Profile hook scripts.";
|
2020-02-02 00:39:17 +01:00
|
|
|
|
default = { };
|
2018-04-09 15:04:43 +02:00
|
|
|
|
};
|
|
|
|
|
};
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
configModule = types.submodule {
|
|
|
|
|
options = {
|
|
|
|
|
enable = mkOption {
|
|
|
|
|
type = types.bool;
|
|
|
|
|
description = "Whether to enable the output.";
|
|
|
|
|
default = true;
|
|
|
|
|
};
|
|
|
|
|
|
2020-05-29 12:56:14 +02:00
|
|
|
|
crtc = mkOption {
|
|
|
|
|
type = types.nullOr types.ints.unsigned;
|
|
|
|
|
description = "Output video display controller.";
|
|
|
|
|
default = null;
|
|
|
|
|
example = 0;
|
|
|
|
|
};
|
|
|
|
|
|
2018-04-09 15:04:43 +02:00
|
|
|
|
primary = mkOption {
|
|
|
|
|
type = types.bool;
|
|
|
|
|
description = "Whether output should be marked as primary";
|
|
|
|
|
default = false;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
position = mkOption {
|
2018-12-04 23:41:42 +01:00
|
|
|
|
type = types.str;
|
2018-04-09 15:04:43 +02:00
|
|
|
|
description = "Output position";
|
|
|
|
|
default = "";
|
|
|
|
|
example = "5760x0";
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
mode = mkOption {
|
2018-12-04 23:41:42 +01:00
|
|
|
|
type = types.str;
|
2018-04-09 15:04:43 +02:00
|
|
|
|
description = "Output resolution.";
|
|
|
|
|
default = "";
|
|
|
|
|
example = "3840x2160";
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
rate = mkOption {
|
2018-12-04 23:41:42 +01:00
|
|
|
|
type = types.str;
|
2018-04-09 15:04:43 +02:00
|
|
|
|
description = "Output framerate.";
|
|
|
|
|
default = "";
|
|
|
|
|
example = "60.00";
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
gamma = mkOption {
|
2018-12-04 23:41:42 +01:00
|
|
|
|
type = types.str;
|
2018-04-09 15:04:43 +02:00
|
|
|
|
description = "Output gamma configuration.";
|
|
|
|
|
default = "";
|
|
|
|
|
example = "1.0:0.909:0.833";
|
|
|
|
|
};
|
2018-06-25 21:24:36 +02:00
|
|
|
|
|
|
|
|
|
rotate = mkOption {
|
2020-02-02 00:39:17 +01:00
|
|
|
|
type = types.nullOr (types.enum [ "normal" "left" "right" "inverted" ]);
|
2018-06-25 21:24:36 +02:00
|
|
|
|
description = "Output rotate configuration.";
|
|
|
|
|
default = null;
|
|
|
|
|
example = "left";
|
|
|
|
|
};
|
2019-01-07 17:38:41 +01:00
|
|
|
|
|
|
|
|
|
transform = mkOption {
|
|
|
|
|
type = types.nullOr (matrixOf 3 3 types.float);
|
|
|
|
|
default = null;
|
|
|
|
|
example = literalExample ''
|
|
|
|
|
[
|
|
|
|
|
[ 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.
|
|
|
|
|
'';
|
|
|
|
|
};
|
2019-02-11 21:23:07 +01:00
|
|
|
|
|
|
|
|
|
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 {
|
2020-02-02 00:39:17 +01:00
|
|
|
|
type = types.enum [ "factor" "pixel" ];
|
2019-02-11 21:23:07 +01:00
|
|
|
|
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 = literalExample ''
|
|
|
|
|
{
|
|
|
|
|
x = 1.25;
|
|
|
|
|
y = 1.25;
|
|
|
|
|
}
|
|
|
|
|
'';
|
|
|
|
|
};
|
2018-04-09 15:04:43 +02:00
|
|
|
|
};
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
hookType = types.lines;
|
|
|
|
|
|
|
|
|
|
globalHooksModule = types.submodule {
|
|
|
|
|
options = {
|
|
|
|
|
postswitch = mkOption {
|
|
|
|
|
type = types.attrsOf hookType;
|
|
|
|
|
description = "Postswitch hook executed after mode switch.";
|
2020-02-02 00:39:17 +01:00
|
|
|
|
default = { };
|
2018-04-09 15:04:43 +02:00
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
preswitch = mkOption {
|
|
|
|
|
type = types.attrsOf hookType;
|
|
|
|
|
description = "Preswitch hook executed before mode switch.";
|
2020-02-02 00:39:17 +01:00
|
|
|
|
default = { };
|
2018-04-09 15:04:43 +02:00
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
predetect = mkOption {
|
|
|
|
|
type = types.attrsOf hookType;
|
2020-02-02 00:39:17 +01:00
|
|
|
|
description = ''
|
|
|
|
|
Predetect hook executed before autorandr attempts to run xrandr.
|
|
|
|
|
'';
|
|
|
|
|
default = { };
|
2018-04-09 15:04:43 +02:00
|
|
|
|
};
|
|
|
|
|
};
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
profileHooksModule = types.submodule {
|
|
|
|
|
options = {
|
|
|
|
|
postswitch = mkOption {
|
|
|
|
|
type = hookType;
|
|
|
|
|
description = "Postswitch hook executed after mode switch.";
|
|
|
|
|
default = "";
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
preswitch = mkOption {
|
|
|
|
|
type = hookType;
|
|
|
|
|
description = "Preswitch hook executed before mode switch.";
|
|
|
|
|
default = "";
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
predetect = mkOption {
|
|
|
|
|
type = hookType;
|
2020-02-02 00:39:17 +01:00
|
|
|
|
description = ''
|
|
|
|
|
Predetect hook executed before autorandr attempts to run xrandr.
|
|
|
|
|
'';
|
2018-04-09 15:04:43 +02:00
|
|
|
|
default = "";
|
|
|
|
|
};
|
|
|
|
|
};
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
hookToFile = folder: name: hook:
|
2020-02-02 00:39:17 +01:00
|
|
|
|
nameValuePair "autorandr/${folder}/${name}" {
|
|
|
|
|
source = "${pkgs.writeShellScriptBin "hook" hook}/bin/hook";
|
|
|
|
|
};
|
|
|
|
|
profileToFiles = name: profile:
|
|
|
|
|
with profile;
|
|
|
|
|
mkMerge ([
|
|
|
|
|
{
|
|
|
|
|
"autorandr/${name}/setup".text = concatStringsSep "\n"
|
|
|
|
|
(mapAttrsToList fingerprintToString fingerprint);
|
|
|
|
|
"autorandr/${name}/config".text =
|
|
|
|
|
concatStringsSep "\n" (mapAttrsToList configToString profile.config);
|
|
|
|
|
}
|
|
|
|
|
(mkIf (hooks.postswitch != "")
|
|
|
|
|
(listToAttrs [ (hookToFile name "postswitch" hooks.postswitch) ]))
|
|
|
|
|
(mkIf (hooks.preswitch != "")
|
|
|
|
|
(listToAttrs [ (hookToFile name "preswitch" hooks.preswitch) ]))
|
|
|
|
|
(mkIf (hooks.predetect != "")
|
|
|
|
|
(listToAttrs [ (hookToFile name "predetect" hooks.predetect) ]))
|
|
|
|
|
]);
|
2018-04-09 15:04:43 +02:00
|
|
|
|
fingerprintToString = name: edid: "${name} ${edid}";
|
2020-02-02 00:39:17 +01:00
|
|
|
|
configToString = name: config:
|
2020-05-26 14:00:33 +02:00
|
|
|
|
if config.enable then
|
|
|
|
|
concatStringsSep "\n" ([ "output ${name}" ]
|
|
|
|
|
++ optional (config.position != "") "pos ${config.position}"
|
2020-05-29 12:56:14 +02:00
|
|
|
|
++ optional (config.crtc != null) "crtc ${toString config.crtc}"
|
2020-05-26 14:00:33 +02:00
|
|
|
|
++ 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 ''
|
2020-02-02 00:39:17 +01:00
|
|
|
|
output ${name}
|
|
|
|
|
off
|
|
|
|
|
'';
|
|
|
|
|
|
|
|
|
|
in {
|
2018-04-09 15:04:43 +02:00
|
|
|
|
options = {
|
|
|
|
|
programs.autorandr = {
|
|
|
|
|
enable = mkEnableOption "Autorandr";
|
|
|
|
|
|
|
|
|
|
hooks = mkOption {
|
|
|
|
|
type = globalHooksModule;
|
|
|
|
|
description = "Global hook scripts";
|
2020-02-02 00:39:17 +01:00
|
|
|
|
default = { };
|
2018-04-09 15:04:43 +02:00
|
|
|
|
example = literalExample ''
|
2020-02-02 00:39:17 +01:00
|
|
|
|
{
|
|
|
|
|
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
|
|
|
|
|
'''
|
|
|
|
|
};
|
|
|
|
|
}
|
2018-04-09 15:04:43 +02:00
|
|
|
|
'';
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
profiles = mkOption {
|
|
|
|
|
type = types.attrsOf profileModule;
|
|
|
|
|
description = "Autorandr profiles specification.";
|
2020-02-02 00:39:17 +01:00
|
|
|
|
default = { };
|
2018-04-09 15:04:43 +02:00
|
|
|
|
example = literalExample ''
|
|
|
|
|
{
|
|
|
|
|
"work" = {
|
|
|
|
|
fingerprint = {
|
|
|
|
|
eDP1 = "<EDID>";
|
|
|
|
|
DP1 = "<EDID>";
|
|
|
|
|
};
|
|
|
|
|
config = {
|
|
|
|
|
eDP1.enable = false;
|
|
|
|
|
DP1 = {
|
|
|
|
|
enable = true;
|
2020-05-29 12:56:14 +02:00
|
|
|
|
crtc = 0;
|
2018-04-09 15:04:43 +02:00
|
|
|
|
primary = true;
|
|
|
|
|
position = "0x0";
|
|
|
|
|
mode = "3840x2160";
|
|
|
|
|
gamma = "1.0:0.909:0.833";
|
|
|
|
|
rate = "60.00";
|
2018-06-25 21:24:36 +02:00
|
|
|
|
rotate = "left";
|
2018-04-09 15:04:43 +02:00
|
|
|
|
};
|
|
|
|
|
};
|
|
|
|
|
hooks.postswitch = readFile ./work-postswitch.sh;
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
'';
|
|
|
|
|
};
|
|
|
|
|
};
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
config = mkIf cfg.enable {
|
2020-02-02 00:39:17 +01:00
|
|
|
|
assertions = flatten (mapAttrsToList (profile:
|
|
|
|
|
{ config, ... }:
|
|
|
|
|
mapAttrsToList (output: opts: {
|
|
|
|
|
assertion = opts.scale == null || opts.transform == null;
|
|
|
|
|
message = ''
|
|
|
|
|
Cannot use the profile output options 'scale' and 'transform' simultaneously.
|
|
|
|
|
Check configuration for: programs.autorandr.profiles.${profile}.config.${output}
|
|
|
|
|
'';
|
|
|
|
|
}) config) cfg.profiles);
|
2019-02-11 21:23:07 +01:00
|
|
|
|
|
2018-04-09 15:04:43 +02:00
|
|
|
|
home.packages = [ pkgs.autorandr ];
|
|
|
|
|
xdg.configFile = mkMerge ([
|
|
|
|
|
(mapAttrs' (hookToFile "postswitch.d") cfg.hooks.postswitch)
|
2020-02-02 00:39:17 +01:00
|
|
|
|
(mapAttrs' (hookToFile "preswitch.d") cfg.hooks.preswitch)
|
|
|
|
|
(mapAttrs' (hookToFile "predetect.d") cfg.hooks.predetect)
|
2018-04-09 15:04:43 +02:00
|
|
|
|
(mkMerge (mapAttrsToList profileToFiles cfg.profiles))
|
|
|
|
|
]);
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
meta.maintainers = [ maintainers.uvnikita ];
|
|
|
|
|
}
|