summaryrefslogtreecommitdiff
path: root/watcher.py
diff options
context:
space:
mode:
Diffstat (limited to 'watcher.py')
-rw-r--r--watcher.py310
1 files changed, 310 insertions, 0 deletions
diff --git a/watcher.py b/watcher.py
new file mode 100644
index 0000000..a6af333
--- /dev/null
+++ b/watcher.py
@@ -0,0 +1,310 @@
+from functools import reduce
+from enum import Enum
+import subprocess
+import time
+import i3ipc
+from time import sleep
+
+
+PRESETS = {
+ "hidden1": [
+ {
+ "run": ["alacritty", "-T", "thing"],
+ "name": "thing",
+ "geometry": {"x": 0, "y": 0, "w": 200, "h": 200},
+ "scratch": "hidden1",
+ },
+ ],
+ "hidden2": [
+ {
+ "run": ["alacritty", "-T", "thing2"],
+ "name": "thing2",
+ "geometry": {"x": 250, "y": 250, "w": 200, "h": 200},
+ "scratch": "hidden2",
+ },
+ ],
+ #
+ #
+ # {
+ # "run": ["/usr/bin/bash", "/home/myo/.config/i3/watcher/script.sh"],
+ # "name": "SystemMonitor",
+ # "geometry": {"x": 10, "y": 10, "w": 400, "h": 1060},
+ # "scratch": "hidden1",
+ # # "skip_startup": False,
+ # },
+ # "hidden2": [
+ # {
+ # "run": ["alacritty", "-T", "newsboatterm", "-e", "newsboat"],
+ # "name": "newsboatterm",
+ # "geometry": {"x": 420, "y": 10, "w": 1200, "h": 353},
+ # "scratch": "hidden2",
+ # # "skip_startup": True,
+ # },
+ # {
+ # "run": ["alacritty", "-T", "termusicoverlay", "-e", "termusic"],
+ # "name": "termusicoverlay",
+ # "geometry": {"x": 420, "y": 373, "w": 1200, "h": 696},
+ # "scratch": "hidden2",
+ # },
+ # ],
+ # "hidden3": [
+ # {
+ # "run": ["alacritty", "-T", "todo", "-e", "helix", "~/Desk/todo_/todo.md"],
+ # "name": "todo",
+ # "geometry": {"x": 420, "y": 10, "w": 1200, "h": 1060},
+ # "scratch": "hidden3",
+ # },
+ # ],
+}
+
+
+# TODO: make it enum instead
+status = 0
+LAST_WORKSPACE = None
+ALL_APPS = reduce(
+ lambda x, y: x + y, PRESETS.values()
+)
+ALL_NAMES = [app['name'] for app in ALL_APPS]
+button_trigger = None
+
+i3 = i3ipc.Connection()
+
+
+# ========================================================================================
+
+
+
+def spawn(targets=None):
+ if not targets:
+ targets = PRESETS["hidden1"]
+ if not cleanup_unneded_instances(i3.get_tree()):
+ for app in targets:
+ res = subprocess.Popen(app["run"])
+ print(res)
+
+
+def cleanup_unneded_instances(
+ tree,
+ scratches=None,
+ keep=None,
+) -> bool:
+ if not scratches:
+ scratches = PRESETS.keys()
+ if not keep:
+ keep = []
+
+ scratches_to_cleanup = [PRESETS[item] for item in scratches if item not in keep]
+
+ if not scratches:
+ return
+
+ all_apps = reduce(
+ lambda x, y: x + y, scratches
+ )
+
+ res = True
+ for app in all_apps:
+ matching = list(
+ filter(
+ lambda w: w.name
+ and w.name == app['name']
+ and w.workspace().name not in PRESETS.keys(),
+ tree.leaves(),
+ )
+ )
+ if len(matching) > 0:
+ for win in matching[1:]:
+ win.command("kill")
+ # float_and_resize(matching[0], app["geometry"])
+ move_to_scratchpad(matching[0], app["scratch"])
+ res = False
+ return
+
+
+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 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 showup(tree, ws, show_scratches=None, focus_win=None):
+ if not show_scratches:
+ show_scratches = ["hidden1"]
+ all_apps = PRESETS["hidden1"]
+ else:
+ all_apps = reduce(
+ lambda x, y: x + y, [PRESETS[item] for item in show_scratches]
+ )
+
+ 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())
+ )
+ if not matching:
+ print("ERR: index out of bounds. Check apps health.")
+ return
+ 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 resp_if_needed(tree, targets):
+ dead = list(filter(lambda app: not list(filter(lambda w: w.name==app["name"], tree.leaves())), targets))
+ if dead:
+ spawn(dead)
+ sleep(0.5)
+ return True
+
+
+# ========================================================================================
+
+
+def on_binding(i3conn, event):
+ global status, button_trigger
+ #
+ # when workpace focused, button_trigger sets to None
+
+ print('~~~~~~~~~~``')
+ print(event.binding.command)
+ print(status)
+ print(button_trigger)
+ print('~~~~~~~~~~``')
+ match event.binding.command:
+
+ case 'exec --no-startup-id echo "show_partial_overlay"':
+ if status != 0:
+ cleanup_unneded_instances(i3.get_tree())
+ status = 0
+ button_trigger = True
+ else:
+ status = 1
+ cleanup_unneded_instances(i3conn.get_tree(), None, ["hidden1"])
+ ws = i3.get_tree().find_focused().workspace()
+ showup(i3.get_tree(), ws, ["hidden1"], focus_win="thing")
+ button_trigger = True
+
+ case 'exec --no-startup-id echo "show_overlay_1"':
+ if status == 2:
+ # cleanup status (current thing opened)
+ status = 0
+ cleanup_unneded_instances(i3.get_tree(), ["hidden1", "hidden2"])
+ button_trigger = True
+ else:
+ if status != 0:
+ cleanup_unneded_instances(i3conn.get_tree(), None, ["hidden1", "hidden2"])
+ status = 2
+ ws = i3.get_tree().find_focused().workspace()
+ showup(i3.get_tree(), ws, ["hidden1", "hidden2"], "thing2")
+ button_trigger = True
+
+ # case 'exec --no-startup-id echo "show_overlay_2"':
+ # if status == 3:
+ # status = 0
+ # cleanup_unneded_instances(i3.get_tree(), ["hidden1", "hidden3"])
+ # button_trigger = True
+ # else:
+ # if status != 0:
+ # cleanup_unneded_instances(i3conn.get_tree(), None, ["hidden1", "hidden3"])
+ # status = 3
+ # ws = i3.get_tree().find_focused().workspace()
+ # showup(i3.get_tree(), ws, ["hidden1", "hidden3"], "todo")
+ # button_trigger = True
+
+i3.on("binding", on_binding)
+
+
+def on_window_close(i3conn, event):
+ global status
+ win = event.container
+ if not win.name or not win.name in ALL_NAMES:
+ ws = i3.get_tree().find_focused().workspace()
+ if status == 0 and not ws.nodes:
+ showup(i3.get_tree(), ws, ["hidden1"])
+ status = 1
+ resp_if_needed(i3conn.get_tree(), ALL_APPS)
+ # if not cleanup_unneded_instances(i3.get_tree()):
+ # spawn()
+ # return
+
+i3.on("window::close", on_window_close)
+
+
+
+def on_workspace_focus(i3conn, event):
+ global LAST_WORKSPACE, status, button_trigger
+ ws = event.current
+ if ws is None:
+ return
+ if button_trigger and LAST_WORKSPACE == ws.name:
+ button_trigger = None
+ LAST_WORKSPACE = ws.name
+ return
+ if (
+ not ws.nodes
+ and LAST_WORKSPACE != ws.name
+ ):
+ showup(i3.get_tree(), ws, ["hidden1"])
+ status = 1
+ else:
+ cleanup_unneded_instances(i3.get_tree())
+ status = 0
+ LAST_WORKSPACE = ws.name
+
+i3.on("workspace::focus", on_workspace_focus)
+
+
+def on_new_windnow(i3conn, event):
+ global status
+ win = event.container
+ if win.name and win.name in ALL_NAMES:
+ if cleanup_unneded_instances(i3.get_tree()):
+ return
+ if status == 0:
+ return
+ ws = win.workspace()
+ if ws and ws.nodes:
+ status = 0
+ return cleanup_unneded_instances(i3.get_tree())
+ if win.floating != "user_on" and win.floating != "auto_on":
+ status = 0
+ 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)
+
+
+# ========================================================================================
+
+spawn(
+ ALL_APPS
+)
+i3.main()