One thing folks try to do in Stump is to interweave lisp code and shell commands, by way of run-shell-command
. When they discover that lisp and shell commands dont run sequentially, the first impulse is to throw (sleep n)
calls between the shell command and the next lisp form, or try to collect the output of the shell command such that Stump waits for it to terminate. This is often considered good enough, and the effort stops there.
But a better way exists. By virtue of Stump supporting only SBCL, we can safely use SBCL specific functions, such as sb-ext:run-program
, and the associated functions that take a process structure. One nice thing about run-program
is that it takes the keyarg status-hook
, a function of arity one that gets called whenever the status of the process changes. So we can pass in a function that contains all the code we wish to run after the program is finished, placing it in a check to see if the process is alive.
(sb-ext:run-program "my-program" '("arg1" "arg2")
:wait nil
:search t
:status-hook
(lambda (p)
(unless (sb-ext:process-alive-p p)
(run-my-code))))
One example of this being useful is in setting up a head using xrandr
:
(sb-ext:run-program "/bin/xrandr"
'("--output" "HDMI2" "--left-of" "HDMI1")
:wait nil
:status-hook
(lambda (p)
(unless (sb-ext:process-alive-p p)
(refresh-heads))))
This will call refresh-heads
after the new head is set up, without resorting to sleep
. And Stump wont freeze while waiting for xrandr
to finish.
We can wrap this up in a macro to make it easier/simpler to use, and get free shell interpretation:
(defmacro do-after-shell-command ((process-var shell-command
&key synchronous (shell '*shell-program*))
&body body)
`(sb-ext:run-program ,shell
(list "-c" ,shell-command)
:wait ,synchronous
:status-hook
(lambda (,process-var)
(unless (sb-ext:process-alive-p ,process-var)
,@body))))))
(do-after-shell-command
(proc "xrandr --output HDMI2 --left-of HDMI1")
(refresh-heads))