Add support for device profiles

This is especially useful with Bluetooth devices.
This commit is contained in:
Milan Zamazal 2022-06-15 21:31:24 +02:00
parent 1065d9d389
commit 2c421bb990
3 changed files with 93 additions and 3 deletions

View File

@ -73,6 +73,30 @@ PROPERTIES is an association list in the same format as in
`pw-access-properties'. It needn't contain all the properties, just `pw-access-properties'. It needn't contain all the properties, just
the properties to be changed.") 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) (cl-defgeneric pw-access-defaults (class)
"Return default sinks and sources. "Return default sinks and sources.
An association lists is returned. Each list element is of the form An association lists is returned. Each list element is of the form
@ -183,11 +207,29 @@ Note this interface may not work with all PipeWire versions.")
(defun pw-cli--format-property (property) (defun pw-cli--format-property (property)
(format "%s: %s" (car property) (pw-cli--format-property-value (cdr property)))) (format "%s: %s" (car property) (pw-cli--format-property-value (cdr property))))
(let* ((formatted (mapconcat #'pw-cli--format-property properties ", ")) (defun pw-cli--set-parameter (object-id parameter value)
(props (concat "{ " formatted " }"))) (let* ((formatted (mapconcat #'pw-cli--format-property value ", "))
(param-value (concat "{ " formatted " }")))
(call-process pw-cli-command nil pw-cli-command nil (call-process pw-cli-command nil pw-cli-command nil
"set-param" (number-to-string node-id) "Props" props))) "set-param" (number-to-string object-id) parameter param-value)))
(cl-defmethod pw-access-set-properties ((_class pw-cli-accessor) node-id properties) (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 () (defun pw-cli--parse-metadata ()
(let ((metadata '())) (let ((metadata '()))

View File

@ -81,6 +81,36 @@ If the given KEY doesn't exist in OBJECT, return DEFAULT."
E.g. \"Device\", \"Node\", \"Port\", \"Client\", ..." E.g. \"Device\", \"Node\", \"Port\", \"Client\", ..."
(pw-lib-object-value object 'type)) (pw-lib-object-value object 'type))
(defun pw-lib--profile-name (profile)
(cdr (or (assoc "description" profile)
(assoc "name" profile))))
(defun pw-lib-current-profile (device-id)
"Return the current profile name of the given device.
DEVICE-ID is the numeric id of the device.
The returned profile name is a string, or nil if it cannot be found."
(pw-lib--profile-name (pw-access-current-profile pw-lib--accessor device-id)))
(defun pw-lib-profiles (device-id)
"Return list of available profiles of the given device.
DEVICE-ID is the numeric id of the device.
A list of strings (possibly empty) is returned."
(mapcar #'pw-lib--profile-name (pw-access-profiles pw-lib--accessor device-id)))
(defun pw-lib-set-profile (device-id profile)
"Set the profile of the given device.
DEVICE-ID is the numeric id of the device.
PROFILE is a string name of the profile, it must be one of the values
returned from `pw-lib-profiles'. "
(let* ((all-profiles (pw-access-profiles pw-lib--accessor device-id))
(properties (cl-find profile all-profiles :key #'pw-lib--profile-name :test #'equal)))
(unless properties
(error "Profile %s of device %s not found" profile device-id))
(let ((index (cdr (assoc "index" properties))))
(unless index
(error "Index of %s profile of device %s not found" profile device-id))
(pw-access-set-profile pw-lib--accessor device-id index))))
(defun pw-lib--node (object) (defun pw-lib--node (object)
(if (equal (pw-lib-object-type object) "Node") (if (equal (pw-lib-object-type object) "Node")
object object

View File

@ -110,10 +110,14 @@ The indicator is displayed only on graphical terminals."
(let* ((id (pw-lib-object-id object)) (let* ((id (pw-lib-object-id object))
(type (pw-lib-object-type object)) (type (pw-lib-object-type object))
(text (format "%4s: %s" id (pw-ui--object-name object))) (text (format "%4s: %s" id (pw-ui--object-name object)))
(profile (when (equal type "Device")
(pw-lib-current-profile (pw-lib-object-id object))))
(face (if (member id default-ids) 'pipewire-default-object-face 'default)) (face (if (member id default-ids) 'pipewire-default-object-face 'default))
(media-class (pw-lib-object-value object "media.class"))) (media-class (pw-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
(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 (pw-lib-muted-p object))
(setq face `(:inherit (pipewire-muted-face ,face)))) (setq face `(:inherit (pipewire-muted-face ,face))))
@ -337,10 +341,24 @@ Otherwise ask for the Node to set as the default Node."
(pw-lib-set-default object t) (pw-lib-set-default object t)
(pw-ui--update))) (pw-ui--update)))
(defun pipewire-set-profile ()
"Set profile of the device at the current point."
(interactive)
(if-let ((device (pw-ui--current-object nil '("Device")))
(device-id (pw-lib-object-id device))
(profiles (pw-lib-profiles device-id)))
(progn
(pw-lib-set-profile device-id (completing-read "Select profile: " profiles nil t))
;; Without this, ports of the device may not be displayed on the update:
(sit-for 0)
(pw-ui--update))
(error "Nothing to set a profile for here")))
(defvar pipewire-mode-map (defvar pipewire-mode-map
(let ((map (make-sparse-keymap))) (let ((map (make-sparse-keymap)))
(define-key map "d" 'pipewire-set-default) (define-key map "d" 'pipewire-set-default)
(define-key map "m" 'pipewire-toggle-muted) (define-key map "m" 'pipewire-toggle-muted)
(define-key map "p" 'pipewire-set-profile)
(define-key map "v" 'pipewire-set-volume) (define-key map "v" 'pipewire-set-volume)
(define-key map "=" 'pipewire-increase-volume) (define-key map "=" 'pipewire-increase-volume)
(define-key map "-" 'pipewire-decrease-volume) (define-key map "-" 'pipewire-decrease-volume)