API Reference

This page provides detailed API documentation for PadRelay’s modules and classes. The documentation is automatically generated from source code docstrings.

Client Module

Client Application

Client application

class padrelay.client.client_app.UDPClientProtocol[source]

Bases: DatagramProtocol

Datagram protocol that queues incoming packets

__init__() None[source]
datagram_received(data: bytes, addr) None[source]

Called when some datagram is received.

error_received(exc: Exception) None[source]

Called when a send or receive operation raises an OSError.

(Other than BlockingIOError or InterruptedError.)

class padrelay.client.client_app.VirtualGamepadClient(server_ip: str, server_port: int, protocol: str, gamepad: GamepadInput, update_rate: int = 60, password: str | None = None, config: object | None = None, enable_tls: bool = False)[source]

Bases: object

Client controller

__init__(server_ip: str, server_port: int, protocol: str, gamepad: GamepadInput, update_rate: int = 60, password: str | None = None, config: object | None = None, enable_tls: bool = False) None[source]

Construct the client and configure networking

async run() bool[source]

Run the client using the chosen protocol

async shutdown() bool[source]

Shut down tasks and clean up resources

Input Handling

Gamepad input helpers for the PadRelay

class padrelay.client.input.GamepadInput(joystick_index: int = 0)[source]

Bases: object

Collects input from a local gamepad

__init__(joystick_index: int = 0) None[source]

Set up input polling for the given joystick index

initialize() bool[source]

Initialize pygame and open the joystick

poll() Dict[str, Any] | None[source]

Return the current gamepad state or None on error

cleanup() None[source]

Release Pygame resources

Client Constants

Constants used by the client portion of the bridge

Server Module

Server Application

Server application for the PadRelay

class padrelay.server.server_app.VirtualGamepadServer(host, port, password, gamepad_type='xbox360', config=None, rate_limit_window=60, max_requests=100, block_duration=2, protocol='tcp', enable_tls=False, cert_path: Path | None = None, key_path: Path | None = None)[source]

Bases: object

Server controller

__init__(host, port, password, gamepad_type='xbox360', config=None, rate_limit_window=60, max_requests=100, block_duration=2, protocol='tcp', enable_tls=False, cert_path: Path | None = None, key_path: Path | None = None)[source]

Construct the server instance

Parameters:
  • host – Host to bind to

  • port – Port to listen on

  • password – Authentication password

  • gamepad_type – Type of virtual gamepad to create

  • config – Configuration object (optional)

  • rate_limit_window – Time window for rate limiting in seconds

  • max_requests – Maximum number of requests per window

  • block_duration – How long to block clients that exceed the rate limit

  • protocol – Transport protocol (‘tcp’ or ‘udp’)

  • enable_tls – Enable TLS/SSL encryption for TCP connections

  • cert_path – Path to TLS certificate (optional)

  • key_path – Path to TLS private key (optional)

async run()[source]

Run the server with the configured protocol

async shutdown()[source]

Close all connections and shut down

Input Processor

Utilities that translate input messages into virtual gamepad actions

class padrelay.server.input_processor.GamepadHandler(gamepad: VirtualGamepad, config: object | None = None)[source]

Bases: object

Apply received messages to a virtual gamepad

__init__(gamepad: VirtualGamepad, config: object | None = None) None[source]
process(message: Dict) None[source]

Process a single input message

process_buttons(buttons: List[bool]) None[source]

Apply button state changes

process_axes(message: Dict) None[source]

Handle axes using the loaded configuration

process_advanced_axes(axes: List[float], message: Dict) None[source]

Handle axes with advanced mapping

process_basic_axes(axes: List[float], message: Dict) None[source]

Handle axes with the default mapping

Virtual Gamepad

Virtual gamepad wrapper for the server.

class padrelay.server.virtual_gamepad.VirtualGamepad(gamepad_type: str = 'xbox360', config: object | None = None)[source]

Bases: object

Wraps vgamepad to provide a unified API

__init__(gamepad_type: str = 'xbox360', config: object | None = None) None[source]

Create the virtual gamepad

setup_gamepad() bool[source]

Setup the requested gamepad type

reset_gamepad_state() bool[source]

Return the gamepad to its default state

load_button_mapping() Dict[int, int][source]

Load button mapping, falling back to defaults.

get_button_mapping() Dict[int, int][source]

Return the current button mapping

press_button(*args, **kwargs) None[source]
release_button(*args, **kwargs) None[source]
left_joystick_float(*args, **kwargs) None[source]
right_joystick_float(*args, **kwargs) None[source]
left_trigger_float(*args, **kwargs) None[source]
right_trigger_float(*args, **kwargs) None[source]
update() None[source]

