or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

device-operations.mdevent-codes.mdevent-processing.mdforce-feedback.mdindex.mdvirtual-devices.md

virtual-devices.mddocs/

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

```