foxygit / RPA-Remisser Log in
commits tags

/rpa/desktop_inspect.py · 3.48 KB

raw
"""Inspektera Windows-fönster för att hitta rätt selectors till desktop_*-stegen
(motsvarigheten till rpa/discover.py fast för skrivbordsappar).

    python -m rpa.desktop_inspect                 # listar synliga toppfönster
    python -m rpa.desktop_inspect "Anteckningar"  # dumpar kontroll-identifierare

Kör på Windows-maskinen med målappen igång. Använd 'name'/'auto_id'/
'control_type' från utskriften i webui:ts desktop-steg (webui:t har samma
funktion under ⚙ → Inspektera fönster).
"""

from __future__ import annotations

import sys


class InspectUnavailable(RuntimeError):
    """pywinauto kunde inte laddas (icke-Windows?)."""


def _desktop():
    try:
        from pywinauto import Desktop
    except Exception as exc:  # noqa: BLE001
        raise InspectUnavailable(
            f"pywinauto kunde inte laddas (Windows-only): {exc}"
        ) from exc
    return Desktop(backend="uia")


def list_windows() -> list[str]:
    """Titlarna på alla synliga toppfönster."""
    out: list[str] = []
    for w in _desktop().windows():
        try:
            if w.is_visible() and w.window_text():
                out.append(w.window_text())
        except Exception:  # noqa: BLE001
            continue
    # unika, i ordning
    seen: set[str] = set()
    return [t for t in out if not (t in seen or seen.add(t))]


def inspect_window(title: str, max_depth: int = 6, limit: int = 800) -> list[dict]:
    """Plattad lista över kontrollerna i första fönstret vars titel matchar
    `title` (regex). Varje rad: {depth, control_type, name, auto_id, class_name}."""
    desktop = _desktop()
    matches = desktop.windows(title_re=title, visible_only=True)
    if not matches:
        raise ValueError(f"Hittade inget synligt fönster med titel som matchar {title!r}")
    win = desktop.window(handle=matches[0].handle)
    win.wait("exists", timeout=10)

    rows: list[dict] = []

    def walk(ctrl, depth: int) -> None:
        if len(rows) >= limit or depth > max_depth:
            return
        try:
            info = ctrl.element_info
            rows.append({
                "depth": depth,
                "control_type": getattr(info, "control_type", "") or "",
                "name": (ctrl.window_text() or "").strip(),
                "auto_id": getattr(info, "automation_id", "") or "",
                "class_name": getattr(info, "class_name", "") or "",
            })
        except Exception:  # noqa: BLE001
            return
        try:
            for child in ctrl.children():
                walk(child, depth + 1)
        except Exception:  # noqa: BLE001
            pass

    walk(win, 0)
    return rows


def main() -> None:
    try:
        if len(sys.argv) < 2:
            wins = list_windows()
            print("Synliga toppfönster:\n")
            for t in wins:
                print(f"  {t!r}")
            print('\nKör igen med en titel (regex), t.ex.:\n  python -m rpa.desktop_inspect "Anteckningar"')
            return

        for row in inspect_window(sys.argv[1]):
            indent = "  " * row["depth"]
            bits = [f'{indent}{row["control_type"] or "?"}']
            if row["name"]:
                bits.append(f'name={row["name"]!r}')
            if row["auto_id"]:
                bits.append(f'auto_id={row["auto_id"]!r}')
            if row["class_name"]:
                bits.append(f'class={row["class_name"]!r}')
            print("  ".join(bits))
    except (InspectUnavailable, ValueError) as exc:
        sys.exit(str(exc))


if __name__ == "__main__":
    main()