Server Constants

Constants used by the server

Protocol Module

TCP Protocol

TCP protocol utilities for the PadRelay

class padrelay.protocol.tcp.TCPProtocolHandler(reader=None, writer=None)[source]

Bases: object

Handle TCP communication

__init__(reader=None, writer=None)[source]

Initialize with optional reader/writer

async read_message()[source]

Read and decode a length-prefixed message

async send_message(message)[source]

Send a length-prefixed message

async drain()[source]

Flush the writer buffer

close()[source]

Close the writer connection

async wait_closed() None[source]

Wait for the writer to close

UDP Protocol

UDP protocol utilities for the PadRelay

class padrelay.protocol.udp.UDPProtocolHandler(transport=None, remote_addr=None)[source]

Bases: object

Handle UDP communication on the client

__init__(transport=None, remote_addr=None)[source]

Initialize with optional transport and address

send_message(message)[source]

Send a UDP message

close()[source]

Close the UDP transport

class padrelay.protocol.udp.UDPServerProtocol(gamepad_handler, authenticator=None, tracker=None)[source]

Bases: DatagramProtocol

UDP server protocol

__init__(gamepad_handler, authenticator=None, tracker=None)[source]

Create the server protocol

connection_made(transport)[source]

Called when the socket is ready

datagram_received(data, addr)[source]

Handle an incoming datagram

error_received(exc: Exception) None[source]

Called when a send or receive operation fails

connection_lost(exc: Exception | None) None[source]

Called when the connection is closed

Messages

Message types and validators for the PadRelay

class padrelay.protocol.messages.BaseMessage(msg_type)[source]

Bases: object

Base message class

__init__(msg_type)[source]

Create a message of msg_type

to_json()[source]

Return a JSON representation

to_bytes()[source]

Return a UTF-8 encoded bytes object

class padrelay.protocol.messages.InputMessage(buttons=None, axes=None, hats=None, triggers=None)[source]

Bases: BaseMessage

Gamepad state message

__init__(buttons=None, axes=None, hats=None, triggers=None)[source]

Create an input message.

class padrelay.protocol.messages.HeartbeatMessage[source]

Bases: BaseMessage

Heartbeat ping

__init__()[source]

Create a heartbeat message

class padrelay.protocol.messages.HeartbeatAckMessage[source]

Bases: BaseMessage

Heartbeat acknowledgment

__init__()[source]

Create an acknowledgment message

class padrelay.protocol.messages.AuthChallengeMessage(challenge)[source]

Bases: BaseMessage

Authentication challenge

__init__(challenge)[source]

Create a challenge with challenge string

class padrelay.protocol.messages.AuthResponseMessage(response)[source]

Bases: BaseMessage

Authentication response

__init__(response)[source]

Create a response containing response

class padrelay.protocol.messages.AuthSuccessMessage[source]

Bases: BaseMessage

Authentication success

__init__()[source]

Create a success message

class padrelay.protocol.messages.AuthFailedMessage(message='Authentication failed')[source]

Bases: BaseMessage

Authentication failed

__init__(message='Authentication failed')[source]

Create a failure message

class padrelay.protocol.messages.AuthParamsRequestMessage[source]

Bases: BaseMessage

Client request for hashing parameters

__init__()[source]

Create a message of msg_type

class padrelay.protocol.messages.AuthParamsMessage(salt: str, iterations: int)[source]

Bases: BaseMessage

Server reply with salt and iterations

__init__(salt: str, iterations: int)[source]

Create a message of msg_type

class padrelay.protocol.messages.ErrorMessage(message='Unknown error')[source]

Bases: BaseMessage

Represents an error

__init__(message='Unknown error')[source]

Create an error message

padrelay.protocol.messages.validate_input_message(message)[source]

Return True if message is a valid input payload

Protocol Constants

Constants shared by client and server.

Security Module

Authentication

Authentication utilities for the PadRelay

class padrelay.security.auth.Authenticator(password: str | None = None)[source]

Bases: object

Authentication handler for both client and server

PBKDF2_PREFIX = 'pbkdf2_sha256'
DEFAULT_ITERATIONS = 100000
UDP_TOKEN_TTL = 60
__init__(password: str | None = None) None[source]

Initialize an authenticator

Parameters:
  • password – Plain text password (optional)

  • salt – Salt for password hashing (optional)

set_parameters(salt: str, iterations: int) None[source]

Set hashing parameters and recompute derived key if needed

