feature: Allow switching between containers

This commit is contained in:
Aleksey Samoilov 2023-08-21 10:23:35 +04:00
parent 312a2f093c
commit 7b8c3c6d98
3 changed files with 112 additions and 0 deletions

View file

@ -79,6 +79,10 @@ $bindsym Mod1+Tab exec env RUST_BACKTRACE=1 swayr switch-to-urgent-or-lru-window
## Navigation // Switch to the last recently used workspace ##
$bindsym $mod+Tab workspace back_and_forth
## Navigation // Switching between containers in the workspace // $mod + [] ##
$bindsym $mod+bracketright nop top_next
$bindsym $mod+bracketleft nop top_prev
#
# Workspaces:
#

View file

@ -0,0 +1,10 @@
[Unit]
Requires=graphical-session.target
[Service]
ExecStart=/usr/share/sway/scripts/switch-top-level.py
Restart=on-failure
RestartSec=1
[Install]
WantedBy=graphical-session.target

View file

@ -0,0 +1,98 @@
#!/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()