107 lines
3.4 KiB
Python
107 lines
3.4 KiB
Python
from __future__ import annotations
|
||
|
||
import socket
|
||
import threading
|
||
import time
|
||
|
||
from vitapad.backends import GamepadBackend
|
||
from vitapad.protocol import (
|
||
DISCOVERY_MAGIC,
|
||
InputState,
|
||
decode_packet,
|
||
sequence_is_newer,
|
||
)
|
||
|
||
|
||
class Receiver:
|
||
def __init__(
|
||
self,
|
||
backend: GamepadBackend,
|
||
bind: str = "0.0.0.0",
|
||
port: int = 5000,
|
||
discovery_port: int = 5001,
|
||
allow: str | None = None,
|
||
timeout_ms: int = 300,
|
||
) -> None:
|
||
self.backend = backend
|
||
self.bind = bind
|
||
self.port = port
|
||
self.discovery_port = discovery_port
|
||
self.allow = allow
|
||
self.timeout = timeout_ms / 1000.0
|
||
self._stop = threading.Event()
|
||
self._last_packet = 0.0
|
||
self._connected_address: str | None = None
|
||
self._last_sequence: int | None = None
|
||
self._neutral_sent = True
|
||
|
||
def _broadcast_discovery(self) -> None:
|
||
with socket.socket(socket.AF_INET, socket.SOCK_DGRAM) as beacon:
|
||
beacon.setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST, 1)
|
||
while not self._stop.wait(1.0):
|
||
try:
|
||
beacon.sendto(
|
||
DISCOVERY_MAGIC, ("255.255.255.255", self.discovery_port)
|
||
)
|
||
except OSError:
|
||
# Interfaces can disappear during sleep/network changes.
|
||
pass
|
||
|
||
def run(self) -> None:
|
||
discovery = threading.Thread(
|
||
target=self._broadcast_discovery, name="discovery", daemon=True
|
||
)
|
||
discovery.start()
|
||
with socket.socket(socket.AF_INET, socket.SOCK_DGRAM) as listener:
|
||
listener.bind((self.bind, self.port))
|
||
listener.settimeout(0.05)
|
||
print(
|
||
f"正在监听 {self.bind}:{self.port},后端: {self.backend.name}\n"
|
||
"等待 PSVita(Ctrl+C 退出)..."
|
||
)
|
||
try:
|
||
while not self._stop.is_set():
|
||
self._receive_once(listener)
|
||
self._apply_failsafe()
|
||
finally:
|
||
self._stop.set()
|
||
discovery.join(timeout=1.2)
|
||
self.backend.close()
|
||
|
||
def _receive_once(self, listener: socket.socket) -> None:
|
||
try:
|
||
data, address = listener.recvfrom(256)
|
||
except socket.timeout:
|
||
return
|
||
if self.allow and address[0] != self.allow:
|
||
return
|
||
try:
|
||
state = decode_packet(data)
|
||
except ValueError:
|
||
return
|
||
if self._connected_address != address[0]:
|
||
self._connected_address = address[0]
|
||
self._last_sequence = None
|
||
print(f"已连接 PSVita: {address[0]}")
|
||
if self._last_sequence is not None and not sequence_is_newer(
|
||
state.sequence, self._last_sequence
|
||
):
|
||
return
|
||
self._last_sequence = state.sequence
|
||
self._last_packet = time.monotonic()
|
||
self._neutral_sent = False
|
||
self.backend.update(state)
|
||
|
||
def _apply_failsafe(self) -> None:
|
||
if (
|
||
not self._neutral_sent
|
||
and time.monotonic() - self._last_packet >= self.timeout
|
||
):
|
||
self.backend.update(InputState.neutral())
|
||
self._neutral_sent = True
|
||
print("连接超时,已释放全部按键;等待重连...")
|
||
|
||
def stop(self) -> None:
|
||
self._stop.set()
|
||
|