This is the fifth and final post in a series on achieving an orderly desktop environment in GNOME 3, using no add-ons, only old school hacks. See also the first, second, third, and fourth post in the series.
So far we’ve covered static workspaces, windows spawning on designated workspaces and starting and moving between the windows on your workspaces. As I said in the introduction I think there are certain applications that do not require your undivided attention. These applications we simply want to appear briefly on top of other windows – what is the name of that song playing, is so-and-so online yet, etc. – and then dispense with. We wish to assign a single keyboard shortcut for toggling this window on and off so as to make this procedure as quick and easy as possible.
I am going to use Empathy – the default instant message front-end for GNOME – for this illustration. It should be applicable with adjustments for a wide variety of windows. Like in the previous post we’ll write some shell scripting that we will attach to an xbindkeys keyboard shortcut.
Since we’re using one shortcut to toggle we’ll need to differentiate between a number of cases. The way we’re going to build it, we’ll only need to distringuish between two states:
- The user is currently looking at the window. Hitting the shortcut in this case we’ll interpret to mean that the user wants the window gone. Kill it, close it, away with it.
- Any other state. This can mean that the application isn’t running, doesn’t have an open window or that it is running and has a window but the window is on another workspace. Hitting the shortcut in this case we’ll interpret to mean that the user wants to see the window (and if necessary for us to start the application first).
Why not distinguish between these three last cases? Because we don’t have to; we can just be lazy and write a line that will deliver the desired results regardless of whether it’s one or the other. “Always be lazy if you can” is sort of the Ockham’s razor of (shell) programming.
How do we determine if the user is looking at the window? Simple. We ask the question: Is there a window with that name on the current workspace? Which breaks down into three questions: 1) What is the current workspace? 2) What is the workspace of the window (if any)? and 3) Are those the same? Translated into code it becomes
current_ws=$(xdotool get_desktop) empathy_ws=$(xdotool search --name 'Contact List' get_desktop_for_window) [ $current_ws = $empathy_ws ]
The Empathy contact list window, unsurprisingly, has the title ‘Contact List’. xdotool is yet another of those ancient but still going strong CLI window manager tools. It can probably do everything that wmctrl can do but where it delivers something special is in output. xdotool get_desktop just outputs the number of the current desktop but whenever you need to get information about specific windows you always start with a search command, then add additional commands at the end that are then applied to the search results. The third line is a test whether the two resulting numbers are the same. If there is no such window, empathy_ws gets the value -1 so the test will always fail as you can’t be on workspace -1. Bash devotees should note the syntax in the test (the bit in the square brackets): one equal sign, not two. Dash, not Bash, is the default shell for Ubuntu. xbindkeys will use the default and in Dash an equality test is run using one equal sign.
In order to implement the distinction above we need to use this test in an if-loop. If the test is succesful ($current_ws IS equal to $empathy_ws) then we’re in the first case scenario and need to close the empathy window. If not then we’re in the second case scenario and we need to get an empathy window to the user ASAP.
current_ws=$(xdotool get_desktop) empathy_ws=$(xdotool search --name 'Contact List' get_desktop_for_window) if [ $current_ws = $empathy_ws ]; then wmctrl -cF 'Contact List' else .... fi
We close the window nicely and politely using wmctrl -c. At least the command is supposedly exactly the same as clicking close but Ubuntu did one time report the closing as ‘unexpected’ and fired a bug reporter at me. We repeat the use of the -F flag from the last post in order to require the window title to match ‘Contact List’ exactly.
Else ….? Else what? As I said we intend to cover three different cases without differentiating. As Empathy might not be running at all, the first thing we do is to launch it:
else empathy .... fi
You could throw in a ‘&’ at the end in order to run the command in the background so we can continue in our script but with Empathy there’s no need. It will do that anyway.
What if the program is already running? Won’t this launch another instance. No, empathy is bright enough to recognise that it’s already running. But it will move an existing window to our current desktop. Or create a window on our current desktop if the application is running but there are no windows. Brilliant, that is exactly what we want. Mind you not all programs are likely to be as intelligent. Testing is advised.
else empathy until wmctrl -rF 'Contact List' -b add,above; do :; done .... fi
Now when the window is either moved or created for us, we want it to appear on top of other windows in the current workspace. We can simply raise it to the top but for this particular window I think it preferable to give it the permanent quality of always floating on top of other windows regardless of whether it has focus or not. That way I can continue browsing in my browser all the while keeping an eye on when person X comes online. Again, clearly a matter of taste.
Notice that the wmctrl command is wrapped in an until loop. Until keeps trying the command (‘wmctrl …’) until it succeeds. Each time the command fails it executes the other command in between ‘do’ and ‘done’ (which in this case is ‘:’ aka ‘do nothing’). Why do we need to keep trying to add the above quality to our ‘Contact List’? Because one of the cases we’re covering is the one in which the application isn’t running. In that case it takes some time before there is a window to apply properties to. We could insert a ‘sleep’ command in order to wait but ‘until’ is neater: We don’t have to guess at how long it takes before we have an empathy window.
Now the problem with launching applications using xbindkeys is that they don’t always get focus. Oftentimes windows appear underneath other windows and all we get is a notification that window so-and-so is ready. We have assigned the ‘above’ quality to the window so it should be visible but that is not the same thing as having focus, i.e. being the application that receives keyboard input. If I’m starting up the window in order to get a chat going immediately it is preferable that the Contact List should have focus. Then I can use the arrow keys right away to navigate my contacts and hit Enter when I find the right one to start messaging. That’s why we add a final wmctrl line to the mix: It raises and gives focus to the named window:
else empathy until wmctrl -rF 'Contact List' -b add,above; do :; done wmctrl -aF 'Contact List' fi
You should always end on this as further commands will likely steal the focus away again.
All together it becomes
current_ws=$(xdotool get_desktop) empathy_ws=$(xdotool search --name 'Contact List' get_desktop_for_window) if [ $current_ws = $empathy_ws ]; then wmctrl -cF 'Contact List' else empathy until wmctrl -rF 'Contact List' -b add,above; do :; done wmctrl -aF 'Contact List' fi
Or in a single xbindkeys-ready line:
"current_ws=$(xdotool get_desktop); empathy_ws=$(xdotool search --name 'Contact List' get_desktop_for_window); if [ $current_ws = $empathy_ws ]; then wmctrl -c 'Contact List'; else empathy; until wmctrl -r 'Contact List' -b add,above; do :; done; wmctrl -a 'Contact List'; fi"
Note that this assumes that closing the empathy window does not actually kill the program only the immediate window. This is a correct assumption in this case – Empathy does keep running in the background. With something like the Transmission GTK application this may or may not be true depending on how you’re running it.
I believe this should be a fairly generally applicable template for toggling windows. You can customize the exact properties you want for your window (dont’ like the floating ‘above’ property? Just insert the final wmctrl into the until loop instead) or set the script to find and kill the owner process instead of just closing a window. The basics framework should be useable still. Wmctrl/xdotool can also set the geometry (the window size and placement on the workspace) but that might be preferable to do using devil’s pie (see previous post for more on that).
And that’s the end of this series on hacking some sense into GNOME shell (or your window manager of choice) in a way that is not dependent on extensions or other fluff that is likely to fail in a year’s time. That stuff is IKEA, this is handcrafted old-school carpentry. I hope it’s been useful to you and that everything now truly is in it’s right place.
And yeah, I know it’s an odd choice of song as everything but the title radiates a distinct lack of the sort of anal retentive order fascism that is the hallmark of these posts but it’s a good song. So enjoy.