Skip to content

Instantly share code, notes, and snippets.

@jared-hughes
Created September 5, 2019 06:31
Show Gist options
  • Save jared-hughes/9bc97838d1b98530274daa3ac0def4db to your computer and use it in GitHub Desktop.
Save jared-hughes/9bc97838d1b98530274daa3ac0def4db to your computer and use it in GitHub Desktop.
Search through windows in GNOME.

I got tired of going through different windows to find the one I wanted, so I made this.

Let's say you have 10 different terminal windows open (please use tabs instead, but this is just an example). You remember that in the window you want to go to, you were in the cool-project directory. So you run this program (normally connected to a keybinding) and type in "cool-project". It then shows you the top few windows which match that name. If in this case your memory failed you and the window actually had the name "great-project" in it, there's no problem! This uses fuzzy matching, so it would probably show your desired window near the top. You can navigate with up/down arrow keys to reach your desired window, then press enter to raise and focus it. Success!

Installation

  1. pip install fuzzywuzzy
  2. Copy the below file to somewhere; I recommend $HOME/.local/bin
  3. Make the file executable. sudo chmod +x $HOME/.local/bin/search_windows.py
  4. Create a keybinding. I use Super+X because they're close together. Paste in the following as the command: gnome-terminal -- "$HOME/.local/bin/search_windows.py" (in this case, replace $HOME with your actual home directory /home/___ because the keybindings don't expand shell variables)
#!/usr/bin/env python3
import sys, tty, termios
import subprocess
from fuzzywuzzy import fuzz
import re
import curses
from curses import wrapper
def getch():
fd = sys.stdin.fileno()
old_settings = termios.tcgetattr(fd)
try:
tty.setraw(sys.stdin.fileno())
ch = sys.stdin.read(1)
finally:
termios.tcsetattr(fd, termios.TCSADRAIN, old_settings)
return ch
def get_windows():
current = subprocess.Popen("xdotool getwindowfocus getwindowname".split(), stdout=subprocess.PIPE)
current = current.stdout.readlines()[0].decode("utf-8")[:-1]
p = subprocess.Popen("wmctrl -l".split(), stdout=subprocess.PIPE)
windows = {}
for line in p.stdout.readlines():
line = line.decode("utf-8")
match = re.match(r"(\S*)\s*\S*\s*\S*\s*(.*)", line)
id, name = match.groups()
# exclude Desktop which would just defocus all windows
# exclude the currently active window
if name not in ["Desktop", current]:
windows[name] = id
return windows
def similarity(name, query):
return fuzz.token_set_ratio(name, query)
def closest_sorted(names, query):
return sorted(names, reverse=True, key=lambda k: similarity(k, query))
def closest_counts(names, query):
names = closest_sorted(names, query)
return list(map(lambda k: (k, similarity(k, query)), names))
class Window:
def __init__(self, window):
self.window = window
self.line = 2
def add_line(self, str, *args, **kwargs):
self.window.addstr(self.line, 0, str, *args, **kwargs)
self.line += 1
def clear(self):
self.window.clear()
self.line = 2
def __getattr__(self, attr):
return getattr(self.window, attr)
windows_map = get_windows()
windows = get_windows().keys()
def open_window(name):
id = windows_map[name]
subprocess.Popen(["wmctrl", "-a", id, "-i"])
# globals, yay
shown_entries = []
query = ""
selected_entry = None
entry_count = 1
def main(stdscr):
scr = Window(stdscr)
def update_query(_query):
global shown_entries, query, selected_entry
query = _query
shown_entries = closest_sorted(windows, query)[:entry_count]
selected_entry = shown_entries[0]
def update_entry(delta=0, set=None):
global shown_entries, selected_entry
for i, entry in enumerate(shown_entries):
if entry == selected_entry:
pos = i + delta
if set is not None:
pos = set
if pos < 0:
pos = 0
if pos >= entry_count:
pos = entry_count - 1
selected_entry = shown_entries[pos]
break
while True:
height, width = scr.getmaxyx()
entry_count = min(height - 2, len(windows))
update_entry(0)
key = scr.getkey()
if key == chr(27):
break
elif key in ["KEY_RIGHT", "KEY_DOWN"]:
update_entry(1)
elif key in ["KEY_LEFT", "KEY_UP"]:
update_entry(-1)
elif key in ["KEY_PPAGE", "KEY_HOME"]:
update_entry(set=0)
elif key in ["KEY_NPAGE", "KEY_END"]:
update_entry(set=entry_count-1)
elif key in ["KEY_BACKSPACE", "KEY_DC"]:
update_query(query[:-1])
elif key in ["KEY_RESIZE", "KEY_IC"]:
# ignore
pass
elif key == "\n":
open_window(selected_entry)
else:
update_query(query + key)
scr.clear()
for name in shown_entries:
try:
if name == selected_entry:
scr.add_line(name, curses.A_STANDOUT)
else:
scr.add_line(name)
except:
pass
scr.line = 0
scr.add_line(query, curses.A_BOLD)
scr.refresh()
scr.refresh()
wrapper(main)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment