Python bindings to the Linux input handling subsystem for reading input events and creating 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.
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."""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.
"""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."""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()
"""Exception class for UInput-specific errors.
class UInputError(Exception):
"""Exception raised for UInput device errors."""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()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()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 contextfrom 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()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 contextfrom 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()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