Skip to main content

ElfDeckAI

tests PyPI License: MIT Python

Virtual keyboard, gamepad and mouse for AI agents on Linux and the Steam Deck.

ElfDeckAI lets an AI agent operate a real Linux machine through its input devices. Three small daemons create virtual devices on /dev/uinput, and an MCP server exposes them, together with a screenshot tool, as tools any MCP client can call:

  • ElfKey: a virtual keyboard
  • ElfPad: a virtual Xbox 360-class gamepad (buttons, analog triggers, two sticks, D-pad)
  • ElfMouse: a virtual mouse (relative pointer, buttons, wheel)

Applications, games, Steam Input and the compositor see these as ordinary hardware. Unlike emulator-scripting approaches, nothing depends on a particular emulator or game: ElfDeckAI has been used to play Pokémon FireRed in RetroArch on a physical Steam Deck from the intro through the first rival battle, with the agent seeing only screenshots and acting only through ElfKey. See notes/driving.md for what was learned.

The daemons and server are single-file Python programs that use only the standard library.

Quick start: Steam Deck

On the Deck, switch to Desktop Mode, open Konsole and run:

curl -fsSL https://raw.githubusercontent.com/Garthnesss/ElfDeckAI/main/install-on-deck.sh | sh

The installer works entirely inside your home directory (no sudo):

  1. installs the elfdeckai package for your user,
  2. installs and starts elfkey, elfpad and elfmouse as systemd user services,
  3. checks that all three virtual devices are present, and
  4. prints the MCP configuration for your PC and the commands to enable SSH.

It is safe to re-run; it upgrades in place.

For a guided setup, give AI_SETUP_GUIDE.md to your AI assistant. It is written for an assistant to follow and walks through the Deck side, the PC side and troubleshooting.

Quick start: any Linux machine

pip install elfdeckai

elfkey &
elfpad &
elfmouse &

echo "key x 150"     > /tmp/elfkey.fifo     # tap X for 150 ms
echo "press a"       > /tmp/elfpad.fifo     # tap A
echo "move 300 -100" > /tmp/elfmouse.fifo   # move the pointer
echo "click left"    > /tmp/elfmouse.fifo   # left click

grep -E "ElfKey|ElfPad|ElfMouse" /proc/bus/input/devices   # confirm the devices exist

Without pip, clone the repository and run python3 elfkey.py (and so on) directly. Runtime state lives in /tmp and is cleared on reboot.

/dev/uinput is user-writable on the Steam Deck by default. On other distributions, install the udev rule first (see Installation).

MCP server

elfdeckai-mcp is a Model Context Protocol server (stdio transport) that exposes 19 tools:

Group Tools
Keyboard key_tap, key_down, key_up, key_release
Gamepad pad_press, pad_down, pad_up, pad_stick, pad_trigger, pad_center, pad_reset
Mouse mouse_move, mouse_click, mouse_down, mouse_up, mouse_scroll, mouse_hscroll, mouse_release
Vision screenshot

screenshot captures the screen of the machine the server runs on and returns a PNG, so an agent can work in a look, act, look again loop without a separate video stream. It supports KDE Plasma (including a Steam Deck in Desktop Mode), wlroots compositors and X11.

Running on the same machine as the daemons:

{"mcpServers": {"elfdeckai": {"command": "elfdeckai-mcp"}}}

Running on a Steam Deck from an AI client on your PC, over SSH:

{"mcpServers": {"elfdeckai": {
    "command": "ssh",
    "args": ["deck@DECK-IP-ADDRESS", "/home/deck/.local/bin/elfdeckai-mcp"]}}}

This needs key-based SSH login from the PC to the Deck, because the MCP client cannot answer a password prompt; AI_SETUP_GUIDE.md covers the setup. Without pip, use "command": "python3", "args": ["/path/to/elfdeckai_mcp.py"].

Every tool call is validated against the tool's schema and the daemon's own parser before anything is written to a FIFO. If a daemon is not running, the call returns an error immediately instead of blocking.

Command reference

ElfKey (/tmp/elfkey.fifo)

Command Effect
key <name> [ms] Tap a key (default 120 ms)
down <name> / up <name> Hold / release a key
release Release every held key
quit Release everything and exit

Keys: a–z, 0–9, arrows, enter, esc, space, tab, backspace, shift, rshift, ctrl, rctrl, alt, ralt, capslock, f1–f10, home, end, pageup, pagedown, insert, delete and common punctuation. The full table is KEYS in elfkey.py.

ElfPad (/tmp/elfpad.fifo)

Command Effect
press <btn> [ms] Tap a button or D-pad direction (default 120 ms)
down <btn> / up <btn> Hold / release
stick left|right <x> <y> [ms] Set an analog stick, −100 to 100 per axis; with ms, recenter afterwards
trigger left|right <0..100> [ms] Set an analog trigger; with ms, release afterwards
center Return sticks, triggers and D-pad to neutral
reset Release everything
quit Release everything and exit

Buttons: a b x y lb rb l2 r2 select start guide l3 r3, plus D-pad directions up down left right.

ElfPad identifies as an Xbox 360 controller, so SDL, Steam Input and RetroArch map it without configuration. l2 and r2 drive the analog trigger axes, as on the real controller; there are intentionally no digital trigger buttons, because declaring them shifts Select, Start and Guide off the standard button indices.

