summary refs log tree commit diff
path: root/nixos/modules/services/desktops/pipewire/pipewire-media-session.nix
blob: 81f4762e1e601db2d5e9b2a36bd4676f28a51cf4 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
# pipewire example session manager.
{ config, lib, pkgs, ... }:

with lib;

let
  cfg = config.services.pipewire.media-session;
  enable32BitAlsaPlugins = cfg.alsa.support32Bit
                           && pkgs.stdenv.isx86_64
                           && pkgs.pkgsi686Linux.pipewire != null;

  # Helpers for generating the pipewire JSON config file
  mkSPAValueString = v:
  if builtins.isList v then "[${lib.concatMapStringsSep " " mkSPAValueString v}]"
  else if lib.types.attrs.check v then
    "{${lib.concatStringsSep " " (mkSPAKeyValue v)}}"
  else lib.generators.mkValueStringDefault { } v;

  mkSPAKeyValue = attrs: map (def: def.content) (
  lib.sortProperties
    (
      lib.mapAttrsToList
        (k: v: lib.mkOrder (v._priority or 1000) "${lib.escape [ "=" ] k} = ${mkSPAValueString (v._content or v)}")
        attrs
    )
  );

  toSPAJSON = attrs: lib.concatStringsSep "\n" (mkSPAKeyValue attrs);
