-
Notifications
You must be signed in to change notification settings - Fork 22
/
switch-top-level.py
executable file
·99 lines (80 loc) · 3.05 KB
/
switch-top-level.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
#!/usr/bin/env python3
import i3ipc
#
# This script requires i3ipc-python package (install it from a system package manager
# or pip).
#
# The scripts allows you to define two new bindings:
# bindsym $mod+bracketright nop top_next
# bindsym $mod+bracketleft nop top_prev
#
# The purpose of it is to switch between top-level containers (windows) in a workspace.
# One possible usecase is having a workspace with two (or more on large displays)
# columns of tabs: one on left and one on right. In such setup, "move left" and
# "move right" will only switch tabs inside the column.
#
# You can add a systemd user service to run this script on startup:
#
# ~> cat .config/systemd/user/switch-top-level.service
# [Install]
# WantedBy=graphical-session.target
# [Service]
# ExecStart=path/to/switch-top-level.py
# Restart=on-failure
# RestartSec=1
# [Unit]
# Requires=graphical-session.target
class TopLevelSwitcher:
def __init__(self):
self.top_to_selected = {} # top i3ipc.Con -> selected container id
self.con_to_top = {} # container id -> top i3ipc.Con
self.prev = None # previously focused container id
self.i3 = i3ipc.Connection()
self.i3.on("window::focus", self.on_window_focus)
self.i3.on(i3ipc.Event.BINDING, self.on_binding)
self.update_top_level()
self.i3.main()
def top_level(self, node):
if len(node.nodes) == 1:
return self.top_level(node.nodes[0])
return node.nodes
def update_top_level(self):
tree = self.i3.get_tree()
for ws in tree.workspaces():
for con in self.top_level(ws):
self.update_top_level_rec(con, con.id)
def update_top_level_rec(self, con: i3ipc.Con, top: i3ipc.Con):
self.con_to_top[con.id] = top
for child in con.nodes:
self.update_top_level_rec(child, top)
if len(con.nodes) == 0 and top not in self.top_to_selected:
self.top_to_selected[top] = con.id
def save_prev(self):
if not self.prev:
return
prev_top = self.con_to_top.get(self.prev)
if not prev_top:
return
self.top_to_selected[prev_top] = self.prev
def on_window_focus(self, _i3, event):
self.update_top_level()
self.save_prev()
self.prev = event.container.id
def on_top(self, _i3, _event, diff: int):
root = self.i3.get_tree()
if not self.prev:
return
top = self.con_to_top[self.prev]
ws = [top.id for top in self.top_level(root.find_focused().workspace())]
top_idx = ws.index(top)
top_idx = (top_idx + diff + len(ws)) % len(ws)
next_top = ws[top_idx]
next_window = self.top_to_selected.get(next_top)
self.i3.command("[con_id=%s] focus" % next_window)
def on_binding(self, i3, event):
if event.binding.command.startswith("nop top_next"):
self.on_top(i3, event, 1)
elif event.binding.command.startswith("nop top_prev"):
self.on_top(i3, event, -1)
if __name__ == "__main__":
TopLevelSwitcher()