padmonk lets desktop Chromium players use mouse and keyboard on Xbox Cloud Gaming by presenting a virtual Xbox controller to the page. No driver install, no native helper app, no account, no telemetry. Open the extension, lock your mouse, tune aim, queue up.
This repo is for players, tinkerers, and contributors who want xCloud controls to feel less like menu wrestling and more like a proper loadout.
Play Xbox Cloud Gaming with mouse and keyboard through a virtual Xbox controller—no drivers or desktop helper needed.
Map keys, mouse buttons, and wheel input to Xbox controls. Tune stick-style mouse aim with look speed, smoothing, deadzone lift, fine aim boost, and invert Y. Lock your pointer on click, release it with Esc, and keep your preferred bindings ready with named control profiles.
Give each game its own default profile so padmonk loads your preferred setup when you return. Use Mapping to manage detected games, refresh cover art, reset assignments, and launch directly into Xbox Cloud Gaming.
Controls use browser storage; existing best-effort Chrome Sync may back up eligible settings. Route-bound cover recovery on play.xbox.com may send active Xbox Store product ID plus locale/market to a first-party Microsoft metadata endpoint. Complete seen-game history remains local. Metadata requests contain no bindings or profile configuration. No telemetry.
Desktop Chrome or Edge required. Xbox Cloud Gaming availability, subscription, and game support remain subject to Xbox and regional availability.
- Maps WASD to left stick movement.
- Maps mouse movement to right stick aim through Pointer Lock.
- Maps keyboard, mouse buttons, and wheel input to Xbox controls.
- Runs entirely in browser page context through Gamepad API injection.
- Keeps local storage as live authority, with best-effort Chrome Sync backup for profiles, globals, and global/game defaults. Backup uses crash-safe, quota-bounded chunks with a 24 KiB decoded-payload ceiling; larger configurations remain local-only. Failed publications clean only this writer's uncommitted generation. Complete
seenGamesproduct names, images, and history stay local and are excluded from Sync. - Ships named control profiles with per-game defaults, plus advanced remapping, sensitivity, smoothing, aim curve, invert-Y, profile/bundle import/export, and reset.
padmonk is now a bundled extension — it builds from TypeScript/Svelte sources into a dist/ folder, and that built folder is what you load.
- Install dependencies:
npm install - Build the extension:
npm run build - Open Chrome or Edge.
- Go to
chrome://extensionsoredge://extensions. - Enable Developer mode.
- Click Load unpacked.
- Select the
dist/folder (the build output — not the repo root). - Visit https://www.xbox.com/play or a gamepad tester.
For development with hot reload, run npm run dev and load the dist/ folder it produces; edits to UI and content scripts rebuild live.
Desktop Chromium required. Safari/WebKit ignores this page-level virtual pad path. Android Chromium lacks needed Pointer Lock behavior for this use case.
- Open https://hardwaretester.com/gamepad or https://gamepad-tester.com.
- Press mapped input such as
Space. - Confirm
padmonk Virtual Xbox 360 Controllerappears. - Press WASD and confirm left stick moves.
- Click page, move mouse, and confirm right stick moves.
- Press
Escto release mouse.
If tester sees pad and inputs move, xCloud should see same controller layer.
- Go to https://www.xbox.com/play.
- Start a game.
- Click game video to lock mouse for aim.
- Press your toggle combo (default
F8) to toggle padmonk if you need real-controller control back. - Press your show-binds combo (default
F9) while the game page has focus to show the visual controller binds overlay. - Tune settings from the extension popup.
The toggle and show-binds shortcuts are configurable combos (defaults F8 / F9). Change them in Advanced remapping.
Tune these from the extension popup (or Advanced remapping). The UI shows player-facing values; internally, padmonk converts them to the right-stick mapper constants.
| Setting | Default | Range | What it does |
|---|---|---|---|
| Look speed | 80% |
10–280% |
How much mouse movement maps to right-stick deflection. Higher = the stick reaches full throw (max in-game turn rate) with less hand motion. This is the main "how fast do I aim" dial. |
| Smoothing | 12% |
0–95% |
Response time-constant for aim. 0% ≈ 25 ms (instant, snappiest, can feel twitchy on a cheap mouse); higher values lengthen it (25% ≈ 85 ms, up to ~250 ms) for buttery-smooth-but-laggier motion. It damps mouse polling jitter — it does not add a turn-speed cap. |
| Deadzone lift | 12% |
0–50% |
Minimum non-zero stick output while the mouse is actively moving. Many games swallow tiny stick deflections inside a hidden deadzone, so slow mouse movement would do nothing. Raise it if slow aim feels dead; lower it if the crosshair creeps on its own. |
| Fine aim boost | +25% |
-100–+75% |
Shape around small mouse movement. 0% is linear; positive values boost micro aim for easier tracking; negative values soften the start for slower, steadier aim. |
| Invert Y | off |
on/off | Flips vertical aim (mouse up → look down). |
| Lock pointer on click | on |
on/off | Auto-locks the mouse pointer when you click the game so aim works; Esc releases. Turn off if you prefer to lock manually. |
Aim is framerate-independent — it's driven by mouse velocity (px/s), so the same Sensitivity/Smoothing feel identical on a 60 Hz, 120 Hz, or 240 Hz display.
Reminder: a virtual thumbstick has a maximum deflection, and at full throw the game sets the turn rate. Sensitivity/Smoothing change how fast you reach and leave that ceiling, but no setting (and no KBM-on-cloud tool) can exceed it — instant mouse-style 180° flicks aren't physically possible through the Gamepad API. If you get shot in the back and turn 180° at a leisurely controller pace, that is not a bug; that is the stick ceiling politely escorting you to the respawn screen.
Shooter starter settings:
- Look speed:
100–165% - Smoothing:
10–25% - Deadzone lift:
10–18% - Fine aim boost:
+15–+40%
In-game, raise look sensitivity and disable aim deadzone when the game allows it. Hidden deadzones are the final boss here.
| Input | Xbox control |
|---|---|
| W/A/S/D | Left stick |
| Mouse move | Right stick |
| Left click | RT |
| Right click | LT |
| Space | A |
| Ctrl | B |
| R | X |
| F | Y |
| Q / E | LB / RB |
| Shift / C | L3 / R3 |
| Tab | View |
| Enter | Menu |
| Backquote | Guide |
| Arrows or 1-4 | D-pad |
| Toggle combo (default F8) | Toggle padmonk |
| Show-binds combo (default F9) | Show/hide keybind overlay |
Open the extension popup, then Advanced remapping, to change bindings or the toggle/show-binds combos. Multiple inputs per Xbox control are supported, and remapping warns when an input is already bound elsewhere.
padmonk keeps your tuning in named profiles — each profile is a full loadout: its own bindings plus sensitivity, smoothing, aim curve, invert-Y, and lock-pointer settings. Create as many as you like (one per game, per genre, or per mood) and switch between them without re-tuning.
- Per-game defaults: assign a default profile to a game and padmonk auto-loads it whenever navigation detection enters that game; that navigation may show a switch toast. Each frame/document resolves independently; explicit overlay choice survives same-document SPA navigation, then resets on full reload.
- In-overlay switching: change the active profile straight from the in-game binds overlay, and use "save as default for this game" to pin the current profile to the game you're in.
- Mapping page: Advanced Settings is two pages — Settings (binds + tuning, with staged saves) and Mapping, a game → profile table where you set or reset each game's default.
- Conflict-safe rebinding: every control is rebindable, including the left stick (WASD). Reusing an input already bound in the same profile asks you to confirm, and a collision with a global toggle/show-binds shortcut is blocked outright.
- Import/export: export a single profile or a full multi-profile bundle, and import bundles to move your whole setup between machines.
padmonk patches navigator.getGamepads() in the page MAIN world at document_start. xCloud asks the browser for gamepads, the browser answers with the padmonk virtual Xbox pad, and input state comes from keyboard and mouse events.
A second content script runs in isolated world as extension-side relay. Popup, options, and relay send narrow commands to service worker; worker serializes mutation, validates payload, then writes chrome.storage.local. Relay forwards resolved updates into page through postMessage, so active games adapt without reinstall or restart. MAIN-world side has no chrome.* access — asset URLs and config arrive only through bridge.
This keeps install surface small:
- No ViGEmBus.
- No DriverKit.
- No kernel extensions.
- No helper daemon.
- No server.
padmonk is TypeScript end to end, built with Vite + @crxjs/vite-plugin (handles MV3 manifest, MAIN-world content-script bundling, and HMR). The UI is Svelte 5; styling is Tailwind v4 with a shared theme palette (no inline hex). The source is modular:
src/core/— pure, framework-free logic (gamepad state, mapper math, config, combos, labels) driven by a single control registry (controller-actions.ts) that is the source of truth for default bindings, the options page, the overlay, and the table above. Fully unit-tested.src/content/—bridge.ts(isolated-world relay: worker commands/storage updates →postMessage, asset URLs) andinject.ts(MAIN-world coordinator) plusinput-capture.ts(keyboard/mouse/wheel/pointer-lock wiring).src/background/— MV3 worker owns serialized durable mutations; UI → worker commands →chrome.storage.localis sole live write path.chrome.storage.syncreceives best-effort, crash-safe chunked backup data below a 24 KiB decoded-payload ceiling, excludingseenGames; oversized configurations remain local-only, and cleanup never touches unknown device generations.src/popup/andsrc/options/— Svelte popup and Advanced Settings pages.src/ui/— shadow-DOM HUD + binds overlay so injected styles never leak into or collide with the host page.
Vitest covers core and extension-boundary contracts. GitHub Actions runs format-check → typecheck → tests → build → transitive dist/ verification → real unpacked Chromium smoke; dist/ uploads before smoke for failure diagnostics.
Current build is hackable, playable, and intentionally small. Expect xCloud changes to occasionally break injection or input assumptions. If that happens, open an issue with browser, OS, game, tester result, console errors, and whether your toggle/show-binds combos respond.
Recent quality-of-life work: named control profiles with per-game defaults (switchable from the overlay), conflict-safe rebinding of every control, legacy per-axis left-stick diagonals (each axis reaches full deflection, magnitude ~1.41), Bahnschrift with system fallbacks, and profile/bundle import/export to file.
pi-coding-agent helped develop current application state and this repo refresh on behalf of esskayesss: icon rollout, README cleanup, keybind overlay investigation, the TypeScript/Svelte rebuild, and ongoing maintenance support. Specific shoutout to gpt-5.5 for tireless grep, patient refactors, and enough controller slander to make aim assist file a complaint. Blame humans for taste; credit robots for stamina.
Good contributions:
- Fix broken xCloud detection or injection timing.
- Improve aim feel without adding native dependencies.
- Make remapping clearer.
- Keep storage and Chrome Sync behavior documented honestly.
- Keep install instructions honest.
- Keep Xbox vibe loud but usable.
Before sending changes, run npm run format:check, npm run typecheck, npm run test, npm run build, and npm run verify:dist. Verifier walks manifest/HTML/CSS/JS local artifact graph and locale tokens. For real unpacked Chromium coverage, run npm run setup:chromium once, then npm run smoke:extension (Linux CI runs it under Xvfb). Smoke forces known optional Bahnschrift CDN failure and checks runtime fallback stack, so network does not decide result.
npm run zipThis zips the current dist/ output to padmonk-<version>.zip. Local builds use package.json as the fallback version; release builds set PADMONK_VERSION from the git tag so the packaged manifest.json version and zip filename match the release tag.
For a local release-style build:
PADMONK_VERSION=1.2.3 npm run build
PADMONK_VERSION=1.2.3 npm run zipPADMONK_VERSION must be a Chrome extension version like 1.2.3 or 1.2.3.4.
Releases are tag-driven. Create an annotated tag and push it:
git tag -a v1.2.3 -m "Release v1.2.3"
git push origin v1.2.3The GitHub release workflow derives PADMONK_VERSION=1.2.3 from tag, runs format/typecheck/tests/build, verifies transitive dist/ artifact graph, runs real unpacked Chromium smoke, then verifies manifest version, rejects dirty release builds, zips extension, and attaches padmonk-1.2.3.zip to GitHub Release. No package/publication step runs before Chromium smoke.
Project page source lives at docs/index.html. It includes storage and privacy disclosure without pretending a tiny open-source extension needs a corporate privacy microsite.
- xCloud only sees padmonk on allow-listed pages.
- Pointer Lock is required for mouse aim.
- Native mouse-and-keyboard mode in xCloud bypasses controller emulation. Use controller input mode.
- Some games have heavy deadzones or aim curves that need tuning.
- Browser and xCloud updates can break behavior.
- Native-controller suppression is document/runtime-local. Cross-frame runtime ownership remains deferred; do not treat it as per-tab consent.
- Bahnschrift CDN font load is best-effort and CSP/network-dependent; local and system font fallbacks keep UI readable.
padmonk is independent software. It is not affiliated with, endorsed by, or sponsored by Microsoft. Xbox is a trademark of Microsoft Corporation. padmonk has no developer-operated telemetry or server. Route-bound cover recovery on play.xbox.com may send active Xbox Store product ID plus locale/market to a first-party Microsoft metadata endpoint; metadata requests contain no bindings or profile configuration, and complete seenGames history remains local. Chrome may best-effort synchronize eligible settings through your signed-in Chrome profile. Sync backup has a 24 KiB decoded-payload ceiling to keep two generations below Chrome's total quota; larger configurations stay local-only. See docs/index.html.
Controllers had good run. Respect to couch warriors, claw-grip elders, stick-drift survivors, and everyone who learned to aim by gently bullying two tiny mushrooms. But some of us want crosshair control, not thumb-based astrology. Some of us want reload on R, not whatever plastic rune decided today. Some of us looked at right stick acceleration and said: absolutely not, send mouse.
padmonk walks into xCloud wearing green LEDs, drops WASD on left stick, bolts mouse aim to right stick, and tells controllers to hold this L in party chat. If someone says “skill issue,” please auto-ricochet that comment directly back to their aim-assist dependency, then ask why getting shot in the back requires turning 180 degrees at the pace of a scenic railway. Is this a little cringe? Yes. Is it still cleaner than pretending stick drift is personality? Also yes.