Compare commits

..

22 Commits

Author SHA1 Message Date
ae7a95230f README: Add info about MELPA 2022-07-26 20:00:57 +02:00
115a8a89a3 Use user-error rather than ‘error’ in interactive functions 2022-07-25 20:58:07 +02:00
8109f43855 Toggle microphone: Better error message if there is no default input
Instead of an Elisp type error.
2022-07-16 21:33:36 +02:00
a81c61ab0a Expand commentary in pipewire.el
This is what would be displayed in MELPA package description.
2022-07-16 21:25:20 +02:00
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
07e8456ce4 Rename pipewire-zero-pkg.el to pipewire-zero.el
And replace the package definition with a simple ‘require’.
This should be enough for MELPA.
2022-07-12 21:38:07 +02:00
4ce4bbda10 Simplify a bit a condition in pipewire--current-object 2022-07-12 21:32:56 +02:00
cc8fa0336e Document CLASS cl-defgeneric arguments
This is needed to make checkdoc happy.  The wording is not very good
because checkdoc insists on using one of the predefined words before
`pw-accessor’.
2022-07-12 21:27:19 +02:00
7ec78e2caf Rename pw-ui.el to pipewire.el
This makes all the prefixes in the file the same and checkdoc is
happy about it.
2022-07-12 21:15:58 +02:00
562b910aa0 Silence checkdoc messages about docstrings of internal objects
Internal objects here don’t have and shouldn’t have documentation
strings because:

- There is nothing useful to specify in them.
- They are not a stable API to be used.
- They would pollute the source file and make it larger.
2022-07-06 10:20:27 +02:00
119aef1b08 Move Code: labels before the beginning of code
From the places where they were inserted by checkdoc.
2022-07-05 13:30:30 +02:00
91888abb91 pw-lib-set-default: Remove ending dot in the error message 2022-07-04 19:17:02 +02:00
22d02e5b7f Document single-p argument in docstrings 2022-07-04 19:16:33 +02:00
dd4c2728a5 Mistakes in docstrings corrected 2022-07-04 19:16:12 +02:00
2b53b19c05 Add useless “Code” headers to make checkdoc happier 2022-07-04 19:15:13 +02:00
3f56c8fc0d Use Version instead of Package-Version in package headers 2022-07-04 19:04:42 +02:00
e9273dcf09 New command pipewire-properties 2022-07-02 09:43:54 +02:00
5047d562c4 Remove trailing spaces 2022-07-02 09:30:44 +02:00
b54e3e4af6 Display nick as the object name if available 2022-07-02 09:18:09 +02:00
7 changed files with 644 additions and 572 deletions

View File

@ -18,14 +18,9 @@ discuss anything.
pipewire-0 currently requires presence of PipeWire command line tools, pipewire-0 currently requires presence of PipeWire command line tools,
namely [[https://docs.pipewire.org/page_man_pw_cli_1.html][pw-cli]] and [[https://docs.pipewire.org/page_man_pw_metadata_1.html][pw-metadata]]. namely [[https://docs.pipewire.org/page_man_pw_cli_1.html][pw-cli]] and [[https://docs.pipewire.org/page_man_pw_metadata_1.html][pw-metadata]].
To install pipewire-0, put the *.el files to a site-lisp directory and You can install pipewire-0 from [[https://melpa.org/#/pipewire][MELPA]]. If you use [[https://github.com/radian-software/straight.el][straight.el]] and
add the following line to your Emacs configuration: prefer using source repos directly, you can install pipewire-0 as
follows:
#+begin_src elisp
(require pw-ui)
#+end_src
Or if you use [[https://github.com/radian-software/straight.el][straight.el]], you can install pipewire-0 as follows:
#+begin_src elisp #+begin_src elisp
(straight-use-package (straight-use-package
@ -34,6 +29,13 @@ Or if you use [[https://github.com/radian-software/straight.el][straight.el]], y
:local-repo "pipewire-0")) :local-repo "pipewire-0"))
#+end_src #+end_src
To install pipewire-0 manually, put the *.el files to a site-lisp
directory and add the following line to your Emacs configuration:
#+begin_src elisp
(require pipewire)
#+end_src
** User interface ** User interface
=M-x pipewire= enters a buffer with PipeWire objects: =M-x pipewire= enters a buffer with PipeWire objects:
@ -68,20 +70,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 +92,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,9 +1,9 @@
;;; 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>
;; Author: Milan Zamazal <pdm@zamazal.org> ;; Author: Milan Zamazal <pdm@zamazal.org>
;; Package-Version: 1 ;; Version: 1
;; Package-Requires: ((emacs "28.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
@ -26,269 +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:
(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-object-type (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 `pipewire-lib-object-value'
function."
(cl-remove-if-not #'stringp (mapcar #'car (pipewire-lib--object-info 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))))
@ -296,29 +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)
;;; pw-lib.el ends here ;; Local Variables:
;; checkdoc-force-docstrings-flag: nil
;; End:
;;; pipewire-lib.el ends here