static hash_password(password: str, iterations: int = 100000) str[source]

Return a PBKDF2 hash string for a password

get_hash_string() str | None[source]
verify_password(password: str) bool[source]
generate_tcp_challenge() str[source]

Generate a challenge for TCP authentication

Returns:

Random challenge string

Return type:

str

verify_tcp_response(challenge: str, response: str) bool[source]

Verify a TCP authentication response

Parameters:
  • challenge – Challenge string

  • response – HMAC response to verify

Returns:

True if response is valid, False otherwise

Return type:

bool

generate_udp_token(current_time: int | None = None) str | None[source]

Generate a token for UDP authentication

Returns:

Authentication token

Return type:

str

generate_tcp_response(challenge: str) str[source]

Generate a response to a TCP authentication challenge

Parameters:

challenge – Challenge string

Returns:

HMAC response

Return type:

str

authenticate_udp(message: dict, current_time: int | None = None) bool[source]

Authenticate a UDP message

Parameters:

message – Message to authenticate

Returns:

True if message is authenticated, False otherwise

Return type:

bool

Rate Limiting

Rate limiting utilities for the PadRelay

class padrelay.security.rate_limiting.ConnectionTracker(max_connections: int = 1, rate_limit_window: int = 60, max_requests: int = 100, block_duration: int = 2)[source]

Bases: object

Track active connections and implement rate limiting

__init__(max_connections: int = 1, rate_limit_window: int = 60, max_requests: int = 100, block_duration: int = 2) None[source]

Initialize connection tracker

Parameters:
  • max_connections – Maximum number of simultaneous connections

  • rate_limit_window – Time window for rate limiting in seconds

  • max_requests – Maximum number of requests per window

can_connect(addr: Tuple[str, int]) bool[source]

Check if a new connection is allowed

Parameters:

addr – Client address tuple

Returns:

True if connection is allowed, False otherwise

Return type:

bool

is_rate_limited(addr: Tuple[str, int]) bool[source]

Check if client is being rate limited or blocked.

is_blocked(addr: Tuple[str, int]) bool[source]

Check if a client is currently blocked

authenticate(addr: Tuple[str, int]) None[source]

Mark a client as authenticated

Parameters:

addr – Client address tuple

disconnect(addr: Tuple[str, int]) None[source]

Handle client disconnection

Parameters:

addr – Client address tuple

TLS Utilities

TLS/SSL utilities for secure communication

padrelay.security.tls_utils.get_default_cert_paths() Tuple[Path, Path][source]

Get default paths for certificate and key files

Returns:

Tuple of (cert_path, key_path)

padrelay.security.tls_utils.ensure_cert_dir_exists() Path[source]

Ensure certificate directory exists with proper permissions

Returns:

Path to certificate directory

padrelay.security.tls_utils.generate_self_signed_cert(cert_path: Path | None = None, key_path: Path | None = None, days_valid: int = 365, country: str = 'US', state: str = 'State', locality: str = 'City', organization: str = 'PadRelay', common_name: str = 'localhost') Tuple[Path, Path][source]

Generate a self-signed certificate for TLS

Parameters:
  • cert_path – Path where certificate will be saved (default: ~/.padrelay/certs/server.crt)

  • key_path – Path where private key will be saved (default: ~/.padrelay/certs/server.key)

  • days_valid – Number of days the certificate is valid (default: 365)

  • country – Country code for certificate (default: US)

  • state – State/Province for certificate

  • locality – City for certificate

  • organization – Organization name for certificate

  • common_name – Common name (hostname) for certificate

Returns:

Tuple of (cert_path, key_path)

padrelay.security.tls_utils.create_server_ssl_context(cert_path: Path | None = None, key_path: Path | None = None, auto_generate: bool = True) SSLContext | None[source]

Create SSL context for server

Parameters:
  • cert_path – Path to certificate file

  • key_path – Path to private key file

  • auto_generate – If True, automatically generate certificate if not found

Returns:

SSLContext or None if TLS is disabled

padrelay.security.tls_utils.create_client_ssl_context(verify_cert: bool = False, ca_path: Path | None = None) SSLContext[source]

Create SSL context for client

Parameters:
  • verify_cert – If True, verify server certificate (requires CA certificate)

  • ca_path – Path to CA certificate for verification (optional)

Returns:

SSLContext configured for client

padrelay.security.tls_utils.check_cert_expiration(cert_path: Path | None = None) datetime | None[source]

Check certificate expiration date

Parameters:

cert_path – Path to certificate file

Returns:

Expiration datetime or None if cert doesn’t exist or error

