3

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 ) :-)

  • What browser are you using? You can try CompizConfing Settings Manager to see if you can change this, but it is probably hard-coded in (e.g. you have to recompile it to change the preview size....). But you may be able to get similar/better behavior in other desktop environments such as Xfce, GNOME 3 etc. – Wilf Jul 13 '15 at 20:29
  • Across all workspaces? all applications? sorted per application or in another specific way? – Jacob Vlijm Jul 13 '15 at 20:41
  • @JacobVlijm configurable, similar to Unity behaviour: current workspace first. Sorted alphabetically: similar window titles probably belong together. – Jean Jordaan Jul 13 '15 at 21:52
  • Posted my answer. curious to what you think :) – Jacob Vlijm Jul 15 '15 at 18:15
  • This one also (switches windows and browser tabs): https://github.com/levioctl/textual-switcher/ – EliranLoi Apr 17 '18 at 0:19
4

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.

Fuzzy?

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

Furthermore:

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

or:

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
  • Impressive answer :) – A.B. Jul 15 '15 at 19:53
  • This is awesome :-) Exemplary reminder that the tools are in our hands. I'll try out the script, and I'm upvoting the answer. I won't accept it just yet, in case any other implementations emerge. – Jean Jordaan Jul 16 '15 at 16:09
  • Hi @JeanJordaan just curious: does it work as expected? – Jacob Vlijm Jul 20 '15 at 17:51
  • @Jacob, I like it! – Jean Jordaan Jul 22 '15 at 15:03
  • @JacobVlijm, added github.com/jean/text-based-switcher , I'll push it to PyPI, with full credit to you :-) – Jean Jordaan Jul 22 '15 at 15:09
2

Tentative answers:

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.