Source code for padrelay.client.input
"""Gamepad input helpers for the PadRelay"""
import pygame
from datetime import datetime
from typing import Any, Dict, List, Optional
from ..core.logging_utils import get_logger
from ..protocol.constants import PROTOCOL_VERSION
logger = get_logger(__name__)
[docs]
class GamepadInput:
"""Collects input from a local gamepad"""
[docs]
def __init__(self, joystick_index: int = 0) -> None:
"""Set up input polling for the given joystick index"""
self.joystick_index = joystick_index
self.joystick = None
self.initialized = False
[docs]
def initialize(self) -> bool:
"""Initialize pygame and open the joystick"""
try:
pygame.init()
pygame.joystick.init()
if pygame.joystick.get_count() == 0:
logger.error("No gamepad detected! Please connect a gamepad and try again.")
return False
self.joystick = pygame.joystick.Joystick(self.joystick_index)
self.joystick.init()
logger.info(f"Gamepad initialized: {self.joystick.get_name()}")
# Log detailed controller information
logger.info(f"Controller details:")
logger.info(f" - Number of axes: {self.joystick.get_numaxes()}")
logger.info(f" - Number of buttons: {self.joystick.get_numbuttons()}")
logger.info(f" - Number of hats: {self.joystick.get_numhats()}")
self.initialized = True
return True
except Exception as e:
logger.error(f"Error initializing gamepad: {e}")
return False
[docs]
def poll(self) -> Optional[Dict[str, Any]]:
"""Return the current gamepad state or ``None`` on error"""
if not self.initialized or not self.joystick:
return None
pygame.event.pump()
try:
# Get buttons state (true if pressed)
buttons = [bool(self.joystick.get_button(i)) for i in range(self.joystick.get_numbuttons())]
# Get axes values (between -1.0 and 1.0)
axes = [self.joystick.get_axis(i) for i in range(self.joystick.get_numaxes())]
# Get hat values (directions)
hats = [self.joystick.get_hat(i) for i in range(self.joystick.get_numhats())]
# Calculate trigger values if this is an Xbox-like controller
# Typically, Xbox controllers use axis 2 for left trigger and axis 5 for right trigger
trigger_left = None
trigger_right = None
if self.joystick.get_numaxes() > 2:
trigger_left = (axes[2] + 1) / 2.0
if self.joystick.get_numaxes() > 5:
trigger_right = (axes[5] + 1) / 2.0
message = {
"type": "input",
"protocol_version": PROTOCOL_VERSION,
"timestamp": datetime.now().isoformat(),
"buttons": buttons,
"axes": axes,
"hats": hats
}
# Add triggers if available
if trigger_left is not None and trigger_right is not None:
message["triggers"] = [trigger_left, trigger_right]
return message
except Exception as e:
logger.error(f"Error polling gamepad: {e}")
return None
[docs]
def cleanup(self) -> None:
"""Release Pygame resources"""
try:
pygame.quit()
self.initialized = False
self.joystick = None
except Exception as e:
logger.error(f"Error during pygame cleanup: {e}")