Ubuntu: Is there a text-based window switcher for Unity?


The visual switcher is very poor at providing context. E.g. the browser window thumbnails are too small to tell apart, and often most of them are blank (see screenshot). Unity switcher with useless thumbnails

Is there any switcher that shows a list of window titles? Preferably with smart fuzzy autocompletion (like https://github.com/ctrlpvim/ctrlp.vim or https://github.com/junegunn/fzf ) :-)


The fun part of "home made products" is always that you can make it pretty much exactly as you like. The possible downside is that you easily get carried away a bit by a project if it is nice to work on...

That might be the case with the script below :). While I would have preferred to add a detailed explanation on how it works "under the hood", this is a "ready to use" solution. Although I added some commentary lines, it is hard to give a simple inside explanation on the code. It seems however to be close to what you are looking for.

What it is

The script is a purely text-based solution to list all opened, "normal" application windows (and raise a chosen one), but it has a number of options:

  1. list the windows, sorted by window name:

    Run with the command:

    python3 <script> -win  

    enter image description here

    type the first character(s) of the sought window and press return to bring the window to front.

  2. list the windows, sorted by application:

    Run with the command:

    python3 <script> -app  

    enter image description here

  3. list the windows, sorted by workspace:

    Run with the command:

    python3 <script> -ws  

    enter image description here

As you can see, the displayed columns are: Window name, Application, Workspace. The pre- set sorted column is always the first one.


To select an item from the list, simply type the first character. If there are more items that meet the typed character(s) the arrow keys will only browse through the items that meet the typed characters:

enter image description here


workspace indication
The current workspace is marked with a *: e.g. if you see 2*, it means the window is on workspace 2 and workspace 2 is the current workspace. This works no matter how many workspaces you have.

The window size
of the slection window is set automatically to the window's (longest) name and the number of windows to be displayed, e.g.:

enter image description here


enter image description here

How to use

The setup is pretty straightforward:

  1. The script (definitely) needs wmctrl:

    sudo apt-get install wmctrl  
  2. Then copy the script below into an empty file, save it as list_windows.py

  3. Then Test- run it with the commands:

    python3 /path/to/list_windows.py -app  python3 /path/to/list_windows.py -win  python3 /path/to/list_windows.py -ws  
  4. If all works fine, add one or more of the preferred commands to one or more shortcut keys: choose: System Settings > "Keyboard" > "Shortcuts" > "Custom Shortcuts". Click the "+" and add the command

The script

(still "unpolished" code)

#!/usr/bin/env python3  import subprocess  import socket  import sys    arg = sys.argv[1]  # list (column) header titles and their (data) position in the produced window data list  cols = [["Workspace", -1], ["Application name", -2] , ["Window name", -3]]  # rearrange columns, depending on the chosen option  if arg == "-app":      cols = [cols[1], cols[2], cols[0]]  elif arg == "-ws":      cols = [cols[0], cols[2], cols[1]]  elif arg == "-win":      cols = [cols[2], cols[1], cols[0]]  # extract headers, list positions, to be used in the zenity list  col1 = cols[0][0]; i1 = cols[0][1]  col2 = cols[1][0]; i2 = cols[1][1]  col3 = cols[2][0]; i3 = cols[2][1]  # just a helper function  get = lambda cmd: subprocess.check_output([      "/bin/bash", "-c", cmd      ]).decode("utf-8")  # analyse viewport data, to be able to calculate relative/absolute position of windows  # and current viewport  def get_spandata():      xr = get("xrandr").split(); pos = xr.index("current")      res = [int(xr[pos+1]), int(xr[pos+3].replace(",", "") )]      spandata = get("wmctrl -d").split()      span = [int(n) for n in spandata[3].split("x")]      cols = int(span[0]/res[0]); rows = int(span[1]/res[1])      curr_vector = [int(n) for n in spandata[5].split(",")]      curr_viewport = int((curr_vector[1]/res[1])*cols + (curr_vector[0]/res[0])+1)      return {"resolution": res, "n_columns": cols, "vector": curr_vector, "current_viewport": curr_viewport}    posdata = get_spandata()  vector = posdata["vector"]; cols = posdata["n_columns"]  res = posdata["resolution"]; currvp = posdata["current_viewport"]  # function to distinguish "normal" windows from other types (like the desktop etc)  def check_window(w_id):      w_type = get("xprop -id "+w_id)      if " _NET_WM_WINDOW_TYPE_NORMAL" in w_type:          return True      else:          return False  # split windowdata by machine name  mach_name = socket.gethostname()  wlist = [[l.strip() for l in w.split(mach_name)] for w in get("wmctrl -lpG").splitlines()]  # split first section of window data  for i, w in enumerate(wlist):      wlist[i][0] = wlist[i][0].split()  # filter only "real" windows  real_wlist = [w for w in wlist if check_window(w[0][0]) == True]  # adding the viewport to the window's data  for w in real_wlist:      w.append(get("ps -p "+w[0][2]+" -o comm=").strip())      loc_rel = [int(n) for n in w[0][3:5]]      loc_abs = [loc_rel[0]+vector[0], loc_rel[1]+vector[1]]      abs_viewport = int((loc_abs[1]/res[1])*cols + (loc_abs[0]/res[0])+1)      abs_viewport = str(abs_viewport)+"*" if abs_viewport == currvp else str(abs_viewport)      w.append(abs_viewport)  # set sorting rules  if arg == "-app":      real_wlist.sort(key=lambda x: x[-2])  elif arg == "-ws":      real_wlist.sort(key=lambda x: x[-1])  elif arg == "-win":      real_wlist.sort(key=lambda x: x[-3])  # calculate width and height of the zenity window:  # height = 140px + 23px per line  h = str(140+(len(real_wlist)*23))  # width = 250px + 8px per character (of the longest window title)  w = str(250+(max([len(w[-3]) for w in real_wlist])*8))  # define the zenity window's content  cmd = "zenity --list --hide-column=4 --print-column=4 --title='Window list' "\        "--width="+w+" --height="+h+" --column='"+col1+"' --column='"+col2+"' --column='"+col3+\        "' --column='w_id' "+(" ").join([(" ").join([            '"'+w[i1]+'"','"'+w[i2]+'"','"'+w[i3]+'"','"'+w[0][0]+'"'            ]) for w in real_wlist])  # finally, call the window list  try:      w_id = subprocess.check_output(["/bin/bash", "-c", cmd]).decode("utf-8").split("|")[0]      subprocess.Popen(["wmctrl", "-ia", w_id])  except subprocess.CalledProcessError:      pass  


Tentative answers:

Note:If u also have question or solution just comment us below or mail us on toontricks1994@gmail.com
Next Post »