diff --git a/modules/misc/news.nix b/modules/misc/news.nix index e903669ed..04669b13e 100644 --- a/modules/misc/news.nix +++ b/modules/misc/news.nix @@ -605,6 +605,13 @@ in can be used. ''; } + + { + time = "2018-04-19T07:42:01+00:00"; + message = '' + A new module is available: 'programs.autorandr'. + ''; + } ]; }; } diff --git a/modules/modules.nix b/modules/modules.nix index eca0678d4..ff48a4350 100644 --- a/modules/modules.nix +++ b/modules/modules.nix @@ -22,6 +22,7 @@ let ./misc/nixpkgs.nix ./misc/pam.nix ./misc/xdg.nix + ./programs/autorandr.nix ./programs/bash.nix ./programs/beets.nix ./programs/browserpass.nix diff --git a/modules/programs/autorandr.nix b/modules/programs/autorandr.nix new file mode 100644 index 000000000..b97b60452 --- /dev/null +++ b/modules/programs/autorandr.nix @@ -0,0 +1,230 @@ +{ config, lib, pkgs, ... }: + +with lib; + +let + + cfg = config.programs.autorandr; + + profileModule = types.submodule { + options = { + fingerprint = mkOption { + type = types.attrsOf types.string; + description = '' + Output name to EDID mapping. + Use autorandr --fingerprint to get current setup values. + ''; + default = {}; + }; + + config = mkOption { + type = types.attrsOf configModule; + description = "Per output profile configuration."; + default = {}; + }; + + hooks = mkOption { + type = profileHooksModule; + description = "Profile hook scripts."; + default = {}; + }; + }; + }; + + configModule = types.submodule { + options = { + enable = mkOption { + type = types.bool; + description = "Whether to enable the output."; + default = true; + }; + + primary = mkOption { + type = types.bool; + description = "Whether output should be marked as primary"; + default = false; + }; + + position = mkOption { + type = types.string; + description = "Output position"; + default = ""; + example = "5760x0"; + }; + + mode = mkOption { + type = types.string; + description = "Output resolution."; + default = ""; + example = "3840x2160"; + }; + + rate = mkOption { + type = types.string; + description = "Output framerate."; + default = ""; + example = "60.00"; + }; + + gamma = mkOption { + type = types.string; + description = "Output gamma configuration."; + default = ""; + example = "1.0:0.909:0.833"; + }; + }; + }; + + hookType = types.lines; + + globalHooksModule = 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 = {}; + }; + }; + }; + + 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; + description = "Predetect hook executed before autorandr attempts to run xrandr."; + default = ""; + }; + }; + }; + + hookToFile = folder: name: hook: + 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) ])) + ]); + fingerprintToString = name: edid: "${name} ${edid}"; + configToString = name: config: if config.enable then '' + output ${name} + ${optionalString (config.position != "") "pos ${config.position}"} + ${optionalString config.primary "primary"} + ${optionalString (config.gamma != "") "gamma ${config.gamma}"} + ${optionalString (config.mode != "") "mode ${config.mode}"} + ${optionalString (config.rate != "") "rate ${config.rate}"} + '' else '' + output ${name} + off + ''; + +in + +{ + options = { + programs.autorandr = { + enable = mkEnableOption "Autorandr"; + + hooks = mkOption { + type = globalHooksModule; + description = "Global hook scripts"; + default = {}; + example = literalExample '' + { + 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 = literalExample '' + { + "work" = { + fingerprint = { + eDP1 = ""; + DP1 = ""; + }; + config = { + eDP1.enable = false; + DP1 = { + enable = true; + primary = true; + position = "0x0"; + mode = "3840x2160"; + gamma = "1.0:0.909:0.833"; + rate = "60.00"; + }; + }; + hooks.postswitch = readFile ./work-postswitch.sh; + }; + } + ''; + }; + }; + }; + + config = mkIf cfg.enable { + home.packages = [ pkgs.autorandr ]; + xdg.configFile = 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)) + ]); + }; + + meta.maintainers = [ maintainers.uvnikita ]; +}