Compare commits
4 Commits
07e8456ce4
...
c23524cd30
Author | SHA1 | Date | |
---|---|---|---|
c23524cd30 | |||
e032234c63 | |||
78c8c6e083 | |||
33fbb2ba1a |
20
README.org
20
README.org
@ -22,7 +22,7 @@ To install pipewire-0, put the *.el files to a site-lisp directory and
|
|||||||
add the following line to your Emacs configuration:
|
add the following line to your Emacs configuration:
|
||||||
|
|
||||||
#+begin_src elisp
|
#+begin_src elisp
|
||||||
(require ’pw-ui)
|
(require ’pipewire)
|
||||||
#+end_src
|
#+end_src
|
||||||
|
|
||||||
Or if you use [[https://github.com/radian-software/straight.el][straight.el]], you can install pipewire-0 as follows:
|
Or if you use [[https://github.com/radian-software/straight.el][straight.el]], you can install pipewire-0 as follows:
|
||||||
@ -68,20 +68,20 @@ They can be bound to multimedia keys:
|
|||||||
|
|
||||||
pipewire-0 consists of the following source files:
|
pipewire-0 consists of the following source files:
|
||||||
|
|
||||||
- [[file:pw-lib.el][pw-lib.el]] :: PipeWire library to be used in Elisp programs.
|
- [[file:pipewire-lib.el][pipewire-lib.el]] :: PipeWire library to be used in Elisp programs.
|
||||||
- [[file:pw-access.el][pw-access.el]] :: PipeWire communication interface, not supposed to be
|
- [[file:pipewire-access.el][pipewire-access.el]] :: PipeWire communication interface, not supposed to be
|
||||||
used outside =pw-lib=.
|
used outside =pipewire-lib=.
|
||||||
- [[file:pw-ui.el][pw-ui.el]] :: User commands and interface.
|
- [[file:pipewire.el][pipewire.el]] :: User commands and interface.
|
||||||
|
|
||||||
Look into [[file:pw-lib.el][pw-lib.el]] to see what public =pw-lib-*= functions are
|
Look into [[file:pipewire-lib.el][pipewire-lib.el]] to see what public =pipewire-lib-*= functions are
|
||||||
available there. For example, the following snippet can be used to
|
available there. For example, the following snippet can be used to
|
||||||
display current volume level of the default audio sink:
|
display current volume level of the default audio sink:
|
||||||
|
|
||||||
#+begin_src elisp
|
#+begin_src elisp
|
||||||
(let ((object (pw-lib-default-audio-sink)))
|
(let ((object (pipewire-lib-default-audio-sink)))
|
||||||
(format "%s%s"
|
(format "%s%s"
|
||||||
(pw-lib-volume object t)
|
(pipewire-lib-volume object t)
|
||||||
(if (pw-lib-muted-p object) "(M)" "")))
|
(if (pipewire-lib-muted-p object) "(M)" "")))
|
||||||
#+end_src
|
#+end_src
|
||||||
|
|
||||||
* Notes
|
* Notes
|
||||||
@ -90,7 +90,7 @@ PipeWire is currently accessed using [[https://docs.pipewire.org/page_man_pw_cli
|
|||||||
pw-cli output is apparently undocumented and changes between versions
|
pw-cli output is apparently undocumented and changes between versions
|
||||||
so this is not a reliable way to communicate with PipeWire. But I
|
so this is not a reliable way to communicate with PipeWire. But I
|
||||||
don’t know about anything better currently. Nevertheless, it’s easy
|
don’t know about anything better currently. Nevertheless, it’s easy
|
||||||
to replace pw-cli with something else in [[file:pw-access.el][pw-access.el]].
|
to replace pw-cli with something else in [[file:pipewire-access.el][pipewire-access.el]].
|
||||||
|
|
||||||
** Why is it named pipewire-0?
|
** Why is it named pipewire-0?
|
||||||
|
|
||||||
|
132
pipewire-access.el
Normal file
132
pipewire-access.el
Normal file
@ -0,0 +1,132 @@
|
|||||||
|
;;; pipewire-access.el --- PipeWire generic access -*- lexical-binding: t -*-
|
||||||
|
|
||||||
|
;; Copyright (C) 2022 Milan Zamazal <pdm@zamazal.org>
|
||||||
|
|
||||||
|
;; Author: Milan Zamazal <pdm@zamazal.org>
|
||||||
|
;; Version: 1
|
||||||
|
;; Package-Requires: ((emacs "25.1"))
|
||||||
|
;; Keywords: multimedia
|
||||||
|
;; URL: https://git.zamazal.org/pdm/pipewire-0
|
||||||
|
|
||||||
|
;; COPYRIGHT NOTICE
|
||||||
|
;;
|
||||||
|
;; This program 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.
|
||||||
|
;;
|
||||||
|
;; This program 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 this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
;;; Commentary:
|
||||||
|
;;
|
||||||
|
;; A generic interface for communication with PipeWire (https://pipewire.org).
|
||||||
|
;; It abstracts communication with PipeWire to be backend independent.
|
||||||
|
;; Only functions from this module may communicate with PipeWire.
|
||||||
|
|
||||||
|
;;; Code:
|
||||||
|
|
||||||
|
(require 'eieio)
|
||||||
|
|
||||||
|
(defclass pipewire-accessor ()
|
||||||
|
()
|
||||||
|
:documentation
|
||||||
|
"Base PipeWire interface class.
|
||||||
|
All PipeWire interfaces should derive from this class.")
|
||||||
|
|
||||||
|
(cl-defgeneric pipewire-access-objects (class)
|
||||||
|
"Return all the objects currently reported by PipeWire.
|
||||||
|
It is a list of object data. Each of the elements has a form
|
||||||
|
\(OBJECT-ID . INFO) where OBJECT-ID is a numeric OBJECT-ID as
|
||||||
|
reported by PipeWire and INFO is an association list of items
|
||||||
|
\(NAME . VALUE) where NAME is a string item name as reported by
|
||||||
|
PipeWire and VALUE is the corresponding value. VALUE is a number for
|
||||||
|
object ids, a string otherwise.
|
||||||
|
A special entry with `type' symbol as its name contains the PipeWire
|
||||||
|
type of the objects, as a string (e.g. \"Device\", \"Node\", \"Port\",
|
||||||
|
\"Client\", ...).
|
||||||
|
CLASS is a PipeWire interface, see symbol `pipewire-accessor'.")
|
||||||
|
|
||||||
|
(cl-defgeneric pipewire-access-properties (class node-id)
|
||||||
|
"Return properties of the given node.
|
||||||
|
NODE-ID is a numeric PipeWire Node id (other kinds of PipeWire objects
|
||||||
|
are not supported in this method).
|
||||||
|
Object properties may be, unlike object info items, settable.
|
||||||
|
|
||||||
|
An assocation list is returned. Each list element is of the form
|
||||||
|
\(PROPERTY . VALUE) where PROPERTY is a string name of the given
|
||||||
|
property. VALUE can be:
|
||||||
|
|
||||||
|
- \"true\" or \"false\" for boolean values (t and nil are not used to
|
||||||
|
avoid confusion with nil representing invalid or unavailable value).
|
||||||
|
- A number for numeric values (ids, integers, floats).
|
||||||
|
- A string for string values.
|
||||||
|
- A list of elements of any of these types for arrays and structs.
|
||||||
|
|
||||||
|
CLASS is a PipeWire interface, see symbol `pipewire-accessor'.")
|
||||||
|
|
||||||
|
(cl-defgeneric pipewire-access-set-properties (class node-id properties)
|
||||||
|
"Set PROPERTIES of the given node.
|
||||||
|
NODE-ID is a numeric PipeWire Node id (other kinds of PipeWire objects
|
||||||
|
are not supported in this method).
|
||||||
|
PROPERTIES is an association list in the same format as in
|
||||||
|
`pipewire-access-properties'. It needn't contain all the properties, just
|
||||||
|
the properties to be changed.
|
||||||
|
CLASS is a PipeWire interface, see symbol `pipewire-accessor'.")
|
||||||
|
|
||||||
|
(cl-defgeneric pipewire-access-current-profile (class device-id)
|
||||||
|
"Return current profile of the given device.
|
||||||
|
DEVICE-ID is a numeric PipeWire Device id (other kinds of PipeWire
|
||||||
|
objects are not supported in this method).
|
||||||
|
|
||||||
|
The profile is an association list with elements of the form
|
||||||
|
\(PROPERTY . VALUE), in the same format as properties in
|
||||||
|
`pipewire-access-properties'.
|
||||||
|
|
||||||
|
CLASS is a PipeWire interface, see symbol `pipewire-accessor'.")
|
||||||
|
|
||||||
|
(cl-defgeneric pipewire-access-profiles (class device-id)
|
||||||
|
"Return available profiles of the given device.
|
||||||
|
DEVICE-ID is a numeric PipeWire Device id (other kinds of PipeWire
|
||||||
|
objects are not supported in this method).
|
||||||
|
|
||||||
|
Return a list of profiles, which are in the same format as in
|
||||||
|
`pipewire-access-current-profile'.
|
||||||
|
|
||||||
|
CLASS is a PipeWire interface, see symbol `pipewire-accessor'.")
|
||||||
|
|
||||||
|
(cl-defgeneric pipewire-access-set-profile (class device-id profile-index)
|
||||||
|
"Set the profile of the given device.
|
||||||
|
DEVICE-ID is a numeric PipeWire Device id (other kinds of PipeWire
|
||||||
|
objects are not supported in this method).
|
||||||
|
PROFILE-INDEX is a numeric index of the profile to set, as returned
|
||||||
|
from PipeWire.
|
||||||
|
CLASS is a PipeWire interface, see symbol `pipewire-accessor'.")
|
||||||
|
|
||||||
|
(cl-defgeneric pipewire-access-defaults (class)
|
||||||
|
"Return default sinks and sources.
|
||||||
|
An association lists is returned. Each list element is of the form
|
||||||
|
\(KEY . NAME) where KEY is a string identifying the given kind of
|
||||||
|
default sink or source as reported by PipeWire and NAME is a string
|
||||||
|
name of the node assigned to the default.
|
||||||
|
CLASS is a PipeWire interface, see symbol `pipewire-accessor'.")
|
||||||
|
|
||||||
|
(cl-defgeneric pipewire-access-set-default (class key node-name)
|
||||||
|
"Set default sink or source.
|
||||||
|
KEY is a string identifying the given kind of default sink or source
|
||||||
|
as reported in `pipewire-access-defaults' and NODE-NAME is a string name of
|
||||||
|
the node that should be assigned to KEY.
|
||||||
|
CLASS is a PipeWire interface, see symbol `pipewire-accessor'.")
|
||||||
|
|
||||||
|
(provide 'pipewire-access)
|
||||||
|
|
||||||
|
;; Local Variables:
|
||||||
|
;; checkdoc-force-docstrings-flag: nil
|
||||||
|
;; End:
|
||||||
|
|
||||||
|
;;; pipewire-access.el ends here
|
185
pipewire-cli.el
Normal file
185
pipewire-cli.el
Normal file
@ -0,0 +1,185 @@
|
|||||||
|
;;; pipewire-cli.el --- PipeWire command line access -*- lexical-binding: t -*-
|
||||||
|
|
||||||
|
;; Copyright (C) 2022 Milan Zamazal <pdm@zamazal.org>
|
||||||
|
|
||||||
|
;; Author: Milan Zamazal <pdm@zamazal.org>
|
||||||
|
;; Version: 1
|
||||||
|
;; Package-Requires: ((emacs "25.1"))
|
||||||
|
;; Keywords: multimedia
|
||||||
|
;; URL: https://git.zamazal.org/pdm/pipewire-0
|
||||||
|
|
||||||
|
;; COPYRIGHT NOTICE
|
||||||
|
;;
|
||||||
|
;; This program 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.
|
||||||
|
;;
|
||||||
|
;; This program 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 this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
;;; Commentary:
|
||||||
|
;;
|
||||||
|
;; Currently, pw-cli is used to talk to PipeWire. This is not optimal
|
||||||
|
;; because pw-cli doesn't seem to have documented output format and
|
||||||
|
;; the format changes accross PipeWire versions. But there seem to be
|
||||||
|
;; no better options currently. This module should allow switching to
|
||||||
|
;; other communication means easily, without any changes needed
|
||||||
|
;; outside this module, except for using a different communication
|
||||||
|
;; class.
|
||||||
|
|
||||||
|
;;; Code:
|
||||||
|
|
||||||
|
(require 'eieio)
|
||||||
|
(require 'pipewire-access)
|
||||||
|
|
||||||
|
(defvar pipewire-cli-command "pw-cli"
|
||||||
|
"Command to invoke pw-cli.")
|
||||||
|
|
||||||
|
(defvar pipewire-cli-metadata-command "pw-metadata"
|
||||||
|
"Command to invoke pw-metadata.")
|
||||||
|
|
||||||
|
(defclass pipewire-cli-accessor (pipewire-accessor)
|
||||||
|
()
|
||||||
|
:documentation
|
||||||
|
"Command line based interface to PipeWire.
|
||||||
|
Note this interface may not work with all PipeWire versions.")
|
||||||
|
|
||||||
|
(defun pipewire-cli--command (command args)
|
||||||
|
(apply #'call-process command nil t nil args)
|
||||||
|
(goto-char (point-min)))
|
||||||
|
|
||||||
|
(defun pipewire-cli--next-line ()
|
||||||
|
(goto-char (line-beginning-position 2)))
|
||||||
|
|
||||||
|
(defun pipewire-cli--parse-list ()
|
||||||
|
(let ((objects '()))
|
||||||
|
(while (re-search-forward "^[\t]id \\([0-9]+\\), type PipeWire:Interface:\\(.*\\)/.*$" nil t)
|
||||||
|
(let ((id (string-to-number (match-string 1)))
|
||||||
|
(properties `((type . ,(match-string 2)))))
|
||||||
|
(pipewire-cli--next-line)
|
||||||
|
(while (looking-at "^ [\t][\t]\\([a-z.]+\\) = \"\\(.*\\)\"")
|
||||||
|
(let ((property (match-string 1))
|
||||||
|
(value (match-string 2)))
|
||||||
|
(when (string-suffix-p ".id" property)
|
||||||
|
(setq value (string-to-number value)))
|
||||||
|
(push (cons property value) properties))
|
||||||
|
(pipewire-cli--next-line))
|
||||||
|
(push (cons id properties) objects)))
|
||||||
|
(nreverse objects)))
|
||||||
|
|
||||||
|
(cl-defmethod pipewire-access-objects ((_class pipewire-cli-accessor))
|
||||||
|
(with-temp-buffer
|
||||||
|
(pipewire-cli--command pipewire-cli-command '("list-objects"))
|
||||||
|
(pipewire-cli--parse-list)))
|
||||||
|
|
||||||
|
(defun pipewire-cli--read-property (&optional nesting)
|
||||||
|
(unless nesting
|
||||||
|
(setq nesting 0))
|
||||||
|
(when (looking-at (concat (make-string (+ 6 (* 2 nesting)) ? )
|
||||||
|
"\\([A-Za-z:]+\\) \\(.*\\)"))
|
||||||
|
(let ((type (match-string 1))
|
||||||
|
(value (match-string 2)))
|
||||||
|
(pcase type
|
||||||
|
("Bool"
|
||||||
|
(if (equal value "true") 'true 'false))
|
||||||
|
((or "Float" "Id" "Int")
|
||||||
|
(string-to-number value))
|
||||||
|
("String"
|
||||||
|
(substring value 1 -1))
|
||||||
|
((or "Array:" "Struct:")
|
||||||
|
(let ((array '())
|
||||||
|
item)
|
||||||
|
(pipewire-cli--next-line)
|
||||||
|
(while (setq item (pipewire-cli--read-property (1+ nesting)))
|
||||||
|
(push item array)
|
||||||
|
(pipewire-cli--next-line))
|
||||||
|
(nreverse array)))))))
|
||||||
|
|
||||||
|
(defun pipewire-cli--parse-properties ()
|
||||||
|
(pipewire-cli--next-line)
|
||||||
|
(let ((end (or (save-excursion (re-search-forward "^ Object:" nil t))
|
||||||
|
(point-max)))
|
||||||
|
(properties '()))
|
||||||
|
(while (and (< (point) end)
|
||||||
|
(re-search-forward "^ Prop: key \\([A-Za-z:]+\\)" end t))
|
||||||
|
(pipewire-cli--next-line)
|
||||||
|
(let ((property (car (last (split-string (match-string 1) ":"))))
|
||||||
|
(value (pipewire-cli--read-property)))
|
||||||
|
(when value
|
||||||
|
(push (cons property value) properties))))
|
||||||
|
(goto-char end)
|
||||||
|
properties))
|
||||||
|
|
||||||
|
(cl-defmethod pipewire-access-properties ((_class pipewire-cli-accessor) node-id)
|
||||||
|
(with-temp-buffer
|
||||||
|
(pipewire-cli--command pipewire-cli-command `("enum-params" ,(number-to-string node-id) "Props"))
|
||||||
|
(pipewire-cli--parse-properties)))
|
||||||
|
|
||||||
|
(defun pipewire-cli--format-property-value (value)
|
||||||
|
(cond
|
||||||
|
((consp value)
|
||||||
|
(concat "[ " (mapconcat #'pipewire-cli--format-property-value value ", ") " ]"))
|
||||||
|
((numberp value)
|
||||||
|
(number-to-string value))
|
||||||
|
(t
|
||||||
|
value)))
|
||||||
|
|
||||||
|
(defun pipewire-cli--format-property (property)
|
||||||
|
(format "%s: %s" (car property) (pipewire-cli--format-property-value (cdr property))))
|
||||||
|
|
||||||
|
(defun pipewire-cli--format-properties (properties)
|
||||||
|
(concat "{ " (mapconcat #'pipewire-cli--format-property properties ", ") " }"))
|
||||||
|
|
||||||
|
(defun pipewire-cli--set-parameter (object-id parameter value)
|
||||||
|
(let* ((formatted (pipewire-cli--format-properties value)))
|
||||||
|
(call-process pipewire-cli-command nil pipewire-cli-command nil
|
||||||
|
"set-param" (number-to-string object-id) parameter formatted)))
|
||||||
|
|
||||||
|
(cl-defmethod pipewire-access-set-properties ((_class pipewire-cli-accessor) node-id properties)
|
||||||
|
(pipewire-cli--set-parameter node-id "Props" properties))
|
||||||
|
|
||||||
|
(cl-defmethod pipewire-access-current-profile ((_class pipewire-cli-accessor) device-id)
|
||||||
|
(with-temp-buffer
|
||||||
|
(pipewire-cli--command pipewire-cli-command `("enum-params" ,(number-to-string device-id) "Profile"))
|
||||||
|
(pipewire-cli--parse-properties)))
|
||||||
|
|
||||||
|
(cl-defmethod pipewire-access-profiles ((_class pipewire-cli-accessor) device-id)
|
||||||
|
(with-temp-buffer
|
||||||
|
(pipewire-cli--command pipewire-cli-command `("enum-params" ,(number-to-string device-id) "EnumProfile"))
|
||||||
|
(cl-loop for profile = (pipewire-cli--parse-properties) then (pipewire-cli--parse-properties)
|
||||||
|
while profile
|
||||||
|
collect profile)))
|
||||||
|
|
||||||
|
(cl-defmethod pipewire-access-set-profile ((_class pipewire-cli-accessor) device-id profile-index)
|
||||||
|
(pipewire-cli--set-parameter device-id "Profile" `(("index" . ,profile-index) ("save" . "true"))))
|
||||||
|
|
||||||
|
(defun pipewire-cli--parse-metadata ()
|
||||||
|
(let ((metadata '()))
|
||||||
|
(while (re-search-forward
|
||||||
|
"key:'\\([a-z.]+\\)'.*\\(value\\|\"name\"\\): ?['\"]\\([^'\"]+\\)['\"]"
|
||||||
|
nil t)
|
||||||
|
(push (cons (match-string 1) (match-string 3)) metadata))
|
||||||
|
metadata))
|
||||||
|
|
||||||
|
(cl-defmethod pipewire-access-defaults ((_class pipewire-cli-accessor))
|
||||||
|
(with-temp-buffer
|
||||||
|
(pipewire-cli--command pipewire-cli-metadata-command '("0"))
|
||||||
|
(pipewire-cli--parse-metadata)))
|
||||||
|
|
||||||
|
(cl-defmethod pipewire-access-set-default ((_class pipewire-cli-accessor) property node-name)
|
||||||
|
(call-process pipewire-cli-metadata-command nil pipewire-cli-metadata-command nil
|
||||||
|
"0" property (format "{ \"name\": \"%s\" }" node-name)))
|
||||||
|
|
||||||
|
(provide 'pipewire-cli)
|
||||||
|
|
||||||
|
;; Local Variables:
|
||||||
|
;; checkdoc-force-docstrings-flag: nil
|
||||||
|
;; End:
|
||||||
|
|
||||||
|
;;; pipewire-cli.el ends here
|
@ -1,4 +1,4 @@
|
|||||||
;;; pw-lib.el --- PipeWire library -*- lexical-binding: t -*-
|
;;; pipewire-lib.el --- PipeWire library -*- lexical-binding: t -*-
|
||||||
|
|
||||||
;; Copyright (C) 2022 Milan Zamazal <pdm@zamazal.org>
|
;; Copyright (C) 2022 Milan Zamazal <pdm@zamazal.org>
|
||||||
|
|
||||||
@ -26,278 +26,279 @@
|
|||||||
;;; Commentary:
|
;;; Commentary:
|
||||||
;;
|
;;
|
||||||
;; Backend-independent library to access PipeWire functionality.
|
;; Backend-independent library to access PipeWire functionality.
|
||||||
;; It abstracts data returned from `pw-access' methods and provides
|
;; It abstracts data returned from `pipewire-access' methods and provides
|
||||||
;; functions to work with them.
|
;; functions to work with them.
|
||||||
;;
|
;;
|
||||||
;; pw-lib caches data retrieved from PipeWire and uses the cached
|
;; pipewire-lib caches data retrieved from PipeWire and uses the cached
|
||||||
;; data. If The cache can be invalidated by calling `pw-lib-refresh'.
|
;; data. The cache can be invalidated by calling `pipewire-lib-refresh'.
|
||||||
|
|
||||||
;;; Code:
|
;;; Code:
|
||||||
|
|
||||||
(require 'cl-lib)
|
(require 'cl-lib)
|
||||||
(require 'pw-access)
|
(require 'pipewire-access)
|
||||||
|
(require 'pipewire-cli)
|
||||||
|
|
||||||
(defvar pw-lib--accessor (pw-cli-accessor))
|
(defvar pipewire-lib--accessor (pipewire-cli-accessor))
|
||||||
|
|
||||||
(defvar pw-lib--objects '())
|
(defvar pipewire-lib--objects '())
|
||||||
(defvar pw-lib--bindings nil)
|
(defvar pipewire-lib--bindings nil)
|
||||||
(defvar pw-lib--defaults nil)
|
(defvar pipewire-lib--defaults nil)
|
||||||
|
|
||||||
(defun pw-lib-refresh ()
|
(defun pipewire-lib-refresh ()
|
||||||
"Clear cache of objects retrieved from PipeWire."
|
"Clear cache of objects retrieved from PipeWire."
|
||||||
(setq pw-lib--objects (pw-access-objects pw-lib--accessor)
|
(setq pipewire-lib--objects (pipewire-access-objects pipewire-lib--accessor)
|
||||||
pw-lib--bindings nil
|
pipewire-lib--bindings nil
|
||||||
pw-lib--defaults nil))
|
pipewire-lib--defaults nil))
|
||||||
|
|
||||||
(defun pw-lib-objects (&optional type)
|
(defun pipewire-lib-objects (&optional type)
|
||||||
"Return a list of PipeWire objects.
|
"Return a list of PipeWire objects.
|
||||||
TYPE is a string identifying PipeWire objects types (e.g. \"Device\",
|
TYPE is a string identifying PipeWire objects types (e.g. \"Device\",
|
||||||
\"Node\", \"Port\", \"Client\", ...). If specified, return only
|
\"Node\", \"Port\", \"Client\", ...). If specified, return only
|
||||||
objects of the given type.
|
objects of the given type.
|
||||||
The format of the list elements is unspecified, use pw-lib functions
|
The format of the list elements is unspecified, use pipewire-lib functions
|
||||||
to access their data.
|
to access their data.
|
||||||
Note that PipeWire data is cached, if you need its up-to-date
|
Note that PipeWire data is cached, if you need its up-to-date
|
||||||
version, call `pw-lib-refresh' first."
|
version, call `pipewire-lib-refresh' first."
|
||||||
(unless pw-lib--objects
|
(unless pipewire-lib--objects
|
||||||
(pw-lib-refresh))
|
(pipewire-lib-refresh))
|
||||||
(let ((objects pw-lib--objects))
|
(let ((objects pipewire-lib--objects))
|
||||||
(when type
|
(when type
|
||||||
(setq objects (cl-remove-if-not
|
(setq objects (cl-remove-if-not
|
||||||
(lambda (o) (string= (cdr (assq 'type (cdr o))) type))
|
(lambda (o) (string= (cdr (assq 'type (cdr o))) type))
|
||||||
objects)))
|
objects)))
|
||||||
objects))
|
objects))
|
||||||
|
|
||||||
(defun pw-lib-get-object (id)
|
(defun pipewire-lib-get-object (id)
|
||||||
"Return PipeWire object identified by ID.
|
"Return PipeWire object identified by ID.
|
||||||
If such an object doesn't exist, return nil.
|
If such an object doesn't exist, return nil.
|
||||||
Note that PipeWire data is cached, if you need its up-to-date
|
Note that PipeWire data is cached, if you need its up-to-date
|
||||||
version, call `pw-lib-refresh' first."
|
version, call `pipewire-lib-refresh' first."
|
||||||
(assoc id pw-lib--objects))
|
(assoc id pipewire-lib--objects))
|
||||||
|
|
||||||
(defun pw-lib-object-id (object)
|
(defun pipewire-lib-object-id (object)
|
||||||
"Return id of the given PipeWire OBJECT."
|
"Return id of the given PipeWire OBJECT."
|
||||||
(car object))
|
(car object))
|
||||||
|
|
||||||
(defun pw-lib--object-info (object)
|
(defun pipewire-lib--object-info (object)
|
||||||
(cdr object))
|
(cdr object))
|
||||||
|
|
||||||
(defun pw-lib-object-value (object key &optional default)
|
(defun pipewire-lib-object-value (object key &optional default)
|
||||||
"Return PipeWire OBJECT value identified by KEY.
|
"Return PipeWire OBJECT value identified by KEY.
|
||||||
KEY is a string corresponding to a PipeWire value identifier.
|
KEY is a string corresponding to a PipeWire value identifier.
|
||||||
If the given KEY doesn't exist in OBJECT, return DEFAULT."
|
If the given KEY doesn't exist in OBJECT, return DEFAULT."
|
||||||
(or (cdr (assoc key (pw-lib--object-info object)))
|
(or (cdr (assoc key (pipewire-lib--object-info object)))
|
||||||
default))
|
default))
|
||||||
|
|
||||||
(defun pw-lib-properties (object)
|
(defun pipewire-lib-properties (object)
|
||||||
"Return names of PipeWire OBJECT properties.
|
"Return names of PipeWire OBJECT properties.
|
||||||
The returned value is a list of strings.
|
The returned value is a list of strings.
|
||||||
The corresponding values can be retrieved using `pw-lib-object-value'
|
The corresponding values can be retrieved using `pipewire-lib-object-value'
|
||||||
function."
|
function."
|
||||||
(cl-remove-if-not #'stringp (mapcar #'car (pw-lib--object-info object))))
|
(cl-remove-if-not #'stringp (mapcar #'car (pipewire-lib--object-info object))))
|
||||||
|
|
||||||
(defun pw-lib-object-type (object)
|
(defun pipewire-lib-object-type (object)
|
||||||
"Return PipeWire type of OBJECT as a string.
|
"Return PipeWire type of OBJECT as a string.
|
||||||
E.g. \"Device\", \"Node\", \"Port\", \"Client\", ..."
|
E.g. \"Device\", \"Node\", \"Port\", \"Client\", ..."
|
||||||
(pw-lib-object-value object 'type))
|
(pipewire-lib-object-value object 'type))
|
||||||
|
|
||||||
(defun pw-lib--profile-name (profile)
|
(defun pipewire-lib--profile-name (profile)
|
||||||
(cdr (or (assoc "description" profile)
|
(cdr (or (assoc "description" profile)
|
||||||
(assoc "name" profile))))
|
(assoc "name" profile))))
|
||||||
|
|
||||||
(defun pw-lib-current-profile (device-id)
|
(defun pipewire-lib-current-profile (device-id)
|
||||||
"Return the current profile name of the given device.
|
"Return the current profile name of the given device.
|
||||||
DEVICE-ID is the numeric id of the device.
|
DEVICE-ID is the numeric id of the device.
|
||||||
The returned profile name is a string, or nil if it cannot be found."
|
The returned profile name is a string, or nil if it cannot be found."
|
||||||
(pw-lib--profile-name (pw-access-current-profile pw-lib--accessor device-id)))
|
(pipewire-lib--profile-name (pipewire-access-current-profile pipewire-lib--accessor device-id)))
|
||||||
|
|
||||||
(defun pw-lib-profiles (device-id)
|
(defun pipewire-lib-profiles (device-id)
|
||||||
"Return list of available profiles of the given device.
|
"Return list of available profiles of the given device.
|
||||||
DEVICE-ID is the numeric id of the device.
|
DEVICE-ID is the numeric id of the device.
|
||||||
A list of strings (possibly empty) is returned."
|
A list of strings (possibly empty) is returned."
|
||||||
(mapcar #'pw-lib--profile-name (pw-access-profiles pw-lib--accessor device-id)))
|
(mapcar #'pipewire-lib--profile-name (pipewire-access-profiles pipewire-lib--accessor device-id)))
|
||||||
|
|
||||||
(defun pw-lib-set-profile (device-id profile)
|
(defun pipewire-lib-set-profile (device-id profile)
|
||||||
"Set the profile of the given device.
|
"Set the profile of the given device.
|
||||||
DEVICE-ID is the numeric id of the device.
|
DEVICE-ID is the numeric id of the device.
|
||||||
PROFILE is a string name of the profile, it must be one of the values
|
PROFILE is a string name of the profile, it must be one of the values
|
||||||
returned from `pw-lib-profiles'."
|
returned from `pipewire-lib-profiles'."
|
||||||
(let* ((all-profiles (pw-access-profiles pw-lib--accessor device-id))
|
(let* ((all-profiles (pipewire-access-profiles pipewire-lib--accessor device-id))
|
||||||
(properties (cl-find profile all-profiles :key #'pw-lib--profile-name :test #'equal)))
|
(properties (cl-find profile all-profiles :key #'pipewire-lib--profile-name :test #'equal)))
|
||||||
(unless properties
|
(unless properties
|
||||||
(error "Profile %s of device %s not found" profile device-id))
|
(error "Profile %s of device %s not found" profile device-id))
|
||||||
(let ((index (cdr (assoc "index" properties))))
|
(let ((index (cdr (assoc "index" properties))))
|
||||||
(unless index
|
(unless index
|
||||||
(error "Index of %s profile of device %s not found" profile device-id))
|
(error "Index of %s profile of device %s not found" profile device-id))
|
||||||
(pw-access-set-profile pw-lib--accessor device-id index))))
|
(pipewire-access-set-profile pipewire-lib--accessor device-id index))))
|
||||||
|
|
||||||
(defun pw-lib-parent-node (object)
|
(defun pipewire-lib-parent-node (object)
|
||||||
"Return parent node of OBJECT.
|
"Return parent node of OBJECT.
|
||||||
This is typically used for ports.
|
This is typically used for ports.
|
||||||
Behavior is undefined if OBJECT has no parent node."
|
Behavior is undefined if OBJECT has no parent node."
|
||||||
(pw-lib-get-object (pw-lib-object-value object "node.id")))
|
(pipewire-lib-get-object (pipewire-lib-object-value object "node.id")))
|
||||||
|
|
||||||
(defun pw-lib--node (object)
|
(defun pipewire-lib--node (object)
|
||||||
(if (equal (pw-lib-object-type object) "Node")
|
(if (equal (pipewire-lib-object-type object) "Node")
|
||||||
object
|
object
|
||||||
(pw-lib-parent-node object)))
|
(pipewire-lib-parent-node object)))
|
||||||
|
|
||||||
(defun pw-lib--node-parameters (object-or-id &optional refresh)
|
(defun pipewire-lib--node-parameters (object-or-id &optional refresh)
|
||||||
(let* ((object (if (numberp object-or-id)
|
(let* ((object (if (numberp object-or-id)
|
||||||
(pw-lib-get-object object-or-id)
|
(pipewire-lib-get-object object-or-id)
|
||||||
object-or-id))
|
object-or-id))
|
||||||
(node (pw-lib--node object))
|
(node (pipewire-lib--node object))
|
||||||
(parameters (pw-lib-object-value node 'parameters)))
|
(parameters (pipewire-lib-object-value node 'parameters)))
|
||||||
(when (or refresh (not parameters))
|
(when (or refresh (not parameters))
|
||||||
(setq parameters (pw-access-properties pw-lib--accessor (pw-lib-object-id node)))
|
(setq parameters (pipewire-access-properties pipewire-lib--accessor (pipewire-lib-object-id node)))
|
||||||
(setcdr node (cons (cons 'parameters parameters)
|
(setcdr node (cons (cons 'parameters parameters)
|
||||||
(assq-delete-all 'parameters (cdr node)))))
|
(assq-delete-all 'parameters (cdr node)))))
|
||||||
parameters))
|
parameters))
|
||||||
|
|
||||||
(defun pw-lib-default-nodes ()
|
(defun pipewire-lib-default-nodes ()
|
||||||
"Return assignments of PipeWire Nodes to default sinks and sources.
|
"Return assignments of PipeWire Nodes to default sinks and sources.
|
||||||
An association lists with elements of the form (KEY . ID) is
|
An association lists with elements of the form (KEY . ID) is
|
||||||
returned, where KEY is a string identifying the given kind of
|
returned, where KEY is a string identifying the given kind of
|
||||||
default sink or source as reported by PipeWire and ID is the
|
default sink or source as reported by PipeWire and ID is the
|
||||||
corresponding PipeWire node numeric id.
|
corresponding PipeWire node numeric id.
|
||||||
Note that PipeWire data is cached, if you need its up-to-date
|
Note that PipeWire data is cached, if you need its up-to-date
|
||||||
version, call `pw-lib-refresh' first."
|
version, call `pipewire-lib-refresh' first."
|
||||||
(unless pw-lib--defaults
|
(unless pipewire-lib--defaults
|
||||||
(let ((defaults (pw-access-defaults pw-lib--accessor))
|
(let ((defaults (pipewire-access-defaults pipewire-lib--accessor))
|
||||||
(nodes (mapcar (lambda (o)
|
(nodes (mapcar (lambda (o)
|
||||||
(cons (pw-lib-object-value o "node.name") (pw-lib-object-id o)))
|
(cons (pipewire-lib-object-value o "node.name") (pipewire-lib-object-id o)))
|
||||||
(pw-lib-objects "Node"))))
|
(pipewire-lib-objects "Node"))))
|
||||||
(setq pw-lib--defaults
|
(setq pipewire-lib--defaults
|
||||||
(cl-remove-if-not #'cdr
|
(cl-remove-if-not #'cdr
|
||||||
(mapcar (lambda (d)
|
(mapcar (lambda (d)
|
||||||
(cons (car d) (cdr (assoc (cdr d) nodes))))
|
(cons (car d) (cdr (assoc (cdr d) nodes))))
|
||||||
defaults)))))
|
defaults)))))
|
||||||
pw-lib--defaults)
|
pipewire-lib--defaults)
|
||||||
|
|
||||||
(defun pw-lib--default-node (key)
|
(defun pipewire-lib--default-node (key)
|
||||||
(pw-lib-get-object (cdr (assoc key (pw-lib-default-nodes)))))
|
(pipewire-lib-get-object (cdr (assoc key (pipewire-lib-default-nodes)))))
|
||||||
|
|
||||||
(defun pw-lib-bindings ()
|
(defun pipewire-lib-bindings ()
|
||||||
"Return bindings between PipeWire objects.
|
"Return bindings between PipeWire objects.
|
||||||
An association lists with elements of the form (PARENT . CHILD) is
|
An association lists with elements of the form (PARENT . CHILD) is
|
||||||
returned where PARENT and CHILD are numeric ids of PipeWire objects.
|
returned where PARENT and CHILD are numeric ids of PipeWire objects.
|
||||||
Note that PipeWire data is cached, if you need its up-to-date
|
Note that PipeWire data is cached, if you need its up-to-date
|
||||||
version, call `pw-lib-refresh' first."
|
version, call `pipewire-lib-refresh' first."
|
||||||
(or pw-lib--bindings
|
(or pipewire-lib--bindings
|
||||||
(setq pw-lib--bindings
|
(setq pipewire-lib--bindings
|
||||||
(apply #'nconc
|
(apply #'nconc
|
||||||
(mapcar (lambda (o)
|
(mapcar (lambda (o)
|
||||||
(let ((o-id (pw-lib-object-id o)))
|
(let ((o-id (pipewire-lib-object-id o)))
|
||||||
(mapcar (lambda (p)
|
(mapcar (lambda (p)
|
||||||
(cons o-id (cdr p)))
|
(cons o-id (cdr p)))
|
||||||
(cl-remove-if-not #'numberp (pw-lib--object-info o)
|
(cl-remove-if-not #'numberp (pipewire-lib--object-info o)
|
||||||
:key #'cdr))))
|
:key #'cdr))))
|
||||||
(pw-lib-objects))))))
|
(pipewire-lib-objects))))))
|
||||||
|
|
||||||
(defun pw-lib-children (id &optional type)
|
(defun pipewire-lib-children (id &optional type)
|
||||||
"Return child objects of the object identified by numeric PipeWire ID.
|
"Return child objects of the object identified by numeric PipeWire ID.
|
||||||
If a string TYPE is specified then only children of the given PipeWire
|
If a string TYPE is specified then only children of the given PipeWire
|
||||||
type are returned.
|
type are returned.
|
||||||
Note that PipeWire data is cached, if you need its up-to-date
|
Note that PipeWire data is cached, if you need its up-to-date
|
||||||
version, call `pw-lib-refresh' first."
|
version, call `pipewire-lib-refresh' first."
|
||||||
(let ((children (mapcar #'pw-lib-get-object
|
(let ((children (mapcar #'pipewire-lib-get-object
|
||||||
(mapcar #'car (cl-remove-if (lambda (b) (/= (cdr b) id))
|
(mapcar #'car (cl-remove-if (lambda (b) (/= (cdr b) id))
|
||||||
(pw-lib-bindings))))))
|
(pipewire-lib-bindings))))))
|
||||||
(when type
|
(when type
|
||||||
(setq children (cl-remove-if-not (lambda (o) (equal (pw-lib-object-type o) type))
|
(setq children (cl-remove-if-not (lambda (o) (equal (pipewire-lib-object-type o) type))
|
||||||
children)))
|
children)))
|
||||||
children))
|
children))
|
||||||
|
|
||||||
(defun pw-lib--node-ports (node &optional regexp)
|
(defun pipewire-lib--node-ports (node &optional regexp)
|
||||||
(when node
|
(when node
|
||||||
(let ((ports (pw-lib-children (pw-lib-object-id node) "Port")))
|
(let ((ports (pipewire-lib-children (pipewire-lib-object-id node) "Port")))
|
||||||
(if regexp
|
(if regexp
|
||||||
(cl-delete-if-not (lambda (o)
|
(cl-delete-if-not (lambda (o)
|
||||||
(if-let ((name (pw-lib-object-value o "port.name")))
|
(if-let ((name (pipewire-lib-object-value o "port.name")))
|
||||||
(string-match regexp name)))
|
(string-match regexp name)))
|
||||||
ports)
|
ports)
|
||||||
ports))))
|
ports))))
|
||||||
|
|
||||||
(defun pw-lib-default-audio-sink ()
|
(defun pipewire-lib-default-audio-sink ()
|
||||||
"Return a PipeWire object that is the current default audio sink."
|
"Return a PipeWire object that is the current default audio sink."
|
||||||
(pw-lib--default-node "default.audio.sink"))
|
(pipewire-lib--default-node "default.audio.sink"))
|
||||||
|
|
||||||
(defun pw-lib-default-audio-source ()
|
(defun pipewire-lib-default-audio-source ()
|
||||||
"Return a PipeWire object that is the current default audio source."
|
"Return a PipeWire object that is the current default audio source."
|
||||||
(pw-lib--default-node "default.audio.source"))
|
(pipewire-lib--default-node "default.audio.source"))
|
||||||
|
|
||||||
(defun pw-lib-default-playback-ports ()
|
(defun pipewire-lib-default-playback-ports ()
|
||||||
"Return list of PipeWire objects that are default playback ports."
|
"Return list of PipeWire objects that are default playback ports."
|
||||||
(pw-lib--node-ports (pw-lib-default-audio-sink) "^playback"))
|
(pipewire-lib--node-ports (pipewire-lib-default-audio-sink) "^playback"))
|
||||||
|
|
||||||
(defun pw-lib-default-capture-ports ()
|
(defun pipewire-lib-default-capture-ports ()
|
||||||
"Return list of PipeWire objects that are default capture ports."
|
"Return list of PipeWire objects that are default capture ports."
|
||||||
(pw-lib--node-ports (pw-lib-default-audio-source) "^capture"))
|
(pipewire-lib--node-ports (pipewire-lib-default-audio-source) "^capture"))
|
||||||
|
|
||||||
(defun pw-lib--volume-% (volume)
|
(defun pipewire-lib--volume-% (volume)
|
||||||
(when volume
|
(when volume
|
||||||
(round (* 100 volume))))
|
(round (* 100 volume))))
|
||||||
|
|
||||||
(defun pw-lib--volume-float (volume)
|
(defun pipewire-lib--volume-float (volume)
|
||||||
(/ (float volume) 100))
|
(/ (float volume) 100))
|
||||||
|
|
||||||
(defun pw-lib--object-parameters (object &optional refresh)
|
(defun pipewire-lib--object-parameters (object &optional refresh)
|
||||||
(let* ((node-p (equal (pw-lib-object-type object) "Node"))
|
(let* ((node-p (equal (pipewire-lib-object-type object) "Node"))
|
||||||
(parameters (pw-lib--node-parameters object refresh))
|
(parameters (pipewire-lib--node-parameters object refresh))
|
||||||
(monitor-p (unless node-p
|
(monitor-p (unless node-p
|
||||||
(equal (pw-lib-object-value object "port.monitor") "true")))
|
(equal (pipewire-lib-object-value object "port.monitor") "true")))
|
||||||
(node-id (pw-lib-object-id (pw-lib--node object)))
|
(node-id (pipewire-lib-object-id (pipewire-lib--node object)))
|
||||||
(port-id (unless node-p
|
(port-id (unless node-p
|
||||||
(pw-lib-object-value object "port.id"))))
|
(pipewire-lib-object-value object "port.id"))))
|
||||||
(list node-p parameters monitor-p node-id port-id)))
|
(list node-p parameters monitor-p node-id port-id)))
|
||||||
|
|
||||||
(defun pw-lib-muted-p (object &optional refresh)
|
(defun pipewire-lib-muted-p (object &optional refresh)
|
||||||
"Return whether the given PipeWire OBJECT is muted.
|
"Return whether the given PipeWire OBJECT is muted.
|
||||||
Applicable only to Nodes and Ports.
|
Applicable only to Nodes and Ports.
|
||||||
If REFRESH is non-nil then retrive fresh information from PipeWire
|
If REFRESH is non-nil then retrive fresh information from PipeWire
|
||||||
rather than using cached data to obtain the result."
|
rather than using cached data to obtain the result."
|
||||||
(cl-destructuring-bind (_node-p parameters monitor-p _node-id _port-id)
|
(cl-destructuring-bind (_node-p parameters monitor-p _node-id _port-id)
|
||||||
(pw-lib--object-parameters object refresh)
|
(pipewire-lib--object-parameters object refresh)
|
||||||
(eq (cdr (assoc (if monitor-p "monitorMute" "mute") parameters)) 'true)))
|
(eq (cdr (assoc (if monitor-p "monitorMute" "mute") parameters)) 'true)))
|
||||||
|
|
||||||
(defun pw-lib-toggle-mute (object &optional refresh)
|
(defun pipewire-lib-toggle-mute (object &optional refresh)
|
||||||
"Toggle mute status of the given PipeWire OBJECT.
|
"Toggle mute status of the given PipeWire OBJECT.
|
||||||
Return the new boolean mute status of OBJECT.
|
Return the new boolean mute status of OBJECT.
|
||||||
Applicable only to Nodes and Ports.
|
Applicable only to Nodes and Ports.
|
||||||
If REFRESH is non-nil then retrive fresh information from PipeWire
|
If REFRESH is non-nil then retrive fresh information from PipeWire
|
||||||
rather than using cached data to obtain the result."
|
rather than using cached data to obtain the result."
|
||||||
(cl-destructuring-bind (_node-p _parameters monitor-p node-id _port-id)
|
(cl-destructuring-bind (_node-p _parameters monitor-p node-id _port-id)
|
||||||
(pw-lib--object-parameters object refresh)
|
(pipewire-lib--object-parameters object refresh)
|
||||||
(let* ((mute (not (pw-lib-muted-p object)))
|
(let* ((mute (not (pipewire-lib-muted-p object)))
|
||||||
(property (if monitor-p "monitorMute" "mute"))
|
(property (if monitor-p "monitorMute" "mute"))
|
||||||
(value (if mute "true" "false")))
|
(value (if mute "true" "false")))
|
||||||
(pw-access-set-properties pw-lib--accessor node-id (list (cons property value)))
|
(pipewire-access-set-properties pipewire-lib--accessor node-id (list (cons property value)))
|
||||||
mute)))
|
mute)))
|
||||||
|
|
||||||
(defun pw-lib-volume (object &optional refresh)
|
(defun pipewire-lib-volume (object &optional refresh)
|
||||||
"Return volume of the given PipeWire OBJECT.
|
"Return volume of the given PipeWire OBJECT.
|
||||||
The returned value is an integer in the range 0-100.
|
The returned value is an integer in the range 0-100.
|
||||||
Applicable only to Nodes and Ports.
|
Applicable only to Nodes and Ports.
|
||||||
If REFRESH is non-nil then retrive fresh information from PipeWire
|
If REFRESH is non-nil then retrive fresh information from PipeWire
|
||||||
rather than using cached data to obtain the result."
|
rather than using cached data to obtain the result."
|
||||||
(cl-destructuring-bind (node-p parameters monitor-p _node-id port-id)
|
(cl-destructuring-bind (node-p parameters monitor-p _node-id port-id)
|
||||||
(pw-lib--object-parameters object refresh)
|
(pipewire-lib--object-parameters object refresh)
|
||||||
(pw-lib--volume-%
|
(pipewire-lib--volume-%
|
||||||
(if node-p
|
(if node-p
|
||||||
(cdr (assoc "volume" parameters))
|
(cdr (assoc "volume" parameters))
|
||||||
(nth port-id (cdr (assoc (if monitor-p "monitorVolumes" "channelVolumes") parameters)))))))
|
(nth port-id (cdr (assoc (if monitor-p "monitorVolumes" "channelVolumes") parameters)))))))
|
||||||
|
|
||||||
(defun pw-lib-set-volume (volume object &optional single-p)
|
(defun pipewire-lib-set-volume (volume object &optional single-p)
|
||||||
"Set the volume of PipeWire OBJECT to VOLUME.
|
"Set the volume of PipeWire OBJECT to VOLUME.
|
||||||
VOLUME must be an integer in the range 0-100.
|
VOLUME must be an integer in the range 0-100.
|
||||||
If SINGLE-P is non-nil, set the volume only for a single channel,
|
If SINGLE-P is non-nil, set the volume only for a single channel,
|
||||||
otherwise set the volume to the same value for all the related channels."
|
otherwise set the volume to the same value for all the related channels."
|
||||||
(cl-destructuring-bind (node-p parameters monitor-p node-id port-id)
|
(cl-destructuring-bind (node-p parameters monitor-p node-id port-id)
|
||||||
(pw-lib--object-parameters object)
|
(pipewire-lib--object-parameters object)
|
||||||
(let* ((property (cond
|
(let* ((property (cond
|
||||||
(node-p "volume")
|
(node-p "volume")
|
||||||
(monitor-p "monitorVolumes")
|
(monitor-p "monitorVolumes")
|
||||||
(t "channelVolumes")))
|
(t "channelVolumes")))
|
||||||
(float-volume (pw-lib--volume-float volume))
|
(float-volume (pipewire-lib--volume-float volume))
|
||||||
(value (if node-p
|
(value (if node-p
|
||||||
float-volume
|
float-volume
|
||||||
(let ((orig-value (cdr (assoc property parameters))))
|
(let ((orig-value (cdr (assoc property parameters))))
|
||||||
@ -305,33 +306,33 @@ otherwise set the volume to the same value for all the related channels."
|
|||||||
(cl-substitute float-volume nil orig-value
|
(cl-substitute float-volume nil orig-value
|
||||||
:test #'always :start port-id :count 1)
|
:test #'always :start port-id :count 1)
|
||||||
(make-list (length orig-value) float-volume))))))
|
(make-list (length orig-value) float-volume))))))
|
||||||
(pw-access-set-properties pw-lib--accessor node-id (list (cons property value))))))
|
(pipewire-access-set-properties pipewire-lib--accessor node-id (list (cons property value))))))
|
||||||
|
|
||||||
(defun pw-lib--set-default-node (object stored-p)
|
(defun pipewire-lib--set-default-node (object stored-p)
|
||||||
(let ((suffix (mapconcat #'downcase
|
(let ((suffix (mapconcat #'downcase
|
||||||
(split-string (pw-lib-object-value object "media.class") "/")
|
(split-string (pipewire-lib-object-value object "media.class") "/")
|
||||||
"."))
|
"."))
|
||||||
(prefix (if stored-p "default.configured." "default."))
|
(prefix (if stored-p "default.configured." "default."))
|
||||||
(node-name (pw-lib-object-value object "node.name")))
|
(node-name (pipewire-lib-object-value object "node.name")))
|
||||||
(pw-access-set-default pw-lib--accessor (concat prefix suffix) node-name)))
|
(pipewire-access-set-default pipewire-lib--accessor (concat prefix suffix) node-name)))
|
||||||
|
|
||||||
(defun pw-lib-set-default (object stored-p)
|
(defun pipewire-lib-set-default (object stored-p)
|
||||||
"Set PipeWire OBJECT as the default sink or source.
|
"Set PipeWire OBJECT as the default sink or source.
|
||||||
If STORED-P is non-nil, set the stored default sink or source,
|
If STORED-P is non-nil, set the stored default sink or source,
|
||||||
otherwise set the current default sink or source."
|
otherwise set the current default sink or source."
|
||||||
(pcase (pw-lib-object-type object)
|
(pcase (pipewire-lib-object-type object)
|
||||||
("Device"
|
("Device"
|
||||||
(dolist (node (pw-lib-children (pw-lib-object-id object) "Node"))
|
(dolist (node (pipewire-lib-children (pipewire-lib-object-id object) "Node"))
|
||||||
(pw-lib--set-default-node node stored-p)))
|
(pipewire-lib--set-default-node node stored-p)))
|
||||||
("Node"
|
("Node"
|
||||||
(pw-lib--set-default-node object stored-p))
|
(pipewire-lib--set-default-node object stored-p))
|
||||||
(_
|
(_
|
||||||
(error "Cannot set this kind of object as default"))))
|
(error "Cannot set this kind of object as default"))))
|
||||||
|
|
||||||
(provide 'pw-lib)
|
(provide 'pipewire-lib)
|
||||||
|
|
||||||
;; Local Variables:
|
;; Local Variables:
|
||||||
;; checkdoc-force-docstrings-flag: nil
|
;; checkdoc-force-docstrings-flag: nil
|
||||||
;; End:
|
;; End:
|
||||||
|
|
||||||
;;; pw-lib.el ends here
|
;;; pipewire-lib.el ends here
|
@ -1,28 +0,0 @@
|
|||||||
;;; pipewire-zero.el --- pipewire-0 package definition -*- lexical-binding: t -*-
|
|
||||||
|
|
||||||
;; Copyright (C) 2022 Milan Zamazal <pdm@zamazal.org>
|
|
||||||
|
|
||||||
;; Author: Milan Zamazal <pdm@zamazal.org>
|
|
||||||
;; Version: 1
|
|
||||||
;; Package-Requires: ((emacs "28.1"))
|
|
||||||
;; Keywords: multimedia
|
|
||||||
;; URL: https://git.zamazal.org/pdm/pipewire-0
|
|
||||||
|
|
||||||
;; COPYRIGHT NOTICE
|
|
||||||
;;
|
|
||||||
;; This program 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.
|
|
||||||
;;
|
|
||||||
;; This program 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 this program. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
|
|
||||||
(require 'pipewire)
|
|
||||||
|
|
||||||
;;; pipewire-zero.el ends here
|
|
94
pipewire.el
94
pipewire.el
@ -4,7 +4,7 @@
|
|||||||
|
|
||||||
;; Author: Milan Zamazal <pdm@zamazal.org>
|
;; Author: Milan Zamazal <pdm@zamazal.org>
|
||||||
;; Version: 1
|
;; Version: 1
|
||||||
;; Package-Requires: ((emacs "25.1"))
|
;; Package-Requires: ((emacs "28.1"))
|
||||||
;; Keywords: multimedia
|
;; Keywords: multimedia
|
||||||
;; URL: https://git.zamazal.org/pdm/pipewire-0
|
;; URL: https://git.zamazal.org/pdm/pipewire-0
|
||||||
|
|
||||||
@ -25,7 +25,7 @@
|
|||||||
|
|
||||||
;;; Commentary:
|
;;; Commentary:
|
||||||
;;
|
;;
|
||||||
;; PipeWire user interface based on pw-lib.
|
;; PipeWire user interface based on pipewire-lib.
|
||||||
;; An interactive buffer can be displayed using `M-x pipewire'.
|
;; An interactive buffer can be displayed using `M-x pipewire'.
|
||||||
;; `pipewire-increase-volume', `pipewire-decrease-volume' and
|
;; `pipewire-increase-volume', `pipewire-decrease-volume' and
|
||||||
;; `pipewire-toggle-muted' functions are also suitable to bind on the
|
;; `pipewire-toggle-muted' functions are also suitable to bind on the
|
||||||
@ -33,7 +33,7 @@
|
|||||||
|
|
||||||
;;; Code:
|
;;; Code:
|
||||||
|
|
||||||
(require 'pw-lib)
|
(require 'pipewire-lib)
|
||||||
|
|
||||||
(defgroup pipewire ()
|
(defgroup pipewire ()
|
||||||
"PipeWire user interface."
|
"PipeWire user interface."
|
||||||
@ -104,38 +104,38 @@ The indicator is displayed only on graphical terminals."
|
|||||||
(propertize (concat label ":") 'face 'pipewire-label))
|
(propertize (concat label ":") 'face 'pipewire-label))
|
||||||
|
|
||||||
(defun pipewire--object-volume (object)
|
(defun pipewire--object-volume (object)
|
||||||
(propertize (pw-lib-volume object) 'face 'pipewire-volume))
|
(propertize (pipewire-lib-volume object) 'face 'pipewire-volume))
|
||||||
|
|
||||||
(defun pipewire--object-name (object)
|
(defun pipewire--object-name (object)
|
||||||
(let* ((type (pw-lib-object-type object))
|
(let* ((type (pipewire-lib-object-type object))
|
||||||
(description-properties (if (equal type "Client")
|
(description-properties (if (equal type "Client")
|
||||||
'("application.name")
|
'("application.name")
|
||||||
(let ((prefix (concat (downcase type) ".")))
|
(let ((prefix (concat (downcase type) ".")))
|
||||||
(mapcar (lambda (suffix) (concat prefix suffix))
|
(mapcar (lambda (suffix) (concat prefix suffix))
|
||||||
'("nick" "description" "name"))))))
|
'("nick" "description" "name"))))))
|
||||||
(or (cl-find-if #'identity
|
(or (cl-find-if #'identity
|
||||||
(mapcar (lambda (p) (pw-lib-object-value object p))
|
(mapcar (lambda (p) (pipewire-lib-object-value object p))
|
||||||
description-properties))
|
description-properties))
|
||||||
"")))
|
"")))
|
||||||
|
|
||||||
(defun pipewire--object-label (object default-ids)
|
(defun pipewire--object-label (object default-ids)
|
||||||
(let* ((id (pw-lib-object-id object))
|
(let* ((id (pipewire-lib-object-id object))
|
||||||
(type (pw-lib-object-type object))
|
(type (pipewire-lib-object-type object))
|
||||||
(text (format "%4s: %s" id (pipewire--object-name object)))
|
(text (format "%4s: %s" id (pipewire--object-name object)))
|
||||||
(profile (when (equal type "Device")
|
(profile (when (equal type "Device")
|
||||||
(pw-lib-current-profile (pw-lib-object-id object))))
|
(pipewire-lib-current-profile (pipewire-lib-object-id object))))
|
||||||
(face (if (member id default-ids) 'pipewire-default-object 'default))
|
(face (if (member id default-ids) 'pipewire-default-object 'default))
|
||||||
(media-class (pw-lib-object-value object "media.class")))
|
(media-class (pipewire-lib-object-value object "media.class")))
|
||||||
(when media-class
|
(when media-class
|
||||||
(setq text (format "%s (%s)" text media-class)))
|
(setq text (format "%s (%s)" text media-class)))
|
||||||
(when profile
|
(when profile
|
||||||
(setq text (format "%s: %s" text profile)))
|
(setq text (format "%s: %s" text profile)))
|
||||||
(let ((volume-p (member type '("Node" "Port"))))
|
(let ((volume-p (member type '("Node" "Port"))))
|
||||||
(when (and volume-p (pw-lib-muted-p object))
|
(when (and volume-p (pipewire-lib-muted-p object))
|
||||||
(setq face `(:inherit (pipewire-muted ,face))))
|
(setq face `(:inherit (pipewire-muted ,face))))
|
||||||
(let ((label (propertize text 'face face)))
|
(let ((label (propertize text 'face face)))
|
||||||
(when volume-p
|
(when volume-p
|
||||||
(let ((volume (pw-lib-volume object)))
|
(let ((volume (pipewire-lib-volume object)))
|
||||||
(when volume
|
(when volume
|
||||||
(setq label (concat label " "
|
(setq label (concat label " "
|
||||||
(propertize (number-to-string volume)
|
(propertize (number-to-string volume)
|
||||||
@ -143,7 +143,7 @@ The indicator is displayed only on graphical terminals."
|
|||||||
label))))
|
label))))
|
||||||
|
|
||||||
(defun pipewire--insert-line (line object)
|
(defun pipewire--insert-line (line object)
|
||||||
(insert (propertize line 'pw-object-id (pw-lib-object-id object)) "\n"))
|
(insert (propertize line 'pipewire-object-id (pipewire-lib-object-id object)) "\n"))
|
||||||
|
|
||||||
(defun pipewire-refresh (&optional _ignore-auto _noconfirm)
|
(defun pipewire-refresh (&optional _ignore-auto _noconfirm)
|
||||||
"Refresh PipeWire buffer."
|
"Refresh PipeWire buffer."
|
||||||
@ -151,37 +151,37 @@ The indicator is displayed only on graphical terminals."
|
|||||||
(when (and (not (eq major-mode 'pipewire-mode))
|
(when (and (not (eq major-mode 'pipewire-mode))
|
||||||
(not (equal (buffer-name) pipewire-buffer)))
|
(not (equal (buffer-name) pipewire-buffer)))
|
||||||
(error "Not in a PipeWire buffer"))
|
(error "Not in a PipeWire buffer"))
|
||||||
(pw-lib-refresh)
|
(pipewire-lib-refresh)
|
||||||
(let ((inhibit-read-only t)
|
(let ((inhibit-read-only t)
|
||||||
(default-ids (mapcar #'cdr (pw-lib-default-nodes)))
|
(default-ids (mapcar #'cdr (pipewire-lib-default-nodes)))
|
||||||
(current-line (count-lines (point-min) (min (1+ (point)) (point-max)))))
|
(current-line (count-lines (point-min) (min (1+ (point)) (point-max)))))
|
||||||
(erase-buffer)
|
(erase-buffer)
|
||||||
(insert (pipewire--label "Devices") "\n")
|
(insert (pipewire--label "Devices") "\n")
|
||||||
(dolist (device (pw-lib-objects "Device"))
|
(dolist (device (pipewire-lib-objects "Device"))
|
||||||
(pipewire--insert-line (pipewire--object-label device default-ids) device)
|
(pipewire--insert-line (pipewire--object-label device default-ids) device)
|
||||||
(dolist (node (pw-lib-children (pw-lib-object-id device) "Node"))
|
(dolist (node (pipewire-lib-children (pipewire-lib-object-id device) "Node"))
|
||||||
(pipewire--insert-line (concat " " (pipewire--object-label node default-ids)) node)
|
(pipewire--insert-line (concat " " (pipewire--object-label node default-ids)) node)
|
||||||
(dolist (port (pw-lib-children (pw-lib-object-id node) "Port"))
|
(dolist (port (pipewire-lib-children (pipewire-lib-object-id node) "Port"))
|
||||||
(pipewire--insert-line (concat " " (pipewire--object-label port default-ids)) port))))
|
(pipewire--insert-line (concat " " (pipewire--object-label port default-ids)) port))))
|
||||||
(insert (pipewire--label "Clients") "\n")
|
(insert (pipewire--label "Clients") "\n")
|
||||||
(dolist (client (pw-lib-objects "Client"))
|
(dolist (client (pipewire-lib-objects "Client"))
|
||||||
(pipewire--insert-line (pipewire--object-label client default-ids) client))
|
(pipewire--insert-line (pipewire--object-label client default-ids) client))
|
||||||
(goto-char (point-min))
|
(goto-char (point-min))
|
||||||
(forward-line (1- current-line))))
|
(forward-line (1- current-line))))
|
||||||
|
|
||||||
(defun pipewire--current-object-id ()
|
(defun pipewire--current-object-id ()
|
||||||
(get-text-property (point) 'pw-object-id))
|
(get-text-property (point) 'pipewire-object-id))
|
||||||
|
|
||||||
(defun pipewire--current-object (&optional use-default-p allowed-types)
|
(defun pipewire--current-object (&optional use-default-p allowed-types)
|
||||||
(let* ((id (pipewire--current-object-id))
|
(let* ((id (pipewire--current-object-id))
|
||||||
(object (when id (pw-lib-get-object id))))
|
(object (when id (pipewire-lib-get-object id))))
|
||||||
(when (and object
|
(when (and object
|
||||||
allowed-types
|
allowed-types
|
||||||
(not (member (pw-lib-object-type object) allowed-types)))
|
(not (member (pipewire-lib-object-type object) allowed-types)))
|
||||||
(setq object nil))
|
(setq object nil))
|
||||||
(when (and use-default-p (not object))
|
(when (and use-default-p (not object))
|
||||||
(setq object (or (car (pw-lib-default-playback-ports))
|
(setq object (or (car (pipewire-lib-default-playback-ports))
|
||||||
(pw-lib-default-audio-sink))))
|
(pipewire-lib-default-audio-sink))))
|
||||||
object))
|
object))
|
||||||
|
|
||||||
(defvar pipewire--osd-timer nil)
|
(defvar pipewire--osd-timer nil)
|
||||||
@ -244,16 +244,16 @@ The indicator is displayed only on graphical terminals."
|
|||||||
(if (get-buffer pipewire-buffer)
|
(if (get-buffer pipewire-buffer)
|
||||||
(with-current-buffer pipewire-buffer
|
(with-current-buffer pipewire-buffer
|
||||||
(pipewire-refresh))
|
(pipewire-refresh))
|
||||||
(pw-lib-refresh))
|
(pipewire-lib-refresh))
|
||||||
(when message
|
(when message
|
||||||
(message message)))
|
(message message)))
|
||||||
|
|
||||||
(defun pipewire--osd-volume (object)
|
(defun pipewire--osd-volume (object)
|
||||||
(pipewire--osd
|
(pipewire--osd
|
||||||
(unless (eq (pipewire--current-object-id) (pw-lib-object-id object))
|
(unless (eq (pipewire--current-object-id) (pipewire-lib-object-id object))
|
||||||
(let* ((object* (pw-lib-get-object (pw-lib-object-id object))) ; refreshed version
|
(let* ((object* (pipewire-lib-get-object (pipewire-lib-object-id object))) ; refreshed version
|
||||||
(volume (pw-lib-volume object*))
|
(volume (pipewire-lib-volume object*))
|
||||||
(muted-p (pw-lib-muted-p object*))
|
(muted-p (pipewire-lib-muted-p object*))
|
||||||
(step (/ 100.0 pipewire-osd-width))
|
(step (/ 100.0 pipewire-osd-width))
|
||||||
(mark (if muted-p ?- ?|))
|
(mark (if muted-p ?- ?|))
|
||||||
(n-active (round (/ volume step)))
|
(n-active (round (/ volume step)))
|
||||||
@ -266,7 +266,7 @@ The indicator is displayed only on graphical terminals."
|
|||||||
|
|
||||||
(defun pipewire--update-muted (object muted-p)
|
(defun pipewire--update-muted (object muted-p)
|
||||||
(let* ((object-name (pipewire--object-name object))
|
(let* ((object-name (pipewire--object-name object))
|
||||||
(parent-node (pw-lib-parent-node object))
|
(parent-node (pipewire-lib-parent-node object))
|
||||||
(node-info (if parent-node
|
(node-info (if parent-node
|
||||||
(format " in %s" (pipewire--object-name parent-node))
|
(format " in %s" (pipewire--object-name parent-node))
|
||||||
"")))
|
"")))
|
||||||
@ -279,7 +279,7 @@ If on a Node or Port in a PipeWire buffer, apply it on the given
|
|||||||
object. Otherwise apply it on the default audio sink."
|
object. Otherwise apply it on the default audio sink."
|
||||||
(interactive)
|
(interactive)
|
||||||
(let* ((object (pipewire--current-object t '("Node" "Port")))
|
(let* ((object (pipewire--current-object t '("Node" "Port")))
|
||||||
(muted-p (pw-lib-toggle-mute object)))
|
(muted-p (pipewire-lib-toggle-mute object)))
|
||||||
(pipewire--update-muted object muted-p)
|
(pipewire--update-muted object muted-p)
|
||||||
(pipewire--osd-volume object)))
|
(pipewire--osd-volume object)))
|
||||||
|
|
||||||
@ -287,8 +287,8 @@ object. Otherwise apply it on the default audio sink."
|
|||||||
(defun pipewire-toggle-microphone ()
|
(defun pipewire-toggle-microphone ()
|
||||||
"Switch mute status of the default audio input."
|
"Switch mute status of the default audio input."
|
||||||
(interactive)
|
(interactive)
|
||||||
(let* ((object (car (pw-lib-default-capture-ports)))
|
(let* ((object (car (pipewire-lib-default-capture-ports)))
|
||||||
(muted-p (pw-lib-toggle-mute object)))
|
(muted-p (pipewire-lib-toggle-mute object)))
|
||||||
(pipewire--update-muted object muted-p)))
|
(pipewire--update-muted object muted-p)))
|
||||||
|
|
||||||
;;;###autoload
|
;;;###autoload
|
||||||
@ -304,13 +304,13 @@ corresponding object only."
|
|||||||
(setq volume (max 0 (min 100 volume)))
|
(setq volume (max 0 (min 100 volume)))
|
||||||
(unless object
|
(unless object
|
||||||
(setq object (pipewire--current-object t '("Node" "Port"))))
|
(setq object (pipewire--current-object t '("Node" "Port"))))
|
||||||
(pw-lib-set-volume volume object single-p)
|
(pipewire-lib-set-volume volume object single-p)
|
||||||
(pipewire--update (format "Volume %s for %s" volume (pipewire--object-name object)))
|
(pipewire--update (format "Volume %s for %s" volume (pipewire--object-name object)))
|
||||||
(pipewire--osd-volume object))
|
(pipewire--osd-volume object))
|
||||||
|
|
||||||
(defun pipewire--change-volume (step &optional single-p)
|
(defun pipewire--change-volume (step &optional single-p)
|
||||||
(let* ((object (pipewire--current-object t '("Node" "Port")))
|
(let* ((object (pipewire--current-object t '("Node" "Port")))
|
||||||
(volume (pw-lib-volume object))
|
(volume (pipewire-lib-volume object))
|
||||||
(new-volume (max 0 (min 100 (+ volume step)))))
|
(new-volume (max 0 (min 100 (+ volume step)))))
|
||||||
(pipewire-set-volume new-volume object single-p)))
|
(pipewire-set-volume new-volume object single-p)))
|
||||||
|
|
||||||
@ -360,27 +360,27 @@ If on a Device, apply it on all its nodes.
|
|||||||
Otherwise ask for the Node to set as the default Node."
|
Otherwise ask for the Node to set as the default Node."
|
||||||
(interactive)
|
(interactive)
|
||||||
(let ((object (or (pipewire--current-object nil '("Device" "Node"))
|
(let ((object (or (pipewire--current-object nil '("Device" "Node"))
|
||||||
(let* ((default-node-ids (mapcar #'cdr (pw-lib-default-nodes)))
|
(let* ((default-node-ids (mapcar #'cdr (pipewire-lib-default-nodes)))
|
||||||
(nodes (cl-remove-if
|
(nodes (cl-remove-if
|
||||||
(lambda (n) (member (pw-lib-object-id n) default-node-ids))
|
(lambda (n) (member (pipewire-lib-object-id n) default-node-ids))
|
||||||
(pw-lib-objects "Node")))
|
(pipewire-lib-objects "Node")))
|
||||||
(node-mapping (mapcar (lambda (n) (cons (pipewire--object-name n)
|
(node-mapping (mapcar (lambda (n) (cons (pipewire--object-name n)
|
||||||
(pw-lib-object-id n)))
|
(pipewire-lib-object-id n)))
|
||||||
nodes))
|
nodes))
|
||||||
(node-name (completing-read "Default node: " node-mapping nil t)))
|
(node-name (completing-read "Default node: " node-mapping nil t)))
|
||||||
(pw-lib-get-object (cdr (assoc node-name node-mapping)))))))
|
(pipewire-lib-get-object (cdr (assoc node-name node-mapping)))))))
|
||||||
(pw-lib-set-default object nil)
|
(pipewire-lib-set-default object nil)
|
||||||
(pw-lib-set-default object t)
|
(pipewire-lib-set-default object t)
|
||||||
(pipewire--update)))
|
(pipewire--update)))
|
||||||
|
|
||||||
(defun pipewire-set-profile ()
|
(defun pipewire-set-profile ()
|
||||||
"Set profile of the device at the current point."
|
"Set profile of the device at the current point."
|
||||||
(interactive)
|
(interactive)
|
||||||
(if-let ((device (pipewire--current-object nil '("Device")))
|
(if-let ((device (pipewire--current-object nil '("Device")))
|
||||||
(device-id (pw-lib-object-id device))
|
(device-id (pipewire-lib-object-id device))
|
||||||
(profiles (pw-lib-profiles device-id)))
|
(profiles (pipewire-lib-profiles device-id)))
|
||||||
(progn
|
(progn
|
||||||
(pw-lib-set-profile device-id (completing-read "Select profile: " profiles nil t))
|
(pipewire-lib-set-profile device-id (completing-read "Select profile: " profiles nil t))
|
||||||
;; Without this, ports of the device may not be displayed on the update:
|
;; Without this, ports of the device may not be displayed on the update:
|
||||||
(sit-for 0)
|
(sit-for 0)
|
||||||
(pipewire--update))
|
(pipewire--update))
|
||||||
@ -394,8 +394,8 @@ Otherwise ask for the Node to set as the default Node."
|
|||||||
(pop-to-buffer pipewire-properties-buffer)
|
(pop-to-buffer pipewire-properties-buffer)
|
||||||
(let ((inhibit-read-only t))
|
(let ((inhibit-read-only t))
|
||||||
(erase-buffer)
|
(erase-buffer)
|
||||||
(dolist (p (sort (pw-lib-properties object) #'string-lessp))
|
(dolist (p (sort (pipewire-lib-properties object) #'string-lessp))
|
||||||
(insert (format "%s: %s\n" p (pw-lib-object-value object p)))))
|
(insert (format "%s: %s\n" p (pipewire-lib-object-value object p)))))
|
||||||
(goto-char (point-min))
|
(goto-char (point-min))
|
||||||
(view-mode))
|
(view-mode))
|
||||||
(error "No PipeWire object here")))
|
(error "No PipeWire object here")))
|
||||||
|
280
pw-access.el
280
pw-access.el
@ -1,280 +0,0 @@
|
|||||||
;;; pw-access.el --- PipeWire access -*- lexical-binding: t -*-
|
|
||||||
|
|
||||||
;; Copyright (C) 2022 Milan Zamazal <pdm@zamazal.org>
|
|
||||||
|
|
||||||
;; Author: Milan Zamazal <pdm@zamazal.org>
|
|
||||||
;; Version: 1
|
|
||||||
;; Package-Requires: ((emacs "25.1"))
|
|
||||||
;; Keywords: multimedia
|
|
||||||
;; URL: https://git.zamazal.org/pdm/pipewire-0
|
|
||||||
|
|
||||||
;; COPYRIGHT NOTICE
|
|
||||||
;;
|
|
||||||
;; This program 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.
|
|
||||||
;;
|
|
||||||
;; This program 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 this program. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
|
|
||||||
;;; Commentary:
|
|
||||||
;;
|
|
||||||
;; A generic interface for communication with PipeWire (https://pipewire.org).
|
|
||||||
;; It abstracts communication with PipeWire to be backend independent.
|
|
||||||
;; Only functions from this module may communicate with PipeWire.
|
|
||||||
;;
|
|
||||||
;; Currently, pw-cli is used to talk to PipeWire. This is not optimal
|
|
||||||
;; because pw-cli doesn't seem to have documented output format and
|
|
||||||
;; the format changes accross PipeWire versions. But there seem to be
|
|
||||||
;; no better options currently. This module should allow switching to
|
|
||||||
;; other communication means easily, without any changes needed
|
|
||||||
;; outside this module, except for using a different communication
|
|
||||||
;; class.
|
|
||||||
|
|
||||||
;;; Code:
|
|
||||||
|
|
||||||
(require 'eieio)
|
|
||||||
|
|
||||||
(defclass pw-accessor ()
|
|
||||||
()
|
|
||||||
:documentation
|
|
||||||
"Base PipeWire interface class.
|
|
||||||
All PipeWire interfaces should derive from this class.")
|
|
||||||
|
|
||||||
(cl-defgeneric pw-access-objects (class)
|
|
||||||
"Return all the objects currently reported by PipeWire.
|
|
||||||
It is a list of object data. Each of the elements has a form
|
|
||||||
\(OBJECT-ID . INFO) where OBJECT-ID is a numeric OBJECT-ID as
|
|
||||||
reported by PipeWire and INFO is an association list of items
|
|
||||||
\(NAME . VALUE) where NAME is a string item name as reported by
|
|
||||||
PipeWire and VALUE is the corresponding value. VALUE is a number for
|
|
||||||
object ids, a string otherwise.
|
|
||||||
A special entry with `type' symbol as its name contains the PipeWire
|
|
||||||
type of the objects, as a string (e.g. \"Device\", \"Node\", \"Port\",
|
|
||||||
\"Client\", ...).
|
|
||||||
CLASS is a PipeWire interface, see symbol `pw-accessor'.")
|
|
||||||
|
|
||||||
(cl-defgeneric pw-access-properties (class node-id)
|
|
||||||
"Return properties of the given node.
|
|
||||||
NODE-ID is a numeric PipeWire Node id (other kinds of PipeWire objects
|
|
||||||
are not supported in this method).
|
|
||||||
Object properties may be, unlike object info items, settable.
|
|
||||||
|
|
||||||
An assocation list is returned. Each list element is of the form
|
|
||||||
\(PROPERTY . VALUE) where PROPERTY is a string name of the given
|
|
||||||
property. VALUE can be:
|
|
||||||
|
|
||||||
- \"true\" or \"false\" for boolean values (t and nil are not used to
|
|
||||||
avoid confusion with nil representing invalid or unavailable value).
|
|
||||||
- A number for numeric values (ids, integers, floats).
|
|
||||||
- A string for string values.
|
|
||||||
- A list of elements of any of these types for arrays and structs.
|
|
||||||
|
|
||||||
CLASS is a PipeWire interface, see symbol `pw-accessor'.")
|
|
||||||
|
|
||||||
(cl-defgeneric pw-access-set-properties (class node-id properties)
|
|
||||||
"Set PROPERTIES of the given node.
|
|
||||||
NODE-ID is a numeric PipeWire Node id (other kinds of PipeWire objects
|
|
||||||
are not supported in this method).
|
|
||||||
PROPERTIES is an association list in the same format as in
|
|
||||||
`pw-access-properties'. It needn't contain all the properties, just
|
|
||||||
the properties to be changed.
|
|
||||||
CLASS is a PipeWire interface, see symbol `pw-accessor'.")
|
|
||||||
|
|
||||||
(cl-defgeneric pw-access-current-profile (class device-id)
|
|
||||||
"Return current profile of the given device.
|
|
||||||
DEVICE-ID is a numeric PipeWire Device id (other kinds of PipeWire
|
|
||||||
objects are not supported in this method).
|
|
||||||
|
|
||||||
The profile is an association list with elements of the form
|
|
||||||
\(PROPERTY . VALUE), in the same format as properties in
|
|
||||||
`pw-access-properties'.
|
|
||||||
|
|
||||||
CLASS is a PipeWire interface, see symbol `pw-accessor'.")
|
|
||||||
|
|
||||||
(cl-defgeneric pw-access-profiles (class device-id)
|
|
||||||
"Return available profiles of the given device.
|
|
||||||
DEVICE-ID is a numeric PipeWire Device id (other kinds of PipeWire
|
|
||||||
objects are not supported in this method).
|
|
||||||
|
|
||||||
Return a list of profiles, which are in the same format as in
|
|
||||||
`pw-access-current-profile'.
|
|
||||||
|
|
||||||
CLASS is a PipeWire interface, see symbol `pw-accessor'.")
|
|
||||||
|
|
||||||
(cl-defgeneric pw-access-set-profile (class device-id profile-index)
|
|
||||||
"Set the profile of the given device.
|
|
||||||
DEVICE-ID is a numeric PipeWire Device id (other kinds of PipeWire
|
|
||||||
objects are not supported in this method).
|
|
||||||
PROFILE-INDEX is a numeric index of the profile to set, as returned
|
|
||||||
from PipeWire.
|
|
||||||
CLASS is a PipeWire interface, see symbol `pw-accessor'.")
|
|
||||||
|
|
||||||
(cl-defgeneric pw-access-defaults (class)
|
|
||||||
"Return default sinks and sources.
|
|
||||||
An association lists is returned. Each list element is of the form
|
|
||||||
\(KEY . NAME) where KEY is a string identifying the given kind of
|
|
||||||
default sink or source as reported by PipeWire and NAME is a string
|
|
||||||
name of the node assigned to the default.
|
|
||||||
CLASS is a PipeWire interface, see symbol `pw-accessor'.")
|
|
||||||
|
|
||||||
(cl-defgeneric pw-access-set-default (class key node-name)
|
|
||||||
"Set default sink or source.
|
|
||||||
KEY is a string identifying the given kind of default sink or source
|
|
||||||
as reported in `pw-access-defaults' and NODE-NAME is a string name of
|
|
||||||
the node that should be assigned to KEY.
|
|
||||||
CLASS is a PipeWire interface, see symbol `pw-accessor'.")
|
|
||||||
|
|
||||||
;; pw-cli interface
|
|
||||||
|
|
||||||
(defvar pw-cli-command "pw-cli"
|
|
||||||
"pw-cli command to use.")
|
|
||||||
|
|
||||||
(defvar pw-cli-metadata-command "pw-metadata"
|
|
||||||
"pw-metadata command to use.")
|
|
||||||
|
|
||||||
(defclass pw-cli-accessor (pw-accessor)
|
|
||||||
()
|
|
||||||
:documentation
|
|
||||||
"pw-cli based interface to PipeWire.
|
|
||||||
Note this interface may not work with all PipeWire versions.")
|
|
||||||
|
|
||||||
(defun pw-cli--command (command args)
|
|
||||||
(apply #'call-process command nil t nil args)
|
|
||||||
(goto-char (point-min)))
|
|
||||||
|
|
||||||
(defun pw-cli--next-line ()
|
|
||||||
(goto-char (line-beginning-position 2)))
|
|
||||||
|
|
||||||
(defun pw-cli--parse-list ()
|
|
||||||
(let ((objects '()))
|
|
||||||
(while (re-search-forward "^[\t]id \\([0-9]+\\), type PipeWire:Interface:\\(.*\\)/.*$" nil t)
|
|
||||||
(let ((id (string-to-number (match-string 1)))
|
|
||||||
(properties `((type . ,(match-string 2)))))
|
|
||||||
(pw-cli--next-line)
|
|
||||||
(while (looking-at "^ [\t][\t]\\([a-z.]+\\) = \"\\(.*\\)\"")
|
|
||||||
(let ((property (match-string 1))
|
|
||||||
(value (match-string 2)))
|
|
||||||
(when (string-suffix-p ".id" property)
|
|
||||||
(setq value (string-to-number value)))
|
|
||||||
(push (cons property value) properties))
|
|
||||||
(pw-cli--next-line))
|
|
||||||
(push (cons id properties) objects)))
|
|
||||||
(nreverse objects)))
|
|
||||||
|
|
||||||
(cl-defmethod pw-access-objects ((_class pw-cli-accessor))
|
|
||||||
(with-temp-buffer
|
|
||||||
(pw-cli--command pw-cli-command '("list-objects"))
|
|
||||||
(pw-cli--parse-list)))
|
|
||||||
|
|
||||||
(defun pw-cli--read-property (&optional nesting)
|
|
||||||
(unless nesting
|
|
||||||
(setq nesting 0))
|
|
||||||
(when (looking-at (concat (make-string (+ 6 (* 2 nesting)) ? )
|
|
||||||
"\\([A-Za-z:]+\\) \\(.*\\)"))
|
|
||||||
(let ((type (match-string 1))
|
|
||||||
(value (match-string 2)))
|
|
||||||
(pcase type
|
|
||||||
("Bool"
|
|
||||||
(if (equal value "true") 'true 'false))
|
|
||||||
((or "Float" "Id" "Int")
|
|
||||||
(string-to-number value))
|
|
||||||
("String"
|
|
||||||
(substring value 1 -1))
|
|
||||||
((or "Array:" "Struct:")
|
|
||||||
(let ((array '())
|
|
||||||
item)
|
|
||||||
(pw-cli--next-line)
|
|
||||||
(while (setq item (pw-cli--read-property (1+ nesting)))
|
|
||||||
(push item array)
|
|
||||||
(pw-cli--next-line))
|
|
||||||
(nreverse array)))))))
|
|
||||||
|
|
||||||
(defun pw-cli--parse-properties ()
|
|
||||||
(pw-cli--next-line)
|
|
||||||
(let ((end (or (save-excursion (re-search-forward "^ Object:" nil t))
|
|
||||||
(point-max)))
|
|
||||||
(properties '()))
|
|
||||||
(while (and (< (point) end)
|
|
||||||
(re-search-forward "^ Prop: key \\([A-Za-z:]+\\)" end t))
|
|
||||||
(pw-cli--next-line)
|
|
||||||
(let ((property (car (last (split-string (match-string 1) ":"))))
|
|
||||||
(value (pw-cli--read-property)))
|
|
||||||
(when value
|
|
||||||
(push (cons property value) properties))))
|
|
||||||
(goto-char end)
|
|
||||||
properties))
|
|
||||||
|
|
||||||
(cl-defmethod pw-access-properties ((_class pw-cli-accessor) node-id)
|
|
||||||
(with-temp-buffer
|
|
||||||
(pw-cli--command pw-cli-command `("enum-params" ,(number-to-string node-id) "Props"))
|
|
||||||
(pw-cli--parse-properties)))
|
|
||||||
|
|
||||||
(defun pw-cli--format-property-value (value)
|
|
||||||
(cond
|
|
||||||
((consp value)
|
|
||||||
(concat "[ " (mapconcat #'pw-cli--format-property-value value ", ") " ]"))
|
|
||||||
((numberp value)
|
|
||||||
(number-to-string value))
|
|
||||||
(t
|
|
||||||
value)))
|
|
||||||
|
|
||||||
(defun pw-cli--format-property (property)
|
|
||||||
(format "%s: %s" (car property) (pw-cli--format-property-value (cdr property))))
|
|
||||||
|
|
||||||
(defun pw-cli--format-properties (properties)
|
|
||||||
(concat "{ " (mapconcat #'pw-cli--format-property properties ", ") " }"))
|
|
||||||
|
|
||||||
(defun pw-cli--set-parameter (object-id parameter value)
|
|
||||||
(let* ((formatted (pw-cli--format-properties value)))
|
|
||||||
(call-process pw-cli-command nil pw-cli-command nil
|
|
||||||
"set-param" (number-to-string object-id) parameter formatted)))
|
|
||||||
|
|
||||||
(cl-defmethod pw-access-set-properties ((_class pw-cli-accessor) node-id properties)
|
|
||||||
(pw-cli--set-parameter node-id "Props" properties))
|
|
||||||
|
|
||||||
(cl-defmethod pw-access-current-profile ((_class pw-cli-accessor) device-id)
|
|
||||||
(with-temp-buffer
|
|
||||||
(pw-cli--command pw-cli-command `("enum-params" ,(number-to-string device-id) "Profile"))
|
|
||||||
(pw-cli--parse-properties)))
|
|
||||||
|
|
||||||
(cl-defmethod pw-access-profiles ((_class pw-cli-accessor) device-id)
|
|
||||||
(with-temp-buffer
|
|
||||||
(pw-cli--command pw-cli-command `("enum-params" ,(number-to-string device-id) "EnumProfile"))
|
|
||||||
(cl-loop for profile = (pw-cli--parse-properties) then (pw-cli--parse-properties)
|
|
||||||
while profile
|
|
||||||
collect profile)))
|
|
||||||
|
|
||||||
(cl-defmethod pw-access-set-profile ((_class pw-cli-accessor) device-id profile-index)
|
|
||||||
(pw-cli--set-parameter device-id "Profile" `(("index" . ,profile-index) ("save" . "true"))))
|
|
||||||
|
|
||||||
(defun pw-cli--parse-metadata ()
|
|
||||||
(let ((metadata '()))
|
|
||||||
(while (re-search-forward
|
|
||||||
"key:'\\([a-z.]+\\)'.*\\(value\\|\"name\"\\): ?['\"]\\([^'\"]+\\)['\"]"
|
|
||||||
nil t)
|
|
||||||
(push (cons (match-string 1) (match-string 3)) metadata))
|
|
||||||
metadata))
|
|
||||||
|
|
||||||
(cl-defmethod pw-access-defaults ((_class pw-cli-accessor))
|
|
||||||
(with-temp-buffer
|
|
||||||
(pw-cli--command pw-cli-metadata-command '("0"))
|
|
||||||
(pw-cli--parse-metadata)))
|
|
||||||
|
|
||||||
(cl-defmethod pw-access-set-default ((_class pw-cli-accessor) property node-name)
|
|
||||||
(call-process pw-cli-metadata-command nil pw-cli-metadata-command nil
|
|
||||||
"0" property (format "{ \"name\": \"%s\" }" node-name)))
|
|
||||||
|
|
||||||
(provide 'pw-access)
|
|
||||||
|
|
||||||
;; Local Variables:
|
|
||||||
;; checkdoc-force-docstrings-flag: nil
|
|
||||||
;; End:
|
|
||||||
|
|
||||||
;;; pw-access.el ends here
|
|
Loading…
Reference in New Issue
Block a user