A Weird Imagination

Reacting to active window

Posted in

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:

x11_watch_active_window.py | while read -r FocusApp
    if [ "Netflix - Google Chrome" = "$FocusApp" ]
        echo Netflix is focused, unmuting.
        pactl set-sink-mute 0 0

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.


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.


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.


Have something to add? Post a comment by sending an email to comments@aweirdimagination.net. You may use Markdown for formatting.

There are no comments yet.