CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/pypi-evdev

Python bindings to the Linux input handling subsystem for reading input events and creating virtual input devices

Pending
Overview
Eval results
Files

virtual-devices.mddocs/

Virtual Input Devices

Creating and managing virtual input devices using UInput to inject events into the Linux input subsystem. Virtual devices enable programmatic simulation of keyboards, mice, joysticks, and other input hardware.

Capabilities

UInput Device Creation

Core functionality for creating virtual input devices with customizable capabilities.

class UInput(EventIO):
    def __init__(
        self,
        events: Optional[Dict[int, Sequence[int]]] = None,
        name: str = "py-evdev-uinput",
        vendor: int = 0x1,
        product: int = 0x1,
        version: int = 0x1,
        bustype: int = 0x3,
        devnode: str = "/dev/uinput",
        phys: str = "py-evdev-uinput",
        input_props=None,
        max_effects: int = 96
    ) -> None:
        """
        Create a virtual input device.
        
        Parameters:
        - events: Dictionary mapping event types to lists of supported codes
                 {ecodes.EV_KEY: [ecodes.KEY_A, ecodes.KEY_B], ...}
        - name: Device name as it appears in the system
        - vendor: USB vendor ID
        - product: USB product ID  
        - version: Device version
        - bustype: Bus type (USB=0x3, Bluetooth=0x5, etc.)
        - devnode: Path to uinput device node
        - phys: Physical device path identifier
        - input_props: Input properties list
        - max_effects: Maximum number of force feedback effects
        
        Raises:
        - UInputError: If device creation fails
        """
    
    @classmethod
    def from_device(
        cls,
        *devices: Union[InputDevice, Union[str, bytes, os.PathLike]],
        filtered_types: Tuple[int] = (ecodes.EV_SYN, ecodes.EV_FF),
        **kwargs
    ) -> "UInput":
        """
        Create UInput device copying capabilities from existing device(s).
        
        Parameters:
        - devices: InputDevice instances or paths to clone capabilities from
        - filtered_types: Event types to exclude from capabilities
        - **kwargs: Additional arguments passed to UInput constructor
        
        Returns:
        UInput instance with merged capabilities from source devices
        """
    
    def close(self) -> None:
        """Close and destroy the virtual device."""

Event Injection

Methods for injecting input events into the virtual device.

class UInput:
    def write(self, etype: int, code: int, value: int) -> None:
        """
        Write input event to the virtual device.
        
        Parameters:
        - etype: Event type (ecodes.EV_KEY, EV_REL, EV_ABS, etc.)
        - code: Event code (KEY_A, REL_X, ABS_X, etc.)
        - value: Event value (1=press, 0=release for keys)
        """
    
    def write_event(self, event: InputEvent) -> None:
        """
        Write InputEvent object to the virtual device.
        
        Parameters:
        - event: InputEvent to inject
        """
    
    def syn(self) -> None:
        """
        Send SYN_REPORT synchronization event.
        Required after writing events to ensure proper event delivery.
        """

Device Information

Properties and methods for accessing device information.

class UInput:
    # Device attributes
    name: str          # Device name
    vendor: int        # Vendor ID
    product: int       # Product ID
    version: int       # Version number
    bustype: int       # Bus type identifier
    devnode: str       # Device node path
    fd: int           # File descriptor
    device: InputDevice  # Associated InputDevice instance
    
    def capabilities(self, verbose: bool = False, absinfo: bool = True) -> Dict:
        """
        Get device capabilities.
        
        Parameters:
        - verbose: Return human-readable names instead of codes
        - absinfo: Include AbsInfo for absolute axes
        
        Returns:
        Dictionary mapping event types to supported codes
        """
    
    def fileno(self) -> int:
        """Return file descriptor for select() operations."""

Force Feedback Management

Methods for managing force feedback effects on virtual devices.

