Brake to interrupt Claude. Gas to send. Shift gears to switch models. Steer to scroll.
And the wheel buzzes when Claude needs you.
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
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
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,
/effortis 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:
- creates
.venvand installsevdev(withuvif available) - adds the Claude Code hooks to
~/.claude/settings.json, leaving your other hooks alone and saving a backup tosettings.json.g923.bak - installs and starts the
g923dsystemd 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 -fforconnected:. Permission deniedon/dev/uinput: add a udev rule:KERNEL=="uinput", TAG+="uaccess"in/etc/udev/rules.d/60-uinput.rules, thensudo 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) andlibcanberra(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
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)
| File | Size | Uploaded | |
|---|---|---|---|
| g923_claude-0.6.1.tar.gz | 549.3 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| 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 logRelease 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