The problem#
Which window I have focused is a signal to the computer for the state I want it to be in. For instance, I normally leave my speaker muted so, for example, I don't accidentally play sound from a website with unexpected videos. But this means that when I do want sound, I need to manually unmute the sound, even though I've already told the computer that I want to watch Netflix, which always involves turning on the sound.
Of course, for the particular problem of unmuting the sound, adding a keyboard shortcut and rereading xkcd 1205: Is It Worth the Time? probably would have been a more appropriate solution. But I wanted a general solution to the problem.
The solution#
Download x11_watch_active_window.py
.
Then the following script will unmute the speakers if Netflix is
focused:
#!/bin/sh
x11_watch_active_window.py | while read -r FocusApp
do
if [ "Netflix - Google Chrome" = "$FocusApp" ]
then
echo Netflix is focused, unmuting.
pactl set-sink-mute 0 0
fi
done
The details#
Current window using xdotool
#
I was originally using this script which uses
xdotool
to get the current window title with the
command xdotool getwindowfocus getwindowname
. Since that just gets
the current title to turn that into a way to watch for the window focus
changing, we have to run that in a loop, calling sleep
to wait 5
seconds (or some other interval) between each call.
xdotool
watch for focus
event#
xdotool
does have the ability to watch for events, including the
focus
event:
xdotool search . behave %@ focus getwindowname | uniq
but with some experimentation it's clear that this attaches and event
listener to all windows that exist when the command is run, and not on
any new windows. The pipe to uniq
is because each focus change
appears to correspond to multiple events, presumably separate unfocus
and focus events but there appear to be two focus events for some
reason. Since unfocus events are included, we can actually do slightly
better:
xdotool search . behave %@ focus\
exec xdotool getwindowfocus getwindowname | uniq
But, of course, that won't get notified of focus changes between two different windows both opened after that command was run.
_NET_ACTIVE_WINDOW
#
I found this StackOverflow answer
which uses the _NET_ACTIVE_WINDOW
property
which modern window managers set on the root window with the ID of the
currently focused window. Since it's a property, we can subscribe to a
property change event, and that's just one event instead of watching the
focus events for every window. That link includes Javascript code using
node-x11 to watch that property; the same can be done in the
shell using xprop
's -spy
option:
xprop -spy -root -notype _NET_ACTIVE_WINDOW\
| stdbuf -oL sed 's/^.*# \([^,]*\),.*$/\1/'\
| stdbuf -oL grep -vF 0x0\
| xargs -n1 xdotool getwindowname
Pipe buffering#
I was having issues where it would work without the pipe but stop
outputting if I made the pipeline too long. It turned out the
problem was the pipe was buffering the results waiting for more
text before passing through the output, which doesn't make sense
in this situation. There are multiple workarounds;
stdbuf
looked like the most portable choice.
x11_watch_active_window.py
#
Linked from the Javascript version I mentioned
above was a more complete Python solution:
x11_watch_active_window.py
. In addition to
watching for active window changes, it also watches for title changes on
the active window, which is useful given what I was actually watching
for was a browser window navigated to a particular page. It also appears
to be written to be robust to various old and broken X11 setups.
My own version just changes the output
to be just the title (as I'm not using the window ID) and sets the
flush option on the print
call to avoid having the
same buffering issue mentioned above, so piping the output to the while
read
loop works properly.
Comments
Have something to add? Post a comment by sending an email to comments@aweirdimagination.net. You may use Markdown for formatting.
1 comment
Justin K
Posted Wed 21 August 2024 18:45
many thanks for this summary from a fellow deb-head. worked a treat!