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