"""Bring the browser to the front before clicking. macOS (and Windows, to a lesser degree) treats a click on an unfocused window as an activation gesture: the click raises the window and is swallowed there, never reaching the control underneath. So an automated click against a background Chrome does nothing at all the first time, then works on the second attempt — which looks like a flaky clicker but is really the window manager doing its job. The extension already calls chrome.windows.update({focused: true}), but that only orders windows *within* Chrome. If the frontmost application is your terminal — which it is, because that's where this script was launched — Chrome as a whole is still in the background. This module raises the application itself. """ import subprocess import sys import time # Chrome ships under several bundle ids; accept whichever is installed. MAC_BUNDLES = ( "com.google.Chrome", "com.google.Chrome.beta", "com.google.Chrome.dev", "com.google.Chrome.canary", "com.brave.Browser", "com.microsoft.edgemac", ) SETTLE = 0.20 # let the window manager finish raising before measuring or clicking class FocusResult: def __init__(self, ok: bool, detail: str): self.ok = ok self.detail = detail def __bool__(self) -> bool: return self.ok def _mac_workspace(): """NSWorkspace via pyobjc. Unlike AppleScript this needs no Automation permission — activating an app is not scripting it.""" try: from AppKit import NSWorkspace except ImportError: return None return NSWorkspace.sharedWorkspace() def frontmost() -> str | None: """Bundle id (macOS) or process name of the frontmost application.""" if sys.platform == "darwin": ws = _mac_workspace() if ws is None: return None app = ws.frontmostApplication() return app.bundleIdentifier() if app else None return None def _mac_activate() -> FocusResult: ws = _mac_workspace() if ws is None: # pyobjc's AppKit isn't present. osascript works but may prompt for # Automation permission the first time. try: subprocess.run( ["osascript", "-e", 'tell application "Google Chrome" to activate'], check=True, capture_output=True, timeout=5, ) return FocusResult(True, "activated via osascript") except Exception as exc: return FocusResult(False, f"could not activate Chrome ({exc})") running = {a.bundleIdentifier(): a for a in ws.runningApplications()} for bundle in MAC_BUNDLES: app = running.get(bundle) if app is None: continue # NSApplicationActivateIgnoringOtherApps — take focus even though the # terminal currently owns it. app.activateWithOptions_(1 << 1) return FocusResult(True, f"activated {bundle}") return FocusResult(False, "no Chrome-family browser is running") def _other_activate() -> FocusResult: """Windows/Linux: pyautogui already depends on pygetwindow, so use it.""" try: import pygetwindow except ImportError: return FocusResult(False, "pygetwindow unavailable — cannot raise the browser") wins = [w for w in pygetwindow.getAllWindows() if w.title and "Chrome" in w.title and w.visible] if not wins: return FocusResult(False, "no Chrome window found") try: win = wins[0] if getattr(win, "isMinimized", False): win.restore() win.activate() return FocusResult(True, f"activated window {win.title[:40]!r}") except Exception as exc: return FocusResult(False, f"could not activate window ({exc})") def activate_browser() -> FocusResult: """Raise the browser application above everything else.""" result = _mac_activate() if sys.platform == "darwin" else _other_activate() if result.ok: time.sleep(SETTLE) return result def ensure_frontmost(timeout: float = 1.5) -> FocusResult: """Raise the browser and, where we can check, confirm it actually came forward. Returning ok=False does not mean the click will fail — only that we could not verify. The caller decides whether to proceed. """ result = activate_browser() if not result.ok: return result if sys.platform != "darwin": return result # no cheap way to verify; activation call succeeded deadline = time.monotonic() + timeout while time.monotonic() < deadline: front = frontmost() if front is None: return FocusResult(True, result.detail + " (unverified)") if front in MAC_BUNDLES: return FocusResult(True, f"{front} is frontmost") time.sleep(0.05) return FocusResult(False, f"browser did not come to the front (frontmost is {frontmost()})")