1{ config, lib, pkgs, ... }:
2
3with lib;
4
5let
6
7 cfg = config.services.picom;
8
9 pairOf = x: with types;
10 addCheck (listOf x) (y: length y == 2)
11 // { description = "pair of ${x.description}"; };
12
13 floatBetween = a: b: with types;
14 let
15 # toString prints floats with hardcoded high precision
16 floatToString = f: builtins.toJSON f;
17 in
18 addCheck float (x: x <= b && x >= a)
19 // { description = "a floating point number in " +
20 "range [${floatToString a}, ${floatToString b}]"; };
21
22 mkDefaultAttrs = mapAttrs (n: v: mkDefault v);
23
24 # Basically a tinkered lib.generators.mkKeyValueDefault
25 # It either serializes a top-level definition "key: { values };"
26 # or an expression "key = { values };"
27 mkAttrsString = top:
28 mapAttrsToList (k: v:
29 let sep = if (top && isAttrs v) then ":" else "=";
30 in "${escape [ sep ] k}${sep}${mkValueString v};");
31
32 # This serializes a Nix expression to the libconfig format.
33 mkValueString = v:
34 if types.bool.check v then boolToString v
35 else if types.int.check v then toString v
36 else if types.float.check v then toString v
37 else if types.str.check v then "\"${escape [ "\"" ] v}\""
38 else if builtins.isList v then "[ ${concatMapStringsSep " , " mkValueString v} ]"
39 else if types.attrs.check v then "{ ${concatStringsSep " " (mkAttrsString false v) } }"
40 else throw ''
41 invalid expression used in option services.picom.settings:
42 ${v}
43 '';
44
45 toConf = attrs: concatStringsSep "\n" (mkAttrsString true cfg.settings);
46
47 configFile = pkgs.writeText "picom.conf" (toConf cfg.settings);
48
49in {
50
51 imports = [
52 (mkAliasOptionModule [ "services" "compton" ] [ "services" "picom" ])
53 ];
54
55 options.services.picom = {
56 enable = mkOption {
57 type = types.bool;
58 default = false;
59 description = ''
60 Whether or not to enable Picom as the X.org composite manager.
61 '';
62 };
63
64 experimentalBackends = mkOption {
65 type = types.bool;
66 default = false;
67 description = ''
68 Whether to use the unstable new reimplementation of the backends.
69 '';
70 };
71
72 fade = mkOption {
73 type = types.bool;
74 default = false;
75 description = ''
76 Fade windows in and out.
77 '';
78 };
79
80 fadeDelta = mkOption {
81 type = types.ints.positive;
82 default = 10;
83 example = 5;
84 description = ''
85 Time between fade animation step (in ms).
86 '';
87 };
88
89 fadeSteps = mkOption {
90 type = pairOf (floatBetween 0.01 1);
91 default = [ 0.028 0.03 ];
92 example = [ 0.04 0.04 ];
93 description = ''
94 Opacity change between fade steps (in and out).
95 '';
96 };
97
98 fadeExclude = mkOption {
99 type = types.listOf types.str;
100 default = [];
101 example = [
102 "window_type *= 'menu'"
103 "name ~= 'Firefox$'"
104 "focused = 1"
105 ];
106 description = ''
107 List of conditions of windows that should not be faded.
108 See <literal>picom(1)</literal> man page for more examples.
109 '';
110 };
111
112 shadow = mkOption {
113 type = types.bool;
114 default = false;
115 description = ''
116 Draw window shadows.
117 '';
118 };
119
120 shadowOffsets = mkOption {
121 type = pairOf types.int;
122 default = [ (-15) (-15) ];
123 example = [ (-10) (-15) ];
124 description = ''
125 Left and right offset for shadows (in pixels).
126 '';
127 };
128
129 shadowOpacity = mkOption {
130 type = floatBetween 0 1;
131 default = 0.75;
132 example = 0.8;
133 description = ''
134 Window shadows opacity.
135 '';
136 };
137
138 shadowExclude = mkOption {
139 type = types.listOf types.str;
140 default = [];
141 example = [
142 "window_type *= 'menu'"
143 "name ~= 'Firefox$'"
144 "focused = 1"
145 ];
146 description = ''
147 List of conditions of windows that should have no shadow.
148 See <literal>picom(1)</literal> man page for more examples.
149 '';
150 };
151
152 activeOpacity = mkOption {
153 type = floatBetween 0 1;
154 default = 1.0;
155 example = 0.8;
156 description = ''
157 Opacity of active windows.
158 '';
159 };
160
161 inactiveOpacity = mkOption {
162 type = floatBetween 0.1 1;
163 default = 1.0;
164 example = 0.8;
165 description = ''
166 Opacity of inactive windows.
167 '';
168 };
169
170 menuOpacity = mkOption {
171 type = floatBetween 0 1;
172 default = 1.0;
173 example = 0.8;
174 description = ''
175 Opacity of dropdown and popup menu.
176 '';
177 };
178
179 wintypes = mkOption {
180 type = types.attrs;
181 default = { popup_menu = { opacity = cfg.menuOpacity; }; dropdown_menu = { opacity = cfg.menuOpacity; }; };
182 example = {};
183 description = ''
184 Rules for specific window types.
185 '';
186 };
187
188 opacityRules = mkOption {
189 type = types.listOf types.str;
190 default = [];
191 example = [
192 "95:class_g = 'URxvt' && !_NET_WM_STATE@:32a"
193 "0:_NET_WM_STATE@:32a *= '_NET_WM_STATE_HIDDEN'"
194 ];
195 description = ''
196 Rules that control the opacity of windows, in format PERCENT:PATTERN.
197 '';
198 };
199
200 backend = mkOption {
201 type = types.enum [ "glx" "xrender" "xr_glx_hybrid" ];
202 default = "xrender";
203 description = ''
204 Backend to use: <literal>glx</literal>, <literal>xrender</literal> or <literal>xr_glx_hybrid</literal>.
205 '';
206 };
207
208 vSync = mkOption {
209 type = with types; either bool
210 (enum [ "none" "drm" "opengl" "opengl-oml" "opengl-swc" "opengl-mswc" ]);
211 default = false;
212 apply = x:
213 let
214 res = x != "none";
215 msg = "The type of services.picom.vSync has changed to bool:"
216 + " interpreting ${x} as ${boolToString res}";
217 in
218 if isBool x then x
219 else warn msg res;
220
221 description = ''
222 Enable vertical synchronization. Chooses the best method
223 (drm, opengl, opengl-oml, opengl-swc, opengl-mswc) automatically.
224 The bool value should be used, the others are just for backwards compatibility.
225 '';
226 };
227
228 refreshRate = mkOption {
229 type = types.ints.unsigned;
230 default = 0;
231 example = 60;
232 description = ''
233 Screen refresh rate (0 = automatically detect).
234 '';
235 };
236
237 settings = with types;
238 let
239 scalar = oneOf [ bool int float str ]
240 // { description = "scalar types"; };
241
242 libConfig = oneOf [ scalar (listOf libConfig) (attrsOf libConfig) ]
243 // { description = "libconfig type"; };
244
245 topLevel = attrsOf libConfig
246 // { description = ''
247 libconfig configuration. The format consists of an attributes
248 set (called a group) of settings. Each setting can be a scalar type
249 (boolean, integer, floating point number or string), a list of
250 scalars or a group itself
251 '';
252 };
253
254 in mkOption {
255 type = topLevel;
256 default = { };
257 example = literalExample ''
258 blur =
259 { method = "gaussian";
260 size = 10;
261 deviation = 5.0;
262 };
263 '';
264 description = ''
265 Picom settings. Use this option to configure Picom settings not exposed
266 in a NixOS option or to bypass one. For the available options see the
267 CONFIGURATION FILES section at <literal>picom(1)</literal>.
268 '';
269 };
270 };
271
272 config = mkIf cfg.enable {
273 services.picom.settings = mkDefaultAttrs {
274 # fading
275 fading = cfg.fade;
276 fade-delta = cfg.fadeDelta;
277 fade-in-step = elemAt cfg.fadeSteps 0;
278 fade-out-step = elemAt cfg.fadeSteps 1;
279 fade-exclude = cfg.fadeExclude;
280
281 # shadows
282 shadow = cfg.shadow;
283 shadow-offset-x = elemAt cfg.shadowOffsets 0;
284 shadow-offset-y = elemAt cfg.shadowOffsets 1;
285 shadow-opacity = cfg.shadowOpacity;
286 shadow-exclude = cfg.shadowExclude;
287
288 # opacity
289 active-opacity = cfg.activeOpacity;
290 inactive-opacity = cfg.inactiveOpacity;
291
292 wintypes = cfg.wintypes;
293
294 opacity-rule = cfg.opacityRules;
295
296 # other options
297 backend = cfg.backend;
298 vsync = cfg.vSync;
299 refresh-rate = cfg.refreshRate;
300 };
301
302 systemd.user.services.picom = {
303 description = "Picom composite manager";
304 wantedBy = [ "graphical-session.target" ];
305 partOf = [ "graphical-session.target" ];
306
307 # Temporarily fixes corrupt colours with Mesa 18
308 environment = mkIf (cfg.backend == "glx") {
309 allow_rgb10_configs = "false";
310 };
311
312 serviceConfig = {
313 ExecStart = "${pkgs.picom}/bin/picom --config ${configFile}"
314 + (optionalString cfg.experimentalBackends " --experimental-backends");
315 RestartSec = 3;
316 Restart = "always";
317 };
318 };
319
320 environment.systemPackages = [ pkgs.picom ];
321 };
322
323 meta.maintainers = with lib.maintainers; [ rnhmjoj ];
324
325}