Skip to main content
g923-claude: drive Claude Code with a racing wheel

Brake to interrupt Claude. Gas to send. Shift gears to switch models. Steer to scroll.
And the wheel buzzes when Claude needs you.

CI Release PyPI Python Platform License: MIT Stars

Quick start · Controls · How it works · Other wheels · Contributing

Why?

You already have a ~£300 force-feedback controller on your desk, and you spend all day telling an AI agent "yes", "no", "stop" and "keep going". Those are pedal-shaped decisions.

  • 🦶 Feet on the flow: slam the brake the moment Claude goes off the rails. Gas to approve. No hunting for Esc.
  • ⚙️ Gears are models: H-pattern shifter maps to Haiku → Sonnet → Opus → Fable. Clutch in, shift, done. Your current gear is your current model.
  • 🎮 Thumbs answer prompts: ✕ yes, ○ no, □ "always allow", same as every PlayStation menu you've ever used.
  • 🛞 Steer to scroll: the further you turn, the faster it scrolls.
  • ⌨️ Type with the wheel: a rotary-dial typewriter with word suggestions learned from your own prompts.
  • 📳 It talks back: Claude Code hooks ping the wheel when Claude finishes or is waiting on a permission prompt.
  • 🔒 Safe by default: starts disarmed; nothing reaches your keyboard until you press PS.

Quick start

git clone https://github.com/Emperor-Z/g923-claude.git
cd g923-claude && ./install.sh

Open Claude Code, focus the terminal, press PS on the wheel. You're driving.

Want the floating HUD too? Run ./install.sh --hud.

Controls

Controls: wheel buttons, pedals and shifter mapped to Claude Code actions

Drive mode (default)

Control Sends In Claude Code
Gas Enter Submit / accept highlighted option
Brake Esc Interrupt Claude; decline a permission prompt
Clutch (tap) Shift+Tab Cycle permission mode
Clutch + shifter /model … Change model (see below)
Paddles, D-pad ▲▼, dial ↑ / ↓ Move through menus and history
D-pad ◀▶ ← / → Cursor, dialog tabs
Dial Enter, ✕ Enter Submit / Yes
○ Esc No / close dialog
□ Shift+Tab "Allow for this session" on prompts, else cycle mode
△ Ctrl+T Toggle task checklist
L2 Ctrl+O Transcript viewer
R2 Ctrl+B Background the running command
R3 Tab Accept autocomplete
L3 (hold) Space (held) Voice dictation, if enabled in Claude Code
+ / − /effort … Raise / lower reasoning effort
Steering mouse wheel Scroll the output
PS — Arm / disarm the whole thing (hold 1.5 s: hide or show the HUD)
Share / Options — Switch to Drive / Type mode

Shifter = model

Hold the clutch and move the stick, like a real car. Shifting without the clutch is ignored (and grinds), so knocking the stick never changes your model.

Gear Model
1 haiku
2 sonnet
3 opus
4 fable
5 opus[1m] (1M context)
6 opusplan
R Rewind (Esc Esc)
N no change

Your draft prompt is stashed (Ctrl+S) while the command is sent and restored after, so shifting mid-sentence doesn't lose it. /model applies immediately, even while Claude is working: the switch takes effect from Claude's next request. If Claude Code shows a prompt-cache warning, confirm it with the gas pedal.

+ / − step reasoning effort through low → medium → high → xhigh → max with /effort. The daemon assumes you start at high (configurable as [effort] start).

Gear presets live in [shifter] in config.toml. Each gear is a list of slash commands, so a gear can set several things at once, for example gear_6 = ["/model fable", "/effort max"].

Type mode

Press Options to type with the wheel; Share returns to Drive mode. The character you're choosing is typed straight into the prompt as a live preview and replaced as you scroll.

Control Action
D-pad ◀▶ Previous / next character
Dial ↻↺ Same, fast
D-pad ▲▼ Switch set: abc → ABC → 123 → symbols
Dial Enter Commit the character
✕ Space
○ Backspace (hold to repeat)
Left paddle / △ / Right paddle Type the left / middle (best) / right suggested word
□ Three more suggestions
Gas / Brake Still Enter / Esc

Letters run a to z and wrap around, so spin anticlockwise to reach the end of the alphabet fast (z is one click back from a). The dial keeps its position between characters, so the first press after a commit re-shows the same letter: double letters are one press plus Enter.

