5

I have 3 monitors on my development machine, which runs Ubuntu + Unity. I usually dedicate one monitor to web browsing and IM, and the other two to terminals where I run vim or enter commands. I'm looking for a single-key-sequence to switch between them at-will.

I would like to know how I can switch focus to (top-) windows on each monitor. The behavior I'm looking or is sort-of like ALT+TAB, except instead of rotating between applications (which only makes the most-recently-used instance of application available in the rotation), I can rotate focus between monitors.

As a compromise, I could stand to use the ALT+TAB mechanism if I could have each and every window in the list. I still imagine this getting annoying, though.

5
  • All can be done, but for my understanding: you would like to switch windows of all applications through all screens? Oct 29, 2015 at 16:13
  • My ideal scenario would be that windows stay on the monitors where they are currently running, and that I could very quickly (from keyboard, using minimal keystrokes) change the focus from one monitor to the top-most widow on the next monitor over (in either direction). If there is no "next" monitor in the current direction, loop back to the monitor on the extreme other side. Think of cycling through tabs in gterm or google chrome. Oct 29, 2015 at 16:42
  • 1
    The thing is that wmctrl, xdotool nor xprop can tell what is the window order (z- wise). There is however an escape: if you let a background script keep track of the history of the active window, it is very well possible to tell what was the last focussed window on a specific screen. That would make a setup possible, exactly as you describe. I ampretty sure the background script could be of an unnoticable load on your resources, but would that be acceptable? Nice question! Oct 29, 2015 at 17:53
  • Yes, if it's nothing more than a blip every couple hundred milliseconds and doesn't do much, I'd be content to run a script. Oct 29, 2015 at 20:17
  • Hi San Jacinto, did you notice the answer? Please let me know. Nov 1, 2015 at 7:38

1 Answer 1

2

Script(s) to rotate focus through monitors

In the setup below, two scripts are involved: one background script to keep track of the history of focussed windows (see the explanation at the bottom to read why that is needed), and one "action" script to place under a shortcut key, to set focus on the next screen. If the next screen currently has no window to set focus on, a message is displayed:

enter image description here

The scripts

Script 1; the background script, save it (exactly) as focus_track.py

#!/usr/bin/env python3
import subprocess
import time
import os

rootdata = os.environ["HOME"]+"/.focus_history"

def get_screendata():
    return sorted([int(s.split("+")[-2]) for s in subprocess.check_output(["xrandr"]).decode("utf-8").split() if s.count("+") == 2])

def current_windows():
    try:
        return subprocess.check_output(["wmctrl", "-lG"]).decode("utf-8")
    except subprocess.CalledProcessError:
        pass

def convert_format(w_id):
    return w_id[:2]+(10-len(w_id))*"0"+w_id[2:]

def read_data():
    return open(rootdata).read().splitlines()

def get_top(wlist):
    top = convert_format([l.split("#")[-1].strip() for l in \
           subprocess.check_output(["xprop", "-root"]).decode("utf-8").splitlines() \
           if "_NET_ACTIVE_WINDOW(WINDOW)" in l][0])       
    return [l for l in wlist if top in l][0]

if __name__ == "__main__":
    open(rootdata, "wt").write("This is an empty line")
    while True:
        time.sleep(1)
        wdata = current_windows()
        if wdata != None:
            wlist = wdata.splitlines()
            # get frontmost window (as in wmctrl -lG)
            top = get_top(wlist)
            oldlist = read_data()
            if not top == oldlist[0]:
                # clean up closed windows
                [oldlist.remove(l) for l in oldlist if not l.split()[0] in wdata]
                # remove possible other mentions of the active window
                [oldlist.remove(l) for l in oldlist if l.startswith(top.split()[0])]
                open(rootdata, "wt").write(("\n").join([top]+oldlist))

Script 2; the action script, save it as next_focus.py In one and the same directory as script 1.

#!/usr/bin/env python3
import subprocess
import focus_track

# read existing windows and their order (x-wise) from file
windows = [w.split() for w in focus_track.read_data()]
w_data = [[w[0], int(w[2])] for w in windows]
# get position of currently focussed window
currfocus = focus_track.get_top(focus_track.current_windows().splitlines()).split()[2]
# get screendata
screens = focus_track.get_screendata()

def screen_pos(x):
    return [(int(x) > n) for n in screens].count(True)