padrelay.security.tls_utils.warn_if_cert_expiring_soon(cert_path: Path | None = None, days_warning: int = 30) bool[source]

Check if certificate is expiring soon and warn

Parameters:
  • cert_path – Path to certificate file

  • days_warning – Warn if certificate expires within this many days

Returns:

True if certificate is expiring soon, False otherwise

Password Strength

Password strength checking utilities

padrelay.security.password_strength.check_password_strength(password: str) Tuple[str, int, List[str]][source]

Check password strength and provide recommendations

Parameters:

password – The password to check

Returns:

Tuple of (strength_level, score, recommendations) - strength_level: “very_weak”, “weak”, “medium”, “strong”, “very_strong” - score: Integer score from 0-100 - recommendations: List of improvement suggestions

padrelay.security.password_strength.warn_weak_password(password: str) bool[source]

Check password strength and warn if weak

Parameters:

password – The password to check

Returns:

True if password is acceptable, False if very weak

padrelay.security.password_strength.generate_password_suggestion() str[source]

Generate a strong password suggestion

Returns:

A suggested strong password

Core Module

Configuration

Helpers for reading and validating configuration

padrelay.core.config.load_config(config_path: str) ConfigParser | None[source]
padrelay.core.config.parse_client_args() Tuple[Namespace, ConfigParser | None][source]
padrelay.core.config.parse_server_args() Tuple[Namespace, ConfigParser | None][source]
padrelay.core.config.validate_server_config(args: Namespace, config_obj: ConfigParser | None) None[source]

Validate the server configuration arguments

Exceptions

Exception hierarchy for the PadRelay

exception padrelay.core.exceptions.GamepadBridgeError(message: str = 'An error occurred in the PadRelay', details: Any | None = None)[source]

Bases: Exception

Base exception for bridge errors

__init__(message: str = 'An error occurred in the PadRelay', details: Any | None = None) None[source]
exception padrelay.core.exceptions.ProtocolError(message: str = 'Protocol error', details: Any | None = None, protocol_version: str | None = None)[source]

Bases: GamepadBridgeError

Protocol-related error

__init__(message: str = 'Protocol error', details: Any | None = None, protocol_version: str | None = None) None[source]
exception padrelay.core.exceptions.AuthenticationError(message: str = 'Authentication failed', details: Any | None = None, addr: str | None = None)[source]

Bases: GamepadBridgeError

Authentication failed

__init__(message: str = 'Authentication failed', details: Any | None = None, addr: str | None = None) None[source]
exception padrelay.core.exceptions.ConnectionError(message: str = 'Connection error', details: Any | None = None, addr: str | None = None, reconnect_attempt: int | None = None)[source]

Bases: GamepadBridgeError

Connection related error

__init__(message: str = 'Connection error', details: Any | None = None, addr: str | None = None, reconnect_attempt: int | None = None) None[source]
exception padrelay.core.exceptions.ConfigurationError(message: str = 'Configuration error', details: Any | None = None, config_file: str | None = None, setting: str | None = None)[source]

Bases: GamepadBridgeError

Configuration problem

__init__(message: str = 'Configuration error', details: Any | None = None, config_file: str | None = None, setting: str | None = None) None[source]
exception padrelay.core.exceptions.InputError(message: str = 'Input error', details: Any | None = None, input_type: str | None = None, input_value: Any | None = None)[source]

Bases: GamepadBridgeError

Gamepad input error

__init__(message: str = 'Input error', details: Any | None = None, input_type: str | None = None, input_value: Any | None = None) None[source]

Logging Utilities

Logging utilities

padrelay.core.logging_utils.get_logger(name: str) Logger[source]

Return a logging.Logger configured for the project

padrelay.core.logging_utils.sanitize_for_logging(value: str, max_length: int = 200) str[source]

Sanitize user input before logging to prevent log injection

Parameters:
  • value – The string to sanitize

  • max_length – Maximum length of the output string

Returns:

Sanitized string safe for logging

Scripts Module

Server Entry Point

Entry point for the PadRelay server

async padrelay.scripts.server.async_main()[source]
padrelay.scripts.server.main()[source]

Client Entry Point

Entry point for the PadRelay client

async padrelay.scripts.client.async_main()[source]
padrelay.scripts.client.main()[source]

Key Mapper

Xbox360/DS4 Controller Key Mapper

This script helps map your physical gamepad buttons to virtual gamepad buttons

class padrelay.scripts.key_mapper.ControllerMapper(output_path, polling_interval=0.1)[source]
__init__(output_path, polling_interval=0.1)[source]
select_gamepad_type()[source]

