From ab69f9941496dbc11bcc1b26f0382f102629d3ae Mon Sep 17 00:00:00 2001 From: laur Date: Thu, 24 Feb 2022 00:00:36 +0100 Subject: [PATCH] add --focused-output & --focused-workspace options - these track window lists (and indices) per output and per workspace respectively. note this deviates from the as-is functionality, where there was only a single global window list. these new options essentially scope the alt-tabbing to specific areas. - existing options are backwards-compatible. --- examples/i3-cycle-focus.py | 120 ++++++++++++++++++++++++++----------- 1 file changed, 84 insertions(+), 36 deletions(-) diff --git a/examples/i3-cycle-focus.py b/examples/i3-cycle-focus.py index 6e80c0c..7e7dc85 100755 --- a/examples/i3-cycle-focus.py +++ b/examples/i3-cycle-focus.py @@ -24,54 +24,74 @@ def on_shutdown(i3_conn, e): class FocusWatcher: def __init__(self): self.i3 = None - self.window_list = [] + self.window_list = {} if KEYED_CONF else [] self.update_task = None - self.window_index = 1 + self.window_index = {} if KEYED_CONF else [1] async def connect(self): self.i3 = await Connection().connect() self.i3.on('window::focus', self.on_window_focus) self.i3.on('shutdown', on_shutdown) - async def update_window_list(self, window_id): + async def update_window_list(self, container): if UPDATE_DELAY != 0.0: await asyncio.sleep(UPDATE_DELAY) logging.info('updating window list') - if window_id in self.window_list: - self.window_list.remove(window_id) - self.window_list.insert(0, window_id) + if KEYED_CONF: + key = (container.ipc_data['output'] if PER_OUTPUT + else (await self.i3.get_tree()).find_focused().workspace().id) + wlist = self.window_list.get(key) + if wlist is None: + wlist = self.window_list[key] = [] - if len(self.window_list) > MAX_WIN_HISTORY: - del self.window_list[MAX_WIN_HISTORY:] + self.window_index[key] = [1] + else: + wlist = self.window_list + self.window_index[0] = 1 + + window_id = container.id + if window_id in wlist: + wlist.remove(window_id) + + wlist.insert(0, window_id) - self.window_index = 1 - logging.info('new window list: {}'.format(self.window_list)) + if len(wlist) > MAX_WIN_HISTORY: + del wlist[MAX_WIN_HISTORY:] - async def get_valid_windows(self): - tree = await self.i3.get_tree() - if args.active_workspace: - return set(w.id for w in tree.find_focused().workspace().leaves()) + logging.info('new window list: {}'.format(wlist)) + + async def get_valid_windows(self, tree, focused_ws): + if args.active_workspace or args.focused_workspace: + return set(w.id for w in focused_ws.leaves()) elif args.visible_workspaces: ws_list = [] w_set = set() outputs = await self.i3.get_outputs() - for item in outputs: - if item.active: - ws_list.append(item.current_workspace) + for output in outputs: + if output.active: + ws_list.append(output.current_workspace) for ws in tree.workspaces(): if str(ws.name) in ws_list: for w in ws.leaves(): w_set.add(w.id) return w_set + elif args.focused_output: + w_set = set() + focused_output = focused_ws.ipc_data['output'] + for ws in tree.workspaces(): + if ws.ipc_data['output'] == focused_output: + for w in ws.leaves(): + w_set.add(w.id) + return w_set else: return set(w.id for w in tree.leaves()) async def on_window_focus(self, i3conn, event): logging.info('got window focus event') - if args.ignore_float and (event.container.floating == "user_on" - or event.container.floating == "auto_on"): + if args.ignore_float and (event.container.floating == 'user_on' + or event.container.floating == 'auto_on'): logging.info('not handling this floating window') return @@ -79,7 +99,7 @@ async def on_window_focus(self, i3conn, event): self.update_task.cancel() logging.info('scheduling task to update window list') - self.update_task = asyncio.create_task(self.update_window_list(event.container.id)) + self.update_task = asyncio.create_task(self.update_window_list(event.container)) async def run(self): async def handle_switch(reader, writer): @@ -87,16 +107,29 @@ async def handle_switch(reader, writer): logging.info('received data: {}'.format(data)) if data == b'switch': logging.info('switching window') - windows = await self.get_valid_windows() + tree = await self.i3.get_tree() + focused_ws = tree.find_focused().workspace() + + wlist = self.window_list + widx = self.window_index + if KEYED_CONF: + key = focused_ws.ipc_data['output'] if PER_OUTPUT else focused_ws.id + wlist = wlist.get(key) + if wlist is None: + return + widx = widx.get(key) + + windows = await self.get_valid_windows(tree, focused_ws) logging.info('valid windows = {}'.format(windows)) - for window_id in self.window_list[self.window_index:]: + + for window_id in wlist[widx[0]:]: if window_id not in windows: - self.window_list.remove(window_id) + wlist.remove(window_id) else: - if self.window_index < (len(self.window_list) - 1): - self.window_index += 1 + if widx[0] < (len(wlist) - 1): + widx[0] += 1 else: - self.window_index = 0 + widx[0] = 0 logging.info('focusing window id={}'.format(window_id)) await self.i3.command('[con_id={}] focus'.format(window_id)) break @@ -142,6 +175,8 @@ async def run_server(): To trigger focus switching, execute the script from a keybinding with the `--switch` option.""") + mutex_group = parser.add_mutually_exclusive_group() + parser.add_argument('--history', dest='history', help='Maximum number of windows in the focus history', @@ -155,17 +190,27 @@ async def run_server(): action='store_true', help='Ignore floating windows ' 'when cycling and updating the focus history') - parser.add_argument('--visible-workspaces', + mutex_group.add_argument('--visible-workspaces', dest='visible_workspaces', action='store_true', help='Include windows on visible ' 'workspaces only when cycling the focus history') - parser.add_argument('--active-workspace', + mutex_group.add_argument('--active-workspace', dest='active_workspace', action='store_true', help='Include windows on the ' 'active workspace only when cycling the focus history') - parser.add_argument('--switch', + mutex_group.add_argument('--focused-workspace', + dest='focused_workspace', + action='store_true', + help='Include windows on the ' + 'focused workspace only when cycling the focus history') + mutex_group.add_argument('--focused-output', + dest='focused_output', + action='store_true', + help='Include windows on the ' + 'focused output/screen only when cycling the focus history') + mutex_group.add_argument('--switch', dest='switch', action='store_true', help='Switch to the previous window', @@ -176,14 +221,17 @@ async def run_server(): if args.debug: logging.basicConfig(level=logging.DEBUG) - if args.history: - MAX_WIN_HISTORY = args.history - if args.delay: - UPDATE_DELAY = args.delay - else: - if args.delay == 0.0: - UPDATE_DELAY = args.delay if args.switch: asyncio.run(send_switch()) else: + if args.history: + MAX_WIN_HISTORY = args.history + + if args.delay or args.delay == 0.0: + UPDATE_DELAY = args.delay + + PER_OUTPUT = args.focused_output + PER_WS = args.focused_workspace + KEYED_CONF = PER_OUTPUT or PER_WS + asyncio.run(run_server())