Suppression du .git embarqué dans agent_v0/ — le code est maintenant tracké normalement dans le repo principal. Inclut : agent_v1 (client), server_v1 (streaming), lea_ui (chat client) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
193 lines
5.1 KiB
Python
193 lines
5.1 KiB
Python
# window_info_crossplatform.py
|
|
"""
|
|
Récupération des informations sur la fenêtre active - CROSS-PLATFORM
|
|
|
|
Supporte:
|
|
- Linux (X11 via xdotool)
|
|
- Windows (via pywin32)
|
|
- macOS (via pyobjc)
|
|
|
|
Installation des dépendances:
|
|
pip install pywin32 # Windows
|
|
pip install pyobjc-framework-Cocoa # macOS
|
|
pip install psutil # Tous OS
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import platform
|
|
import subprocess
|
|
from typing import Dict, Optional
|
|
|
|
|
|
def _run_cmd(cmd: list[str]) -> Optional[str]:
|
|
"""Exécute une commande et renvoie la sortie texte (strippée), ou None en cas d'erreur."""
|
|
try:
|
|
out = subprocess.check_output(cmd, stderr=subprocess.DEVNULL)
|
|
return out.decode("utf-8", errors="ignore").strip()
|
|
except Exception:
|
|
return None
|
|
|
|
|
|
def get_active_window_info() -> Dict[str, str]:
|
|
"""
|
|
Renvoie un dict :
|
|
{
|
|
"title": "...",
|
|
"app_name": "..."
|
|
}
|
|
|
|
Détecte automatiquement l'OS et utilise la méthode appropriée.
|
|
"""
|
|
system = platform.system()
|
|
|
|
if system == "Linux":
|
|
return _get_window_info_linux()
|
|
elif system == "Windows":
|
|
return _get_window_info_windows()
|
|
elif system == "Darwin": # macOS
|
|
return _get_window_info_macos()
|
|
else:
|
|
return {"title": "unknown_window", "app_name": "unknown_app"}
|
|
|
|
|
|
def _get_window_info_linux() -> Dict[str, str]:
|
|
"""
|
|
Linux: utilise xdotool (X11)
|
|
|
|
Nécessite: sudo apt-get install xdotool
|
|
"""
|
|
title = _run_cmd(["xdotool", "getactivewindow", "getwindowname"])
|
|
pid_str = _run_cmd(["xdotool", "getactivewindow", "getwindowpid"])
|
|
|
|
app_name: Optional[str] = None
|
|
if pid_str:
|
|
pid_str = pid_str.strip()
|
|
# On récupère le nom du binaire via ps
|
|
app_name = _run_cmd(["ps", "-p", pid_str, "-o", "comm="])
|
|
|
|
if not title:
|
|
title = "unknown_window"
|
|
if not app_name:
|
|
app_name = "unknown_app"
|
|
|
|
return {
|
|
"title": title,
|
|
"app_name": app_name,
|
|
}
|
|
|
|
|
|
def _get_window_info_windows() -> Dict[str, str]:
|
|
"""
|
|
Windows: utilise pywin32 + psutil
|
|
|
|
Nécessite: pip install pywin32 psutil
|
|
"""
|
|
try:
|
|
import win32gui
|
|
import win32process
|
|
import psutil
|
|
|
|
# Fenêtre au premier plan
|
|
hwnd = win32gui.GetForegroundWindow()
|
|
|
|
# Titre de la fenêtre
|
|
title = win32gui.GetWindowText(hwnd)
|
|
if not title:
|
|
title = "unknown_window"
|
|
|
|
# PID du processus
|
|
_, pid = win32process.GetWindowThreadProcessId(hwnd)
|
|
|
|
# Nom du processus
|
|
try:
|
|
process = psutil.Process(pid)
|
|
app_name = process.name()
|
|
except (psutil.NoSuchProcess, psutil.AccessDenied):
|
|
app_name = "unknown_app"
|
|
|
|
return {
|
|
"title": title,
|
|
"app_name": app_name,
|
|
}
|
|
|
|
except ImportError:
|
|
# pywin32 ou psutil non installé
|
|
return {
|
|
"title": "unknown_window (pywin32 missing)",
|
|
"app_name": "unknown_app (pywin32 missing)",
|
|
}
|
|
except Exception as e:
|
|
return {
|
|
"title": f"error: {e}",
|
|
"app_name": "unknown_app",
|
|
}
|
|
|
|
|
|
def _get_window_info_macos() -> Dict[str, str]:
|
|
"""
|
|
macOS: utilise pyobjc (AppKit)
|
|
|
|
Nécessite: pip install pyobjc-framework-Cocoa
|
|
|
|
Note: Nécessite les permissions "Accessibility" dans System Preferences
|
|
"""
|
|
try:
|
|
from AppKit import NSWorkspace
|
|
from Quartz import (
|
|
CGWindowListCopyWindowInfo,
|
|
kCGWindowListOptionOnScreenOnly,
|
|
kCGNullWindowID
|
|
)
|
|
|
|
# Application active
|
|
active_app = NSWorkspace.sharedWorkspace().activeApplication()
|
|
app_name = active_app.get('NSApplicationName', 'unknown_app')
|
|
|
|
# Titre de la fenêtre (via Quartz)
|
|
# On cherche la fenêtre de l'app active qui est au premier plan
|
|
window_list = CGWindowListCopyWindowInfo(
|
|
kCGWindowListOptionOnScreenOnly,
|
|
kCGNullWindowID
|
|
)
|
|
|
|
title = "unknown_window"
|
|
for window in window_list:
|
|
owner_name = window.get('kCGWindowOwnerName', '')
|
|
if owner_name == app_name:
|
|
window_title = window.get('kCGWindowName', '')
|
|
if window_title:
|
|
title = window_title
|
|
break
|
|
|
|
return {
|
|
"title": title,
|
|
"app_name": app_name,
|
|
}
|
|
|
|
except ImportError:
|
|
# pyobjc non installé
|
|
return {
|
|
"title": "unknown_window (pyobjc missing)",
|
|
"app_name": "unknown_app (pyobjc missing)",
|
|
}
|
|
except Exception as e:
|
|
return {
|
|
"title": f"error: {e}",
|
|
"app_name": "unknown_app",
|
|
}
|
|
|
|
|
|
# Test rapide
|
|
if __name__ == "__main__":
|
|
import time
|
|
|
|
print(f"OS détecté: {platform.system()}")
|
|
print("\nTest de capture fenêtre active (5 secondes)...")
|
|
print("Changez de fenêtre pour tester!\n")
|
|
|
|
for i in range(5):
|
|
info = get_active_window_info()
|
|
print(f"[{i+1}] App: {info['app_name']:20s} | Title: {info['title']}")
|
|
time.sleep(1)
|