2020-04-24 23:36:52 +00:00
|
|
|
{ config, lib, pkgs, ... }:
|
|
|
|
|
|
|
|
with pkgs;
|
|
|
|
with lib;
|
|
|
|
|
|
|
|
let
|
|
|
|
|
|
|
|
cfg = config.hardware.pulseaudio;
|
|
|
|
alsaCfg = config.sound;
|
|
|
|
|
|
|
|
systemWide = cfg.enable && cfg.systemWide;
|
|
|
|
nonSystemWide = cfg.enable && !cfg.systemWide;
|
|
|
|
hasZeroconf = let z = cfg.zeroconf; in z.publish.enable || z.discovery.enable;
|
|
|
|
|
|
|
|
overriddenPackage = cfg.package.override
|
|
|
|
(optionalAttrs hasZeroconf { zeroconfSupport = true; });
|
|
|
|
binary = "${getBin overriddenPackage}/bin/pulseaudio";
|
|
|
|
binaryNoDaemon = "${binary} --daemonize=no";
|
|
|
|
|
2021-06-28 23:13:55 +00:00
|
|
|
# Forces 32bit pulseaudio and alsa-plugins to be built/supported for apps
|
2020-04-24 23:36:52 +00:00
|
|
|
# using 32bit alsa on 64bit linux.
|
2021-06-28 23:13:55 +00:00
|
|
|
enable32BitAlsaPlugins = cfg.support32Bit && stdenv.isx86_64 && (pkgs.pkgsi686Linux.alsa-lib != null && pkgs.pkgsi686Linux.libpulseaudio != null);
|
2020-04-24 23:36:52 +00:00
|
|
|
|
|
|
|
|
|
|
|
myConfigFile =
|
|
|
|
let
|
|
|
|
addModuleIf = cond: mod: optionalString cond "load-module ${mod}";
|
|
|
|
allAnon = optional cfg.tcp.anonymousClients.allowAll "auth-anonymous=1";
|
|
|
|
ipAnon = let a = cfg.tcp.anonymousClients.allowedIpRanges;
|
|
|
|
in optional (a != []) ''auth-ip-acl=${concatStringsSep ";" a}'';
|
|
|
|
in writeTextFile {
|
|
|
|
name = "default.pa";
|
|
|
|
text = ''
|
|
|
|
.include ${cfg.configFile}
|
|
|
|
${addModuleIf cfg.zeroconf.publish.enable "module-zeroconf-publish"}
|
|
|
|
${addModuleIf cfg.zeroconf.discovery.enable "module-zeroconf-discover"}
|
|
|
|
${addModuleIf cfg.tcp.enable (concatStringsSep " "
|
|
|
|
([ "module-native-protocol-tcp" ] ++ allAnon ++ ipAnon))}
|
2020-11-12 09:05:59 +00:00
|
|
|
${addModuleIf config.services.jack.jackd.enable "module-jack-sink"}
|
|
|
|
${addModuleIf config.services.jack.jackd.enable "module-jack-source"}
|
2020-04-24 23:36:52 +00:00
|
|
|
${cfg.extraConfig}
|
|
|
|
'';
|
|
|
|
};
|
|
|
|
|
|
|
|
ids = config.ids;
|
|
|
|
|
|
|
|
uid = ids.uids.pulseaudio;
|
|
|
|
gid = ids.gids.pulseaudio;
|
|
|
|
|
|
|
|
stateDir = "/run/pulse";
|
|
|
|
|
|
|
|
# Create pulse/client.conf even if PulseAudio is disabled so
|
|
|
|
# that we can disable the autospawn feature in programs that
|
|
|
|
# are built with PulseAudio support (like KDE).
|
|
|
|
clientConf = writeText "client.conf" ''
|
|
|
|
autospawn=no
|
|
|
|
${cfg.extraClientConf}
|
|
|
|
'';
|
|
|
|
|
|
|
|
# Write an /etc/asound.conf that causes all ALSA applications to
|
|
|
|
# be re-routed to the PulseAudio server through ALSA's Pulse
|
|
|
|
# plugin.
|
|
|
|
alsaConf = writeText "asound.conf" (''
|
|
|
|
pcm_type.pulse {
|
2021-06-28 23:13:55 +00:00
|
|
|
libs.native = ${pkgs.alsa-plugins}/lib/alsa-lib/libasound_module_pcm_pulse.so ;
|
2020-04-24 23:36:52 +00:00
|
|
|
${lib.optionalString enable32BitAlsaPlugins
|
2021-06-28 23:13:55 +00:00
|
|
|
"libs.32Bit = ${pkgs.pkgsi686Linux.alsa-plugins}/lib/alsa-lib/libasound_module_pcm_pulse.so ;"}
|
2020-04-24 23:36:52 +00:00
|
|
|
}
|
|
|
|
pcm.!default {
|
|
|
|
type pulse
|
|
|
|
hint.description "Default Audio Device (via PulseAudio)"
|
|
|
|
}
|
|
|
|
ctl_type.pulse {
|
2021-06-28 23:13:55 +00:00
|
|
|
libs.native = ${pkgs.alsa-plugins}/lib/alsa-lib/libasound_module_ctl_pulse.so ;
|
2020-04-24 23:36:52 +00:00
|
|
|
${lib.optionalString enable32BitAlsaPlugins
|
2021-06-28 23:13:55 +00:00
|
|
|
"libs.32Bit = ${pkgs.pkgsi686Linux.alsa-plugins}/lib/alsa-lib/libasound_module_ctl_pulse.so ;"}
|
2020-04-24 23:36:52 +00:00
|
|
|
}
|
|
|
|
ctl.!default {
|
|
|
|
type pulse
|
|
|
|
}
|
|
|
|
${alsaCfg.extraConfig}
|
|
|
|
'');
|
|
|
|
|
|
|
|
in {
|
|
|
|
|
|
|
|
options = {
|
|
|
|
|
|
|
|
hardware.pulseaudio = {
|
|
|
|
enable = mkOption {
|
|
|
|
type = types.bool;
|
|
|
|
default = false;
|
2022-08-12 12:06:08 +00:00
|
|
|
description = lib.mdDoc ''
|
2020-04-24 23:36:52 +00:00
|
|
|
Whether to enable the PulseAudio sound server.
|
|
|
|
'';
|
|
|
|
};
|
|
|
|
|
|
|
|
systemWide = mkOption {
|
|
|
|
type = types.bool;
|
|
|
|
default = false;
|
2022-08-12 12:06:08 +00:00
|
|
|
description = lib.mdDoc ''
|
2020-04-24 23:36:52 +00:00
|
|
|
If false, a PulseAudio server is launched automatically for
|
|
|
|
each user that tries to use the sound system. The server runs
|
|
|
|
with user privileges. If true, one system-wide PulseAudio
|
|
|
|
server is launched on boot, running as the user "pulse", and
|
2022-10-21 18:38:19 +00:00
|
|
|
only users in the "pulse-access" group will have access to the server.
|
2020-04-24 23:36:52 +00:00
|
|
|
Please read the PulseAudio documentation for more details.
|
|
|
|
|
|
|
|
Don't enable this option unless you know what you are doing.
|
|
|
|
'';
|
|
|
|
};
|
|
|
|
|
|
|
|
support32Bit = mkOption {
|
|
|
|
type = types.bool;
|
|
|
|
default = false;
|
2022-08-12 12:06:08 +00:00
|
|
|
description = lib.mdDoc ''
|
2020-04-24 23:36:52 +00:00
|
|
|
Whether to include the 32-bit pulseaudio libraries in the system or not.
|
|
|
|
This is only useful on 64-bit systems and currently limited to x86_64-linux.
|
|
|
|
'';
|
|
|
|
};
|
|
|
|
|
|
|
|
configFile = mkOption {
|
|
|
|
type = types.nullOr types.path;
|
2022-08-12 12:06:08 +00:00
|
|
|
description = lib.mdDoc ''
|
2020-04-24 23:36:52 +00:00
|
|
|
The path to the default configuration options the PulseAudio server
|
|
|
|
should use. By default, the "default.pa" configuration
|
|
|
|
from the PulseAudio distribution is used.
|
|
|
|
'';
|
|
|
|
};
|
|
|
|
|
|
|
|
extraConfig = mkOption {
|
|
|
|
type = types.lines;
|
|
|
|
default = "";
|
2022-08-12 12:06:08 +00:00
|
|
|
description = lib.mdDoc ''
|
|
|
|
Literal string to append to `configFile`
|
2020-04-24 23:36:52 +00:00
|
|
|
and the config file generated by the pulseaudio module.
|
|
|
|
'';
|
|
|
|
};
|
|
|
|
|
|
|
|
extraClientConf = mkOption {
|
|
|
|
type = types.lines;
|
|
|
|
default = "";
|
2022-08-12 12:06:08 +00:00
|
|
|
description = lib.mdDoc ''
|
2020-04-24 23:36:52 +00:00
|
|
|
Extra configuration appended to pulse/client.conf file.
|
|
|
|
'';
|
|
|
|
};
|
|
|
|
|
|
|
|
package = mkOption {
|
|
|
|
type = types.package;
|
2020-11-12 09:05:59 +00:00
|
|
|
default = if config.services.jack.jackd.enable
|
|
|
|
then pkgs.pulseaudioFull
|
|
|
|
else pkgs.pulseaudio;
|
2021-10-06 13:57:05 +00:00
|
|
|
defaultText = literalExpression "pkgs.pulseaudio";
|
|
|
|
example = literalExpression "pkgs.pulseaudioFull";
|
2022-08-12 12:06:08 +00:00
|
|
|
description = lib.mdDoc ''
|
2020-04-24 23:36:52 +00:00
|
|
|
The PulseAudio derivation to use. This can be used to enable
|
|
|
|
features (such as JACK support, Bluetooth) via the
|
2022-08-12 12:06:08 +00:00
|
|
|
`pulseaudioFull` package.
|
2020-04-24 23:36:52 +00:00
|
|
|
'';
|
|
|
|
};
|
|
|
|
|
|
|
|
extraModules = mkOption {
|
|
|
|
type = types.listOf types.package;
|
|
|
|
default = [];
|
2021-10-06 13:57:05 +00:00
|
|
|
example = literalExpression "[ pkgs.pulseaudio-modules-bt ]";
|
2022-08-12 12:06:08 +00:00
|
|
|
description = lib.mdDoc ''
|
2020-04-24 23:36:52 +00:00
|
|
|
Extra pulseaudio modules to use. This is intended for out-of-tree
|
|
|
|
pulseaudio modules like extra bluetooth codecs.
|
|
|
|
|
|
|
|
Extra modules take precedence over built-in pulseaudio modules.
|
|
|
|
'';
|
|
|
|
};
|
|
|
|
|
|
|
|
daemon = {
|
|
|
|
logLevel = mkOption {
|
|
|
|
type = types.str;
|
|
|
|
default = "notice";
|
2022-08-12 12:06:08 +00:00
|
|
|
description = lib.mdDoc ''
|
2020-04-24 23:36:52 +00:00
|
|
|
The log level that the system-wide pulseaudio daemon should use,
|
|
|
|
if activated.
|
|
|
|
'';
|
|
|
|
};
|
|
|
|
|
|
|
|
config = mkOption {
|
|
|
|
type = types.attrsOf types.unspecified;
|
|
|
|
default = {};
|
2022-08-12 12:06:08 +00:00
|
|
|
description = lib.mdDoc "Config of the pulse daemon. See `man pulse-daemon.conf`.";
|
2021-10-06 13:57:05 +00:00
|
|
|
example = literalExpression ''{ realtime-scheduling = "yes"; }'';
|
2020-04-24 23:36:52 +00:00
|
|
|
};
|
|
|
|
};
|
|
|
|
|
|
|
|
zeroconf = {
|
|
|
|
discovery.enable =
|
2022-09-09 14:08:57 +00:00
|
|
|
mkEnableOption (lib.mdDoc "discovery of pulseaudio sinks in the local network");
|
2020-04-24 23:36:52 +00:00
|
|
|
publish.enable =
|
2022-09-09 14:08:57 +00:00
|
|
|
mkEnableOption (lib.mdDoc "publishing the pulseaudio sink in the local network");
|
2020-04-24 23:36:52 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
# TODO: enable by default?
|
|
|
|
tcp = {
|
2022-09-09 14:08:57 +00:00
|
|
|
enable = mkEnableOption (lib.mdDoc "tcp streaming support");
|
2020-04-24 23:36:52 +00:00
|
|
|
|
|
|
|
anonymousClients = {
|
2022-09-09 14:08:57 +00:00
|
|
|
allowAll = mkEnableOption (lib.mdDoc "all anonymous clients to stream to the server");
|
2020-04-24 23:36:52 +00:00
|
|
|
allowedIpRanges = mkOption {
|
|
|
|
type = types.listOf types.str;
|
|
|
|
default = [];
|
2021-10-06 13:57:05 +00:00
|
|
|
example = literalExpression ''[ "127.0.0.1" "192.168.1.0/24" ]'';
|
2022-08-12 12:06:08 +00:00
|
|
|
description = lib.mdDoc ''
|
2020-04-24 23:36:52 +00:00
|
|
|
A list of IP subnets that are allowed to stream to the server.
|
|
|
|
'';
|
|
|
|
};
|
|
|
|
};
|
|
|
|
};
|
|
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
config = mkMerge [
|
|
|
|
{
|
|
|
|
environment.etc = {
|
|
|
|
"pulse/client.conf".source = clientConf;
|
|
|
|
};
|
|
|
|
|
|
|
|
hardware.pulseaudio.configFile = mkDefault "${getBin overriddenPackage}/etc/pulse/default.pa";
|
|
|
|
}
|
|
|
|
|
|
|
|
(mkIf cfg.enable {
|
|
|
|
environment.systemPackages = [ overriddenPackage ];
|
|
|
|
|
|
|
|
sound.enable = true;
|
|
|
|
|
|
|
|
environment.etc = {
|
|
|
|
"asound.conf".source = alsaConf;
|
|
|
|
|
|
|
|
"pulse/daemon.conf".source = writeText "daemon.conf"
|
|
|
|
(lib.generators.toKeyValue {} cfg.daemon.config);
|
|
|
|
|
|
|
|
"openal/alsoft.conf".source = writeText "alsoft.conf" "drivers=pulse";
|
|
|
|
|
|
|
|
"libao.conf".source = writeText "libao.conf" "default_driver=pulse";
|
|
|
|
};
|
|
|
|
|
|
|
|
# Disable flat volumes to enable relative ones
|
|
|
|
hardware.pulseaudio.daemon.config.flat-volumes = mkDefault "no";
|
|
|
|
|
|
|
|
# Upstream defaults to speex-float-1 which results in audible artifacts
|
|
|
|
hardware.pulseaudio.daemon.config.resample-method = mkDefault "speex-float-5";
|
|
|
|
|
|
|
|
# Allow PulseAudio to get realtime priority using rtkit.
|
|
|
|
security.rtkit.enable = true;
|
|
|
|
|
|
|
|
systemd.packages = [ overriddenPackage ];
|
|
|
|
|
|
|
|
# PulseAudio is packaged with udev rules to handle various audio device quirks
|
|
|
|
services.udev.packages = [ overriddenPackage ];
|
|
|
|
})
|
|
|
|
|
|
|
|
(mkIf (cfg.extraModules != []) {
|
|
|
|
hardware.pulseaudio.daemon.config.dl-search-path = let
|
|
|
|
overriddenModules = builtins.map
|
|
|
|
(drv: drv.override { pulseaudio = overriddenPackage; })
|
|
|
|
cfg.extraModules;
|
|
|
|
modulePaths = builtins.map
|
2022-09-09 14:08:57 +00:00
|
|
|
(drv: "${drv}/lib/pulseaudio/modules")
|
2020-04-24 23:36:52 +00:00
|
|
|
# User-provided extra modules take precedence
|
|
|
|
(overriddenModules ++ [ overriddenPackage ]);
|
|
|
|
in lib.concatStringsSep ":" modulePaths;
|
|
|
|
})
|
|
|
|
|
|
|
|
(mkIf hasZeroconf {
|
|
|
|
services.avahi.enable = true;
|
|
|
|
})
|
|
|
|
(mkIf cfg.zeroconf.publish.enable {
|
|
|
|
services.avahi.publish.enable = true;
|
|
|
|
services.avahi.publish.userServices = true;
|
|
|
|
})
|
|
|
|
|
|
|
|
(mkIf nonSystemWide {
|
|
|
|
environment.etc = {
|
|
|
|
"pulse/default.pa".source = myConfigFile;
|
|
|
|
};
|
|
|
|
systemd.user = {
|
|
|
|
services.pulseaudio = {
|
|
|
|
restartIfChanged = true;
|
|
|
|
serviceConfig = {
|
|
|
|
RestartSec = "500ms";
|
|
|
|
PassEnvironment = "DISPLAY";
|
|
|
|
};
|
2020-11-12 09:05:59 +00:00
|
|
|
} // optionalAttrs config.services.jack.jackd.enable {
|
|
|
|
environment.JACK_PROMISCUOUS_SERVER = "jackaudio";
|
2020-04-24 23:36:52 +00:00
|
|
|
};
|
|
|
|
sockets.pulseaudio = {
|
|
|
|
wantedBy = [ "sockets.target" ];
|
|
|
|
};
|
|
|
|
};
|
|
|
|
})
|
|
|
|
|
|
|
|
(mkIf systemWide {
|
|
|
|
users.users.pulse = {
|
|
|
|
# For some reason, PulseAudio wants UID == GID.
|
|
|
|
uid = assert uid == gid; uid;
|
|
|
|
group = "pulse";
|
|
|
|
extraGroups = [ "audio" ];
|
|
|
|
description = "PulseAudio system service user";
|
|
|
|
home = stateDir;
|
|
|
|
createHome = true;
|
2021-04-15 00:37:46 +00:00
|
|
|
isSystemUser = true;
|
2020-04-24 23:36:52 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
users.groups.pulse.gid = gid;
|
2022-10-21 18:38:19 +00:00
|
|
|
users.groups.pulse-access = {};
|
2020-04-24 23:36:52 +00:00
|
|
|
|
|
|
|
systemd.services.pulseaudio = {
|
|
|
|
description = "PulseAudio System-Wide Server";
|
|
|
|
wantedBy = [ "sound.target" ];
|
|
|
|
before = [ "sound.target" ];
|
|
|
|
environment.PULSE_RUNTIME_PATH = stateDir;
|
|
|
|
serviceConfig = {
|
|
|
|
Type = "notify";
|
|
|
|
ExecStart = "${binaryNoDaemon} --log-level=${cfg.daemon.logLevel} --system -n --file=${myConfigFile}";
|
|
|
|
Restart = "on-failure";
|
|
|
|
RestartSec = "500ms";
|
|
|
|
};
|
|
|
|
};
|
|
|
|
|
|
|
|
environment.variables.PULSE_COOKIE = "${stateDir}/.config/pulse/cookie";
|
|
|
|
})
|
|
|
|
];
|
|
|
|
|
|
|
|
}
|