nixpkgs/nixos/modules/services/x11/compton.nix

288 lines
8 KiB
Nix

{ 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 <literal>compton(1)</literal> 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 <literal>compton(1)</literal> 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: <literal>glx</literal>, <literal>xrender</literal> or <literal>xr_glx_hybrid</literal>.
'';
};
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 ];
};
}