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 100 101 102 103 104 105
|
#!/usr/bin/env python3
import os
import socket
import selectors
import tempfile
import threading
from argparse import ArgumentParser
import i3ipc
SOCKET_DIR = '{}/i3_focus_last.{}{}'.format(tempfile.gettempdir(), os.geteuid(),
os.getenv("DISPLAY"))
SOCKET_FILE = '{}/socket'.format(SOCKET_DIR)
MAX_WIN_HISTORY = 15
class FocusWatcher:
def __init__(self):
self.i3 = i3ipc.Connection()
self.i3.on('window::focus', self.on_window_focus)
# Make a directory with permissions that restrict access to
# the user only.
os.makedirs(SOCKET_DIR, mode=0o700, exist_ok=True)
self.listening_socket = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
if os.path.exists(SOCKET_FILE):
os.remove(SOCKET_FILE)
self.listening_socket.bind(SOCKET_FILE)
self.listening_socket.listen(1)
self.window_list = []
self.window_list_lock = threading.RLock()
def on_window_focus(self, i3conn, event):
with self.window_list_lock:
window_id = event.container.id
if window_id in self.window_list:
self.window_list.remove(window_id)
self.window_list.insert(0, window_id)
if len(self.window_list) > MAX_WIN_HISTORY:
del self.window_list[MAX_WIN_HISTORY:]
def launch_i3(self):
self.i3.main()
def launch_server(self):
selector = selectors.DefaultSelector()
def accept(sock):
conn, addr = sock.accept()
selector.register(conn, selectors.EVENT_READ, read)
def read(conn):
data = conn.recv(1024)
if data == b'switch':
with self.window_list_lock:
tree = self.i3.get_tree()
windows = set(w.id for w in tree.leaves())
for window_id in self.window_list[1:]:
if window_id not in windows:
self.window_list.remove(window_id)
else:
self.i3.command('[con_id=%s] focus' % window_id)
break
elif not data:
selector.unregister(conn)
conn.close()
selector.register(self.listening_socket, selectors.EVENT_READ, accept)
while True:
for key, event in selector.select():
callback = key.data
callback(key.fileobj)
def run(self):
t_i3 = threading.Thread(target=self.launch_i3)
t_server = threading.Thread(target=self.launch_server)
for t in (t_i3, t_server):
t.start()
if __name__ == '__main__':
parser = ArgumentParser(prog='focus-last.py',
description='''
Focus last focused window.
This script should be launch from the .xsessionrc without argument.
Then you can bind this script with the `--switch` option to one of your
i3 keybinding.
''')
parser.add_argument('--switch',
dest='switch',
action='store_true',
help='Switch to the previous window',
default=False)
args = parser.parse_args()
if not args.switch:
focus_watcher = FocusWatcher()
focus_watcher.run()
else:
client_socket = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
client_socket.connect(SOCKET_FILE)
client_socket.send(b'switch')
client_socket.close()
|