ElfMouse (/tmp/elfmouse.fifo)

Command Effect
move <dx> <dy> [glide_ms] Move the pointer relatively; with glide_ms, spread the move over that time
click <btn> [ms] Click left, right or middle (default 100 ms)
down <btn> / up <btn> Hold / release a button (for drags: down, move, up)
scroll <ticks> / hscroll <ticks> Vertical / horizontal wheel (positive is up / right)
release Release every held button
quit Release everything and exit

Movement is relative: negative dy moves up. Units are relative motion counts, not guaranteed screen pixels, because pointer acceleration applies. Several small moves checked with screenshots are more reliable than one large move. Each command is limited to ±10,000 counts and ±120 wheel ticks.

Safety model

Because the caller may be a language model, the daemons are built so that bad input cannot leave the machine in a bad state:

  • Malformed input is refused, not fatal. Every line passes a strict parser; invalid commands are logged and ignored.
  • Taps are bounded. A timed hold lasts at most 5 seconds. Longer holds use down/up.
  • Nothing stays stuck. Timed actions restore in a finally block, held inputs are tracked, and on quit, SIGTERM or SIGINT every input is released before the device is removed. key_release, mouse_release and pad_reset release everything on demand.
  • Owner-only access. Each FIFO is created with mode 0600.
  • Audit log. Every accepted command is written to stderr (the journal, when run as a service).
  • Robust protocol handling. The MCP server returns proper JSON-RPC errors for malformed or oversized requests and never blocks on a stopped daemon.

Scope: these are real OS-level input devices. They act on whichever window has focus, including terminals and browsers. ElfDeckAI keeps input bounded, validated and releasable; deciding where an agent is allowed to act is up to the operator. Only run agents you trust, in sessions you can supervise.

Comparison with other approaches

Approach Limitation
Emulator APIs (PyBoy, mGBA scripting) Only work inside a specific emulator.
Sending keys through Moonlight or other streaming clients Mouse clicks are forwarded, but synthetic keyboard events are not.
xdotool Cannot reach native Wayland clients.
ydotool, dotool Same uinput approach for keyboard and mouse, but no gamepad, raw keycodes, and no agent-facing interface (MCP, input bounds, release commands).
Hardware input emulators (KVM gadgets, controller adapters) Work, but require extra hardware.

Installation

Requirements: 64-bit Linux with /dev/uinput and Python 3.9 or newer.

Where /dev/uinput is not writable by your user, install the udev rule once:

sudo cp 70-elfdeckai-uinput.rules /etc/udev/rules.d/
sudo udevadm control --reload-rules && sudo udevadm trigger
getfacl /dev/uinput   # your user should appear in the ACL

To run the daemons as systemd user services after pip install --user elfdeckai (the unit files expect the commands in ~/.local/bin):

mkdir -p ~/.config/systemd/user
cp systemd/*.service ~/.config/systemd/user/
systemctl --user daemon-reload
systemctl --user enable --now elfkey.service elfpad.service elfmouse.service

Development

pip install -e ".[test]"
python3 -m pytest

The test suite covers each daemon's input parser, device behaviour against a fake uinput device, and the MCP server over real JSON-RPC. No /dev/uinput is required to run it.

Roadmap

  • A single combined daemon for keyboard, gamepad and mouse.
  • Screenshot support in Steam Deck Gaming Mode.
  • A demo recording of the Pokémon run.

Changelog

See CHANGELOG.md.

License

MIT. See LICENSE.

Author

Created and maintained by Garth Elfstrom (@Garthnesss). Issues and pull requests are welcome.

Metadata

Release files for elfdeckai 1.0.7

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for elfdeckai 1.0.7
File Size Uploaded Provenance
elfdeckai-1.0.7.tar.gz 30.1 kB With provenance Details

Built distribution (wheel)

Table of built distributions (wheels) for elfdeckai 1.0.7
File Interpreter ABI Platform
elfdeckai-1.0.7-py3-none-any.whl Python 3 none any Details

Total release size: 57.0 kB

Release files / elfdeckai-1.0.7.tar.gz

Download URL elfdeckai-1.0.7.tar.gz
Size 30.1 kB
Tags Source
SHA-256 checksum
How to use checksums
0b7132357822526efb20e19ff08faca462946f2a5457c4f609f76d2fc6a6649b
BLAKE2b-256 checksum
How to use checksums
d8305ba53310e9c7ad2b5803bdfd437f18fd9ddc97a4dfa45edb7d10b5ad4a36
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.16

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 28, 2026.

Transparency log

Release files / elfdeckai-1.0.7-py3-none-any.whl

Download URL elfdeckai-1.0.7-py3-none-any.whl
Size 26.8 kB
Tags Python 3
SHA-256 checksum
How to use checksums
0477ec0922221137bc9908607b878df2ac5be7550f0970ee47f031d792ff9336
BLAKE2b-256 checksum
How to use checksums
554d2879b185c7320c265c991563fc9319223960935a5e4d27dc1a0eadfd93f9
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.16

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 28, 2026.

Transparency log

Security for elfdeckai 1.0.7

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

Full provenance

All 2 files in this release have been uploaded with provenance metadata.

Select from 2 files

Attestation for elfdeckai-1.0.7.tar.gz

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 28, 2026.

Transparency log

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

To learn more about protecting yourself from malicious actors, visit our package security documentation.

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page