aboutsummaryrefslogtreecommitdiff
;;; GNU Guix --- Functional package management for GNU
;;; Copyright © 2018, 2020 Oleg Pykhalov <go.wigust@gmail.com>
;;; Copyright © 2020 Liliana Marie Prikler <liliana.prikler@gmail.com>
;;; Copyright © 2020 Marius Bakke <mbakke@fastmail.com>
;;; Copyright © 2022 Maxim Cournoyer <maxim.cournoyer@gmail.com>
;;;
;;; This file is part of GNU Guix.
;;;
;;; GNU Guix is free software; you can redistribute it and/or modify it
;;; under the terms of the GNU General Public License as published by
;;; the Free Software Foundation; either version 3 of the License, or (at
;;; your option) any later version.
;;;
;;; GNU Guix is distributed in the hope that it will be useful, but
;;; WITHOUT ANY WARRANTY; without even the implied warranty of
;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
;;; GNU General Public License for more details.
;;;
;;; You should have received a copy of the GNU General Public License
;;; along with GNU Guix.  If not, see <http://www.gnu.org/licenses/>.

(define-module (gnu services sound)
  #:use-module (gnu services base)
  #:use-module (gnu services configuration)
  #:use-module (gnu services shepherd)
  #:use-module (gnu services)
  #:use-module (gnu system pam)
  #:use-module (gnu system shadow)
  #:use-module (guix diagnostics)
  #:use-module (guix gexp)
  #:use-module (guix packages)
  #:use-module (guix records)
  #:use-module (guix store)
  #:use-module (guix ui)
  #:use-module (gnu packages audio)
  #:use-module (gnu packages linux)
  #:use-module (gnu packages pulseaudio)
  #:use-module (ice-9 match)
  #:use-module (srfi srfi-1)
  #:export (alsa-configuration
            alsa-configuration?
            alsa-configuration-alsa-plugins
            alsa-configuration-pulseaudio?
            alsa-configuration-extra-options
            alsa-service-type

            pulseaudio-configuration
            pulseaudio-configuration?
            pulseaudio-configuration-client-conf
            pulseaudio-configuration-daemon-conf
            pulseaudio-configuration-script-file
            pulseaudio-configuration-extra-script-files
            pulseaudio-configuration-system-script-file
            pulseaudio-service-type

            ladspa-configuration
            ladspa-configuration?
            ladspa-configuration-plugins
            ladspa-service-type))

;;; Commentary:
;;;
;;; Sound services.
;;;
;;; Code:


;;;
;;; ALSA
;;;

(define-record-type* <alsa-configuration>
  alsa-configuration make-alsa-configuration alsa-configuration?
  (alsa-plugins alsa-configuration-alsa-plugins ;file-like
                (default alsa-plugins))
  (pulseaudio?   alsa-configuration-pulseaudio? ;boolean
                 (default #t))
  (extra-options alsa-configuration-extra-options ;string
                 (default "")))

(define alsa-config-file
  ;; Return the ALSA configuration file.
  (match-lambda
    (($ <alsa-configuration> alsa-plugins pulseaudio? extra-options)
     (apply mixed-text-file "asound.conf"
            `("# Generated by 'alsa-service'.\n\n"
              ,@(if pulseaudio?
                    `("# Use PulseAudio by default
pcm_type.pulse {
  lib \"" ,#~(string-append #$alsa-plugins:pulseaudio
                            "/lib/alsa-lib/libasound_module_pcm_pulse.so") "\"
}

ctl_type.pulse {
  lib \"" ,#~(string-append #$alsa-plugins:pulseaudio
                            "/lib/alsa-lib/libasound_module_ctl_pulse.so") "\"
}

pcm.!default {
  type pulse
  fallback \"sysdefault\"
  hint {
    show on
    description \"Default ALSA Output (currently PulseAudio Sound Server)\"
  }
}

ctl.!default {
  type pulse
  fallback \"sysdefault\"
}\n\n")
                    '())
              ,extra-options)))))

