#!/bin/env python3 import sys import re import json from subprocess import run, PIPE import argparse def get_ws(): return json.loads(run('i3-msg -t get_workspaces'.split(), stdout=PIPE).stdout) def to_empty(current=None, strict=False, right=True, move=False, wrap=True, min_num=1): """Move to nearest empty numbered workspace""" if current is None: current = [w for w in get_ws() if w['focused']][0] numbered = re.compile('^\d+$' if strict else '^\d+|(\d+:.*)$') ns = {w['num'] for w in get_ws() if numbered.match(w['name']) != None and (not strict or w['name'] == str(w['num']))} if numbered.match(current['name']) is None: # If current workspace is unnumbered, pick rightmost or leftmost if len(ns) == 0: new_ix = min_num else: new_ix = min(ns) - 1 if right else max(ns) + 1 else: # Find numbered workspace nearest to current new_ix = current['num'] while new_ix in ns: new_ix += 1 if right else -1 # Wrap around if new_ix < min_num: if wrap: new_ix = max(ns) + 1 else: return if move: s = 'i3-msg move container to workspace {0}; workspace {0}'.format(new_ix) else: s = 'i3-msg workspace ' + str(new_ix) print(s) run(s.split()) def to_empty_near(num, relative=False, **kwargs): """Move to empty numbered workspace nearest to workspace 'num'""" if relative: if 0 <= num < len(get_ws()): current = get_ws()[num] else: return else: ns = [w for w in get_ws() if w['num'] == num] current = ns[0] if len(ns) > 0 else {'num': num, 'name': str(num)} to_empty(current, **kwargs) if __name__ == '__main__': parser = argparse.ArgumentParser(description='Switch to an empty numbered workspace.') parser.add_argument('direction', type=str, nargs='?', default='next', help='either next (default) or prev') parser.add_argument('number', type=int, nargs='?', help='workspace to start searching from (default: current)') parser.add_argument('-r', '--relative', dest='rel', action='store_true', help='use workspace indices, not numbers (default: no)') parser.add_argument('-w', '--nowrap', dest='wrap', action='store_false', help='if at edge, wrap around to other edge (default: yes)') parser.add_argument('-s', '--nostrict', dest='strict', action='store_false', help='numbered workspaces have a numeric name (default: yes)') parser.add_argument('-m', '--move', dest='move', action='store_true', help='move container to new workspace (default: no)') args = parser.parse_args() kwargs = {'right': args.direction.lower().strip() == 'next', 'wrap': args.wrap, 'strict': args.strict, 'move': args.move} if type(args.number) is int: to_empty_near(args.number, relative=args.rel, **kwargs) else: to_empty(**kwargs)