in {

  meta = {
    maintainers = teams.freedesktop.members;
  };

  ###### interface
  options = {
    services.pipewire.media-session = {
      enable = mkOption {
        type = types.bool;
        default = config.services.pipewire.enable;
        defaultText = "config.services.pipewire.enable";
        description = "Example pipewire session manager";
      };

      package = mkOption {
        type = types.package;
        default = pkgs.pipewire.mediaSession;
        example = literalExample "pkgs.pipewire.mediaSession";
        description = ''
          The pipewire-media-session derivation to use.
        '';
      };

      config = mkOption {
        type = types.attrs;
        description = ''
          Configuration for the media session core.
        '';
        default = {
          # media-session config file
          properties = {
            # Properties to configure the session and some
            # modules
            #mem.mlock-all = false;
            #context.profile.modules = "default,rtkit";
          };

          spa-libs = {
            # Mapping from factory name to library.
            "api.bluez5.*" = "bluez5/libspa-bluez5";
            "api.alsa.*" = "alsa/libspa-alsa";
            "api.v4l2.*" = "v4l2/libspa-v4l2";
            "api.libcamera.*" = "libcamera/libspa-libcamera";
          };

          modules = {
            # These are the modules that are enabled when a file with
            # the key name is found in the media-session.d config directory.
            # the default bundle is always enabled.

            default = [
              "flatpak"			# manages flatpak access
              "portal"			# manage portal permissions
              "v4l2"			# video for linux udev detection
              #"libcamera"		# libcamera udev detection
              "suspend-node"		# suspend inactive nodes
              "policy-node"		# configure and link nodes
              #"metadata"		# export metadata API
              #"default-nodes"		# restore default nodes
              #"default-profile"	# restore default profiles
              #"default-routes"		# restore default route
              #"streams-follow-default"	# move streams when default changes
              #"alsa-seq"		# alsa seq midi support
              #"alsa-monitor"		# alsa udev detection
              #"bluez5"			# bluetooth support
              #"restore-stream"		# restore stream settings
            ];
            "with-audio" = [
              "metadata"
              "default-nodes"
              "default-profile"
              "default-routes"
              "alsa-seq"
              "alsa-monitor"
            ];
            "with-alsa" = [
              "with-audio"
            ];
            "with-jack" = [
              "with-audio"
            ];
            "with-pulseaudio" = [
              "with-audio"
              "bluez5"
              "restore-stream"
              "streams-follow-default"
            ];
          };
        };
      };

      alsaMonitorConfig = mkOption {
        type = types.attrs;
        description = ''
          Configuration for the alsa monitor.
        '';
        default = {
          # alsa-monitor config file
          properties = {
            #alsa.jack-device = true
          };

          rules = [
          # an array of matches/actions to evaluate
          {
            # rules for matching a device or node. It is an array of
            # properties that all need to match the regexp. If any of the
            # matches work, the actions are executed for the object.
            matches = [
              {
                # this matches all cards
                device.name = "~alsa_card.*";
              }
            ];
            actions = {
              # actions can update properties on the matched object.
              update-props = {
                api.alsa.use-acp = true;
                #api.alsa.use-ucm = true;
                #api.alsa.soft-mixer = false;
                #api.alsa.ignore-dB = false;
                #device.profile-set = "profileset-name";
                #device.profile = "default profile name";
                api.acp.auto-profile = false;
                api.acp.auto-port = false;
                #device.nick = "My Device";
              };
            };
          }
          {
            matches = [
              {
                # matches all sinks
                node.name = "~alsa_input.*";
              }
              {
                # matches all sources
                node.name = "~alsa_output.*";
              }
            ];
            actions = {
              update-props = {
                #node.nick = 			"My Node";
                #node.nick = 			null;
                #priority.driver = 		100;
                #priority.session = 		100;
                #node.pause-on-idle = 		false;
                #resample.quality = 		4;
                #channelmix.normalize =		false;
                #channelmix.mix-lfe = 		false;
                #audio.channels = 		2;
                #audio.format = 		"S16LE";
                #audio.rate = 			44100;
                #audio.position = 		"FL,FR";
                #api.alsa.period-size =         1024;
                #api.alsa.headroom =            0;
                #api.alsa.disable-mmap =        false;
                #api.alsa.disable-batch =       false;
              };
            };
          }
          ];
        };
      };

      bluezMonitorConfig = mkOption {
        type = types.attrs;
        description = ''
          Configuration for the bluez5 monitor.
        '';
        default = {
          # bluez-monitor config file
          properties = {
            # msbc is not expected to work on all headset + adapter combinations.
            #bluez5.msbc-support = true;
            #bluez5.sbc-xq-support = true;

            # Enabled headset roles (default: [ hsp_hs hfp_ag ]), this
            # property only applies to native backend. Currently some headsets
            # (Sony WH-1000XM3) are not working with both hsp_ag and hfp_ag
            # enabled, disable either hsp_ag or hfp_ag to work around it.
            #
            # Supported headset roles: hsp_hs (HSP Headset),
            #                          hsp_ag (HSP Audio Gateway),
            #                          hfp_ag (HFP Audio Gateway)
            #bluez5.headset-roles = [ "hsp_hs" "hsp_ag" "hfp_ag" ];

            # Enabled A2DP codecs (default: all)
            #bluez5.codecs = [ "sbc" "aac" "ldac" "aptx" "aptx_hd" ];
          };

          rules = [
          # an array of matches/actions to evaluate
          {
            # rules for matching a device or node. It is an array of
            # properties that all need to match the regexp. If any of the
            # matches work, the actions are executed for the object.
            matches = [
              {
                # this matches all cards
                device.name = "~bluez_card.*";
              }
            ];
            actions = {
              # actions can update properties on the matched object.
              update-props = {
                #device.nick = 			"My Device";
              };
            };
          }
          {
            matches = [
              {
                # matches all sinks
                node.name = "~bluez_input.*";
              }
              {
                # matches all sources
                node.name = "~bluez_output.*";
              }
            ];
            actions = {
              update-props = {
                #node.nick = 			"My Node"
                #node.nick = 			null;
                #priority.driver = 		100;
                #priority.session = 		100;
                #node.pause-on-idle = 		false;
                #resample.quality = 		4;
                #channelmix.normalize =		false;
                #channelmix.mix-lfe = 		false;
              };
            };
          }
          ];
        };
      };

      v4l2MonitorConfig = mkOption {
        type = types.attrs;
        description = ''
          Configuration for the V4L2 monitor.
        '';
        default = {
          # v4l2-monitor config file
          properties = {
          };

          rules = [
            # an array of matches/actions to evaluate
            {
              # rules for matching a device or node. It is an array of
              # properties that all need to match the regexp. If any of the
              # matches work, the actions are executed for the object.
              matches = [
                {
                  # this matches all devices
                  device.name = "~v4l2_device.*";
                }
              ];
              actions = {
                # actions can update properties on the matched object.
                update-props = {
                  #device.nick = 			"My Device";
                };
              };
            }
            {
              matches = [
                {
                  # matches all sinks
                  node.name = "~v4l2_input.*";
                }
                {
                  # matches all sources
                  node.name = "~v4l2_output.*";
                }
              ];
              actions = {
                update-props = {
                  #node.nick = 			"My Node";
                  #node.nick = 			null;
                  #priority.driver = 		100;
                  #priority.session = 		100;
                  #node.pause-on-idle = 		true;
                };
              };
            }
          ];
        };
      };
    };
  };

  ###### implementation
  config = mkIf cfg.enable {
    environment.systemPackages = [ cfg.package ];
    services.pipewire.sessionManagerExecutable = "${cfg.package}/bin/pipewire-media-session";

    environment.etc."pipewire/media-session.d/media-session.conf" = { text = toSPAJSON cfg.config; };
    environment.etc."pipewire/media-session.d/v4l2-monitor.conf" = { text = toSPAJSON cfg.v4l2MonitorConfig; };

    environment.etc."pipewire/media-session.d/with-alsa" = mkIf config.services.pipewire.alsa.enable { text = ""; };
    environment.etc."pipewire/media-session.d/alsa-monitor.conf" = mkIf config.services.pipewire.alsa.enable { text = toSPAJSON cfg.alsaMonitorConfig; };

    environment.etc."pipewire/media-session.d/with-pulseaudio" = mkIf config.services.pipewire.pulse.enable { text = ""; };
    environment.etc."pipewire/media-session.d/bluez-monitor.conf" = mkIf config.services.pipewire.pulse.enable { text = toSPAJSON cfg.bluezMonitorConfig; };

    environment.etc."pipewire/media-session.d/with-jack" = mkIf config.services.pipewire.jack.enable { text = ""; };
  };
}