class UInput:
    def begin_upload(self, effect_id: int) -> "UInputUpload":
        """
        Begin uploading force feedback effect.
        
        Parameters:
        - effect_id: Effect identifier
        
        Returns:
        UInputUpload structure for effect configuration
        """
    
    def end_upload(self, upload: "UInputUpload") -> None:
        """
        Complete force feedback effect upload.
        
        Parameters:
        - upload: UInputUpload structure from begin_upload()
        """
    
    def begin_erase(self, effect_id: int) -> "UInputErase":
        """
        Begin erasing force feedback effect.
        
        Parameters:
        - effect_id: Effect identifier to erase
        
        Returns:
        UInputErase structure
        """
    
    def end_erase(self, erase: "UInputErase") -> None:
        """
        Complete force feedback effect erase.
        
        Parameters:
        - erase: UInputErase structure from begin_erase()
        """

Error Handling

Exception class for UInput-specific errors.

class UInputError(Exception):
    """Exception raised for UInput device errors."""

Usage Examples

Basic Virtual Keyboard

from evdev import UInput, ecodes
import time

# Create virtual keyboard with specific keys
ui = UInput({
    ecodes.EV_KEY: [
        ecodes.KEY_A, ecodes.KEY_B, ecodes.KEY_C,
        ecodes.KEY_SPACE, ecodes.KEY_ENTER
    ]
}, name='virtual-keyboard')

try:
    # Type "ABC" + Enter
    for key in [ecodes.KEY_A, ecodes.KEY_B, ecodes.KEY_C]:
        ui.write(ecodes.EV_KEY, key, 1)  # Press
        ui.syn()
        time.sleep(0.1)
        ui.write(ecodes.EV_KEY, key, 0)  # Release
        ui.syn()
        time.sleep(0.1)
    
    # Press Enter
    ui.write(ecodes.EV_KEY, ecodes.KEY_ENTER, 1)
    ui.syn()
    time.sleep(0.1)
    ui.write(ecodes.EV_KEY, ecodes.KEY_ENTER, 0)
    ui.syn()

finally:
    ui.close()

Virtual Mouse

from evdev import UInput, ecodes
import time

# Create virtual mouse
ui = UInput({
    ecodes.EV_KEY: [ecodes.BTN_LEFT, ecodes.BTN_RIGHT, ecodes.BTN_MIDDLE],
    ecodes.EV_REL: [ecodes.REL_X, ecodes.REL_Y, ecodes.REL_WHEEL]
}, name='virtual-mouse')

try:
    # Move mouse cursor
    ui.write(ecodes.EV_REL, ecodes.REL_X, 10)  # Move right
    ui.write(ecodes.EV_REL, ecodes.REL_Y, 10)  # Move down
    ui.syn()
    time.sleep(0.1)
    
    # Left click
    ui.write(ecodes.EV_KEY, ecodes.BTN_LEFT, 1)  # Press
    ui.syn()
    time.sleep(0.1)
    ui.write(ecodes.EV_KEY, ecodes.BTN_LEFT, 0)  # Release
    ui.syn()
    
    # Scroll wheel
    ui.write(ecodes.EV_REL, ecodes.REL_WHEEL, 1)  # Scroll up
    ui.syn()

finally:
    ui.close()

Virtual Gamepad

from evdev import UInput, ecodes, AbsInfo

# Create virtual gamepad with analog sticks and buttons
ui = UInput({
    ecodes.EV_KEY: [
        ecodes.BTN_A, ecodes.BTN_B, ecodes.BTN_X, ecodes.BTN_Y,
        ecodes.BTN_START, ecodes.BTN_SELECT
    ],
    ecodes.EV_ABS: [
        (ecodes.ABS_X, AbsInfo(value=0, min=-32768, max=32767, fuzz=0, flat=0, resolution=0)),
        (ecodes.ABS_Y, AbsInfo(value=0, min=-32768, max=32767, fuzz=0, flat=0, resolution=0)),
        (ecodes.ABS_RX, AbsInfo(value=0, min=-32768, max=32767, fuzz=0, flat=0, resolution=0)),
        (ecodes.ABS_RY, AbsInfo(value=0, min=-32768, max=32767, fuzz=0, flat=0, resolution=0))
    ]
}, name='virtual-gamepad')