What you see is what you get. A previewed character that's visible in the prompt stays there unless you press ○ to remove it, so ✕, Gas or moving on all keep it.

Holding ○ clears faster. One press is one character; hold it for 0.4 s and backspace repeats like a keyboard key, and after 1.5 s of holding it deletes whole words (Ctrl+W) instead. Word deletes repeat more slowly (every 0.3 s) so you can let go in time. The timings live in [type] as backspace_repeat_after_s, backspace_repeat_rate_s, backspace_word_after_s and backspace_word_rate_s.

Word suggestions, like a phone keyboard. Three words are always on offer: endings for the word you're typing (the letter you're scrolling over counts, so landing on r already offers refactor), or, once a word is done, the word most likely to come next. Pick with the left paddle, △ (the best guess, in the middle) or the right paddle; the word and a space are typed for you, and the next three appear, so tapping △ a few times can finish a whole phrase like fix the failing tests. □ shows three more. The HUD shows them as a suggestion bar; without it, they pop up as a notification. Suggestions learn which words follow which from your past Claude Code prompts (~/.claude/history.jsonl, read locally at startup and never written anywhere), on top of a built-in word list and some common phrases (g923/words.txt, g923/phrases.txt).

Tips:

  • / (top of the symbols set) opens Claude Code's command menu and @ opens the file picker. There, and whenever there's no word to pick, the paddles are ↑/↓ again, so drive those menus with the paddles and Gas.
  • Pedals, gears and the other buttons keep their Drive mode actions while you type.
  • For long prompts, hold L3 for voice dictation instead.

Floating HUD

The HUD: Claude editing a file, asking to run a command with the wheel buttons that answer it, done with its steps listed, and Type mode picking the next letter

A card that floats in the bottom-right corner of your screen: half agent status, half racing dash.

  • What Claude is doing, in plain words: "Thinking…", "Editing hud.py", "Running npm test", with a timer, your prompt, and the steps so far as breadcrumbs (Read ✓ › Grep ✓ › Edit). It ends with "Done in 1:42".
  • F1 rev lights as the status light: they rev while Claude works (faster while a tool runs), flash amber when it needs you, and go green when it's done.
  • Approvals you answer from the wheel: the card turns amber and shows the command along with the buttons that answer it: ✕ Yes, □ Always, ○ No.
  • A racing dial: its rim turns with your real wheel. The gear sits in the middle, /effort is the tach arc around it (max is the red zone), and the model is underneath. Live clutch, brake and gas bars sit beside it.
  • Type mode: your word, the letter you're on, and its neighbours on the dial.

Drag it anywhere and it remembers where; double-click to snap it back to its corner. Hover over it for a × that closes it. Hold PS on the wheel for 1.5 s to hide or show it, or to bring it back after closing it. It never takes keyboard focus, so the wheel keeps typing into Claude even while you move it. Prefer clicks to pass straight through it? Set click_through = true.

./install.sh --hud                  # from a clone: adds Qt and starts it as a user service
pipx install 'g923-claude[hud]'     # from PyPI, then run: g923-hud
g923-hud --demo                     # preview it without a wheel

Qt adds about 250 MB, which is why the HUD is optional. Its starting place and size are in the [hud] section of your config, with screen (for example "HDMI-1"), corner, margin, scale and opacity. Set show_details = false to keep your prompts and commands off the card when you share your screen. The gear and model use the bundled Titillium Web font (SIL Open Font License, g923/fonts/OFL.txt). On GNOME Wayland it runs through XWayland, the only way an app can stay on top in a corner there.

How it works

 G923 ──evdev──▶ g923d (Python daemon, systemd user service)
                   │  maps inputs → virtual keyboard + mouse (uinput) ──▶ focused terminal
                   │
                   ◀── Unix socket ◀── Claude Code hooks (prompt submitted, stop, permission)
                   │
                   ├─▶ feedback: force feedback on the wheel, or notification + sound
                   │
                   └─▶ live state on the same socket ──▶ g923-hud (optional on-screen HUD)

The daemon reads the wheel through evdev and types into whichever window has focus through a virtual uinput keyboard, so it works on any Wayland compositor (GNOME included) and X11. Claude Code hooks report back over a Unix socket so the wheel can react.

Install

The quick start is all most people need. Details:

Requires Linux, Python 3.11+, systemd, and write access to /dev/uinput (granted to the logged-in user on most systemd distros).

git clone https://github.com/Emperor-Z/g923-claude.git
cd g923-claude
./install.sh