scr_position = screen_pos(currfocus)
next_screen = 1 if scr_position == len(screens) else scr_position+1
try:
    next_focus = [w for w in w_data if screen_pos(w[1]) == next_screen][0]
    subprocess.Popen(["wmctrl", "-ia", next_focus[0]])
except IndexError:
    subprocess.Popen(["notify-send", "No window to focus on next screen"])

How to use

  1. The scripts needs wmctrl to be installed

    sudo apt-get install wmctrl
    
  2. Copy script 1 into an empty file, save it as (exactly) focus_track.py. The name is important, since both scripts share functions; script 1 is imported into script 2.
  3. Copy script 2 into an empty file, save it as (exactly) next_focus.py in one and the same directory as script 1.
  4. Test- run the setup: N.B. Start the background script before opening (and thus focussing) windows. Windows, opened before the background script starts are not "recorded" untill they are focussed

    • Start the background script (from e.g. a terminal) with the command:

      python3 /path/to/focus_track.py
      
    • On your different screens, open windows.

    • Run script 2 with the command:

      python3 /path/to/next_focus.py
      

    The focus should switch to the next screen. If the current screen is the last in the row, the focus switches to the first screen.

  5. If all works fine, add script 1 to Startup Applications: Dash > Startup Applications > Add the command:

    python3 /path/to/focus_track.py
    

    and add script 2 to a keyboard shortcut: choose: System Settings > "Keyboard" > "Shortcuts" > "Custom Shortcuts". Click the "+" and add the command:

    python3 /path/to/next_focus.py
    

    to a shortcut key of your liking.

Notes

  • Since the background script keeps track of focus history, for immediate proper functioning, it should preferably start before you start working on your computer, e.g. on log in (using Startup Applications).
  • The script assumes the screens are set up from left to right, non- overlapping. Their vertical alignment is irrelevant however.
  • To "save fuel", and make the script's load negligibly small, the script updates the focus history (only) once per second. A window is therefore defined as focussed if it has focus for at least between 0.5 and 1 second.
  • Although I tested it on a two- screen setup, it should (and I am pretty sure it does) work properly on a setup with 3(+) screens.




Explanation:

What it takes

To switch focus from one screen to another, it is necessary to determine which is the front most window per screen. The major issue is however that windows, spread over multiple screens are actually all in one and the same stack, and thus ordered in one and the same succession (z-wise). The tools we have (wmctrl, xdotool, xprop etc.) in the best case can only determine the currently active window. They give us no information whatsoever about the window order on the other screens, since the windows are below the active window.

enter image description here

Therefore, at first sight, it seems pretty impossible to switch focus from one screen to another.
However:

How to get the information

With a workaround however, there is an escape: if we make a background script keep track of the currently focussed window, and maintain a history record of changes (for as long as the window exists), we actually can conclude what is the z-order of the currently opened windows. If we also keep track of their geometry and position, we have all information we need.

An example:
We have currently five windows: A, B, C, D, E. If their focus changes through D, E, A, C, B, we know the z-order of the windows is: B, C, A, E, D (front to back)

Together with their positions (x-wise) and the screen data (the x-resolution of the screens) we have all information we need. To switch focus to the next screen, we then simply have to look up the first window, located on the next screen.

10
  • What a great answer! I look forward to trying it out this week. Thanks! Nov 1, 2015 at 13:15
  • This is a neat script, gives me a place to start working from. It doesn't quite do what I want, but I think that's because wmctrl isn't listing the windows in the correct order for what is desired. Nov 2, 2015 at 12:50
  • @SanJacinto That surprises me, since the windows are not ordered here by wmctrl, but by the focus-history, as determined by the script. Can you be a bit more specific on how it fails? If I press my shortcut key, focus switches to the next (right) screen, until it reaches the last one, then turns to the first screen again, starting over. Isn't that happening? Mind that a window is considered "focuseed" if it has focus for at least a second (as mentioned in the answer). Nov 2, 2015 at 13:10
  • I had a terminal open in the leftmost monitor (laptop screen), a terminal on the middle monitor, and a terminal on the rightmost monitor. If I used my shortcut to cycle to next from leftmost, it went to the extreme right. If I went to next from the middle, it went to extreme right. If I went to next from extreme right, I get a message that "No window to focus on next screen ". Nov 2, 2015 at 18:05
  • I think you're right, maybe nothing at all to do with wmctrl, sorry. Nov 2, 2015 at 18:05

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .