0
0
Просмотр исходного кода

feat(yabai): use BetterDisplay (if available) to control display brightness)

Joe 1 год назад
Родитель
Сommit
3798787394
1 измененных файлов с 106 добавлено и 23 удалено
  1. 106 23
      .config/yabai/yabai.py

+ 106 - 23
.config/yabai/yabai.py

@@ -1,4 +1,5 @@
 #!/usr/bin/env python3
 #!/usr/bin/env python3
+from os.path import exists
 from json import loads
 from json import loads
 from logging import NOTSET, basicConfig, debug, info
 from logging import NOTSET, basicConfig, debug, info
 from os import getenv
 from os import getenv
@@ -58,7 +59,7 @@ class Space:
         return self.index.__gt__(other.index)
         return self.index.__gt__(other.index)
 
 
 
 
-class Display:
+class YabaiDisplay:
     def __init__(self, data: dict[str, Any]):
     def __init__(self, data: dict[str, Any]):
         self.id: int = data["id"]
         self.id: int = data["id"]
         self.uuid: str = data["uuid"]
         self.uuid: str = data["uuid"]
@@ -70,20 +71,90 @@ class Display:
 
 
     def __repr__(self) -> str:
     def __repr__(self) -> str:
         return (
         return (
-            f"Space({self.label if self.label and len(self.label) > 0 else '<NoLabel>'}"
+            f"Display({self.label if self.label and len(self.label) > 0 else '<NoLabel>'}"
             f", {self.index}"
             f", {self.index}"
             f", Frame ({self.frame})"
             f", Frame ({self.frame})"
             f"{', Focused' if self.has_focus else ''})"
             f"{', Focused' if self.has_focus else ''})"
         )
         )
 
 
-    def __gt__(self, other: "Display"):
+    def __gt__(self, other: "YabaiDisplay"):
         return self.frame["w"].__gt__(other.frame["w"])
         return self.frame["w"].__gt__(other.frame["w"])
 
 
 
 
+class BetterDisplayDisplay:
+    def __init__(self, data: dict[str, Any]):
+        self.alphanumeric_serial: str = data["alphanumericSerial"]
+        self.device_type: str = data["deviceType"]
+        self.display_id: str = data["displayID"]
+        self.model: str = data["model"]
+        self.name: str = data["name"]
+        self.original_name: str = data["originalName"]
+        self.product_name: str = data["productName"]
+        self.registry_location: str = data["registryLocation"]
+        self.serial: str = data["serial"]
+        self.tagID: str = data["tagID"]
+        self.uuid: str = data["UUID"]
+        self.vendor: str = data["vendor"]
+        self.manufacture_week: str = data["weekOfManufacture"]
+        self.manufacture_year: str = data["yearOfManufacture"]
+
+    def __repr__(self) -> str:
+        return f"BetterDisplayDisplay({self.name})"
+
+    def __gt__(self, other: "BetterDisplayDisplay"):
+        return self.display_id.__gt__(other.display_id)
+
+    @property
+    def built_in(self) -> bool:
+        return self.name == "Built-in Display"
+
+
 SpaceSel: TypeAlias = int | str
 SpaceSel: TypeAlias = int | str
 
 
 
 
