dulus-bar 0.3.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,205 @@
1
+ """macOS native backend: camera-notch geometry + always-visible overlay.
2
+
3
+ Depends on pyobjc (``pyobjc-framework-Cocoa`` / ``-Quartz``). Every AppKit call
4
+ is guarded so that on a machine without pyobjc the backend simply degrades to
5
+ osascript-based activation and a plain top-most window (no notch hugging).
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ import subprocess
11
+ from typing import List, Optional
12
+
13
+ from .base import NativeBackend, NotchGeometry, WindowInfo
14
+
15
+ try: # pragma: no cover - platform specific
16
+ import AppKit # type: ignore
17
+ import objc # type: ignore
18
+
19
+ _HAVE_APPKIT = True
20
+ except Exception: # pragma: no cover
21
+ _HAVE_APPKIT = False
22
+
23
+ try: # pragma: no cover - platform specific
24
+ import Quartz # type: ignore
25
+
26
+ _HAVE_QUARTZ = True
27
+ except Exception: # pragma: no cover
28
+ _HAVE_QUARTZ = False
29
+
30
+
31
+ # NSWindow level / collection-behavior constants (avoid importing symbols that
32
+ # may be missing on older pyobjc builds).
33
+ _NS_MAIN_MENU_LEVEL = 24
34
+ _CB_CAN_JOIN_ALL_SPACES = 1 << 0 # 1
35
+ _CB_STATIONARY = 1 << 4 # 16
36
+ _CB_FULLSCREEN_AUX = 1 << 8 # 256
37
+
38
+
39
+ class MacOSBackend(NativeBackend):
40
+ name = "macos"
41
+
42
+ # --- notch ----------------------------------------------------------
43
+ def notch_geometry(self) -> Optional[NotchGeometry]:
44
+ if not _HAVE_APPKIT:
45
+ return None
46
+ try:
47
+ screen = AppKit.NSScreen.mainScreen()
48
+ if screen is None:
49
+ return None
50
+ frame = screen.frame()
51
+ screen_w = int(round(frame.size.width))
52
+ screen_h = int(round(frame.size.height))
53
+
54
+ # safeAreaInsets.top > 0 is the reliable "has notch" signal (macOS 12+).
55
+ top_inset = 0.0
56
+ if screen.respondsToSelector_("safeAreaInsets"):
57
+ try:
58
+ top_inset = float(screen.safeAreaInsets().top)
59
+ except Exception:
60
+ top_inset = 0.0
61
+
62
+ if top_inset <= 0:
63
+ return None # no notch on this display
64
+
65
+ # Width of the notch = screen width minus the two menu-bar wings.
66
+ notch_w = 0
67
+ if screen.respondsToSelector_("auxiliaryTopLeftArea") and screen.respondsToSelector_(
68
+ "auxiliaryTopRightArea"
69
+ ):
70
+ try:
71
+ left = screen.auxiliaryTopLeftArea()
72
+ right = screen.auxiliaryTopRightArea()
73
+ notch_w = int(round(screen_w - left.size.width - right.size.width))
74
+ except Exception:
75
+ notch_w = 0
76
+ if notch_w <= 0:
77
+ notch_w = 210 # sensible default (~14"/16" MBP notch width in pt)
78
+
79
+ notch_h = int(round(top_inset))
80
+ x = (screen_w - notch_w) // 2
81
+ return NotchGeometry(
82
+ x=x,
83
+ y=0,
84
+ width=notch_w,
85
+ height=notch_h,
86
+ screen_width=screen_w,
87
+ screen_height=screen_h,
88
+ )
89
+ except Exception:
90
+ return None
91
+
92
+ # --- always visible -------------------------------------------------
93
+ def configure_always_visible(self, win: object) -> None:
94
+ if not _HAVE_APPKIT or win is None:
95
+ return
96
+ try:
97
+ win_id = int(win.winId()) # NSView* pointer on macOS
98
+ view = objc.objc_object(c_void_p=win_id)
99
+ ns_window = view.window()
100
+ if ns_window is None:
101
+ return
102
+ # Sit just above the menu bar so the island reads as part of the notch.
103
+ ns_window.setLevel_(_NS_MAIN_MENU_LEVEL + 1)
104
+ ns_window.setCollectionBehavior_(
105
+ _CB_CAN_JOIN_ALL_SPACES | _CB_STATIONARY | _CB_FULLSCREEN_AUX
106
+ )
107
+ ns_window.setHidesOnDeactivate_(False)
108
+ try:
109
+ ns_window.setMovableByWindowBackground_(False)
110
+ except Exception:
111
+ pass
112
+ except Exception:
113
+ # Best effort — worst case it behaves like a normal top-most window.
114
+ return
115
+
116
+ # --- window discovery / focus (Quartz + osascript) ------------------
117
+ def list_windows(self, only_visible: bool = True) -> List[WindowInfo]:
118
+ if not _HAVE_QUARTZ:
119
+ return []
120
+ try:
121
+ opts = Quartz.kCGWindowListOptionOnScreenOnly | Quartz.kCGWindowListExcludeDesktopElements
122
+ infos = Quartz.CGWindowListCopyWindowInfo(opts, Quartz.kCGNullWindowID)
123
+ except Exception:
124
+ return []
125
+ out: List[WindowInfo] = []
126
+ for info in infos or []:
127
+ try:
128
+ owner = str(info.get("kCGWindowOwnerName", "") or "")
129
+ title = str(info.get("kCGWindowName", "") or "")
130
+ pid = int(info.get("kCGWindowOwnerPID", 0) or 0)
131
+ except Exception:
132
+ continue
133
+ # handle carries the pid so activate_window can raise the whole app.
134
+ out.append(WindowInfo(handle=pid, title=title or owner, pid=pid, process_name=owner.lower()))
135
+ return out
136
+
137
+ def activate_window(self, handle: object) -> bool:
138
+ pid = handle if isinstance(handle, int) else 0
139
+ if _HAVE_APPKIT and pid:
140
+ try:
141
+ app = AppKit.NSRunningApplication.runningApplicationWithProcessIdentifier_(pid)
142
+ if app is not None:
143
+ # NSApplicationActivateIgnoringOtherApps = 1 << 1
144
+ return bool(app.activateWithOptions_(1 << 1))
145
+ except Exception:
146
+ pass
147
+ return False
148
+
149
+ def jump_to_terminal(self, title_hint: str = "") -> bool:
150
+ # Try the precise window first (needs Screen-Recording perm for titles).
151
+ if super().jump_to_terminal(title_hint):
152
+ return True
153
+ # Fallback: activate a common terminal / editor by app name.
154
+ for app in ("iTerm", "Terminal", "Ghostty", "Warp", "Visual Studio Code", "Cursor"):
155
+ if self._osascript_activate(app):
156
+ return True
157
+ return False
158
+
159
+ def open_terminal(self) -> bool:
160
+ try:
161
+ subprocess.Popen(["open", "-a", "Terminal"])
162
+ return True
163
+ except Exception:
164
+ return False
165
+
166
+ def open_terminal_running(self, command, title="", cwd=None) -> bool:
167
+ import shlex
168
+
169
+ parts = []
170
+ if cwd:
171
+ parts.append(f"cd {shlex.quote(str(cwd))}")
172
+ parts.append(" ".join(shlex.quote(str(c)) for c in command))
173
+ shell_cmd = " && ".join(parts)
174
+ # Prefer iTerm if present, else Terminal.
175
+ script_iterm = (
176
+ 'tell application "iTerm"\n'
177
+ ' create window with default profile\n'
178
+ f' tell current session of current window to write text "{shell_cmd}"\n'
179
+ ' activate\n'
180
+ 'end tell'
181
+ )
182
+ script_term = f'tell application "Terminal" to do script "{shell_cmd}"\ntell application "Terminal" to activate'
183
+ for script in (script_term, script_iterm):
184
+ try:
185
+ r = subprocess.run(["osascript", "-e", script], capture_output=True, timeout=5)
186
+ if r.returncode == 0:
187
+ return True
188
+ except Exception:
189
+ continue
190
+ return False
191
+
192
+ @staticmethod
193
+ def _osascript_activate(app_name: str) -> bool:
194
+ try:
195
+ r = subprocess.run(
196
+ ["osascript", "-e", f'tell application "{app_name}" to activate'],
197
+ capture_output=True,
198
+ timeout=2,
199
+ )
200
+ return r.returncode == 0
201
+ except Exception:
202
+ return False
203
+
204
+ def default_font_family(self) -> str:
205
+ return "SF Pro Text"
@@ -0,0 +1,97 @@
1
+ """Windows (win32) native backend."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import subprocess
6
+ from typing import List, Optional
7
+
8
+ import psutil
9
+ import win32api
10
+ import win32con
11
+ import win32gui
12
+ import win32process
13
+
14
+ from .base import NativeBackend, WindowInfo
15
+
16
+
17
+ class WindowsBackend(NativeBackend):
18
+ name = "windows"
19
+
20
+ def _process_name(self, pid: int) -> str:
21
+ try:
22
+ return psutil.Process(pid).name().lower()
23
+ except Exception:
24
+ return ""
25
+
26
+ def list_windows(self, only_visible: bool = True) -> List[WindowInfo]:
27
+ results: List[WindowInfo] = []
28
+
29
+ def callback(hwnd, _):
30
+ if only_visible and not win32gui.IsWindowVisible(hwnd):
31
+ return
32
+ title = win32gui.GetWindowText(hwnd).strip()
33
+ if not title:
34
+ return
35
+ _, pid = win32process.GetWindowThreadProcessId(hwnd)
36
+ results.append(WindowInfo(hwnd, title, pid, self._process_name(pid)))
37
+
38
+ win32gui.EnumWindows(callback, None)
39
+ return results
40
+
41
+ def activate_window(self, handle: object) -> bool:
42
+ hwnd = handle
43
+ try:
44
+ if win32gui.IsIconic(hwnd):
45
+ win32gui.ShowWindow(hwnd, win32con.SW_RESTORE)
46
+ current_thread = win32api.GetCurrentThreadId()
47
+ target_thread, _ = win32process.GetWindowThreadProcessId(hwnd)
48
+ attached = False
49
+ if current_thread != target_thread:
50
+ try:
51
+ win32process.AttachThreadInput(current_thread, target_thread, True)
52
+ attached = True
53
+ except Exception:
54
+ attached = False
55
+ try:
56
+ win32gui.ShowWindow(hwnd, win32con.SW_SHOW)
57
+ win32gui.BringWindowToTop(hwnd)
58
+ win32gui.SetForegroundWindow(hwnd)
59
+ except Exception:
60
+ # Windows often blocks SetForegroundWindow from a background
61
+ # thread — not fatal; the user can still alt-tab.
62
+ pass
63
+ finally:
64
+ if attached:
65
+ try:
66
+ win32process.AttachThreadInput(current_thread, target_thread, False)
67
+ except Exception:
68
+ pass
69
+ return True
70
+ except Exception:
71
+ return False
72
+
73
+ def open_terminal(self) -> bool:
74
+ for exe in ("wt.exe", "cmd.exe"):
75
+ try:
76
+ subprocess.Popen([exe], shell=False, creationflags=subprocess.CREATE_NEW_CONSOLE)
77
+ return True
78
+ except Exception:
79
+ continue
80
+ return False
81
+
82
+ def open_terminal_running(self, command, title="", cwd=None) -> bool:
83
+ # CREATE_NEW_CONSOLE gives the child its own console with a live stdin,
84
+ # so the wrapper can pipe the agent and inject Allow/Deny.
85
+ try:
86
+ subprocess.Popen(
87
+ list(command),
88
+ cwd=cwd,
89
+ creationflags=subprocess.CREATE_NEW_CONSOLE,
90
+ )
91
+ return True
92
+ except Exception as exc:
93
+ print(f"[native] open_terminal_running failed: {exc}")
94
+ return False
95
+
96
+ def default_font_family(self) -> str:
97
+ return "Segoe UI"