Allow user to select gamepad type

detect_controller()[source]

Detect and initialize the first available controller

wait_for_button_release()[source]

Wait until all buttons are released

wait_for_neutral(duration=1.0)[source]

Wait for controller to return to neutral state

wait_for_button_press(timeout=10)[source]

Wait for a button press and return the button index

check_for_skip()[source]

Non-blocking check for skip input (Enter key)

wait_for_hat_movement(timeout=10)[source]

Wait for hat (D-pad) movement and return the value

map_buttons()[source]

Guide user through mapping each button

detect_axis_movement(axis_name, instruction, timeout=5.0, threshold=0.3)[source]

Detect significant axis movement with improved feedback and direction detection

Parameters:
  • axis_name – The name of the axis being mapped

  • instruction – Basic instruction to display to the user

  • timeout – Time in seconds to wait for movement

  • threshold – Minimum change to be considered significant

map_axes()[source]

Guide user through mapping each axis with improved usability

verify_mapping()[source]

Let user verify the mappings and make changes if needed

manual_adjustments()[source]

Allow manual adjustments to mappings

adjust_button_mapping()[source]

Allow manual adjustment of button mappings

adjust_axis_mapping()[source]

Allow manual adjustment of axis mappings

generate_config()[source]

Generate configuration file for server

run()[source]

Run the mapping process

padrelay.scripts.key_mapper.main()[source]

Usage Examples

Creating a Client Programmatically

import asyncio
from padrelay.client.client_app import VirtualGamepadClient
from padrelay.client.input import GamepadInput

async def main():
    # Initialize gamepad
    gamepad = GamepadInput(joystick_index=0)
    if not gamepad.initialize():
        print("Failed to initialize gamepad")
        return

    # Create client
    client = VirtualGamepadClient(
        server_ip="192.168.1.100",
        server_port=9999,
        protocol="tcp",
        gamepad=gamepad,
        update_rate=60,
        password="my_password",
        enable_tls=True
    )

    # Run client
    await client.run()

if __name__ == "__main__":
    asyncio.run(main())

Creating a Server Programmatically

import asyncio
from padrelay.server.server_app import VirtualGamepadServer

async def main():
    # Create server
    server = VirtualGamepadServer(
        host="0.0.0.0",
        port=9999,
        password="my_password",
        gamepad_type="xbox360",
        protocol="tcp",
        enable_tls=True
    )

    # Run server
    await server.run()

if __name__ == "__main__":
    asyncio.run(main())

Using the Authenticator

from padrelay.security.auth import Authenticator

# Hash a password
password_hash = Authenticator.hash_password("my_password")
print(password_hash)
# Output: pbkdf2_sha256$100000$abc123...$def456...

# Create authenticator with plaintext password
auth1 = Authenticator(password="my_password")

# Create authenticator with hashed password
auth2 = Authenticator(password=password_hash)

# Generate challenge
challenge = auth1.generate_challenge()

# Compute response
response = auth2.compute_response(challenge, auth1.salt, auth1.iterations)

# Verify response
is_valid = auth1.verify_response(challenge, response)
print(is_valid)  # True

Custom Message Creation

from padrelay.protocol.messages import InputMessage

# Create input message
msg = InputMessage(
    buttons=[0, 1, 4],  # Buttons A, B, and LB pressed
    axes=[0.0, -0.5, 0.8, 0.0],  # Left stick up, right stick right
    hats=[[0, 0]],  # D-pad centered
    triggers={"left": 0.0, "right": 0.7}  # Right trigger pressed
)

# Serialize to JSON
json_str = msg.to_json()

# Serialize to bytes
data = msg.to_bytes()

Rate Limiting Example

from padrelay.security.rate_limiting import RateLimiter

# Create rate limiter (100 requests per 60 seconds)
limiter = RateLimiter(
    max_requests=100,
    window_seconds=60,
    block_duration=120
)

# Check if client is allowed
client_addr = "192.168.1.50"
if limiter.is_allowed(client_addr):
    # Process request
    pass
else:
    # Reject request
    print("Rate limit exceeded")

TLS Certificate Generation

from pathlib import Path
from padrelay.security.tls_utils import generate_self_signed_cert

# Generate certificate
cert_path = Path("server.crt")
key_path = Path("server.key")

generate_self_signed_cert(
    cert_path=cert_path,
    key_path=key_path,
    hostname="localhost",
    days=365
)

print(f"Certificate created: {cert_path}")
print(f"Private key created: {key_path}")

See Also