chore: archive tools/ui_inspector snapshot before migration removal.
Keep the last in-template copy on main so history can restore it; product continues in jiangchang-ui-recorder. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
79
tools/ui_inspector/recorder/base.py
Normal file
79
tools/ui_inspector/recorder/base.py
Normal file
@@ -0,0 +1,79 @@
|
||||
"""Recorder abstractions."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import threading
|
||||
from abc import ABC, abstractmethod
|
||||
from dataclasses import dataclass, field
|
||||
from datetime import datetime
|
||||
from typing import Any, Callable, Optional
|
||||
|
||||
|
||||
def _now_iso() -> str:
|
||||
return datetime.now().astimezone().isoformat()
|
||||
|
||||
|
||||
@dataclass
|
||||
class RawUserEvent:
|
||||
kind: str # mouse_down | mouse_up | key | input | navigation | focus | checkpoint
|
||||
surface: str
|
||||
timestamp: str = field(default_factory=_now_iso)
|
||||
button: str = ""
|
||||
key: str = ""
|
||||
text: str = ""
|
||||
x: Optional[int] = None
|
||||
y: Optional[int] = None
|
||||
target: dict[str, Any] = field(default_factory=dict)
|
||||
frame: dict[str, Any] = field(default_factory=dict)
|
||||
url: str = ""
|
||||
meta: dict[str, Any] = field(default_factory=dict)
|
||||
from_inspector: bool = False
|
||||
|
||||
|
||||
class RecorderState:
|
||||
IDLE = "idle"
|
||||
RECORDING = "recording"
|
||||
PAUSED = "paused"
|
||||
STOPPED = "stopped"
|
||||
|
||||
|
||||
class RecorderBase(ABC):
|
||||
"""Process-external observer. No injection / hooks into target."""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
*,
|
||||
on_event: Optional[Callable[[RawUserEvent], None]] = None,
|
||||
exclude_hwnds: Optional[Callable[[], set[int]]] = None,
|
||||
) -> None:
|
||||
self.on_event = on_event
|
||||
self.exclude_hwnds = exclude_hwnds or (lambda: set())
|
||||
self.status = RecorderState.IDLE
|
||||
self._thread: Optional[threading.Thread] = None
|
||||
self._stop = threading.Event()
|
||||
|
||||
def emit(self, event: RawUserEvent) -> None:
|
||||
if self.status != RecorderState.RECORDING:
|
||||
return
|
||||
if event.from_inspector:
|
||||
return
|
||||
if self.on_event:
|
||||
self.on_event(event)
|
||||
|
||||
@abstractmethod
|
||||
def start(self) -> None: ...
|
||||
|
||||
@abstractmethod
|
||||
def stop(self) -> None: ...
|
||||
|
||||
def pause(self) -> None:
|
||||
if self.status == RecorderState.RECORDING:
|
||||
self.status = RecorderState.PAUSED
|
||||
|
||||
def resume(self) -> None:
|
||||
if self.status == RecorderState.PAUSED:
|
||||
self.status = RecorderState.RECORDING
|
||||
|
||||
@property
|
||||
def capability(self) -> dict[str, str]:
|
||||
return {}
|
||||
Reference in New Issue
Block a user