commits
tags
"""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("<ButtonPress-1>", on_press)
canvas.bind("<B1-Motion>", on_drag)
canvas.bind("<ButtonRelease-1>", on_release)
root.bind("<Escape>", 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()