0
# Virtual Input Devices
1
2
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.
3
4
## Capabilities
5
6
### UInput Device Creation
7
8
Core functionality for creating virtual input devices with customizable capabilities.
9
10
```python { .api }
11
class UInput(EventIO):
12
def __init__(
13
self,
14
events: Optional[Dict[int, Sequence[int]]] = None,
15
name: str = "py-evdev-uinput",
16
vendor: int = 0x1,
17
product: int = 0x1,
18
version: int = 0x1,
19
bustype: int = 0x3,
20
devnode: str = "/dev/uinput",
21
phys: str = "py-evdev-uinput",
22
input_props=None,
23
max_effects: int = 96
24
) -> None:
25
"""
26
Create a virtual input device.
27
28
Parameters:
29
- events: Dictionary mapping event types to lists of supported codes
30
{ecodes.EV_KEY: [ecodes.KEY_A, ecodes.KEY_B], ...}
31
- name: Device name as it appears in the system
32
- vendor: USB vendor ID
33
- product: USB product ID
34
- version: Device version
35
- bustype: Bus type (USB=0x3, Bluetooth=0x5, etc.)
36
- devnode: Path to uinput device node
37
- phys: Physical device path identifier
38
- input_props: Input properties list
39
- max_effects: Maximum number of force feedback effects
40
41
Raises:
42
- UInputError: If device creation fails
43
"""
44
45
@classmethod
46
def from_device(
47
cls,
48
*devices: Union[InputDevice, Union[str, bytes, os.PathLike]],
49
filtered_types: Tuple[int] = (ecodes.EV_SYN, ecodes.EV_FF),
50
**kwargs
51
) -> "UInput":
52
"""
53
Create UInput device copying capabilities from existing device(s).
54
55
Parameters:
56
- devices: InputDevice instances or paths to clone capabilities from
57
- filtered_types: Event types to exclude from capabilities
58
- **kwargs: Additional arguments passed to UInput constructor
59
60
Returns:
61
UInput instance with merged capabilities from source devices
62
"""
63
64
def close(self) -> None:
65
"""Close and destroy the virtual device."""
66
```
67
68
### Event Injection
69
70
Methods for injecting input events into the virtual device.
71
72
```python { .api }
73
class UInput:
74
def write(self, etype: int, code: int, value: int) -> None:
75
"""
76
Write input event to the virtual device.
77
78
Parameters:
79
- etype: Event type (ecodes.EV_KEY, EV_REL, EV_ABS, etc.)
80
- code: Event code (KEY_A, REL_X, ABS_X, etc.)
81
- value: Event value (1=press, 0=release for keys)
82
"""
83
84
def write_event(self, event: InputEvent) -> None:
85
"""
86
Write InputEvent object to the virtual device.
87
88
Parameters:
89
- event: InputEvent to inject
90
"""
91
92
def syn(self) -> None:
93
"""
94
Send SYN_REPORT synchronization event.
95
Required after writing events to ensure proper event delivery.
96
"""
97
```
98
99
### Device Information
100
101
Properties and methods for accessing device information.
102
103
```python { .api }
104
class UInput:
105
# Device attributes
106
name: str # Device name
107
vendor: int # Vendor ID
108
product: int # Product ID
109
version: int # Version number
110
bustype: int # Bus type identifier
111
devnode: str # Device node path
112
fd: int # File descriptor
113
device: InputDevice # Associated InputDevice instance
114
115
def capabilities(self, verbose: bool = False, absinfo: bool = True) -> Dict:
116
"""
117
Get device capabilities.
118
119
Parameters:
120
- verbose: Return human-readable names instead of codes
121
- absinfo: Include AbsInfo for absolute axes
122
123
Returns:
124
Dictionary mapping event types to supported codes
125
"""
126
127
def fileno(self) -> int:
128
"""Return file descriptor for select() operations."""
129
```
130
131
### Force Feedback Management
132
133
Methods for managing force feedback effects on virtual devices.
134
135
```python { .api }
136
class UInput:
137
def begin_upload(self, effect_id: int) -> "UInputUpload":
138
"""
139
Begin uploading force feedback effect.
140
141
Parameters:
142
- effect_id: Effect identifier
143
144
Returns:
145
UInputUpload structure for effect configuration
146
"""
147
148
def end_upload(self, upload: "UInputUpload") -> None:
149
"""
150
Complete force feedback effect upload.
151
152
Parameters:
153
- upload: UInputUpload structure from begin_upload()
154
"""
155
156
def begin_erase(self, effect_id: int) -> "UInputErase":
157
"""
158
Begin erasing force feedback effect.
159
160
Parameters:
161
- effect_id: Effect identifier to erase
162
163
Returns:
164
UInputErase structure
165
"""
166
167
def end_erase(self, erase: "UInputErase") -> None:
168
"""
169
Complete force feedback effect erase.
170
171
Parameters:
172
- erase: UInputErase structure from begin_erase()
173
"""
174
```
175
176
### Error Handling
177
178
Exception class for UInput-specific errors.
179
180
```python { .api }
181
class UInputError(Exception):
182
"""Exception raised for UInput device errors."""
183
```
184
185
## Usage Examples
186
187
### Basic Virtual Keyboard
188
189
```python
190
from evdev import UInput, ecodes
191
import time
192
193
# Create virtual keyboard with specific keys
194
ui = UInput({
195
ecodes.EV_KEY: [
196
ecodes.KEY_A, ecodes.KEY_B, ecodes.KEY_C,
197
ecodes.KEY_SPACE, ecodes.KEY_ENTER
198
]
199
}, name='virtual-keyboard')
200
201
try:
202
# Type "ABC" + Enter
203
for key in [ecodes.KEY_A, ecodes.KEY_B, ecodes.KEY_C]:
204
ui.write(ecodes.EV_KEY, key, 1) # Press
205
ui.syn()
206
time.sleep(0.1)
207
ui.write(ecodes.EV_KEY, key, 0) # Release
208
ui.syn()
209
time.sleep(0.1)
210
211
# Press Enter
212
ui.write(ecodes.EV_KEY, ecodes.KEY_ENTER, 1)
213
ui.syn()
214
time.sleep(0.1)
215
ui.write(ecodes.EV_KEY, ecodes.KEY_ENTER, 0)
216
ui.syn()
217
218
finally:
219
ui.close()
220
```
221
222
### Virtual Mouse
223
224
```python
225
from evdev import UInput, ecodes
226
import time
227
228
# Create virtual mouse
229
ui = UInput({
230
ecodes.EV_KEY: [ecodes.BTN_LEFT, ecodes.BTN_RIGHT, ecodes.BTN_MIDDLE],
231
ecodes.EV_REL: [ecodes.REL_X, ecodes.REL_Y, ecodes.REL_WHEEL]
232
}, name='virtual-mouse')
233
234
try:
235
# Move mouse cursor
236
ui.write(ecodes.EV_REL, ecodes.REL_X, 10) # Move right
237
ui.write(ecodes.EV_REL, ecodes.REL_Y, 10) # Move down
238
ui.syn()
239
time.sleep(0.1)
240
241
# Left click
242
ui.write(ecodes.EV_KEY, ecodes.BTN_LEFT, 1) # Press
243
ui.syn()
244
time.sleep(0.1)
245
ui.write(ecodes.EV_KEY, ecodes.BTN_LEFT, 0) # Release
246
ui.syn()
247
248
# Scroll wheel
249
ui.write(ecodes.EV_REL, ecodes.REL_WHEEL, 1) # Scroll up
250
ui.syn()
251
252
finally:
253
ui.close()
254
```
255
256
### Virtual Gamepad
257
258
```python
259
from evdev import UInput, ecodes, AbsInfo
260
261
# Create virtual gamepad with analog sticks and buttons
262
ui = UInput({
263
ecodes.EV_KEY: [
264
ecodes.BTN_A, ecodes.BTN_B, ecodes.BTN_X, ecodes.BTN_Y,
265
ecodes.BTN_START, ecodes.BTN_SELECT
266
],
267
ecodes.EV_ABS: [
268
(ecodes.ABS_X, AbsInfo(value=0, min=-32768, max=32767, fuzz=0, flat=0, resolution=0)),
269
(ecodes.ABS_Y, AbsInfo(value=0, min=-32768, max=32767, fuzz=0, flat=0, resolution=0)),
270
(ecodes.ABS_RX, AbsInfo(value=0, min=-32768, max=32767, fuzz=0, flat=0, resolution=0)),
271
(ecodes.ABS_RY, AbsInfo(value=0, min=-32768, max=32767, fuzz=0, flat=0, resolution=0))
272
]
273
}, name='virtual-gamepad')
274
275
try:
276
# Press A button
277
ui.write(ecodes.EV_KEY, ecodes.BTN_A, 1)
278
ui.syn()
279
time.sleep(0.1)
280
ui.write(ecodes.EV_KEY, ecodes.BTN_A, 0)
281
ui.syn()
282
283
# Move left analog stick
284
ui.write(ecodes.EV_ABS, ecodes.ABS_X, 16000) # Right
285
ui.write(ecodes.EV_ABS, ecodes.ABS_Y, -16000) # Up
286
ui.syn()
287
time.sleep(0.5)
288
289
# Center analog stick
290
ui.write(ecodes.EV_ABS, ecodes.ABS_X, 0)
291
ui.write(ecodes.EV_ABS, ecodes.ABS_Y, 0)
292
ui.syn()
293
294
finally:
295
ui.close()
296
```
297
298
**Context Manager Support:**
299
300
UInput devices support the context manager protocol for automatic cleanup:
301
302
```python
303
from evdev import UInput, ecodes
304
305
# Automatic cleanup with context manager
306
with UInput() as ui:
307
ui.write(ecodes.EV_KEY, ecodes.KEY_SPACE, 1) # Press space
308
ui.syn()
309
time.sleep(0.1)
310
ui.write(ecodes.EV_KEY, ecodes.KEY_SPACE, 0) # Release space
311
ui.syn()
312
# Device automatically closed when exiting context
313
```
314
315
### Cloning Existing Device
316
317
```python
318
from evdev import InputDevice, UInput
319
320
# Clone capabilities from existing device
321
original_device = InputDevice('/dev/input/event0')
322
print(f"Cloning capabilities from: {original_device.name}")
323
324
# Create virtual device with same capabilities
325
ui = UInput.from_device(original_device, name='cloned-device')
326
327
try:
328
# Virtual device now has same capabilities as original
329
print(f"Virtual device created: {ui.name}")
330
print(f"Capabilities: {ui.capabilities()}")
331
332
# Use virtual device...
333
334
finally:
335
ui.close()
336
original_device.close()
337
```
338
339
### Context Manager Usage
340
341
```python
342
from evdev import UInput, ecodes
343
344
# Recommended: Use context manager for automatic cleanup
345
with UInput({ecodes.EV_KEY: [ecodes.KEY_A]}, name='temp-keyboard') as ui:
346
# Send key press
347
ui.write(ecodes.EV_KEY, ecodes.KEY_A, 1)
348
ui.syn()
349
time.sleep(0.1)
350
ui.write(ecodes.EV_KEY, ecodes.KEY_A, 0)
351
ui.syn()
352
# Device automatically closed when exiting context
353
```
354
355
### Multi-Device Capabilities
356
357
```python
358
from evdev import InputDevice, UInput, ecodes
359
360
# Merge capabilities from multiple devices
361
keyboard = InputDevice('/dev/input/event0') # Keyboard
362
mouse = InputDevice('/dev/input/event1') # Mouse
363
364
# Create virtual device combining both
365
ui = UInput.from_device(
366
keyboard, mouse,
367
name='combo-device',
368
filtered_types=(ecodes.EV_SYN, ecodes.EV_FF) # Exclude sync and force feedback
369
)
370
371
try:
372
# Device now supports both keyboard and mouse events
373
caps = ui.capabilities(verbose=True)
374
print("Combined capabilities:", caps)
375
376
# Can send both keyboard and mouse events
377
ui.write(ecodes.EV_KEY, ecodes.KEY_H, 1) # Keyboard
378
ui.write(ecodes.EV_REL, ecodes.REL_X, 5) # Mouse
379
ui.syn()
380
381
finally:
382
ui.close()
383
keyboard.close()
384
mouse.close()
385
```
386
387
### Error Handling
388
389
```python
390
from evdev import UInput, UInputError, ecodes
391
392
try:
393
ui = UInput({
394
ecodes.EV_KEY: [ecodes.KEY_A]
395
}, name='test-device')
396
397
# Device operations...
398
ui.write(ecodes.EV_KEY, ecodes.KEY_A, 1)
399
ui.syn()
400
401
except UInputError as e:
402
print(f"UInput error: {e}")
403
# Handle device creation or operation errors
404
except PermissionError:
405
print("Permission denied - check /dev/uinput access")
406
except Exception as e:
407
print(f"Unexpected error: {e}")
408
finally:
409
if 'ui' in locals():
410
ui.close()
411
```