186 lines
7.0 KiB
EmacsLisp
186 lines
7.0 KiB
EmacsLisp
|
;;; 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
|