Vita Gamepad
局域网虚拟游戏手柄控制台
开启后,程序会广播发现信号并等待同一 Wi-Fi 下的 PSVita。
暂停时会立即发送中立状态、释放所有按键并移除虚拟手柄。再次开启即可重新连接。
请保持此程序运行;关闭这个网页不会停止后台接收服务。
from __future__ import annotations import argparse from collections import deque from dataclasses import asdict, dataclass from datetime import datetime from http import HTTPStatus from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer import json import queue import secrets import threading import time from urllib.parse import parse_qs, urlparse import webbrowser from vitapad.backends import create_backend from vitapad.protocol import InputState from vitapad.receiver import Receiver @dataclass(frozen=True, slots=True) class LogEntry: id: int time: str level: str message: str class DashboardController: def __init__( self, backend_name: str, bind: str, port: int, discovery_port: int, allow: str | None, timeout_ms: int, ) -> None: self.backend_name = backend_name self._resolved_backend = backend_name self.bind = bind self.port = port self.discovery_port = discovery_port self.allow = allow self.timeout_ms = timeout_ms self._lock = threading.RLock() self._status = "paused" self._receiver: Receiver | None = None self._worker: threading.Thread | None = None self._started_at: float | None = None self._error: str | None = None self._stop_requested = False self._logs: deque[LogEntry] = deque(maxlen=500) self._next_log_id = 1 self._input = InputState.neutral() self._subscribers: set[queue.Queue[InputState]] = set() self.log("控制面板已启动,点击“开启使用”开始接收。") def log(self, message: str, level: str = "info") -> None: with self._lock: entry = LogEntry( id=self._next_log_id, time=datetime.now().strftime("%H:%M:%S"), level=level, message=message, ) self._next_log_id += 1 self._logs.append(entry) def start(self) -> tuple[bool, str]: with self._lock: if self._status in {"starting", "running"}: return False, "接收服务已经开启" if self._status == "stopping": return False, "服务正在暂停,请稍候" self._status = "starting" self._error = None self._stop_requested = False self.log("正在创建虚拟手柄并启动网络服务...") self._worker = threading.Thread( target=self._run_receiver, name="receiver", daemon=True ) self._worker.start() return True, "正在开启" def _run_receiver(self) -> None: try: backend = create_backend(self.backend_name) receiver = Receiver( backend=backend, bind=self.bind, port=self.port, discovery_port=self.discovery_port, allow=self.allow, timeout_ms=self.timeout_ms, log=self.log, on_input=self.handle_input, ) with self._lock: if self._stop_requested: backend.close() self.log("服务已暂停。") return self._resolved_backend = backend.name self._receiver = receiver self._status = "running" self._started_at = time.monotonic() self.log(f"服务已开启,虚拟手柄后端:{backend.name}", "success") receiver.run() except Exception as exc: with self._lock: self._error = str(exc) self.log(f"启动失败:{exc}", "error") finally: with self._lock: if self._stop_requested: self.log("服务已暂停,虚拟手柄已释放。", "success") self._receiver = None self._started_at = None self._status = "paused" def pause(self) -> tuple[bool, str]: with self._lock: if self._status == "paused": return False, "接收服务已经暂停" if self._status == "stopping": return False, "服务正在暂停" self._status = "stopping" self._stop_requested = True receiver = self._receiver self.log("正在暂停,释放全部手柄按键...") if receiver is not None: receiver.stop() return True, "正在暂停" def status(self) -> dict[str, object]: with self._lock: connected = ( self._receiver.connected_address if self._receiver else None ) uptime = ( int(time.monotonic() - self._started_at) if self._started_at is not None else 0 ) return { "status": self._status, "connected": connected, "uptime": uptime, "backend": self._resolved_backend, "inputPort": self.port, "discoveryPort": self.discovery_port, "error": self._error, "input": asdict(self._input), } def logs_after(self, after: int) -> list[dict[str, object]]: with self._lock: return [asdict(entry) for entry in self._logs if entry.id > after] def handle_input(self, state: InputState) -> None: with self._lock: self._input = state subscribers = tuple(self._subscribers) for subscriber in subscribers: try: subscriber.put_nowait(state) except queue.Full: try: subscriber.get_nowait() subscriber.put_nowait(state) except (queue.Empty, queue.Full): pass def subscribe(self) -> queue.Queue[InputState]: subscriber: queue.Queue[InputState] = queue.Queue(maxsize=16) with self._lock: self._subscribers.add(subscriber) subscriber.put_nowait(self._input) return subscriber def unsubscribe(self, subscriber: queue.Queue[InputState]) -> None: with self._lock: self._subscribers.discard(subscriber) HTML = r"""
局域网虚拟游戏手柄控制台
开启后,程序会广播发现信号并等待同一 Wi-Fi 下的 PSVita。
暂停时会立即发送中立状态、释放所有按键并移除虚拟手柄。再次开启即可重新连接。
请保持此程序运行;关闭这个网页不会停止后台接收服务。