Last active
September 21, 2020 11:29
-
-
Save elundmark/f1f5cdee35a6305af23b62847ae79386 to your computer and use it in GitHub Desktop.
i3wm: Switch between same program windows
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/env python3 | |
""" | |
i3wm Python Script: Jump fwd/backward N positions in app windows sharing the | |
same 'class' | |
Download: https://gist.github.com/elundmark/f1f5cdee35a6305af23b62847ae79386 | |
Updated: 2020-09-21 13:28:54 | |
""" | |
import sys | |
import json | |
import subprocess | |
# often used key names | |
wp = "window_properties" | |
wr = "window_role" | |
wi = "window" | |
si = "siblings" | |
foc = "focused" | |
cl = "class" | |
pos = "position" | |
def usage(): | |
"""Help text""" | |
print("Usage: %s [ -N | +N ] [ --debug ]" % sys.argv[0], file=sys.stderr) | |
sys.exit(1) | |
def main(): | |
"""Main program function""" | |
def extract_same_windows(nested_json): | |
""" | |
Flatten json object with nested "nodes" lists down to what we want | |
Thanks: https://bit.ly/3jZRSgh | |
""" | |
out = { "siblings": [], "focused": {} } | |
def flatten(x): | |
# see output of 'i3-msg -t get_tree' to understand this better | |
if isinstance(x, dict): | |
if "nodes" in x and isinstance(x["nodes"], list): | |
# traverse all "nodes" lists | |
for v in x["nodes"]: | |
# user windows has a wi integer - but we still don't | |
# know what the class is we're looking for, so for now | |
# save them all and filter them later | |
if wi in v and isinstance(v[wi], int): | |
out[si].append(v) | |
# Find the focused window | |
if foc in v and v[foc]: | |
out[foc][wi] = int(v[wi]) | |
# save the focused class name | |
if wp in v and cl in v[wp]: | |
out[foc][cl] = v[wp][cl] | |
for a in x: | |
flatten(x[a]) | |
elif isinstance(x, list): | |
i = 0 | |
for a in x: | |
flatten(a) | |
i += 1 | |
# start the flattening process | |
flatten(nested_json) | |
# filter out dict's that share the focus'd class | |
out[si] = [ | |
v for v in out[si] if wp in v and cl in v[wp] and v[wp][cl] \ | |
== out[foc][cl] | |
] | |
# my own preference: filter out any scratchpads | |
out[si] = [ v for v in out[si] if not ( wp in v and wr in v[wp] \ | |
and isinstance(v[wp][wr], str) and "scratchpad" in v[wp][wr] ) ] | |
# save the position so we know "where" we are later | |
for i in range(len(out[si])): | |
if out[si][i][wi] == out[foc][wi]: | |
out[foc][pos] = i | |
# finished! | |
return out | |
# main vars | |
debugging = False | |
args = sys.argv[1:] | |
if "--debug" in args: | |
debugging = True | |
args.pop(args.index("--debug")) | |
if len(args) == 1: | |
# you can set this number to any integer, to jump 2 windows back: `-2' | |
# throws an error if not an integer looking string | |
direction = int(args[0]) | |
else: | |
usage() | |
# execute i3-msg and get its stdout | |
i3_tree_stdout = subprocess.check_output(["i3-msg", "-t", "get_tree"]) | |
# convert to object | |
i3_tree = json.loads(i3_tree_stdout.decode("utf8")) | |
if debugging: | |
print("i3_tree:\n\n%s" % json.dumps(i3_tree)) | |
# extract all same windows plus get the focused one | |
found = extract_same_windows(i3_tree) | |
app_win_len = len(found[si]) | |
# if we didn't find any position we should exit | |
if pos not in found[foc]: | |
if debugging: | |
print("No %s found" % pos) | |
sys.exit(1) | |
# calculate target window considering direction could be Any integer | |
# and we need to round robin | |
target_window = found[si][ ( found[foc][pos] + direction ) % app_win_len ] | |
if debugging: | |
print("\nfound:\n\n%s" % json.dumps(found)) | |
print("\ntarget_window:\n\n%s" % json.dumps(target_window)) | |
print(""" | |
app_win_len: %s, found['%s']['%s']: %s, target sibling index: %s | |
""" % ( | |
app_win_len, | |
foc, | |
pos, | |
found[foc][pos], | |
(found[foc][pos] + direction) % app_win_len | |
) | |
) | |
# focus it using i3-msg - even if it's the same window targeted | |
subprocess.call(["i3-msg", '[id="%s"] focus' % target_window[wi]]) | |
if __name__ == "__main__": | |
main() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment