Upload
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,184 @@
|
||||
"""What's on screen right now — the active window's title, and whether
|
||||
something is running fullscreen.
|
||||
|
||||
Two consumers:
|
||||
* annotate() tacks the focused window's title onto what you said, so
|
||||
"what's this error?" has a referent without you describing it (the desk
|
||||
API takes text only, so this is a text annotation — no screenshot upload).
|
||||
* is_fullscreen_active() feeds do-not-disturb: the pet shouldn't announce
|
||||
anything over a call or a fullscreen game.
|
||||
|
||||
Everything here is best-effort and must never raise: on a locked-down Wayland
|
||||
session none of it is available, and the correct behaviour is simply "no
|
||||
context", not a crashed pipeline. The subprocess *parsing* is split into pure
|
||||
functions so it can be tested without a display server.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import platform
|
||||
import re
|
||||
import shutil
|
||||
import subprocess
|
||||
from typing import Optional
|
||||
|
||||
_TIMEOUT = 2.0
|
||||
_MAX_TITLE_CHARS = 160
|
||||
|
||||
# Titles that are just the desktop itself — annotating with these is noise.
|
||||
_BORING_TITLES = {"", "desktop", "@!0,0;bdib", "plasmashell", "gnome-shell", "xfdesktop"}
|
||||
|
||||
|
||||
def _run(args: list[str]) -> Optional[str]:
|
||||
try:
|
||||
completed = subprocess.run(args, capture_output=True, text=True, timeout=_TIMEOUT)
|
||||
except Exception:
|
||||
return None
|
||||
if completed.returncode != 0:
|
||||
return None
|
||||
return completed.stdout
|
||||
|
||||
|
||||
# ── pure parsing helpers (unit tested; no display server needed) ─────────────
|
||||
|
||||
def parse_xprop_window_id(output: str) -> Optional[str]:
|
||||
"""`xprop -root _NET_ACTIVE_WINDOW` -> '_NET_ACTIVE_WINDOW(WINDOW): window id # 0x3c00007'"""
|
||||
match = re.search(r"(0x[0-9a-fA-F]+)", output or "")
|
||||
if not match or int(match.group(1), 16) == 0:
|
||||
return None
|
||||
return match.group(1)
|
||||
|
||||
|
||||
def parse_xprop_window_name(output: str) -> Optional[str]:
|
||||
"""`xprop -id <id> _NET_WM_NAME` -> '_NET_WM_NAME(UTF8_STRING) = "Firefox"'"""
|
||||
match = re.search(r'=\s*"(.*)"\s*$', (output or "").strip(), re.DOTALL)
|
||||
if not match:
|
||||
return None
|
||||
return match.group(1).strip()
|
||||
|
||||
|
||||
def parse_xprop_fullscreen(output: str) -> bool:
|
||||
return "_NET_WM_STATE_FULLSCREEN" in (output or "")
|
||||
|
||||
|
||||
def clean_title(title: Optional[str]) -> Optional[str]:
|
||||
title = (title or "").strip().replace("\n", " ")
|
||||
if title.lower() in _BORING_TITLES:
|
||||
return None
|
||||
if len(title) > _MAX_TITLE_CHARS:
|
||||
title = title[: _MAX_TITLE_CHARS - 1].rstrip() + "…"
|
||||
return title or None
|
||||
|
||||
|
||||
def annotate(text: str, title: Optional[str]) -> str:
|
||||
"""Attach the window title as an explicit aside rather than splicing it
|
||||
into the sentence, so the model can ignore it when it's irrelevant."""
|
||||
text = (text or "").strip()
|
||||
title = clean_title(title)
|
||||
if not text or not title:
|
||||
return text
|
||||
return f"{text}\n\n[on screen right now: {title}]"
|
||||
|
||||
|
||||
# ── platform probes ──────────────────────────────────────────────────────────
|
||||
|
||||
def _linux_active_window_id() -> Optional[str]:
|
||||
if not shutil.which("xprop"):
|
||||
return None
|
||||
output = _run(["xprop", "-root", "_NET_ACTIVE_WINDOW"])
|
||||
return parse_xprop_window_id(output or "")
|
||||
|
||||
|
||||
def _linux_title() -> Optional[str]:
|
||||
if shutil.which("xdotool"):
|
||||
output = _run(["xdotool", "getactivewindow", "getwindowname"])
|
||||
if output and output.strip():
|
||||
return output.strip()
|
||||
window_id = _linux_active_window_id()
|
||||
if window_id is None:
|
||||
return None
|
||||
for prop in ("_NET_WM_NAME", "WM_NAME"):
|
||||
output = _run(["xprop", "-id", window_id, prop])
|
||||
title = parse_xprop_window_name(output or "")
|
||||
if title:
|
||||
return title
|
||||
return None
|
||||
|
||||
|
||||
def _windows_title() -> Optional[str]:
|
||||
try:
|
||||
import ctypes
|
||||
|
||||
user32 = ctypes.windll.user32
|
||||
handle = user32.GetForegroundWindow()
|
||||
if not handle:
|
||||
return None
|
||||
length = user32.GetWindowTextLengthW(handle)
|
||||
buffer = ctypes.create_unicode_buffer(length + 1)
|
||||
user32.GetWindowTextW(handle, buffer, length + 1)
|
||||
return buffer.value or None
|
||||
except Exception:
|
||||
return None
|
||||
|
||||
|
||||
def _macos_title() -> Optional[str]:
|
||||
script = (
|
||||
'tell application "System Events" to get name of first application process '
|
||||
"whose frontmost is true"
|
||||
)
|
||||
output = _run(["osascript", "-e", script])
|
||||
return (output or "").strip() or None
|
||||
|
||||
|
||||
def active_window_title() -> Optional[str]:
|
||||
"""Focused window's title, or None if the platform won't tell us."""
|
||||
try:
|
||||
system = platform.system()
|
||||
if system == "Linux":
|
||||
return clean_title(_linux_title())
|
||||
if system == "Windows":
|
||||
return clean_title(_windows_title())
|
||||
if system == "Darwin":
|
||||
return clean_title(_macos_title())
|
||||
except Exception:
|
||||
pass
|
||||
return None
|
||||
|
||||
|
||||
def is_fullscreen_active() -> bool:
|
||||
"""True when the focused window is fullscreen (call, game, presentation).
|
||||
False whenever we can't tell — do-not-disturb should be something you opt
|
||||
into, not something a failed probe turns on."""
|
||||
try:
|
||||
system = platform.system()
|
||||
if system == "Linux":
|
||||
window_id = _linux_active_window_id()
|
||||
if window_id is None:
|
||||
return False
|
||||
return parse_xprop_fullscreen(_run(["xprop", "-id", window_id, "_NET_WM_STATE"]) or "")
|
||||
if system == "Windows":
|
||||
import ctypes
|
||||
from ctypes import wintypes
|
||||
|
||||
user32 = ctypes.windll.user32
|
||||
handle = user32.GetForegroundWindow()
|
||||
if not handle:
|
||||
return False
|
||||
rect = wintypes.RECT()
|
||||
user32.GetWindowRect(handle, ctypes.byref(rect))
|
||||
screen_w = user32.GetSystemMetrics(0)
|
||||
screen_h = user32.GetSystemMetrics(1)
|
||||
return (rect.right - rect.left) >= screen_w and (rect.bottom - rect.top) >= screen_h
|
||||
except Exception:
|
||||
pass
|
||||
return False
|
||||
|
||||
|
||||
def context_for(text: str) -> str:
|
||||
"""What the controller sends: *text* plus the active window title, when
|
||||
SCREEN_CONTEXT is on and there's a title worth mentioning."""
|
||||
from . import config
|
||||
|
||||
if not config.SCREEN_CONTEXT:
|
||||
return text
|
||||
return annotate(text, active_window_title())
|
||||
Reference in New Issue
Block a user