{ config, lib, pkgs, ... }: with lib; with builtins; let cfg = config.services.compton; pairOf = x: with types; addCheck (listOf x) (y: length y == 2); floatBetween = a: b: with lib; with types; addCheck str (x: versionAtLeast x a && versionOlder x b); toConf = attrs: concatStringsSep "\n" (mapAttrsToList (k: v: let sep = if isAttrs v then ":" else "="; # Basically a tinkered lib.generators.mkKeyValueDefault mkValueString = v: if isBool v then boolToString v else if isInt v then toString v else if isFloat v then toString v else if isString v then ''"${escape [ ''"'' ] v}"'' else if isList v then "[ " + concatMapStringsSep " , " mkValueString v + " ]" else if isAttrs v then "{ " + concatStringsSep " " (mapAttrsToList (key: value: "${toString key}=${mkValueString value};") v) + " }" else abort "compton.mkValueString: unexpected type (v = ${v})"; in "${escape [ sep ] k}${sep}${mkValueString v};") attrs); configFile = pkgs.writeText "compton.conf" (toConf cfg.settings); in { options.services.compton = { enable = mkOption { type = types.bool; default = false; description = '' Whether of not to enable Compton as the X.org composite manager. ''; }; fade = mkOption { type = types.bool; default = false; description = '' Fade windows in and out. ''; }; fadeDelta = mkOption { type = types.addCheck types.int (x: x > 0); default = 10; example = 5; description = '' Time between fade animation step (in ms). ''; }; fadeSteps = mkOption { type = pairOf (floatBetween "0.01" "1.01"); default = [ "0.028" "0.03" ]; example = [ "0.04" "0.04" ]; description = '' Opacity change between fade steps (in and out). (numbers in range 0.01 - 1.0) ''; }; fadeExclude = mkOption { type = types.listOf types.str; default = []; example = [ "window_type *= 'menu'" "name ~= 'Firefox$'" "focused = 1" ]; description = '' List of conditions of windows that should not be faded. See compton(1) man page for more examples. ''; }; shadow = mkOption { type = types.bool; default = false; description = '' Draw window shadows. ''; }; shadowOffsets = mkOption { type = pairOf types.int; default = [ (-15) (-15) ]; example = [ (-10) (-15) ]; description = '' Left and right offset for shadows (in pixels). ''; }; shadowOpacity = mkOption { type = floatBetween "0.0" "1.01"; default = "0.75"; example = "0.8"; description = '' Window shadows opacity (number in range 0.0 - 1.0). ''; }; shadowExclude = mkOption { type = types.listOf types.str; default = []; example = [ "window_type *= 'menu'" "name ~= 'Firefox$'" "focused = 1" ]; description = '' List of conditions of windows that should have no shadow. See compton(1) man page for more examples. ''; }; activeOpacity = mkOption { type = floatBetween "0.0" "1.01"; default = "1.0"; example = "0.8"; description = '' Opacity of active windows (number in range 0.0 - 1.0). ''; }; inactiveOpacity = mkOption { type = floatBetween "0.1" "1.01"; default = "1.0"; example = "0.8"; description = '' Opacity of inactive windows (number in range 0.1 - 1.0). ''; }; menuOpacity = mkOption { type = floatBetween "0.0" "1.01"; default = "1.0"; example = "0.8"; description = '' Opacity of dropdown and popup menu (number in range 0.0 - 1.0). ''; }; wintypes = mkOption { type = types.attrs; default = { popup_menu = { opacity = cfg.menuOpacity; }; dropdown_menu = { opacity = cfg.menuOpacity; }; }; example = {}; description = '' Rules for specific window types. ''; }; opacityRules = mkOption { type = types.listOf types.str; default = []; example = [ "95:class_g = 'URxvt' && !_NET_WM_STATE@:32a" "0:_NET_WM_STATE@:32a *= '_NET_WM_STATE_HIDDEN'" ]; description = '' Rules that control the opacity of windows, in format PERCENT:PATTERN. ''; }; backend = mkOption { type = types.enum [ "glx" "xrender" "xr_glx_hybrid" ]; default = "xrender"; description = '' Backend to use: glx, xrender or xr_glx_hybrid. ''; }; vSync = mkOption { type = with types; either bool (enum [ "none" "drm" "opengl" "opengl-oml" "opengl-swc" "opengl-mswc" ]); default = false; apply = x: let res = x != "none"; msg = "The type of services.compton.vSync has changed to bool:" + " interpreting ${x} as ${boolToString res}"; in if isBool x then x else warn msg res; description = '' Enable vertical synchronization. Chooses the best method (drm, opengl, opengl-oml, opengl-swc, opengl-mswc) automatically. The bool value should be used, the others are just for backwards compatibility. ''; }; refreshRate = mkOption { type = types.addCheck types.int (x: x >= 0); default = 0; example = 60; description = '' Screen refresh rate (0 = automatically detect). ''; }; settings = let configTypes = with types; oneOf [ bool int float str ]; # types.loaOf converts lists to sets loaOf = t: with types; either (listOf t) (attrsOf t); in mkOption { type = loaOf (types.either configTypes (loaOf (types.either configTypes (loaOf configTypes)))); default = {}; description = '' Additional Compton configuration. ''; }; }; config = mkIf cfg.enable { services.compton.settings = let # Hard conversion to float, literally lib.toInt but toFloat toFloat = str: let may_be_float = builtins.fromJSON str; in if builtins.isFloat may_be_float then may_be_float else throw "Could not convert ${str} to float."; in { # fading fading = mkDefault cfg.fade; fade-delta = mkDefault cfg.fadeDelta; fade-in-step = mkDefault (toFloat (elemAt cfg.fadeSteps 0)); fade-out-step = mkDefault (toFloat (elemAt cfg.fadeSteps 1)); fade-exclude = mkDefault cfg.fadeExclude; # shadows shadow = mkDefault cfg.shadow; shadow-offset-x = mkDefault (elemAt cfg.shadowOffsets 0); shadow-offset-y = mkDefault (elemAt cfg.shadowOffsets 1); shadow-opacity = mkDefault (toFloat cfg.shadowOpacity); shadow-exclude = mkDefault cfg.shadowExclude; # opacity active-opacity = mkDefault (toFloat cfg.activeOpacity); inactive-opacity = mkDefault (toFloat cfg.inactiveOpacity); wintypes = mkDefault cfg.wintypes; opacity-rule = mkDefault cfg.opacityRules; # other options backend = mkDefault cfg.backend; vsync = mkDefault cfg.vSync; refresh-rate = mkDefault cfg.refreshRate; }; systemd.user.services.compton = { description = "Compton composite manager"; wantedBy = [ "graphical-session.target" ]; partOf = [ "graphical-session.target" ]; # Temporarily fixes corrupt colours with Mesa 18 environment = mkIf (cfg.backend == "glx") { allow_rgb10_configs = "false"; }; serviceConfig = { ExecStart = "${pkgs.compton}/bin/compton --config ${configFile}"; RestartSec = 3; Restart = "always"; }; }; environment.systemPackages = [ pkgs.compton ]; }; }