View File

@ -1,30 +0,0 @@
;;; pipewire-0-pkg.el --- pipewire-0 package definition -*- lexical-binding: t -*-
;; Copyright (C) 2022 Milan Zamazal <pdm@zamazal.org>
;; Author: Milan Zamazal <pdm@zamazal.org>
;; Package-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/>.
(define-package "pipewire-zero" "1"
"Interface to PipeWire."
'((emacs "28.1")))
;;; pipewire-0-pkg.el ends here

View File

@ -1,10 +1,10 @@
;;; pw-ui.el --- PipeWire user interface -*- lexical-binding: t -*- ;;; pipewire.el --- PipeWire user interface -*- lexical-binding: t -*-
;; Copyright (C) 2022 Milan Zamazal <pdm@zamazal.org> ;; Copyright (C) 2022 Milan Zamazal <pdm@zamazal.org>
;; Author: Milan Zamazal <pdm@zamazal.org> ;; Author: Milan Zamazal <pdm@zamazal.org>
;; Package-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,13 +25,22 @@
;;; Commentary: ;;; Commentary:
;; ;;
;; PipeWire user interface based on pw-lib. ;; PipeWire user interface and library.
;; An interactive buffer can be displayed using `M-x pipewire'. ;; It currently uses pw-cli and pw-metadata command line utilities to
;; interact with PipeWire.
;;
;; An interactive PipeWire buffer can be displayed using `M-x pipewire'.
;; There you can view basic PipeWire status and change some settings.
;; `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 can be used also standalone and
;; multimedia keys. ;; are suitable to bind on the multimedia keys.
;;
;; The package can be used also non-interactively in Elisp programs.
;; See pipewire-lib.el source file for available functions.
(require 'pw-lib) ;;; Code:
(require 'pipewire-lib)
(defgroup pipewire () (defgroup pipewire ()
"PipeWire user interface." "PipeWire user interface."
@ -96,110 +105,111 @@ The indicator is displayed only on graphical terminals."
:group 'pipewire) :group 'pipewire)
(defvar pipewire-buffer "*PipeWire*") (defvar pipewire-buffer "*PipeWire*")
(defvar pipewire-properties-buffer "*PipWire-properties*")
(defun pw-ui--label (label) (defun pipewire--label (label)
(propertize (concat label ":") 'face 'pipewire-label)) (propertize (concat label ":") 'face 'pipewire-label))
(defun pw-ui--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 pw-ui--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))
'("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 pw-ui--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 (pw-ui--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)
'face 'pipewire-volume)))))) 'face 'pipewire-volume))))))
label)))) label))))
(defun pw-ui--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."
(interactive) (interactive)
(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")) (user-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 (pw-ui--label "Devices") "\n") (insert (pipewire--label "Devices") "\n")
(dolist (device (pw-lib-objects "Device")) (dolist (device (pipewire-lib-objects "Device"))
(pw-ui--insert-line (pw-ui--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"))
(pw-ui--insert-line (concat " " (pw-ui--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"))
(pw-ui--insert-line (concat " " (pw-ui--object-label port default-ids)) port)))) (pipewire--insert-line (concat " " (pipewire--object-label port default-ids)) port))))
(insert (pw-ui--label "Clients") "\n") (insert (pipewire--label "Clients") "\n")
(dolist (client (pw-lib-objects "Client")) (dolist (client (pipewire-lib-objects "Client"))
(pw-ui--insert-line (pw-ui--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 pw-ui--current-object-id () (defun pipewire--current-object-id ()
(get-text-property (point) 'pw-object-id)) (get-text-property (point) 'pipewire-object-id))
(defun pw-ui--current-object (&optional use-default-p allowed-types) (defun pipewire--current-object (&optional use-default-p allowed-types)
(let* ((id (pw-ui--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
(not (null 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 pw-ui--osd-timer nil) (defvar pipewire--osd-timer nil)
(defvar pw-ui--osd-frame nil) (defvar pipewire--osd-frame nil)
(defvar pw-ui--osd-buffer nil) (defvar pipewire--osd-buffer nil)
(defvar pw-ui--osd-buffer-name "*pipewire-osd*") (defvar pipewire--osd-buffer-name "*pipewire-osd*")
(defun pw-ui--osd-display (string) (defun pipewire--osd-display (string)
(when pw-ui--osd-timer (when pipewire--osd-timer
(cancel-timer pw-ui--osd-timer)) (cancel-timer pipewire--osd-timer))
(let ((frame-width (+ 2 (length string)))) (let ((frame-width (+ 2 (length string))))
(when (and pw-ui--osd-frame (when (and pipewire--osd-frame
(not (= frame-width (frame-width pw-ui--osd-frame)))) (not (= frame-width (frame-width pipewire--osd-frame))))
(delete-frame pw-ui--osd-frame) (delete-frame pipewire--osd-frame)
(setq pw-ui--osd-frame nil)) (setq pipewire--osd-frame nil))
(with-current-buffer (setq pw-ui--osd-buffer (get-buffer-create pw-ui--osd-buffer-name)) (with-current-buffer (setq pipewire--osd-buffer (get-buffer-create pipewire--osd-buffer-name))
(erase-buffer) (erase-buffer)
(insert " " string) (insert " " string)
(setq mode-line-format nil) (setq mode-line-format nil)
(unless pw-ui--osd-frame (unless pipewire--osd-frame
(setq pw-ui--osd-frame (make-frame `((unsplittable . t) (setq pipewire--osd-frame (make-frame `((unsplittable . t)
,@pipewire-osd-frame-parameters ,@pipewire-osd-frame-parameters
(minibuffer . nil) (minibuffer . nil)
(parent-frame . ,(selected-frame)) (parent-frame . ,(selected-frame))
@ -217,40 +227,40 @@ The indicator is displayed only on graphical terminals."
(tool-bar-lines . 0) (tool-bar-lines . 0)
(tab-bar-lines . 0) (tab-bar-lines . 0)
(cursor-type . nil))))))) (cursor-type . nil)))))))
(setq pw-ui--osd-timer (setq pipewire--osd-timer
(run-with-timer (run-with-timer
pipewire-osd-timeout nil pipewire-osd-timeout nil
(lambda () (lambda ()
(when pw-ui--osd-frame (when pipewire--osd-frame
(ignore-errors (delete-frame pw-ui--osd-frame))) (ignore-errors (delete-frame pipewire--osd-frame)))
(when pw-ui--osd-buffer (when pipewire--osd-buffer
(ignore-errors (kill-buffer pw-ui--osd-buffer))) (ignore-errors (kill-buffer pipewire--osd-buffer)))
(setq pw-ui--osd-frame nil (setq pipewire--osd-frame nil
pw-ui--osd-timer nil pipewire--osd-timer nil
pw-ui--osd-buffer nil))))) pipewire--osd-buffer nil)))))
(defmacro pw-ui--osd (&rest body) (defmacro pipewire--osd (&rest body)
(declare (debug (body)) (declare (debug (body))
(indent defun)) (indent defun))
(let (($string (gensym))) (let (($string (gensym)))
`(when (and window-system pipewire-osd-enable) `(when (and window-system pipewire-osd-enable)
(if-let ((,$string (progn ,@body))) (if-let ((,$string (progn ,@body)))
(pw-ui--osd-display ,$string))))) (pipewire--osd-display ,$string)))))
(defun pw-ui--update (&optional message) (defun pipewire--update (&optional message)
(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 pw-ui--osd-volume (object) (defun pipewire--osd-volume (object)
(pw-ui--osd (pipewire--osd
(unless (eq (pw-ui--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)))
@ -261,13 +271,13 @@ The indicator is displayed only on graphical terminals."
(propertize (make-string n-inactive mark) (propertize (make-string n-inactive mark)
'face `(:background ,pipewire-osd-volume-off-color))))))) 'face `(:background ,pipewire-osd-volume-off-color)))))))
(defun pw-ui--update-muted (object muted-p) (defun pipewire--update-muted (object muted-p)
(let* ((object-name (pw-ui--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" (pw-ui--object-name parent-node)) (format " in %s" (pipewire--object-name parent-node))
""))) "")))
(pw-ui--update (format "%s%s %s" object-name node-info (if muted-p "muted" "unmuted"))))) (pipewire--update (format "%s%s %s" object-name node-info (if muted-p "muted" "unmuted")))))
;;;###autoload ;;;###autoload
(defun pipewire-toggle-muted () (defun pipewire-toggle-muted ()
@ -275,18 +285,19 @@ The indicator is displayed only on graphical terminals."
If on a Node or Port in a PipeWire buffer, apply it on the given 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 (pw-ui--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)))
(pw-ui--update-muted object muted-p) (pipewire--update-muted object muted-p)
(pw-ui--osd-volume object))) (pipewire--osd-volume object)))
;;;###autoload ;;;###autoload
(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))) (if object
(pw-ui--update-muted object muted-p))) (pipewire--update-muted object (pipewire-lib-toggle-mute object))
(user-error "No default audio input"))))
;;;###autoload ;;;###autoload
(defun pipewire-set-volume (volume &optional object single-p) (defun pipewire-set-volume (volume &optional object single-p)
@ -294,18 +305,20 @@ object. Otherwise apply it on the default audio sink."
VOLUME must be a number in the range 0-100. VOLUME must be a number in the range 0-100.
If OBJECT is given (only Nodes and Ports are allowed) or if on a Node If OBJECT is given (only Nodes and Ports are allowed) or if on a Node
or Port in a PipeWire buffer, apply it on the given object. or Port in a PipeWire buffer, apply it on the given object.
Otherwise apply it on the default audio sink." Otherwise apply it on the default audio sink.
If SINGLE-P is nil, apply it on all related channels, otherwise on the
corresponding object only."
(interactive "nVolume: ") (interactive "nVolume: ")
(setq volume (max 0 (min 100 volume))) (setq volume (max 0 (min 100 volume)))
(unless object (unless object
(setq object (pw-ui--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)
(pw-ui--update (format "Volume %s for %s" volume (pw-ui--object-name object))) (pipewire--update (format "Volume %s for %s" volume (pipewire--object-name object)))
(pw-ui--osd-volume object)) (pipewire--osd-volume object))
(defun pw-ui--change-volume (step &optional single-p) (defun pipewire--change-volume (step &optional single-p)
(let* ((object (pw-ui--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)))
@ -314,10 +327,10 @@ Otherwise apply it on the default audio sink."
"Increase volume of an audio output or input. "Increase volume of an audio output or input.
The volume is increased by `pipewire-volume-step'. The volume is increased by `pipewire-volume-step'.
If on a Node or Port in a PipeWire buffer, apply it on all the If on a Node or Port in a PipeWire buffer, apply it on all the
channels of the given object. Otherwise apply it on the default audio channels of the given object, unless SINGLE-P is non-nil.
sink." Otherwise apply it on the default audio sink."
(interactive) (interactive)
(pw-ui--change-volume pipewire-volume-step single-p)) (pipewire--change-volume pipewire-volume-step single-p))
;;;###autoload ;;;###autoload
(defun pipewire-increase-volume-single () (defun pipewire-increase-volume-single ()
@ -333,10 +346,10 @@ object. Otherwise apply it on the default audio sink."
"Decrease volume of an audio output or input. "Decrease volume of an audio output or input.
The volume is decreased by `pipewire-volume-step'. The volume is decreased by `pipewire-volume-step'.
If on a Node or Port in a PipeWire buffer, apply it on all the If on a Node or Port in a PipeWire buffer, apply it on all the
channels of the given object. Otherwise apply it on the default audio channels of the given object, unless SINGLE-P is non-nil.
sink." Otherwise apply it on the default audio sink."
(interactive) (interactive)
(pw-ui--change-volume (- pipewire-volume-step) single-p)) (pipewire--change-volume (- pipewire-volume-step) single-p))
;;;###autoload ;;;###autoload
(defun pipewire-decrease-volume-single () (defun pipewire-decrease-volume-single ()
@ -354,32 +367,46 @@ If on a Node in a PipeWire buffer, apply it on the given object.
If on a Device, apply it on all its nodes. 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 (pw-ui--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 (pw-ui--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)
(pw-ui--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 (pw-ui--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)
(pw-ui--update)) (pipewire--update))
(error "Nothing to set a profile for here"))) (user-error "Nothing to set a profile for here")))
(defun pipewire-properties ()
"Display properties of the object at the current point."
(interactive)
(if-let ((object (pipewire--current-object)))
(progn
(pop-to-buffer pipewire-properties-buffer)
(let ((inhibit-read-only t))
(erase-buffer)
(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))
(user-error "No PipeWire object here")))
(defvar pipewire-mode-map (defvar pipewire-mode-map
(let ((map (make-sparse-keymap))) (let ((map (make-sparse-keymap)))
@ -391,6 +418,7 @@ Otherwise ask for the Node to set as the default Node."
(define-key map "-" 'pipewire-decrease-volume) (define-key map "-" 'pipewire-decrease-volume)
(define-key map "+" 'pipewire-increase-volume-single) (define-key map "+" 'pipewire-increase-volume-single)
(define-key map "_" 'pipewire-decrease-volume-single) (define-key map "_" 'pipewire-decrease-volume-single)
(define-key map " " 'pipewire-properties)
map)) map))
(define-derived-mode pipewire-mode special-mode "PW" (define-derived-mode pipewire-mode special-mode "PW"
@ -409,6 +437,10 @@ applied on some of them or the buffer:
(pipewire-refresh) (pipewire-refresh)
(pipewire-mode)) (pipewire-mode))
(provide 'pw-ui) (provide 'pipewire)
;;; pw-ui.el ends here ;; Local Variables:
;; checkdoc-force-docstrings-flag: nil
;; End:
;;; pipewire.el ends here

View File

@ -1,263 +0,0 @@
;;; pw-access.el --- PipeWire access -*- lexical-binding: t -*-
;; Copyright (C) 2022 Milan Zamazal <pdm@zamazal.org>
;; Author: Milan Zamazal <pdm@zamazal.org>
;; Package-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.
(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\", ...).")
(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.")
(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.")
(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'.")
(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'.")
(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.")
(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.")
(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.")
;; 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)
;;; pw-access.el ends here