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: 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
dont know about anything better currently. Nevertheless, its easy 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? ** 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> ;; 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

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> ;; 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")))

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