Skip to content

Creating Virtual Input Devices

This tutorial covers creating virtual keyboards, relative pointer devices, and synthetic input events.

Prerequisites ​

/dev/uinput must exist and be writable. See UInput Access Setup for configuring permissions properly.

Creating a virtual keyboard ​

The simplest way to create a virtual keyboard using an evdev.uinputSpec:

lua
local evdev = require "evdev"
local UInput = evdev.uinput.create
local ui = assert(UInput({ name = "Lua Virtual Keyboard" }))

-- Give the kernel time to register the device
os.execute("sleep 0.5") -- Replace with your preferred sleep helper
print(ui.path)

When keys option is omitted, evdev enables all real KEY_* and BTN_* codes. The name is what tools like evtest will display.

WARNING

Without the delay, accessing device info right after creation may fail.

Emitting key presses ​

Use emit() to queue an event and sync() to flush it:

lua
ui:emit(evdev.ecodes.EV_KEY, evdev.ecodes.KEY_A, 1)
ui:sync()
ui:emit(evdev.ecodes.EV_KEY, evdev.ecodes.KEY_A, 0)
ui:sync()

Custom key set ​

Instead of enabling all keys, specify exactly which keys your virtual device supports:

lua
local gamepad = assert(evdev.uinput.create {
  name = "Lua Gamepad",
  keys = {
    evdev.ecodes.BTN_SOUTH,
    evdev.ecodes.BTN_EAST,
    evdev.ecodes.BTN_NORTH,
    evdev.ecodes.BTN_WEST,
    evdev.ecodes.BTN_TL,
    evdev.ecodes.BTN_TR,
    evdev.ecodes.BTN_START,
    evdev.ecodes.BTN_SELECT,
  },
  event_types = {
    evdev.ecodes.EV_KEY,
    evdev.ecodes.EV_SYN,
  },
})

Mouse / relative axis device ​

lua
local mouse = assert(UInput({
  name = "Lua Mouse",
  keys = {
    evdev.ecodes.BTN_LEFT,
    evdev.ecodes.BTN_RIGHT,
    evdev.ecodes.BTN_MIDDLE,
  },
  rels = {
    evdev.ecodes.REL_X,
    evdev.ecodes.REL_Y,
    evdev.ecodes.REL_WHEEL,
  },
}))

-- Give the kernel time to register the device
os.execute("sleep 0.5") -- Replace with your preferred sleep helper

-- Move right and down in one input frame.
mouse:emit(evdev.ecodes.EV_REL, evdev.ecodes.REL_X, 50)
mouse:emit(evdev.ecodes.EV_REL, evdev.ecodes.REL_Y, 25)
mouse:sync()

-- Click right.
mouse:emit(evdev.ecodes.EV_KEY, evdev.ecodes.BTN_RIGHT, 1)
mouse:sync()
mouse:emit(evdev.ecodes.EV_KEY, evdev.ecodes.BTN_RIGHT, 0)
mouse:sync()