try:
    # Press A button
    ui.write(ecodes.EV_KEY, ecodes.BTN_A, 1)
    ui.syn()
    time.sleep(0.1)
    ui.write(ecodes.EV_KEY, ecodes.BTN_A, 0)
    ui.syn()
    
    # Move left analog stick
    ui.write(ecodes.EV_ABS, ecodes.ABS_X, 16000)  # Right
    ui.write(ecodes.EV_ABS, ecodes.ABS_Y, -16000) # Up
    ui.syn()
    time.sleep(0.5)
    
    # Center analog stick
    ui.write(ecodes.EV_ABS, ecodes.ABS_X, 0)
    ui.write(ecodes.EV_ABS, ecodes.ABS_Y, 0)
    ui.syn()

finally:
    ui.close()

Context Manager Support:

UInput devices support the context manager protocol for automatic cleanup:

from evdev import UInput, ecodes

# Automatic cleanup with context manager
with UInput() as ui:
    ui.write(ecodes.EV_KEY, ecodes.KEY_SPACE, 1)  # Press space
    ui.syn()
    time.sleep(0.1)
    ui.write(ecodes.EV_KEY, ecodes.KEY_SPACE, 0)  # Release space
    ui.syn()
# Device automatically closed when exiting context

Cloning Existing Device

from evdev import InputDevice, UInput

# Clone capabilities from existing device
original_device = InputDevice('/dev/input/event0')
print(f"Cloning capabilities from: {original_device.name}")

# Create virtual device with same capabilities
ui = UInput.from_device(original_device, name='cloned-device')

try:
    # Virtual device now has same capabilities as original
    print(f"Virtual device created: {ui.name}")
    print(f"Capabilities: {ui.capabilities()}")
    
    # Use virtual device...
    
finally:
    ui.close()
    original_device.close()

Context Manager Usage

from evdev import UInput, ecodes

# Recommended: Use context manager for automatic cleanup
with UInput({ecodes.EV_KEY: [ecodes.KEY_A]}, name='temp-keyboard') as ui:
    # Send key press
    ui.write(ecodes.EV_KEY, ecodes.KEY_A, 1)
    ui.syn()
    time.sleep(0.1)
    ui.write(ecodes.EV_KEY, ecodes.KEY_A, 0)
    ui.syn()
# Device automatically closed when exiting context

Multi-Device Capabilities

from evdev import InputDevice, UInput, ecodes

# Merge capabilities from multiple devices
keyboard = InputDevice('/dev/input/event0')  # Keyboard
mouse = InputDevice('/dev/input/event1')     # Mouse

# Create virtual device combining both
ui = UInput.from_device(
    keyboard, mouse,
    name='combo-device',
    filtered_types=(ecodes.EV_SYN, ecodes.EV_FF)  # Exclude sync and force feedback
)

try:
    # Device now supports both keyboard and mouse events
    caps = ui.capabilities(verbose=True)
    print("Combined capabilities:", caps)
    
    # Can send both keyboard and mouse events
    ui.write(ecodes.EV_KEY, ecodes.KEY_H, 1)      # Keyboard
    ui.write(ecodes.EV_REL, ecodes.REL_X, 5)      # Mouse
    ui.syn()

finally:
    ui.close()
    keyboard.close() 
    mouse.close()

Error Handling

from evdev import UInput, UInputError, ecodes

try:
    ui = UInput({
        ecodes.EV_KEY: [ecodes.KEY_A]
    }, name='test-device')
    
    # Device operations...
    ui.write(ecodes.EV_KEY, ecodes.KEY_A, 1)
    ui.syn()
    
except UInputError as e:
    print(f"UInput error: {e}")
    # Handle device creation or operation errors
except PermissionError:
    print("Permission denied - check /dev/uinput access")
except Exception as e:
    print(f"Unexpected error: {e}")
finally:
    if 'ui' in locals():
        ui.close()

Install with Tessl CLI

npx tessl i tessl/pypi-evdev

docs

device-operations.md

event-codes.md

event-processing.md

force-feedback.md

index.md

virtual-devices.md

tile.json