Files
wehub-resource-sync 91e75e620b
CI: cua-driver distro-compat matrix / debian:12 (glibc 2.36) (push) Has been cancelled
CI: SPDX Headers / Check SPDX headers (warn-only) (push) Has been cancelled
CD: Docs MCP Server / build (linux/amd64) (push) Has been cancelled
CD: Docs MCP Server / build (linux/arm64) (push) Has been cancelled
CD: Docs MCP Server / merge (push) Has been cancelled
CI: cua-driver distro-compat matrix / Resolve release version (push) Has been cancelled
CI: cua-driver distro-compat matrix / fedora:41 (glibc 2.40) (push) Has been cancelled
CI: cua-driver distro-compat matrix / rockylinux:9 (glibc 2.34) (push) Has been cancelled
CI: cua-driver distro-compat matrix / ubuntu:22.04 (glibc 2.35) (push) Has been cancelled
CI: cua-driver distro-compat matrix / ubuntu:24.04 (glibc 2.39) (push) Has been cancelled
CI: cua-driver distro-compat matrix / Distro compat summary (push) Has been cancelled
CI: Rust Linux unit / Rust Linux unit and compile (push) Has been cancelled
CI: Rust Windows unit / Rust Windows unit and compile (push) Has been cancelled
CI: Nix Linux Rust source / Nix / compositor build (push) Has been cancelled
CI: Nix Linux Rust source / Nix / driver package (push) Has been cancelled
CI: Nix Linux Rust source / Nix / Rust unit tests (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 13:03:19 +08:00

189 lines
5.6 KiB
Python

"""Cross-platform window management via pywinctl."""
import os
import platform
import subprocess
import webbrowser
from typing import Any, List, Optional, Tuple
try:
import pywinctl as _pwc # type: ignore[import-untyped]
except Exception:
_pwc = None # type: ignore[assignment]
def _require_pwc() -> Any:
if _pwc is None:
raise RuntimeError("pywinctl is not available. Install it with: pip install pywinctl")
return _pwc
def _get_by_handle(handle: Any) -> Optional[Any]:
"""Find a pywinctl window object by its native handle."""
pwc = _require_pwc()
try:
for w in pwc.getAllWindows():
if str(w.getHandle()) == str(handle):
return w
except Exception:
pass
return None
# ── Active window ─────────────────────────────────────────────────────────────
def get_active_window() -> Optional[Any]:
"""Return the pywinctl window object for the currently focused window."""
pwc = _require_pwc()
return pwc.getActiveWindow()
def get_active_window_title() -> str:
"""Return the title of the currently focused window, or 'Desktop'."""
try:
win = get_active_window()
if win:
return win.title or "Desktop"
except Exception:
pass
return "Desktop"
def get_active_window_handle() -> Optional[str]:
"""Return the native handle of the active window as a string, or None."""
try:
win = get_active_window()
if win:
return str(win.getHandle())
except Exception:
pass
return None
# ── Window lookup ─────────────────────────────────────────────────────────────
def get_windows_with_title(title: str) -> List[str]:
"""Return a list of native handle strings for windows whose title contains *title*."""
pwc = _require_pwc()
try:
wins = pwc.getWindowsWithTitle(title, condition=pwc.Re.CONTAINS, flags=pwc.Re.IGNORECASE)
return [str(w.getHandle()) for w in wins]
except Exception:
return []
def get_window_name(handle: Any) -> Optional[str]:
"""Return the title of the window with the given handle, or None."""
w = _get_by_handle(handle)
return w.title if w else None
def get_window_size(handle: Any) -> Optional[Tuple[int, int]]:
"""Return (width, height) of the window, or None if not found."""
w = _get_by_handle(handle)
if not w:
return None
width, height = w.size
return int(width), int(height)
def get_window_position(handle: Any) -> Optional[Tuple[int, int]]:
"""Return (x, y) position of the window, or None if not found."""
w = _get_by_handle(handle)
if not w:
return None
x, y = w.position
return int(x), int(y)
# ── Window actions ────────────────────────────────────────────────────────────
def activate_window(handle: Any) -> bool:
"""Bring the window to the foreground and give it focus."""
w = _get_by_handle(handle)
if not w:
return False
return bool(w.activate())
def minimize_window(handle: Any) -> bool:
"""Minimize the window."""
w = _get_by_handle(handle)
if not w:
return False
return bool(w.minimize())
def maximize_window(handle: Any) -> bool:
"""Maximize the window."""
w = _get_by_handle(handle)
if not w:
return False
return bool(w.maximize())
def close_window(handle: Any) -> bool:
"""Close the window."""
w = _get_by_handle(handle)
if not w:
return False
return bool(w.close())
def set_window_size(handle: Any, width: int, height: int) -> bool:
"""Resize the window to (width, height)."""
w = _get_by_handle(handle)
if not w:
return False
return bool(w.resizeTo(int(width), int(height)))
def set_window_position(handle: Any, x: int, y: int) -> bool:
"""Move the window to (x, y)."""
w = _get_by_handle(handle)
if not w:
return False
return bool(w.moveTo(int(x), int(y)))
# ── Open / launch ─────────────────────────────────────────────────────────────
def open(target: str) -> bool: # noqa: A001
"""Open a URL or file path with the default application.
URLs are opened in the default browser. Files are opened with the OS
default handler (``open`` on macOS, ``xdg-open`` on Linux,
``os.startfile`` on Windows).
"""
if target.startswith("http://") or target.startswith("https://"):
return bool(webbrowser.open(target))
sys = platform.system().lower()
if sys == "darwin":
subprocess.Popen(["open", target])
elif sys == "linux":
subprocess.Popen(["xdg-open", target])
elif sys == "windows":
os.startfile(target) # type: ignore[attr-defined]
else:
raise RuntimeError(f"Unsupported OS: {sys}")
return True
def launch(app: str, args: Optional[List[str]] = None) -> int:
"""Launch an application and return its PID.
If *args* is given, the app is launched with those arguments.
Otherwise the command string is passed to the system shell, allowing
strings like ``"libreoffice --writer"``.
"""
if args:
proc = subprocess.Popen([app, *args])
else:
proc = subprocess.Popen(app, shell=True)
return proc.pid