114 lines
4.1 KiB
Common Lisp
114 lines
4.1 KiB
Common Lisp
(eval-when (:compile-toplevel :load-toplevel :execute)
|
|
(ql:quickload '(:uiop :com.inuoe.jzon)))
|
|
|
|
(defpackage :inhibit-sleep-for-audio
|
|
(:use :cl)
|
|
(:local-nicknames (:jzon :com.inuoe.jzon))
|
|
(:export :toplevel))
|
|
|
|
(in-package :inhibit-sleep-for-audio)
|
|
|
|
(defparameter *debug-output* (progn #+slynk t #-slynk nil) ;; unconfuse emacs
|
|
"Whether or not to print debug output.")
|
|
|
|
(declaim (inline debug-format))
|
|
(defun debug-format (control-string &rest args)
|
|
"FORMAT to stdout, but only when *debug-output* is non-nil."
|
|
(when *debug-output*
|
|
(apply 'format t control-string args)))
|
|
|
|
(defun event-has-live-stream-p (event)
|
|
"Return non-nil if EVENT has a live stream."
|
|
(loop for obj across event
|
|
for info = (gethash "info" obj)
|
|
when (and (hash-table-p info)
|
|
(equal (gethash "type" obj) "PipeWire:Interface:Node")
|
|
(equal (gethash "state" info) "running")
|
|
(or (not (equal (gethash "n-input-ports" info) 0))
|
|
(not (equal (gethash "n-output-ports" info) 0))))
|
|
do (return t)))
|
|
|
|
(defvar *inhibitor-process* nil
|
|
"The systemd-inhibit process object.")
|
|
|
|
(defun inhibitor-running-p ()
|
|
"Return non-nil if the inhibitor is active."
|
|
(and *inhibitor-process* (uiop:process-alive-p *inhibitor-process*)))
|
|
|
|
(defun start-inhibitor ()
|
|
"Start the inhibitor process."
|
|
(let ((cmd (list "systemd-inhibit"
|
|
"--mode=block"
|
|
"--what=sleep:idle"
|
|
"--who=inhibit-sleep-for-audio"
|
|
"--why=PipeWire audio playing or recording"
|
|
"sleep"
|
|
(princ-to-string most-positive-fixnum))))
|
|
(setq *inhibitor-process* (uiop:launch-program cmd :output "/dev/null"))
|
|
(debug-format "Started inhibitor process ~S~%" cmd)))
|
|
|
|
(defun stop-inhibitor ()
|
|
"Stop the inhibitor process."
|
|
(uiop:terminate-process *inhibitor-process*)
|
|
(uiop:wait-process *inhibitor-process*)
|
|
(setq *inhibitor-process* nil)
|
|
(debug-format "Stopped inhibitor process~%"))
|
|
|
|
(defun process-event (event)
|
|
"Process one event from pw-dump."
|
|
(let ((has-live-stream (event-has-live-stream-p event)))
|
|
(cond
|
|
((and has-live-stream (not (inhibitor-running-p)))
|
|
(start-inhibitor))
|
|
((and (not has-live-stream) (inhibitor-running-p))
|
|
(stop-inhibitor)))))
|
|
|
|
(defun print-help-and-exit ()
|
|
"Print a help message and then exit."
|
|
(format t "usage: ~A [-h|--help] [-d|--debug]
|
|
-h|--help print this message, then exit
|
|
-d|--debug print debug output as program runs~%"
|
|
(or (uiop:argv0) "inhibit-sleep-for-audio.lisp"))
|
|
#-slynk (uiop:quit))
|
|
|
|
(defun handle-cli-args ()
|
|
"Process command-line arguments."
|
|
(dolist (arg (uiop:command-line-arguments))
|
|
(when (or (equal arg "-h") (equal arg "--help"))
|
|
(print-help-and-exit))
|
|
(when (or (equal arg "-d") (equal arg "--debug"))
|
|
(setq *debug-output* t))))
|
|
|
|
(defun read-next-event (stream)
|
|
"Read the next pw-dump event from STREAM."
|
|
(jzon:with-parser (parse stream)
|
|
(jzon:parse-next-element parse)))
|
|
|
|
(defun main ()
|
|
(handle-cli-args)
|
|
(let ((monitor-process (uiop:launch-program '("pw-dump" "-m")
|
|
:output :stream)))
|
|
(unwind-protect
|
|
(progn
|
|
(debug-format "Started pw-dump monitor process with pid ~A~%"
|
|
(uiop:process-info-pid monitor-process))
|
|
(loop with stream = (uiop:process-info-output monitor-process)
|
|
while (uiop:process-alive-p monitor-process)
|
|
do (process-event (read-next-event stream))))
|
|
(when (inhibitor-running-p)
|
|
(stop-inhibitor))
|
|
(when (uiop:process-alive-p monitor-process)
|
|
(uiop:terminate-process monitor-process)
|
|
(uiop:wait-process monitor-process)
|
|
(debug-format "Terminated pw-dump monitor process...~%")))))
|
|
|
|
#+sbcl (sb-ext:disable-debugger)
|
|
(defun toplevel ()
|
|
"Toplevel of the program."
|
|
#+sbcl (handler-case
|
|
(main)
|
|
(sb-sys:interactive-interrupt ()
|
|
(format t "Exiting because of keyboard interrupt...~%")
|
|
(uiop:quit 1)))
|
|
#-sbcl (main))
|