{ config, lib, pkgs, ... }: let cfg = config.c3d2.audioServer; in { options.c3d2.audioServer = { enable = lib.mkEnableOption "PulseAudio and Bluetooth sinks"; ledfx = lib.mkEnableOption "LedFX service"; }; config = lib.mkIf cfg.enable { boot.kernelPackages = lib.mkOverride 900 pkgs.linuxPackages-rt_latest; environment = { # _____ _______ ____ _____ # / ____|__ __/ __ \| __ \ # | (___ | | | | | | |__) | # \___ \ | | | | | | ___/ # ____) | | | | |__| | | # |_____/ |_| \____/|_| # # errors such as: # mod.zeroconf-publish: error id:47 seq:349 res:-2 (No such file or directory): enum params id:16 (Spa:Enum:ParamId:ProcessLatency) failed # are harmless and can be ignored. You most likely want to restart your local avahi-daemon: sudo systemctl restart avahi-daemon etc = { "pipewire/pipewire.conf.d/audio-server.conf".text = builtins.toJSON { "context.modules" = [ { # https://wiki.archlinux.org/title/PulseAudio/Examples#PulseAudio_over_network name = "libpipewire-module-rtp-sap"; args = { "sap.ip" = "0.0.0.0"; "sess.latency.msec" = 10; }; } ]; }; "pipewire/pipewire-pulse.conf.d/audio-server.conf".text = builtins.toJSON { "context.exec" = [ { path = "${pkgs.pulseaudio}/bin/pactl"; args = "load-module module-zeroconf-publish"; } ]; "pulse.properties" = { "auth-ip-acl" = [ "127.0.0.0/8" "::1/128" "fd23:42:c3d2:500::/56" "172.22.99.0/24" "172.20.72.0/21" "2a00:8180:2c00:200::/56" "2a0f:5382:acab:1400::/56" ]; "pulse.default.tlength" = "96000/48000"; "server.address" = [ "unix:native" "tcp:4713" ]; }; }; }; systemPackages = with pkgs; [ mpd # draws in mesa (wrapMpv (mpv-unwrapped.override { drmSupport = false; }) { }) ncmpcpp ncpamixer pulseaudio # required for pactl somafm-cli ]; }; hardware = { bluetooth.settings = { Policy.AutoEnable = true; General.DiscoverableTimeout = 0; }; pulseaudio = { enable = !config.services.pipewire.pulse.enable; systemWide = true; tcp = { enable = true; anonymousClients.allowedIpRanges = [ "127.0.0.0/8" "::1/128" "fd23:42:c3d2:500::/56" "172.22.99.0/24" "172.20.72.0/21" "2a00:8180:2c00:200::/56" "2a0f:5382:acab:1400::/56" ]; }; zeroconf.publish.enable = true; package = (pkgs.pulseaudio.override { bluetoothSupport = true; advancedBluetoothCodecs = true; zeroconfSupport = true; }).overrideAttrs (_: { # one test times out doCheck = false; }); }; }; networking.firewall = { allowedTCPPorts = lib.optional cfg.ledfx 80 ++ [ 4713 # pulseaudio/pipewire tcp sink ]; allowedUDPPorts = [ 5353 # mdns 9875 # pulseaudio/pipewire rtp sink ]; }; nixpkgs.overlays = [ (final: prev: { python3 = prev.python3.override { packageOverrides = python-final: python-prev: (lib.optionalAttrs config.environment.noXlibs { # remove x11 dependencies from pkgs.ledfx pystray = null; } // { # avoid dependency on x11 libraries samplerate = python-prev.samplerate.overrideAttrs (_: { dontUseSetuptoolsCheck = true; }); }); }; python3Packages = final.python3.pkgs; }) ]; security = { polkit.extraConfig = /* javascript */ '' // https://www.reddit.com/r/voidlinux/comments/o74i76/comment/h2z9u11/?utm_source=reddit&utm_medium=web2x&context=3 polkit.addRule(function(action, subject) { if (action.id == "org.freedesktop.RealtimeKit1.acquire-high-priority" || action.id == "org.freedesktop.RealtimeKit1.acquire-real-time") { return polkit.Result.YES; } }); // broader alternative if the above ever breaks // polkit.addRule(function(action, subject) { // if (subject.isInGroup("rtkit")) { // if (action.id.indexOf("org.freedesktop.RealtimeKit1.") == 0) { // return polkit.Result.YES; // } // } // }); ''; rtkit.enable = true; }; services.pipewire = { enable = true; alsa.enable = lib.mkIf cfg.ledfx true; # required for ledfx pulse.enable = true; }; # tell Avahi to publish services like Pipewire/PulseAudio services.avahi = { enable = true; nssmdns4 = true; publish = { enable = true; addresses = true; userServices = true; }; }; sound.enable = true; system.userActivationScripts.symlinkPipewireSystemdUnit.text = lib.mkIf config.c3d2.k-ot.enable /* bash */ '' if [[ $USERNAME == k-ot ]]; then systemctl --user enable pipewire-pulse fi ''; systemd = { services = { bluetooth-agent = lib.mkIf config.hardware.bluetooth.enable { description = "Allow anyone to pair via Bluetooth"; wantedBy = [ "multi-user.target" ]; requires = [ "bluetooth.target" ]; after = [ "bluetooth.service" ]; serviceConfig = { Type = "simple"; ExecStart = "${pkgs.bluez-tools}/bin/bt-agent -c NoInputNoOutput"; Restart = "on-failure"; RestartSec = 60; }; }; mpv-pause = { script = '' echo '{ "command": ["set_property", "pause", true] }' | ${lib.getExe pkgs.socat} - /tmp/mpvsocket ''; serviceConfig = { Type = "oneshot"; User = "root"; }; }; }; timers.mpv-pause = { wantedBy = [ "timers.target" ]; timerConfig = { Unit = "mpv-pause.service"; OnCalendar = "*-*-* 05:30:00"; }; }; user.services = { ledfx = lib.mkIf cfg.ledfx { after = [ "pipewire.target" ]; wantedBy = [ "default.target" ]; serviceConfig = { ExecStart = lib.getExe pkgs.ledfx; }; }; pipewire-pulse.enable = true; }; }; users = { groups.pulse-access = { }; users.k-ot = lib.mkIf config.c3d2.k-ot.enable { extraGroups = [ "pipewire" "pulse-access" # required for system wide pulseaudio "rtkit" ]; linger = true; packages = with pkgs; [ (yt-dlp.override { withAlias = true; }) ]; }; }; }; }