(define (alsa-etc-service config)
  (list `("asound.conf" ,(alsa-config-file config))))

(define alsa-service-type
  (service-type
   (name 'alsa)
   (extensions
    (list (service-extension etc-service-type alsa-etc-service)))
   (default-value (alsa-configuration))
   (description "Configure low-level Linux sound support, ALSA.")))


;;;
;;; PulseAudio
;;;

(define-record-type* <pulseaudio-configuration>
  pulseaudio-configuration make-pulseaudio-configuration
  pulseaudio-configuration?
  (client-conf pulseaudio-configuration-client-conf
               (default '()))
  (daemon-conf pulseaudio-configuration-daemon-conf
               ;; Flat volumes may cause unpleasant experiences to users
               ;; when applications inadvertently max out the system volume
               ;; (see e.g. <https://bugs.gnu.org/38172>).
               (default '((flat-volumes . no))))
  (script-file pulseaudio-configuration-script-file
               (default (file-append pulseaudio "/etc/pulse/default.pa")))
  (extra-script-files pulseaudio-configuration-extra-script-files
                      (default '()))
  (system-script-file pulseaudio-configuration-system-script-file
                      (default
                        (file-append pulseaudio "/etc/pulse/system.pa"))))

(define (pulseaudio-conf-entry arg)
  (match arg
    ((key . value)
     (format #f "~a = ~s~%" key value))
    ((? string? _)
     (string-append arg "\n"))))

(define pulseaudio-environment
  (match-lambda
    (($ <pulseaudio-configuration> client-conf daemon-conf default-script-file)
     ;; These config files kept at a fixed location, so that the following
     ;; environment values are stable and do not require the user to reboot to
     ;; effect their PulseAudio configuration changes.
     '(("PULSE_CONFIG" . "/etc/pulse/daemon.conf")
       ("PULSE_CLIENTCONFIG" . "/etc/pulse/client.conf")))))

(define (extra-script-files->file-union extra-script-files)
  "Return a G-exp obtained by processing EXTRA-SCRIPT-FILES with FILE-UNION."

  (define (file-like->name file)
    (match file
      ((? local-file?)
       (local-file-name file))
      ((? plain-file?)
       (plain-file-name file))
      ((? computed-file?)
       (computed-file-name file))
      (_ (leave (G_ "~a is not a local-file, plain-file or \
computed-file object~%") file))))

  (define (assert-pulseaudio-script-file-name name)
    (unless (string-suffix? ".pa" name)
      (leave (G_ "`~a' lacks the required `.pa' file name extension~%") name))
    name)

  (let ((labels (map (compose assert-pulseaudio-script-file-name
                              file-like->name)
                     extra-script-files)))
    (file-union "default.pa.d" (zip labels extra-script-files))))

