Compare commits

..

4 Commits

Author SHA1 Message Date
c23524cd30 Take pipewire-cli out of pipewire-access.el
This should resolve the remaining issues with name prefixes.
2022-07-16 21:03:29 +02:00
e032234c63 Fix typo in pipewire-lib commentary 2022-07-16 20:53:02 +02:00
78c8c6e083 Use ‘pipewire-’ prefix everywhere
This is necessary for MELPA.
2022-07-16 20:53:00 +02:00
33fbb2ba1a Rephrase docstrings starting with lower cases to stop checkdoc complaints 2022-07-16 20:12:05 +02:00
7 changed files with 491 additions and 481 deletions

View File

@ -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:
#+begin_src elisp
(require pw-ui)
(require pipewire)
#+end_src
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:
- [[file:pw-lib.el][pw-lib.el]] :: PipeWire library to be used in Elisp programs.
- [[file:pw-access.el][pw-access.el]] :: PipeWire communication interface, not supposed to be
used outside =pw-lib=.
- [[file:pw-ui.el][pw-ui.el]] :: User commands and interface.
- [[file:pipewire-lib.el][pipewire-lib.el]] :: PipeWire library to be used in Elisp programs.
- [[file:pipewire-access.el][pipewire-access.el]] :: PipeWire communication interface, not supposed to be
used outside =pipewire-lib=.
- [[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
display current volume level of the default audio sink:
#+begin_src elisp
(let ((object (pw-lib-default-audio-sink)))
(let ((object (pipewire-lib-default-audio-sink)))
(format "%s%s"
(pw-lib-volume object t)
(if (pw-lib-muted-p object) "(M)" "")))
(pipewire-lib-volume object t)
(if (pipewire-lib-muted-p object) "(M)" "")))
#+end_src
* 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
so this is not a reliable way to communicate with PipeWire. But I
dont know about anything better currently. Nevertheless, its 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?

132
pipewire-access.el Normal file
View 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
View 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

View File

@ -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>
@ -26,278 +26,279 @@
;;; Commentary:
;;
;; 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.
;;
;; pw-lib caches data retrieved from PipeWire and uses the cached
;; data. If The cache can be invalidated by calling `pw-lib-refresh'.
;; pipewire-lib caches data retrieved from PipeWire and uses the cached
;; data. The cache can be invalidated by calling `pipewire-lib-refresh'.
;;; Code:
(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 pw-lib--bindings nil)
(defvar pw-lib--defaults nil)
(defvar pipewire-lib--objects '())
(defvar pipewire-lib--bindings nil)
(defvar pipewire-lib--defaults nil)
(defun pw-lib-refresh ()
(defun pipewire-lib-refresh ()
"Clear cache of objects retrieved from PipeWire."
(setq pw-lib--objects (pw-access-objects pw-lib--accessor)
pw-lib--bindings nil
pw-lib--defaults nil))
(setq pipewire-lib--objects (pipewire-access-objects pipewire-lib--accessor)
pipewire-lib--bindings nil
pipewire-lib--defaults nil))
(defun pw-lib-objects (&optional type)
(defun pipewire-lib-objects (&optional type)
"Return a list of PipeWire objects.
TYPE is a string identifying PipeWire objects types (e.g. \"Device\",
\"Node\", \"Port\", \"Client\", ...). If specified, return only
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.
Note that PipeWire data is cached, if you need its up-to-date
version, call `pw-lib-refresh' first."
(unless pw-lib--objects
(pw-lib-refresh))
(let ((objects pw-lib--objects))
version, call `pipewire-lib-refresh' first."
(unless pipewire-lib--objects
(pipewire-lib-refresh))
(let ((objects pipewire-lib--objects))
(when type
(setq objects (cl-remove-if-not
(lambda (o) (string= (cdr (assq 'type (cdr o))) type))
objects)))
objects))
(defun pw-lib-get-object (id)
(defun pipewire-lib-get-object (id)
"Return PipeWire object identified by ID.
If such an object doesn't exist, return nil.
Note that PipeWire data is cached, if you need its up-to-date
version, call `pw-lib-refresh' first."
(assoc id pw-lib--objects))
version, call `pipewire-lib-refresh' first."
(assoc id pipewire-lib--objects))
(defun pw-lib-object-id (object)
(defun pipewire-lib-object-id (object)
"Return id of the given PipeWire OBJECT."
(car object))
(defun pw-lib--object-info (object)
(defun pipewire-lib--object-info (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.
KEY is a string corresponding to a PipeWire value identifier.
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))
(defun pw-lib-properties (object)
(defun pipewire-lib-properties (object)
"Return names of PipeWire OBJECT properties.
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."
(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.
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)
(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.
DEVICE-ID is the numeric id of the device.
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.
DEVICE-ID is the numeric id of the device.
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.
DEVICE-ID is the numeric id of the device.
PROFILE is a string name of the profile, it must be one of the values
returned from `pw-lib-profiles'."
(let* ((all-profiles (pw-access-profiles pw-lib--accessor device-id))
(properties (cl-find profile all-profiles :key #'pw-lib--profile-name :test #'equal)))
returned from `pipewire-lib-profiles'."
(let* ((all-profiles (pipewire-access-profiles pipewire-lib--accessor device-id))
(properties (cl-find profile all-profiles :key #'pipewire-lib--profile-name :test #'equal)))
(unless properties
(error "Profile %s of device %s not found" profile device-id))
(let ((index (cdr (assoc "index" properties))))
(unless index
(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.
This is typically used for ports.
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)
(if (equal (pw-lib-object-type object) "Node")
(defun pipewire-lib--node (object)
(if (equal (pipewire-lib-object-type object) "Node")
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)
(pw-lib-get-object object-or-id)
(pipewire-lib-get-object object-or-id)
object-or-id))
(node (pw-lib--node object))
(parameters (pw-lib-object-value node 'parameters)))
(node (pipewire-lib--node object))
(parameters (pipewire-lib-object-value node '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)
(assq-delete-all 'parameters (cdr node)))))
parameters))
(defun pw-lib-default-nodes ()
(defun pipewire-lib-default-nodes ()
"Return assignments of PipeWire Nodes to default sinks and sources.
An association lists with elements of the form (KEY . ID) is
returned, where KEY is a string identifying the given kind of
default sink or source as reported by PipeWire and ID is the
corresponding PipeWire node numeric id.
Note that PipeWire data is cached, if you need its up-to-date
version, call `pw-lib-refresh' first."
(unless pw-lib--defaults
(let ((defaults (pw-access-defaults pw-lib--accessor))
version, call `pipewire-lib-refresh' first."
(unless pipewire-lib--defaults
(let ((defaults (pipewire-access-defaults pipewire-lib--accessor))
(nodes (mapcar (lambda (o)
(cons (pw-lib-object-value o "node.name") (pw-lib-object-id o)))
(pw-lib-objects "Node"))))
(setq pw-lib--defaults
(cons (pipewire-lib-object-value o "node.name") (pipewire-lib-object-id o)))
(pipewire-lib-objects "Node"))))
(setq pipewire-lib--defaults
(cl-remove-if-not #'cdr
(mapcar (lambda (d)
(cons (car d) (cdr (assoc (cdr d) nodes))))
defaults)))))
pw-lib--defaults)
pipewire-lib--defaults)
(defun pw-lib--default-node (key)
(pw-lib-get-object (cdr (assoc key (pw-lib-default-nodes)))))
(defun pipewire-lib--default-node (key)
(pipewire-lib-get-object (cdr (assoc key (pipewire-lib-default-nodes)))))
(defun pw-lib-bindings ()
(defun pipewire-lib-bindings ()
"Return bindings between PipeWire objects.
An association lists with elements of the form (PARENT . CHILD) is
returned where PARENT and CHILD are numeric ids of PipeWire objects.
Note that PipeWire data is cached, if you need its up-to-date
version, call `pw-lib-refresh' first."
(or pw-lib--bindings
(setq pw-lib--bindings
version, call `pipewire-lib-refresh' first."
(or pipewire-lib--bindings
(setq pipewire-lib--bindings
(apply #'nconc
(mapcar (lambda (o)
(let ((o-id (pw-lib-object-id o)))
(let ((o-id (pipewire-lib-object-id o)))
(mapcar (lambda (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))))
(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.
If a string TYPE is specified then only children of the given PipeWire
type are returned.
Note that PipeWire data is cached, if you need its up-to-date
version, call `pw-lib-refresh' first."
(let ((children (mapcar #'pw-lib-get-object
version, call `pipewire-lib-refresh' first."
(let ((children (mapcar #'pipewire-lib-get-object
(mapcar #'car (cl-remove-if (lambda (b) (/= (cdr b) id))
(pw-lib-bindings))))))
(pipewire-lib-bindings))))))
(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))
(defun pw-lib--node-ports (node &optional regexp)
(defun pipewire-lib--node-ports (node &optional regexp)
(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
(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)))
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."
(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."
(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."
(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."
(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
(round (* 100 volume))))
(defun pw-lib--volume-float (volume)
(defun pipewire-lib--volume-float (volume)
(/ (float volume) 100))
(defun pw-lib--object-parameters (object &optional refresh)
(let* ((node-p (equal (pw-lib-object-type object) "Node"))
(parameters (pw-lib--node-parameters object refresh))
(defun pipewire-lib--object-parameters (object &optional refresh)
(let* ((node-p (equal (pipewire-lib-object-type object) "Node"))
(parameters (pipewire-lib--node-parameters object refresh))
(monitor-p (unless node-p
(equal (pw-lib-object-value object "port.monitor") "true")))
(node-id (pw-lib-object-id (pw-lib--node object)))
(equal (pipewire-lib-object-value object "port.monitor") "true")))
(node-id (pipewire-lib-object-id (pipewire-lib--node object)))
(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)))
(defun pw-lib-muted-p (object &optional refresh)
(defun pipewire-lib-muted-p (object &optional refresh)
"Return whether the given PipeWire OBJECT is muted.
Applicable only to Nodes and Ports.
If REFRESH is non-nil then retrive fresh information from PipeWire
rather than using cached data to obtain the result."
(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)))
(defun pw-lib-toggle-mute (object &optional refresh)
(defun pipewire-lib-toggle-mute (object &optional refresh)
"Toggle mute status of the given PipeWire OBJECT.
Return the new boolean mute status of OBJECT.
Applicable only to Nodes and Ports.
If REFRESH is non-nil then retrive fresh information from PipeWire
rather than using cached data to obtain the result."
(cl-destructuring-bind (_node-p _parameters monitor-p node-id _port-id)
(pw-lib--object-parameters object refresh)
(let* ((mute (not (pw-lib-muted-p object)))
(pipewire-lib--object-parameters object refresh)
(let* ((mute (not (pipewire-lib-muted-p object)))
(property (if monitor-p "monitorMute" "mute"))
(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)))
(defun pw-lib-volume (object &optional refresh)
(defun pipewire-lib-volume (object &optional refresh)
"Return volume of the given PipeWire OBJECT.
The returned value is an integer in the range 0-100.
Applicable only to Nodes and Ports.
If REFRESH is non-nil then retrive fresh information from PipeWire
rather than using cached data to obtain the result."
(cl-destructuring-bind (node-p parameters monitor-p _node-id port-id)
(pw-lib--object-parameters object refresh)
(pw-lib--volume-%
(pipewire-lib--object-parameters object refresh)
(pipewire-lib--volume-%
(if node-p
(cdr (assoc "volume" 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.
VOLUME must be an integer in the range 0-100.
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."
(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
(node-p "volume")
(monitor-p "monitorVolumes")
(t "channelVolumes")))
(float-volume (pw-lib--volume-float volume))
(float-volume (pipewire-lib--volume-float volume))
(value (if node-p
float-volume
(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
:test #'always :start port-id :count 1)
(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
(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."))
(node-name (pw-lib-object-value object "node.name")))
(pw-access-set-default pw-lib--accessor (concat prefix suffix) node-name)))
(node-name (pipewire-lib-object-value object "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.
If STORED-P is non-nil, set the stored 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"
(dolist (node (pw-lib-children (pw-lib-object-id object) "Node"))
(pw-lib--set-default-node node stored-p)))
(dolist (node (pipewire-lib-children (pipewire-lib-object-id object) "Node"))
(pipewire-lib--set-default-node node stored-p)))
("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"))))
(provide 'pw-lib)
(provide 'pipewire-lib)
;; Local Variables:
;; checkdoc-force-docstrings-flag: nil
;; End:
;;; pw-lib.el ends here
;;; pipewire-lib.el ends here

View File

@ -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

View File

@ -4,7 +4,7 @@
;; Author: Milan Zamazal <pdm@zamazal.org>
;; Version: 1
;; Package-Requires: ((emacs "25.1"))
;; Package-Requires: ((emacs "28.1"))
;; Keywords: multimedia
;; URL: https://git.zamazal.org/pdm/pipewire-0
@ -25,7 +25,7 @@
;;; 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'.
;; `pipewire-increase-volume', `pipewire-decrease-volume' and
;; `pipewire-toggle-muted' functions are also suitable to bind on the
@ -33,7 +33,7 @@
;;; Code:
(require 'pw-lib)
(require 'pipewire-lib)
(defgroup pipewire ()
"PipeWire user interface."
@ -104,38 +104,38 @@ The indicator is displayed only on graphical terminals."
(propertize (concat label ":") 'face 'pipewire-label))
(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)
(let* ((type (pw-lib-object-type object))
(let* ((type (pipewire-lib-object-type object))
(description-properties (if (equal type "Client")
'("application.name")
(let ((prefix (concat (downcase type) ".")))
(mapcar (lambda (suffix) (concat prefix suffix))
'("nick" "description" "name"))))))
(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))
"")))
(defun pipewire--object-label (object default-ids)
(let* ((id (pw-lib-object-id object))
(type (pw-lib-object-type object))
(let* ((id (pipewire-lib-object-id object))
(type (pipewire-lib-object-type object))
(text (format "%4s: %s" id (pipewire--object-name object)))
(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))
(media-class (pw-lib-object-value object "media.class")))
(media-class (pipewire-lib-object-value object "media.class")))
(when media-class
(setq text (format "%s (%s)" text media-class)))
(when profile
(setq text (format "%s: %s" text profile)))
(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))))
(let ((label (propertize text 'face face)))
(when volume-p
(let ((volume (pw-lib-volume object)))
(let ((volume (pipewire-lib-volume object)))
(when volume
(setq label (concat label " "
(propertize (number-to-string volume)
@ -143,7 +143,7 @@ The indicator is displayed only on graphical terminals."
label))))
(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)
"Refresh PipeWire buffer."
@ -151,37 +151,37 @@ The indicator is displayed only on graphical terminals."
(when (and (not (eq major-mode 'pipewire-mode))
(not (equal (buffer-name) pipewire-buffer)))
(error "Not in a PipeWire buffer"))
(pw-lib-refresh)
(pipewire-lib-refresh)
(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)))))
(erase-buffer)
(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)
(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)
(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))))
(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))
(goto-char (point-min))
(forward-line (1- current-line))))
(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)
(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
allowed-types
(not (member (pw-lib-object-type object) allowed-types)))
(not (member (pipewire-lib-object-type object) allowed-types)))
(setq object nil))
(when (and use-default-p (not object))
(setq object (or (car (pw-lib-default-playback-ports))
(pw-lib-default-audio-sink))))
(setq object (or (car (pipewire-lib-default-playback-ports))
(pipewire-lib-default-audio-sink))))
object))
(defvar pipewire--osd-timer nil)
@ -244,16 +244,16 @@ The indicator is displayed only on graphical terminals."
(if (get-buffer pipewire-buffer)
(with-current-buffer pipewire-buffer
(pipewire-refresh))
(pw-lib-refresh))
(pipewire-lib-refresh))
(when message
(message message)))
(defun pipewire--osd-volume (object)
(pipewire--osd
(unless (eq (pipewire--current-object-id) (pw-lib-object-id object))
(let* ((object* (pw-lib-get-object (pw-lib-object-id object))) ; refreshed version
(volume (pw-lib-volume object*))
(muted-p (pw-lib-muted-p object*))
(unless (eq (pipewire--current-object-id) (pipewire-lib-object-id object))
(let* ((object* (pipewire-lib-get-object (pipewire-lib-object-id object))) ; refreshed version
(volume (pipewire-lib-volume object*))
(muted-p (pipewire-lib-muted-p object*))
(step (/ 100.0 pipewire-osd-width))
(mark (if muted-p ?- ?|))
(n-active (round (/ volume step)))
@ -266,7 +266,7 @@ The indicator is displayed only on graphical terminals."
(defun pipewire--update-muted (object muted-p)
(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
(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."
(interactive)
(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--osd-volume object)))
@ -287,8 +287,8 @@ object. Otherwise apply it on the default audio sink."
(defun pipewire-toggle-microphone ()
"Switch mute status of the default audio input."
(interactive)
(let* ((object (car (pw-lib-default-capture-ports)))
(muted-p (pw-lib-toggle-mute object)))
(let* ((object (car (pipewire-lib-default-capture-ports)))
(muted-p (pipewire-lib-toggle-mute object)))
(pipewire--update-muted object muted-p)))
;;;###autoload
@ -304,13 +304,13 @@ corresponding object only."
(setq volume (max 0 (min 100 volume)))
(unless object
(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--osd-volume object))
(defun pipewire--change-volume (step &optional single-p)
(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)))))
(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."
(interactive)
(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
(lambda (n) (member (pw-lib-object-id n) default-node-ids))
(pw-lib-objects "Node")))
(lambda (n) (member (pipewire-lib-object-id n) default-node-ids))
(pipewire-lib-objects "Node")))
(node-mapping (mapcar (lambda (n) (cons (pipewire--object-name n)
(pw-lib-object-id n)))
(pipewire-lib-object-id n)))
nodes))
(node-name (completing-read "Default node: " node-mapping nil t)))
(pw-lib-get-object (cdr (assoc node-name node-mapping)))))))
(pw-lib-set-default object nil)
(pw-lib-set-default object t)
(pipewire-lib-get-object (cdr (assoc node-name node-mapping)))))))
(pipewire-lib-set-default object nil)
(pipewire-lib-set-default object t)
(pipewire--update)))
(defun pipewire-set-profile ()
"Set profile of the device at the current point."
(interactive)
(if-let ((device (pipewire--current-object nil '("Device")))
(device-id (pw-lib-object-id device))
(profiles (pw-lib-profiles device-id)))
(device-id (pipewire-lib-object-id device))
(profiles (pipewire-lib-profiles device-id)))
(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:
(sit-for 0)
(pipewire--update))
@ -394,8 +394,8 @@ Otherwise ask for the Node to set as the default Node."
(pop-to-buffer pipewire-properties-buffer)
(let ((inhibit-read-only t))
(erase-buffer)
(dolist (p (sort (pw-lib-properties object) #'string-lessp))
(insert (format "%s: %s\n" p (pw-lib-object-value object p)))))
(dolist (p (sort (pipewire-lib-properties object) #'string-lessp))
(insert (format "%s: %s\n" p (pipewire-lib-object-value object p)))))
(goto-char (point-min))
(view-mode))
(error "No PipeWire object here")))

View File

@ -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