install.sh:

  1. creates .venv and installs evdev (with uv if available)
  2. adds the Claude Code hooks to ~/.claude/settings.json, leaving your other hooks alone and saving a backup to settings.json.g923.bak
  3. installs and starts the g923d systemd user service

./uninstall.sh stops the service and removes only the g923 hooks.

Upgrading: after git pull, run ./install.sh again (with --hud if you use the HUD). It updates the hooks and restarts the services. With pipx: pipx upgrade g923-claude, then g923-hooks.

From PyPI:

pipx install g923-claude
g923-hooks            # add the Claude Code hooks
g923d                 # run the daemon

Each release also ships the wheel and sdist.

Customising: put only the settings you want to change in ~/.config/g923-claude/config.toml; it's layered over the defaults. Ready-made configs for other wheels live in configs/.

Container (GHCR): for the curious. Notifications and sounds don't work inside it, so prefer the install script.

docker run --rm -it --device /dev/uinput --device /dev/input \
  -v "$XDG_RUNTIME_DIR:$XDG_RUNTIME_DIR" -e XDG_RUNTIME_DIR \
  ghcr.io/emperor-z/g923-claude:latest

Claude Code integration

Hooks tell the daemon what Claude is doing via bin/g923-ping, a tiny client that talks to the daemon's Unix socket ($XDG_RUNTIME_DIR/g923.sock) and silently does nothing if the daemon isn't running.

Hook Message Wheel reaction
UserPromptSubmit busy — (the HUD shows your prompt)
PreToolUse tool — (the HUD shows what Claude is running)
Stop done Two quick kicks on the wheel (or a chime without force feedback)
PermissionRequest attention Notification + side-to-side wiggle (or warning sound), repeated every 3 s until you answer
PostToolUse clear Stops the repeat

Any wheel or pedal input also counts as answering, so the nagging stops as soon as you hit ✕, ○, Gas or Brake.

Works in VS Code too

The daemon acts like a keyboard and mouse, so it drives whatever is focused. That includes Claude Code in any terminal, VS Code's integrated terminal, and the Claude Code VS Code extension chat panel.

