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