"""Fånga ett skärmområde som referensbild till desktop_click_image / desktop_wait_for_image. python -m rpa.desktop_capture # auto-namn (tidsstämpel) python -m rpa.desktop_capture knappen_ok # sparar data/desktop_images/knappen_ok.png Öppnar ett halvgenomskinligt heltäckande lager – dra en ruta över det du vill fånga, släpp musknappen. Esc avbryter. Skriver en rad JSON till stdout så webui:t kan visa resultatet. Windows-only (tkinter-overlay + PIL ImageGrab). Fångar hela det virtuella skrivbordet (flera skärmar stöds). """ from __future__ import annotations import json import re import sys import unicodedata from datetime import datetime from . import config _BAD = re.compile(r'[<>:"/\\|?*\x00-\x1f]') def _safe_name(name: str) -> str: name = unicodedata.normalize("NFC", (name or "").strip()) name = _BAD.sub("_", name).strip(". ") if name.lower().endswith(".png"): name = name[:-4] return name or datetime.now().strftime("bild_%Y%m%d_%H%M%S") def capture(name: str | None = None) -> dict: try: import tkinter as tk from PIL import ImageGrab except Exception as exc: # noqa: BLE001 # pragma: no cover return {"ok": False, "reason": f"tkinter/PIL saknas: {exc}"} # DPI-medveten så koordinaterna matchar de faktiska pixlarna. try: # pragma: no cover - Windows-specifikt import ctypes ctypes.windll.shcore.SetProcessDpiAwareness(2) except Exception: # noqa: BLE001 pass try: # virtuella skrivbordets bounds (alla skärmar) import ctypes u = ctypes.windll.user32 vx, vy = u.GetSystemMetrics(76), u.GetSystemMetrics(77) vw, vh = u.GetSystemMetrics(78), u.GetSystemMetrics(79) except Exception: # noqa: BLE001 vx = vy = 0 vw = vh = None state: dict = {"start": None, "bbox": None, "cancelled": False} root = tk.Tk() root.title("Fånga bild") if vw: root.overrideredirect(True) root.geometry(f"{vw}x{vh}+{vx}+{vy}") else: root.attributes("-fullscreen", True) root.attributes("-alpha", 0.3) root.attributes("-topmost", True) root.configure(bg="black") canvas = tk.Canvas(root, cursor="crosshair", bg="gray12", highlightthickness=0) canvas.pack(fill="both", expand=True) canvas.create_text( (vw or root.winfo_screenwidth()) // 2, 30, text="Dra en ruta över det du vill fånga · Esc = avbryt", fill="white", font=("Segoe UI", 16), ) rect = canvas.create_rectangle(0, 0, 0, 0, outline="#ff3b30", width=2) def on_press(e): state["start"] = (e.x, e.y) def on_drag(e): if state["start"]: x0, y0 = state["start"] canvas.coords(rect, x0, y0, e.x, e.y) def on_release(e): if not state["start"]: return x0, y0 = state["start"] x1, y1 = e.x, e.y state["bbox"] = ( vx + min(x0, x1), vy + min(y0, y1), vx + max(x0, x1), vy + max(y0, y1), ) root.destroy() def on_escape(_e): state["cancelled"] = True root.destroy() canvas.bind("", on_press) canvas.bind("", on_drag) canvas.bind("", on_release) root.bind("", on_escape) root.after(100, lambda: root.focus_force()) root.mainloop() if state["cancelled"] or not state["bbox"]: return {"ok": False, "reason": "cancelled"} x0, y0, x1, y1 = state["bbox"] if x1 - x0 < 4 or y1 - y0 < 4: return {"ok": False, "reason": "för liten ruta"} config.DESKTOP_IMAGE_DIR.mkdir(parents=True, exist_ok=True) target = config.DESKTOP_IMAGE_DIR / f"{_safe_name(name)}.png" img = ImageGrab.grab(bbox=(x0, y0, x1, y1), all_screens=True) img.save(str(target)) return { "ok": True, "name": target.name, "path": str(target), "width": img.width, "height": img.height, } def main() -> None: name = sys.argv[1] if len(sys.argv) > 1 else None result = capture(name) print(json.dumps(result, ensure_ascii=False)) sys.exit(0 if result.get("ok") else 1) if __name__ == "__main__": main()