Where Status
Any terminal running claude ✅ Everything
VS Code integrated terminal ✅ Everything (if VS Code grabs a shortcut like Ctrl+B, set "terminal.integrated.sendKeybindingsToShell": true)
VS Code extension chat panel 🟡 Pedals, arrows, buttons, Type mode and force feedback work; gear and +/− presets type x/model sonnet instead of running (#22)

Steering scrolls whatever is under the mouse pointer, so keep it over Claude.

Force feedback

With a driver that exposes force feedback, the wheel itself reacts:

Event Feel
Claude finished Two quick kicks, left then right
Permission waiting Slow side-to-side wiggle, every 3 s until you answer
Shifted without the clutch Harsh buzz
Armed / disarmed One tap / two soft taps
Always Light centring spring, so letting go of the wheel stops the scroll

Without force feedback, each event falls back to a sound. Tune [ffb] strength and autocenter in config.toml, and try the effects with bin/g923d --test-ffb (or --test-ffb done).

PlayStation G923 (046d:c266) on Linux: the stock kernel binds it to hid-generic, which has no force feedback. Install new-lg4ff:

sudo git clone https://github.com/berarma/new-lg4ff.git /usr/src/new-lg4ff
sudo dkms install /usr/src/new-lg4ff
# unplug and replug the wheel, then:
bin/g923d --test-ffb

DKMS rebuilds it on kernel upgrades. The Xbox G923 (046d:c26e) already has force feedback in mainline via HID++.

Usage

With the service installed it runs in the background; follow it with journalctl --user -u g923d -f. To run it by hand instead (stop the service first):

bin/g923d              # run the daemon, then press PS to arm
bin/g923d --dry-run    # print what each input would send, without sending it
bin/g923d --dump       # print raw wheel events (find button codes)
bin/g923d --test-ffb   # play every force-feedback effect
bin/g923d --config my.toml

Focus the terminal running Claude Code, press PS, and drive.

Tests

.venv/bin/python -m unittest discover -s tests -t .

The tests replay synthetic wheel events through the daemon, so they don't need the wheel connected.

Safety

Keys go to whichever window is focused. The daemon starts disarmed — press PS to arm it (you get a notification). Disarm before gaming or using other apps.

Troubleshooting

  • Nothing happens: press PS to arm. Check journalctl --user -u g923d -f for connected:.
  • Permission denied on /dev/uinput: add a udev rule: KERNEL=="uinput", TAG+="uaccess" in /etc/udev/rules.d/60-uinput.rules, then sudo udevadm control --reload && sudo udevadm trigger.
  • Wheel not found: run bin/g923d --dump; if your wheel's name doesn't contain "G923", set [device] name_match.
  • Wrong buttons: run bin/g923d --dump, press the button, and fix its code in [buttons].
  • No notifications or sounds: install libnotify (notify-send) and libcanberra (canberra-gtk-play).
  • Keys go to the wrong window: they always go to the focused window. Disarm (PS) before switching apps.

Roadmap

  • Phase 0 — skeleton, config, README
  • Phase 1 — daemon core, Drive mode, steering scroll, arm/disarm
  • Phase 2 — clutch + shifter model switching, effort
  • Phase 3 — Type mode with word suggestions
  • Phase 4 — Claude Code hooks, feedback, systemd service, installer
  • Phase 5 — force feedback effects (new-lg4ff for the PS G923)
  • Floating HUD
  • Rev LEDs showing Claude's context usage

Supported hardware

Device Status
Logitech G923 PlayStation/PC (046d:c266) ✅ Fully mapped
Logitech Driving Force Shifter ✅ Model switching
Logitech G923 Xbox/PC (046d:c26e) 🟡 Should work, button codes differ (#12)
Logitech G29 🟡 configs/logitech-g29.toml, untested (#11)
Logitech G920 🟡 Needs a config (#12)
Thrustmaster, Fanatec, Moza, anything evdev 🙋 Help wanted (#13)

Everything device-specific lives in config.toml, and per-wheel overrides live in configs/. Supporting a new wheel usually means running bin/g923d --dump, pressing each button, and writing a small config with only the codes that differ. If you get your wheel working, please open a PR with your config. See CONTRIBUTING.md.

FAQ

Is this a joke? It started as one. Then braking to stop a runaway refactor turned out to be really satisfying.

Does it work on macOS or Windows? Not yet. It relies on Linux evdev and uinput. A port is a welcome contribution.

Does it send my data anywhere? No. Word suggestions read your local ~/.claude/history.jsonl at startup, in memory only. The hooks only talk to a local Unix socket.

Will it type into the wrong window? Keys go to whatever is focused, which is why it starts disarmed. Press PS to toggle.

Can I remap everything? Yes. Every button, pedal threshold, gear preset and type-mode key is in config.toml.

Contributing

Bug reports, wheel configs and new feedback ideas are all welcome. Start with CONTRIBUTING.md. If this made you smile, a ⭐ helps other people find it.

Star history

Star History Chart

License

MIT. Not affiliated with Logitech or Anthropic. "G923" and "Claude" are trademarks of their respective owners.

Metadata

Release files for g923-claude 0.6.1

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

Source distribution (sdist)

Source distribution for g923-claude 0.6.1
File Size Uploaded
g923_claude-0.6.1.tar.gz 549.3 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for g923-claude 0.6.1
File Interpreter ABI Platform
g923_claude-0.6.1-py3-none-any.whl Python 3 none any Details

Total release size: 632.4 kB

Release files / g923_claude-0.6.1.tar.gz

Download URL g923_claude-0.6.1.tar.gz
Size 549.3 kB
Tags Source
SHA-256 checksum
How to use checksums
c5bd11d57a3970a2aa9262b1562fb3edaecf55dbb50021075b012ec72bce1b95
BLAKE2b-256 checksum
How to use checksums
c23d6dc8364c3f3bed953ef1b37f1d75285e35735cb1c7b6f73586182c2fe4f5
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

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 11, 2026.

Transparency log

Release files / g923_claude-0.6.1-py3-none-any.whl

Download URL g923_claude-0.6.1-py3-none-any.whl
Size 83.1 kB
Tags Python 3
SHA-256 checksum
How to use checksums
f2e8dd611abeefeaf1b5ebb919411310fcd32838b53245fb0d125f77c1ed981f
BLAKE2b-256 checksum
How to use checksums
c0b5c18a197ead02802c0c04f7e8e7b4052e29b474d100e14712405f267fd78c
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

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 11, 2026.

Transparency log

Release history Release notifications | RSS feed

This release

0.6.1 This release

2 release files

0.6.0

2 release files

0.5.1

2 release files

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