Add ADB-based Android screen capture engine with display picker integration

New scrcpy/ADB capture engine that captures Android device screens over
ADB using screencap polling. Supports USB and WiFi ADB connections with
device auto-discovery. Engine-aware display picker shows Android devices
when scrcpy engine is selected, with inline ADB connect form for WiFi
devices.

Key changes:
- New scrcpy_engine.py using adb screencap polling (~1-2 FPS over WiFi)
- Engine-aware GET /config/displays?engine_type= API
- ADB connect/disconnect API endpoints (POST /adb/connect, /adb/disconnect)
- Display picker supports engine-specific device lists
- Stream/test modals pass engine type to display picker
- Test template handler changed to sync def to prevent event loop blocking
- Restart script merges registry PATH for newly-installed tools
- All engines (including unavailable) shown in engine list with status flag

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-02-23 18:06:15 +03:00
parent cc08bb1c19
commit 199039326b
12 changed files with 644 additions and 26 deletions

View File

@@ -215,22 +215,23 @@ async def delete_template(
@router.get("/api/v1/capture-engines", response_model=EngineListResponse, tags=["Templates"])
async def list_engines(_auth: AuthRequired):
"""List available capture engines on this system.
"""List all registered capture engines.
Returns all registered engines that are available on the current platform.
Returns every registered engine with an ``available`` flag showing
whether it can be used on the current system.
"""
try:
available_engine_types = EngineRegistry.get_available_engines()
available_set = set(EngineRegistry.get_available_engines())
all_engines = EngineRegistry.get_all_engines()
engines = []
for engine_type in available_engine_types:
engine_class = EngineRegistry.get_engine(engine_type)
for engine_type, engine_class in all_engines.items():
engines.append(
EngineInfo(
type=engine_type,
name=engine_type.upper(),
default_config=engine_class.get_default_config(),
available=True,
available=(engine_type in available_set),
)
)
@@ -242,7 +243,7 @@ async def list_engines(_auth: AuthRequired):
@router.post("/api/v1/capture-templates/test", response_model=TemplateTestResponse, tags=["Templates"])
async def test_template(
def test_template(
test_request: TemplateTestRequest,
_auth: AuthRequired,
processor_manager: ProcessorManager = Depends(get_processor_manager),
@@ -250,6 +251,10 @@ async def test_template(
):
"""Test a capture template configuration.
Uses sync ``def`` so FastAPI runs it in a thread pool — the engine
initialisation and capture loop are blocking and would stall the
event loop if run in an ``async def`` handler.
Temporarily instantiates an engine with the provided configuration,
captures frames for the specified duration, and returns actual FPS metrics.
"""
@@ -301,8 +306,9 @@ async def test_template(
screen_capture = stream.capture_frame()
capture_elapsed = time.perf_counter() - capture_start
# Skip if no new frame (screen unchanged)
# Skip if no new frame (screen unchanged); yield CPU
if screen_capture is None:
time.sleep(0.005)
continue
total_capture_time += capture_elapsed