Kaynağa Gözat

feat(yabai): automatically rotate windows to enlarged focused

Joe 1 yıl önce
ebeveyn
işleme
e510fa02b3
1 değiştirilmiş dosya ile 76 ekleme ve 37 silme
  1. 76 37
      .config/yabai/yabai.py

+ 76 - 37
.config/yabai/yabai.py

@@ -1,6 +1,6 @@
 #!/usr/bin/env python3
 from json import loads
-from logging import NOTSET, basicConfig, critical, debug, error, info, warning
+from logging import NOTSET, basicConfig, debug, info
 from os import getenv
 from subprocess import CalledProcessError, check_output
 from sys import argv
@@ -11,6 +11,30 @@ XDG_CONFIG_HOME = getenv("XDG_CONFIG_HOME")
 TARGET_DISPLAY_WIDTH = 1680
 TARGET_DISPLAY_HEIGHT = 1050
 
+class Window:
+    def __init__(self, data: dict):
+        self.id: int = data["id"]
+        self.app: str = data["app"]
+        self.space: str = data["space"]
+        self.title: str = data["title"]
+        self.has_focus: bool = data["has-focus"]
+        self.frame_w: int = data["frame"]["w"]
+        self.frame_h: int = data["frame"]["h"]
+
+    @property
+    def size(self) -> int:
+        return self.frame_w * self.frame_h
+
+    def __repr__(self) -> str:
+        return (
+            f"Window({self.title} | {self.app}"
+            f", {self.id}"
+            f"{', Focused' if self.has_focus else ''})"
+        )
+
+    def __gt__(self, other: "Window"):
+        return self.id.__gt__(other.id)
+ 
 
 class Space:
     def __init__(self, data: dict):
@@ -88,13 +112,14 @@ class Yabai:
     def message(self, args: list[str | int]) -> str:
         return self.execute(["yabai", "-m"] + [str(arg) for arg in args]).decode("utf8")
 
+    def get_windows(self) -> set[Window]:
+        return { Window(window) for window in loads(self.message(["query", "--windows"])) }
+
     def get_spaces(self) -> set[Space]:
         return {Space(space) for space in loads(self.message(["query", "--spaces"]))}
 
     def get_displays(self) -> set[Display]:
-        return {
-            Display(display) for display in loads(self.message(["query", "--displays"]))
-        }
+        return { Display(display) for display in loads(self.message(["query", "--displays"])) }
 
     def get_main_display(self, displays: set[Display] | None = None) -> Display:
         return sorted(list(displays if displays != None else self.get_displays()))[-1]
@@ -238,9 +263,9 @@ class Yabai:
             [
                 "rule",
                 "--add",
-                f"label=DefaultDesktopRule",
-                f"subrole=AXStandardWindow",
-                f"space=^Desktop",
+                "label=DefaultDesktopRule",
+                "subrole=AXStandardWindow",
+                "space=^Desktop",
             ]
         )
         # Rules for applications that get their own spaces
@@ -257,32 +282,21 @@ class Yabai:
                         f"space=^{label}",
                     ]
                 )
-        # Google Meet and Slack Huddles should be "sticky"
-        self.message(
-            [
-                "rule",
-                "--add",
-                "label=GoogleMeetFloatingWindowRule",
-                "app=Google Meet",
-                "sticky=on",
-                "manage=on",
-                "opacity=0.9",
-                "grid=10:10:6:6:4:4",
-            ]
-        )
-        self.message(
-            [
-                "rule",
-                "--add",
-                "label=SlackHuddleFloatingWindowRule",
-                "app=Slack",
-                "title=Huddle.*",
-                "sticky=on",
-                "manage=on",
-                "opacity=0.9",
-                "grid=10:10:6:6:4:4",
-            ]
-        )
+        # Safari and Slack Huddles should be "sticky"
+        for app, title in (("Safari", ".*"), ("Slack", "Huddle.*")):
+            self.message(
+                [
+                    "rule",
+                    "--add",
+                    f"label={app}VideoCallFloatingWindowRule",
+                    f"app={app}",
+                    f"title={title}",
+                    "sticky=on",
+                    "manage=on",
+                    "opacity=0.9",
+                    "grid=10:10:6:6:4:4",
+                ]
+            )
         # Compile SurfingKeys configuration when Firefox is launched
         self.message(
             [
@@ -305,6 +319,31 @@ class Yabai:
                 f"action=/bin/zsh {HOME}/.scripts/lightmode.zsh",
             ]
         )
+        # When focusing an that may share a space with at least two others, rotate the
+        # space's windows to ensure that the focused window is the largest.
+        for label, _, apps in self.spaces:
+            if len(apps) < 3:
+                continue
+            for app in apps:
+                self.message(
+                [
+                    "signal",
+                    "--add",
+                    "event=window_focused",
+                    f"app={app}",
+                    f"label={app}RotateToFocus",
+                    f"action=python3 {XDG_CONFIG_HOME}/yabai/yabai.py rotate",
+                ]
+            )
+
+    def rotate(self) -> None:
+        windows = self.get_windows()
+        focus_window = next(w for w in windows if w.has_focus)
+        same_space_windows = sorted((w for w in windows if w.space == focus_window.space), key = lambda w: w.size)
+        if same_space_windows[-1].has_focus:
+            return
+        self.message(["window", focus_window.id, "--swap", same_space_windows[-1].id])
+        return
 
     def get_focused_window(self) -> int:
         return [
@@ -317,11 +356,11 @@ class Yabai:
 if __name__ == "__main__":
     basicConfig(level=NOTSET)
     debug(f"Called with parameters {argv}")
-    with Yabai() as yabai:
-        if argv[1] == "manage" or argv[1] == "initialize":
+    if argv[1] == "manage" or argv[1] == "initialize":
+        with Yabai() as yabai:
             yabai.manage_displays()
             yabai.manage_spaces()
         if argv[1] == "initialize":
             yabai.set_rules_and_signals()
-        else:
-            raise Exception(argv)
+    if argv[1] == "rotate":
+        Yabai().rotate()