[Re-adding the mailing list and reformatting] On Sun, Sep 05, 2021 at 10:23:14AM +1000, Bernd Wechner wrote: > On 5/9/21 06:13, Sean Greenslade wrote: > > On Tue, Jun 08, 2021 at 08:17:00PM -0700, Peter Meilstrup wrote: > > > Hello pulseaudio list, > > > I am wondering if it is possible to have a command be executed when a > > > sink is suspended or resumed (via module-suspend-on-idle). > > > Specifically I want to switch my amplifier on and off automatically > > > via home automation. Is this supported in pulseaudio? > > > > I realize this is a reply to a pretty old message, but in case you or > > others are still wondering about this: > > > > It's pretty straightforward to do this with a custom pulseaudio client > > application. I took a quick stab at it with the "pulsectl" python > > library. Example script attached, just modify the sink.name line to > > match your target device. > > This looks awesome, been wondering for a while if there was a way I could > watch for pulse events and enforce a default device. I have endless trouble > on a HTPC that seems when the projector is switched on or off seeing the > surround sound device disappear and reappear and with that cycle a new > device inadvertently selected. I would like to somehow say, no matter what > the F happens, unless it's user specified select the surround sound system > and configure it for 5.1 (it also flips to stereo a lot on such power > cycles). This part of your particular issue was solved in the most recent pulse release (15.0). From the release notes: >> Card profiles can be set to sticky >> >> Card profiles can be set to sticky using the command "pactl send-message >> /card/<card name> set-profile-sticky 'true|false'". The current status >> can be queried using pactl send-message /card/<card name> >> get-profile-sticky. If a card profile is sticky, it will always be >> restored, even if the profile is unavailable. Also port availability >> changes on the card will not impact the profile choice. This is for >> example useful to set the card profile permanently to "off" for HDMI >> devices. Setting profiles to sticky is already implemented in >> pavucontrol and will be provided with the next pavucontrol release. The various module-*-restore modules should also migrate wayward streams back to the originally selected sound card once it reappears, though it's possible that applications might be trying to outsmart pulse and do their own output selection / migration. > I see some scope with pulse.event_listen() but looking at the scant docs I > see also event_callback_set() which seems more appropriate (setting a > callback and parking) but I find the docs are very poor here as to what > either of these actually do, and I'm a bit concerned running a script like > yours with a "while True" loop as a daemon monitoring changes. > > Thoughts? Perhaps theres another approach to this altogether I'm no aware > of? I agree the docs are a little sparse for pulsectl. I did have to do some source reading to figure it out. Maybe I should have put some more comments in that script. The event_mask_set and event_callback_set lines outside the while loop establish which types of events will be received and establish the callback function as the handler of those events. Once inside the loop, the event_listen() function when called with no arguments will block indefinitely until an event is received. It's not busy-waiting; the script will sleep and use no CPU until a matching event happens. At that point, the script is woken up and the callback is fired. Since this callback is called from within the pulse mainloop, we can't call other pulse functions (for reasons related to the way the pulseaudio C APIs are structured). So we use the callback to store the sink index that triggered it and then break out of event_listen function. Once that happens, our while loop goes on to process the event. We can now call other pulseaudio functions (like sink_list() to get the state of the sink that triggered our callback). Once we're done processing, the while loop re-fires the event_listen(), and thus goes back to sleep until the next event. There may be a small race condition in this code, as I'm not certain if events that happen while we're not in event_listen() get queued up. In this example it's probably fine, as the default auto-suspend takes 5 seconds to transition state. If you were really concerned about not losing any events, you could rearchitect the script to spawn a separate processing thread and just flag processing events from the main thread using a thread-safe shared object (e.g. queue.Queue) without exiting the event_listen function. --Sean