-class Yabai:
+class CLIWrapper:
+    _base_args: list[str] = []
+
+    def message(self, args: list[str | int]) -> str:
+        return self.execute(self._base_args + [str(arg) for arg in args]).decode("utf8")
+
+    def execute(self, *args: Any, **kwargs: Any) -> Any:
+        debug(f"Executing: ({args}), ({kwargs})")
+        return check_output(*args, **kwargs)
+
+
+class BetterDisplay(CLIWrapper):
+    _available: bool = False
+    _base_args: list[str] = [
+        "/Applications/BetterDisplay.app/Contents/MacOS/BetterDisplay"
+    ]
+
+    def __init__(self):
+        if exists(self._base_args[0]) and self.execute(self._base_args + ["help"], timeout=0.5):
+            self._available = True
+
+    @property
+    def available(self) -> bool:
+        return self._available
+
+    def set_brightness(
+        self, display: BetterDisplayDisplay, value: float
+    ):
+        self.message(
+            ["set", f"-uuid={display.uuid}", "-feature=brightness", f"-value={value}"]
+        )
+
+    def get_displays(self) -> list[BetterDisplayDisplay]:
+        return [
+            BetterDisplayDisplay(display)
+            for display in loads(
+                "[" + self.message(["get", "-feature=identifiers"]) + "]"
+            )
+        ]
+
+
+class Yabai(CLIWrapper):
+    _base_args: list[str] = ["yabai", "-m"]
     spaces: list[tuple[str, bool, list[str]]] = [
     spaces: list[tuple[str, bool, list[str]]] = [
         ("Desktop", False, ["*"]),
         ("Desktop", False, ["*"]),
         ("Finder", False, ["Finder"]),
         ("Finder", False, ["Finder"]),
@@ -107,13 +178,6 @@ class Yabai:
         if exc_type is None:
         if exc_type is None:
             debug(f"Executed successfully")
             debug(f"Executed successfully")
 
 
-    def execute(self, *args: Any, **kwargs: Any) -> Any:
-        debug(f"Executing: ({args}), ({kwargs})")
-        return check_output(*args, **kwargs)
-
-    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]:
     def get_windows(self) -> set[Window]:
         return {
         return {
             Window(window) for window in loads(self.message(["query", "--windows"]))
             Window(window) for window in loads(self.message(["query", "--windows"]))
@@ -122,12 +186,15 @@ class Yabai:
     def get_spaces(self) -> set[Space]:
     def get_spaces(self) -> set[Space]:
         return {Space(space) for space in loads(self.message(["query", "--spaces"]))}
         return {Space(space) for space in loads(self.message(["query", "--spaces"]))}
 
 
-    def get_displays(self) -> set[Display]:
+    def get_displays(self) -> set[YabaiDisplay]:
         return {
         return {
-            Display(display) for display in loads(self.message(["query", "--displays"]))
+            YabaiDisplay(display)
+            for display in loads(self.message(["query", "--displays"]))
         }
         }
 
 
-    def get_main_display(self, displays: set[Display] | None = None) -> Display:
+    def get_main_display(
+        self, displays: set[YabaiDisplay] | None = None
+    ) -> YabaiDisplay:
         return sorted(list(displays if displays != None else self.get_displays()))[-1]
         return sorted(list(displays if displays != None else self.get_displays()))[-1]
 
 
     def is_blank_space(self, space: Space) -> bool:
     def is_blank_space(self, space: Space) -> bool:
@@ -144,8 +211,22 @@ class Yabai:
                 self.message(["display", display.index, "--label", "Main"])
                 self.message(["display", display.index, "--label", "Main"])
             else:
             else:
                 self.message(
                 self.message(
-                    ["display", display.index, "--label", f"Display {display.index}"]
+                    [
+                        "display",
+                        display.index,
+                        "--label",
+                        f"YabaiDisplay {display.index}",
+                    ]
                 )
                 )
+        better_display = BetterDisplay()
+        if better_display.available:
+            displays = better_display.get_displays()
+            if len(displays) > 1:
+                for display in displays:
+                    if display.built_in:
+                        better_display.set_brightness(display, 0)
+                    else:
+                        better_display.set_brightness(display, 1)
 
 
     def manage_spaces(self):
     def manage_spaces(self):
         initial_window = self.get_focused_window()
         initial_window = self.get_focused_window()
@@ -195,7 +276,7 @@ class Yabai:
                     (main_display.frame["h"] - TARGET_DISPLAY_HEIGHT) / 2
                     (main_display.frame["h"] - TARGET_DISPLAY_HEIGHT) / 2
                 ),
                 ),
             )
             )
-        if len(created_space_labels) > 0:
+        if len(created_space_labels) > 0 and initial_window is not None:
             self.message(["window", "--focus", initial_window])
             self.message(["window", "--focus", initial_window])
         # Sort the remaining spaces
         # Sort the remaining spaces
         for space_index, space_label in enumerate(self.spaces):
         for space_index, space_label in enumerate(self.spaces):
@@ -205,7 +286,8 @@ class Yabai:
                 # Almost certainly thrown because space is already in place, so no problem
                 # Almost certainly thrown because space is already in place, so no problem
                 pass
                 pass
         # Return focus
         # Return focus
-        self.message(["window", "--focus", initial_window])
+        if initial_window is not None:
+            self.message(["window", "--focus", initial_window])
         info(f"Spaces configured: {sorted(self.get_spaces())}")
         info(f"Spaces configured: {sorted(self.get_spaces())}")
 
 
     def set_space_background(self, space: SpaceSel):
     def set_space_background(self, space: SpaceSel):
@@ -330,12 +412,13 @@ class Yabai:
             ]
             ]
         )
         )
 
 
-    def get_focused_window(self) -> int:
-        return [
-            window
-            for window in loads(self.message(["query", "--windows"]))
-            if window["has-focus"]
-        ].pop()["id"]
+    def get_focused_window(self) -> Window | None:
+        windows = [
+            window for window in [Window(window) for window in loads(self.message(["query", "--windows"]))] if window.has_focus
+        ]
+        if len(windows) > 0:
+            return windows.pop()
+        return None
 
 
 
 
 if __name__ == "__main__":
 if __name__ == "__main__":