diff options
| author | myortv <myortv@proton.me> | 2026-01-17 08:37:20 +0300 |
|---|---|---|
| committer | myortv <myortv@proton.me> | 2026-01-17 08:37:20 +0300 |
| commit | f0749b308158456712159c03874a2ade957e610f (patch) | |
| tree | f2b36872245ddadac7dea17632d6513dd5aca716 /workspace_watcher.py | |
| parent | 2bfe0ea5d0399700f972962c3a77798caf8269b6 (diff) | |
Multihead support
Rewriten Layout setup
Support for restorable windows
Diffstat (limited to 'workspace_watcher.py')
| -rw-r--r-- | workspace_watcher.py | 342 |
1 files changed, 0 insertions, 342 deletions
diff --git a/workspace_watcher.py b/workspace_watcher.py deleted file mode 100644 index 03a65ac..0000000 --- a/workspace_watcher.py +++ /dev/null @@ -1,342 +0,0 @@ -from functools import reduce -from enum import Enum -import subprocess -import time -import i3ipc -from time import sleep - - -APP_CMD = { - "SystemMonitor": { - "run": ['bash', "/home/myo/script.sh"], - "name": "SystemMonitor", - "geometry": { - "x": 10, "y": 10, "w": 400, "h": 1060 - }, - "scratch": "hidden1", - # "skip_startup": False, - }, - "newsboatterm":{ - "run": ['alacritty', '-T', 'newsboatterm', '-e', 'newsboat'], - "name": "newsboatterm", - "geometry": { - "x": 420, "y": 10, "w": 1200, "h": 353 - }, - "scratch": "hidden2", - # "skip_startup": True, - }, - "termusicoverlay":{ - "run": ['alacritty', '-T', 'termusicoverlay', '-e', 'termusic'], - "name": "termusicoverlay", - "geometry": { - "x": 420, "y": 373, "w": 1200, "h": 696 - }, - "scratch": "hidden2", - # "skip_startup": True, - }, - "todo":{ - "run": ['alacritty', '-T', 'todo', '-e', 'helix', '~/todo/todo'], - "name": "todo", - "geometry": { - "x": 420, "y": 373, "w": 1200, "h": 696 - }, - "scratch": "hidden3", - # "skip_startup": True, - }, -} - -def gen_app_list_by_scratches(): - res = dict() - for item in APP_CMD.values(): - if res.get(item["scratch"]): - res[item["scratch"]].append(item) - else: - res[item["scratch"]] = [item] - return res - - -APP_NAMES = APP_CMD.keys() -APPS_BY_SCRATCHES = gen_app_list_by_scratches() -SCRATCHES = {item["scratch"] for item in APP_CMD.values()} -LAST_WORKSPACE = None - -i3 = i3ipc.Connection() - -is_stashed = True - -class Status(Enum): - stashed=0 - hidden1=1 - hidden2=2 - hidden3=3 - -overlay_status = Status(0) - -toggled = False -# stashed_focus_hist = [True, True] - -def float_and_resize(win, geometry): - win_id = win.id - i3.command(f"[con_id={win_id}] floating enable") - i3.command(f"[con_id={win_id}] resize set {geometry['w']} {geometry['h']}") - i3.command(f"[con_id={win_id}] move position {geometry['x']} {geometry['y']}") - - -def cleanup_unneded_instances( - tree, - scratches=None, -) -> bool: - print("======= cleanup") - print("scratches", scratches) - if not scratches: - scratches = list(SCRATCHES) - - all_apps = reduce(lambda x, y: x + y, [APPS_BY_SCRATCHES[item] for item in scratches]) - print("all apps", all_apps) - - res = True - for name in APP_NAMES: - matching = list(filter(lambda w: w.workspace() and w.workspace().name not in SCRATCHES, search(tree, name))) - if len(matching) > 0: - for win in matching[1:]: - win.command('kill') - float_and_resize(matching[0], APP_CMD[name]["geometry"]) - move_to_scratchpad(matching[0], APP_CMD[name]['scratch']) - res = False - return - - -def search(tree, name): - return filter(lambda w: w.name and w.name == name, tree.leaves()) - -def resp_if_needed(tree, targets): - # dead = gather_dead( - # tree, - # targets - # ) - dead = filter( - lambda app: not all(search(tree, app["name"])), targets - ) - if dead: - spawn(dead) - sleep(0.5) - return True - -def gather_dead(tree, targets): - res = list() - for app in targets: - if not list(search(tree, app["name"])): - res.append(app) - return res - - -def cleanup( - tree, - name, -) -> bool: - matching = list(filter(lambda w: w.workspace() and w.workspace().name not in SCRATCHES, search(tree, name))) - if len(matching) > 0: - for win in matching[1:]: - win.command('kill') - float_and_resize(matching[0], APP_CMD[name]["geometry"]) - move_to_scratchpad(matching[0], APP_CMD[name]['scratch']) - global overlay_status - overlay_status = Status(0) - return True - return False - -def move_to_scratchpad(win, scratch): - if scratch == "scratchpad": - i3.command(f'[con_id={win.id}] move to {scratch}') - else: - i3.command(f'[con_id={win.id}] move to workspace {scratch}') - - -def on_window_close(i3conn, event): - win = event.container - if not win.name or not win.name in APP_NAMES: - ws = i3.get_tree().find_focused().workspace() - if overlay_status == Status.stashed and not ws.nodes: - showup(i3.get_tree(), ws) - # resp_if_needed(i3conn.get_tree()) - # if not cleanup_unneded_instances(i3.get_tree()): - # spawn() - # return - -i3.on("window::close", on_window_close) - -def showup(tree, ws, show_scratches=None, focus_win=None): - print('==== showup') - print('show scratches', show_scratches) - if not show_scratches: - show_scratches = ['hidden1'] - all_apps = APPS_BY_SCRATCHES['hidden1'] - else: - all_apps = reduce(lambda x, y: x + y,[APPS_BY_SCRATCHES[item] for item in show_scratches]) - - # global overlay_status - # overlay_status = Status() - if resp_if_needed( - tree, - all_apps, - ): - tree = i3.get_tree() - for app in all_apps: - # if app["skip_startup"] and not app["scratch"] == show_scratch and not FULL_OVERLAY_SPAWN: - # return - matching = list(filter(lambda w: w.name and w.name == app["name"], tree.leaves())) - win = matching[0] - float_and_resize(win, app['geometry']) - win.command(f'move container to workspace {ws.name}') - if focus_win == app["name"]: - win.command('focus') - - -def on_workspace_focus(i3conn, event): - print('========== focus ') - global LAST_WORKSPACE, overlay_status - ws = event.current - if ws is None: - return - if ( - not ws.nodes - and ws.name != LAST_WORKSPACE - and overlay_status == Status.stashed - ): - showup(i3.get_tree(), ws) - else: - cleanup_unneded_instances(i3.get_tree()) - LAST_WORKSPACE = ws.name - overlay_shown = False - overlay_shown2 = False - - -i3.on("workspace::focus", on_workspace_focus) - - -def on_new_windnow(i3conn, event): - win = event.container - if win.name and win.name in APP_NAMES: - if cleanup_unneded_instances(i3.get_tree()): - return - if overlay_status == Status.stashed: - return - ws = win.workspace() - if ws and ws.nodes: - return cleanup_unneded_instances(i3.get_tree()) - if win.floating != "user_on" and win.floating != "auto_on": - return cleanup_unneded_instances(i3.get_tree()) - - -i3.on("window::new", on_new_windnow) - -def on_window_floating(i3conn, event): - win = event.container - ws = i3.get_tree().find_focused().workspace() - if ws and ws.nodes: - cleanup_unneded_instances(i3.get_tree()) - elif win.floating == "user_on": - showup(i3.get_tree(), ws) - else: - cleanup_unneded_instances(i3.get_tree()) - -i3.on("window::floating", on_window_floating) - -def clean_other_scratches(tree, keep=None): - if not keep: - keep={"hidden1"} - cleanup_unneded_instances( - tree, - list(SCRATCHES - keep) - ) - -def on_binding(i3conn, event): - global overlay_status - match event.binding.command: - case 'exec --no-startup-id echo "show_partial_overlay"': - if overlay_status == Status.hidden1: - cleanup_unneded_instances( - i3.get_tree(), - ["hidden1"] - ) - overlay_status = Status(0) - else: - ws = i3.get_tree().find_focused().workspace() - showup(i3.get_tree(), ws, ['hidden1'], focus_win="SystemMonitor") - case 'exec --no-startup-id echo "show_overlay_1"': - if overlay_status == Status.hidden2: - overlay_status = Status(0) - cleanup_unneded_instances( - i3.get_tree(), - ["hidden1", 'hidden2'] - ) - else: - clean_other_scratches(i3conn.get_tree(), {"hidden1", "hidden2"}) - ws = i3.get_tree().find_focused().workspace() - showup(i3.get_tree(), ws, ['hidden1', 'hidden2'], "SystemMonitor") - case 'exec --no-startup-id echo "show_overlay_2"': - if overlay_status == Status.hidden3: - overlay_status = Status(0) - cleanup_unneded_instances(i3.get_tree(), ["hidden1", "hidden3"]) - else: - clean_other_scratches(i3conn.get_tree(), {"hidden1", "hidden3"}) - ws = i3.get_tree().find_focused().workspace() - showup(i3.get_tree(), ws, ['hidden1', 'hidden3'], "todo") - -i3.on("binding", on_binding) - -def spawn(targets=None): - if not targets: - targets = APP_CMD.values() - if not cleanup_unneded_instances(i3.get_tree()): - for app in targets: - res = subprocess.Popen(app["run"]) - print(res) - - - - - # # Wait and find its window - # for _ in range(50): - # time.sleep(0.1) - # for win in i3.get_tree().leaves(): - # if APP_MARK in win.marks: - # return # already marked - # # Find unmarked new window in focused workspace - # new_win = [ - # w for w in focused_ws.leaves() - # if APP_MARK not in w.marks - # ] - # if new_win: - # float_and_resize(new_win[0].id) - # return - -# def move_to_scratch_if_other_windows(): -# ws = i3.get_tree().find_focused().workspace() -# app_win = next((w for w in ws.leaves() if APP_MARK in w.marks), None) -# if not app_win: -# return -# others = [w for w in ws.leaves() if w.id != app_win.id] -# if others: -# i3.command(f"[con_mark={APP_MARK}] move scratchpad") - -# def kill_duplicate_instances(): -# app_windows = [w for w in i3.get_tree().leaves() if APP_MARK in w.marks] -# if len(app_windows) <= 1: -# return -# # Keep one, kill others -# keep_id = app_windows[0].id -# for w in app_windows[1:]: -# i3.command(f"[con_id={w.id}] kill") - -# def on_window_new(i3conn, e): -# move_to_scratch_if_other_windows() -# kill_duplicate_instances() - -# spawn_and_mark_app() - -# i3.on("window::new", on_window_new) -# i3.on('workspace::focus', on_workspace_focus) - -spawn() -i3.main() |