(define (append-include-directive script-file)
  "Append an include directive to source scripts under /etc/pulse/default.pa.d."
  (computed-file "default.pa"
                 #~(begin
                     (use-modules (ice-9 textual-ports))
                     (define script-text
                       (call-with-input-file #$script-file get-string-all))
                     (call-with-output-file #$output
                       (lambda (port)
                         (format port (string-append script-text "
### Added by Guix to include scripts specified in extra-script-files.
.nofail
.include /etc/pulse/default.pa.d~%")))))))

(define pulseaudio-etc
  (match-lambda
    (($ <pulseaudio-configuration> client-conf daemon-conf default-script-file
                                   extra-script-files system-script-file)
     `(("pulse"
        ,(file-union
          "pulse"
          `(("default.pa"
             ,(if (null? extra-script-files)
                  default-script-file
                  (append-include-directive default-script-file)))
            ("system.pa" ,system-script-file)
            ,@(if (null? extra-script-files)
                  '()
                  `(("default.pa.d" ,(extra-script-files->file-union
                                      extra-script-files))))
            ("daemon.conf"
             ,(apply mixed-text-file "daemon.conf"
                     "default-script-file = /etc/pulse/default.pa\n"
                     (map pulseaudio-conf-entry daemon-conf)))
            ("client.conf"
             ,(apply mixed-text-file "client.conf"
                     (map pulseaudio-conf-entry client-conf))))))))))

(define pulseaudio-service-type
  (service-type
   (name 'pulseaudio)
   (extensions
    (list (service-extension session-environment-service-type
                             pulseaudio-environment)
          (service-extension etc-service-type pulseaudio-etc)
          (service-extension udev-service-type (const (list pulseaudio)))))
   (default-value (pulseaudio-configuration))
   (description "Configure PulseAudio sound support.")))


;;;
;;; LADSPA
;;;

(define-record-type* <ladspa-configuration>
  ladspa-configuration make-ladspa-configuration
  ladspa-configuration?
  (plugins ladspa-configuration-plugins (default '())))

(define (ladspa-environment config)
  ;; Define this variable in the global environment such that
  ;; pulseaudio swh-plugins (and similar LADSPA plugins) work.
  `(("LADSPA_PATH" .
     (string-join
      ',(map (lambda (package) (file-append package "/lib/ladspa"))
             (ladspa-configuration-plugins config))
      ":"))))

(define ladspa-service-type
  (service-type
   (name 'ladspa)
   (extensions
    (list (service-extension session-environment-service-type
                             ladspa-environment)))
   (default-value (ladspa-configuration))
   (description "Configure LADSPA plugins.")))

;;; sound.scm ends here
hash.hh \ %D%/libutil/serialise.hh \ %D%/libutil/util.hh \ %D%/libutil/archive.hh \ %D%/libutil/types.hh \ %D%/libutil/gcrypt-hash.hh \ %D%/libutil/md5.h \ %D%/libutil/sha1.h \ %D%/libutil/sha256.h \ %D%/libutil/sha512.h libutil_a_CPPFLAGS = \ -I$(top_builddir)/nix \ -I$(top_srcdir)/%D%/libutil \ $(libformat_a_CPPFLAGS) libstore_a_SOURCES = \ %D%/libstore/gc.cc \ %D%/libstore/globals.cc \ %D%/libstore/misc.cc \ %D%/libstore/references.cc \ %D%/libstore/store-api.cc \ %D%/libstore/optimise-store.cc \ %D%/libstore/local-store.cc \ %D%/libstore/build.cc \ %D%/libstore/pathlocks.cc \ %D%/libstore/derivations.cc \ %D%/libstore/builtins.cc \ %D%/libstore/sqlite.cc libstore_headers = \ %D%/libstore/references.hh \ %D%/libstore/pathlocks.hh \ %D%/libstore/globals.hh \ %D%/libstore/worker-protocol.hh \ %D%/libstore/derivations.hh \ %D%/libstore/misc.hh \ %D%/libstore/local-store.hh \ %D%/libstore/sqlite.hh \ %D%/libstore/builtins.hh \ %D%/libstore/store-api.hh libstore_a_CPPFLAGS = \ $(libutil_a_CPPFLAGS) \ -I$(top_srcdir)/%D%/libstore \ -I$(top_builddir)/%D%/libstore \ -DNIX_STORE_DIR=\"$(storedir)\" \ -DNIX_DATA_DIR=\"$(datadir)\" \ -DNIX_STATE_DIR=\"$(localstatedir)/guix\" \ -DNIX_LOG_DIR=\"$(localstatedir)/log/guix\" \ -DGUIX_CONFIGURATION_DIRECTORY=\"$(sysconfdir)/guix\" \ -DNIX_LIBEXEC_DIR=\"$(libexecdir)\" \ -DNIX_BIN_DIR=\"$(bindir)\" \ -DOPENSSL_PATH="\"guix-authenticate\"" \ -DDEFAULT_CHROOT_DIRS="\"\"" libstore_a_CXXFLAGS = $(AM_CXXFLAGS) \ $(SQLITE3_CFLAGS) $(LIBGCRYPT_CFLAGS) bin_PROGRAMS = guix-daemon sbin_PROGRAMS = guix-register guix_daemon_SOURCES = \ %D%/nix-daemon/nix-daemon.cc \ %D%/nix-daemon/guix-daemon.cc guix_daemon_CPPFLAGS = \ -DLOCALEDIR=\"$(localedir)\" \ $(libutil_a_CPPFLAGS) \ -I$(top_srcdir)/%D%/libstore guix_daemon_LDADD = \ libstore.a libutil.a libformat.a -lbz2 \ $(SQLITE3_LIBS) $(LIBGCRYPT_LIBS) guix_daemon_headers = \ %D%/nix-daemon/shared.hh guix_register_SOURCES = \ %D%/guix-register/guix-register.cc guix_register_CPPFLAGS = \ $(libutil_a_CPPFLAGS) \ $(libstore_a_CPPFLAGS) \ -I$(top_srcdir)/%D%/libstore # XXX: Should we start using shared libs? guix_register_LDADD = \ libstore.a libutil.a libformat.a -lbz2 \ $(SQLITE3_LIBS) $(LIBGCRYPT_LIBS) noinst_HEADERS = \ $(libformat_headers) $(libutil_headers) $(libstore_headers) \ $(guix_daemon_headers) %D%/libstore/schema.sql.hh: %D%/libstore/schema.sql $(AM_V_GEN)$(GUILE) --no-auto-compile -c \ "(use-modules (rnrs io ports)) \ (call-with-output-file \"$@\" \ (lambda (out) \ (call-with-input-file \"$^\" \ (lambda (in) \ (write (get-string-all in) out)))))" nodist_pkglibexec_SCRIPTS = \ %D%/scripts/list-runtime-roots \ %D%/scripts/substitute \ %D%/scripts/download if BUILD_DAEMON_OFFLOAD nodist_pkglibexec_SCRIPTS += \ %D%/scripts/offload endif BUILD_DAEMON_OFFLOAD # XXX: It'd be better to hide it in $(pkglibexecdir). nodist_libexec_SCRIPTS = \ %D%/scripts/guix-authenticate # The '.service' files for systemd. systemdservicedir = $(libdir)/systemd/system nodist_systemdservice_DATA = etc/guix-daemon.service etc/guix-publish.service etc/guix-%.service: etc/guix-%.service.in \ $(top_builddir)/config.status $(AM_V_GEN)$(MKDIR_P) "`dirname $@`"; \ $(SED) -e 's|@''localstatedir''@|$(localstatedir)|' < \ "$<" > "$@.tmp"; \ mv "$@.tmp" "$@" # The '.conf' jobs for Upstart. upstartjobdir = $(libdir)/upstart/system nodist_upstartjob_DATA = etc/guix-daemon.conf etc/guix-publish.conf etc/guix-%.conf: etc/guix-%.conf.in \ $(top_builddir)/config.status $(AM_V_GEN)$(MKDIR_P) "`dirname $@`"; \ $(SED) -e 's|@''localstatedir''@|$(localstatedir)|' < \ "$<" > "$@.tmp"; \ mv "$@.tmp" "$@" CLEANFILES += \ $(nodist_systemdservice_DATA) \ $(nodist_upstartjob_DATA) EXTRA_DIST += \ %D%/libstore/schema.sql \ %D%/AUTHORS \ %D%/COPYING \ etc/guix-daemon.service.in \ etc/guix-daemon.conf.in \ etc/guix-publish.service.in \ etc/guix-publish.conf.in if CAN_RUN_TESTS AM_TESTS_ENVIRONMENT += \ top_builddir="$(abs_top_builddir)" TESTS += \ tests/guix-daemon.sh endif CAN_RUN_TESTS clean-local: -if test -d "$(GUIX_TEST_ROOT)"; then \ find "$(GUIX_TEST_ROOT)" | xargs chmod +w; \ fi -rm -rf "$(GUIX_TEST_ROOT)"