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