Skip to content

Repository files navigation

JAX-V4

JAXController

Turn your phone into a gamepad for your PC.

JAXController — Joint adaptive Xinput Controller — is a cross-platform virtual gamepad system that transforms your Android device into a fully functional game controller for Windows and Linux. It delivers low-latency, system-level input injection over Wi-Fi — no drivers, no dongles, no compromise.


The Three Tiers

JAX-V4 is designed in three performance tiers to match every use case, from casual browsing to competitive esports.

Tier Name Target Status
Low JAX CORE Casual gaming, lightweight apps, retro emulators, indie titles Experimental
Mid JAX FLASH Mainstream PC gaming, media control, productivity, modern AAA Experimental
High JAX FORGE AAA titles, precision input, low-latency competitive play, rhythm games Experimental

All tiers are currently experimental. Features, APIs, and behavior may change before stable release.

Tier Breakdown

  • JAX CORE: Optimized for responsiveness over absolute fidelity. Ideal for emulators, pixel-art games, and casual mobile ports where responsiveness matters more than feature completeness.
  • JAX FLASH: The general-purpose tier. Balances input fidelity, polling rate, and feature set for the broadest compatibility with modern PC games.
  • JAX FORGE: Maximum fidelity and minimum latency. Targets high-refresh-rate competitive titles, fighting games, and precision-dependent genres where every millisecond counts.

How It Works

  1. Connect: The PC server starts a TCP listener and displays its IP and port as a QR code. The Android client scans the QR code (or enters the address manually) and opens a TCP socket.
  2. Serialize: Every frame, the mobile client packs the current gamepad state — buttons, triggers, thumbsticks — into a compact binary message using Colfer serialization from the shared VGP_Data_Exchange schema.
  3. Receive & Parse: The server reads bytes off the socket into a ring buffer. parse_gamepad_state() deserializes complete Colfer packets into a vgp_data_exchange_gamepad_reading struct, handling incomplete data, schema mismatches, and oversized payloads.
  4. Execute: The parsed reading is handed to the active Executor:
    • GamepadExecutor maps the reading onto a virtual XInput gamepad (Windows: ViGEmBus/XUSB; Linux: libevdev uinput) and injects it at the OS level.
    • KeyboardMouseExecutor applies the active keymap profile to convert buttons/thumbsticks/triggers into keyboard keys and mouse actions (including smooth cursor movement via stepped SendInput/libevdev).
  5. Repeat: The client streams readings at display refresh rate; the server processes each one in order. No cloud, no tracking, no telemetry.

Architecture

┌─────────────────────────────────────────────────────────────────────┐
│                         Android Client                              │
│  ┌─────────────┐   ┌───────────────┐   ┌────────────────────────┐  │
│  │  Compose UI │   │  GamepadState │   │   Colfer Serializer   │  │
│  │  (screens,  │──▶│  (buttons,    │──▶│   (VGP_Data_Exchange  │  │
│  │  composables│   │  axes, touch) │   │    schema)            │  │
│  └─────────────┘   └──────┬────────┘   └───────────┬────────────┘  │
│                           │                        │               │
│                     TCP Socket                  binary payload     │
└───────────────────────────┼────────────────────────┼───────────────┘
                            │                        │
                     local Wi-Fi / USB RNDIS      Colfer encoded
                            │                        │
┌───────────────────────────▼────────────────────────▼───────────────┐
│                        PC Server (Qt 6 / C++20)                    │
│                                                                     │
│  ┌──────────────┐    ┌────────────────┐    ┌───────────────────┐   │
│  │  QTcpServer  │───▶│  Data Buffer   │───▶│  Parser           │   │
│  │  + QR code   │    │  + readyRead   │    │  parse_gamepad_   │   │
│  │  generation  │    │  event loop    │    │  state()          │   │
│  └──────────────┘    └────────────────┘    └────────┬──────────┘   │
│                                                      │              │
│                                            vgp_data_exchange_      │
│                                            gamepad_reading         │
│                                                      │              │
│                                      ┌───────────────▼───────────┐ │
│                                      │   ExecutorInterface       │ │
│                                      │   (strategy pattern)      │ │
│                                      └───────────┬───────────────┘ │
│                                                  │                 │
│                            ┌────────────────────┼──────────────┐   │
│                            │                    │              │   │
│                   ┌────────▼────────┐  ┌────────▼────────┐     │   │
│                   │  GamepadExecutor│  │ KeyboardMouse   │     │   │
│                   │                 │  │ Executor        │     │   │
│                   │  - Button map  │  │ - Keymap profile│     │   │
│                   │  - Thumbsticks  │  │ - Thumb→mouse   │     │   │
│                   │  - Triggers     │  │ - Thumb→keys    │     │   │
│                   │  - deadzone/    │  │ - Trigger→keys  │     │   │
│                   │    response     │  │                 │     │   │
│                   └────────┬────────┘  └────────┬────────┘     │   │
│                            │                    │              │   │
│            ┌───────────────┼────────────┐        │              │   │
│            │               │            │        │              │   │
│     ┌──────▼──────┐ ┌──────▼──────┐ ┌──▼─────────▼──┐          │   │
│     │ GamepadSim  │ │KeyboardSim │ │   MouseSim    │          │   │
│     │ (Windows)   │ │(Windows +  │ │ (Windows +    │          │   │
│     │ - ViGEmBus  │ │ Linux)     │ │  Linux)       │          │   │
│     │ - XUSB      │ │ - SendInput│ │ - SendInput   │          │   │
│     │ - report    │ │ - libevdev │ │ - libevdev    │          │   │
│     │   injection │ │   uinput   │ │   uinput      │          │   │
│     └─────────────┘ └────────────┘ └───────────────┘          │   │
│                                                                     │
│  SettingsSingleton (QSettings) stores executor mode, port,        │
│  keymap profiles, thumbstick/trigger mappings per profile.        │
└─────────────────────────────────────────────────────────────────────┘

Component details

Mobile Client (VirtualGamePad-Mobile)

  • Language: Kotlin, Jetpack Compose
  • Build: Gradle (Android Gradle Plugin)
  • UI: Compose screens for main menu, gamepad layout, connection (manual IP + QR scan), settings, button customization
  • Networking: ConnectionViewModel manages a Socket and a LinkedBlockingQueue<NetworkCommand>; a coroutine-based command processor serializes sends/receives so Colfer packets never interleave
  • Input: Touch-driven virtual gamepad with configurable buttons, analog sticks, triggers, and D-pad; haptic feedback via HapticUtils

Transport

  • Protocol: TCP, local-network only
  • Connection: Server binds QHostAddress::AnyIPv4; mobile connects by IP/port. Port is configurable via SettingsSingleton
  • QR flow: Server renders each global IP as a QR code (createQR) encoding ip:port; mobile scans and resolves both parts
  • Latency tuning: LowDelayOption is set on the socket; Nagle's algorithm is disabled to keep small gamepad packets unbundled
  • Alternative transport: Android USB tethering (RNDIS/NCM) works without code changes because the socket layer is IP-agnostic

Wire Protocol (VGP_Data_Exchange)

  • Schema: GamePadReading.colf defines the single active message type GamepadReading
  • Fields: buttons_up (bitmask), buttons_down (bitmask), left_trigger, right_trigger, left_thumbstick_x/y, right_thumbstick_x/y (all float)
  • Serialization: Colfer — compact, schema-enforced, zero-copy friendly. C and Java code is generated from the same .colf source, guaranteeing both ends agree on layout
  • Button enum: GamepadButtons mirrors Windows.Gaming.Input.GamepadButtons bit flags (Menu, View, A/B/X/Y, DPad, shoulders, thumbsticks)

PC Server (VirtualGamePad-PC)

  • Language: C++20 with Qt6 (Core, Widgets, Network, Gui)
  • Build: CMake 3.20+, Ninja; platform presets for Windows and Linux
  • Entry: main.cppMainWindowServer widget
  • Networking: Server owns a QTcpServer. On newConnection it grabs the pending socket, pauses accepting, and connects readyRead to serveClient(). serveClient() appends to dataBuffer and loops calling parse_gamepad_state() until no complete packet remains
  • Parser: parse_gamepad_state() calls vgp_data_exchange_gamepad_reading_unmarshal() and translates Colfer error codes (EWOULDBLOCK, EFBIG, EILSEQ) into ParseResult::FailureReason
  • Executor: Strategy pattern (ExecutorInterface) with two implementations:
    • KeyboardMouseExecutor: reads the active KeymapProfile from SettingsSingleton; maps buttons via buttonMap(), converts thumbstick deflection into either smooth mouse steps (using circleToSquare + per-step moveMouseByOffset) or directional key holds, and maps triggers to key presses with a THRESHOLD cutoff
    • GamepadExecutor: feeds readings straight into GamepadInjector; maps JAX button flags to XUSB (Windows) or Linux input.h codes, clamps thumbsticks to [-1, 1] and triggers to [0, 1], then calls inject()

Simulation Layer

  • GamepadInjector
    • Windows: Allocates a ViGEm client, connects to ViGEmBus, creates an XUSB (Xbox 360) target, and pushes XUSB_REPORT updates via vigem_target_x360_update
    • Linux: Creates a libevdev device + uinput fd; tracks button state to emit press/release events; writes thumbstick/trigger absolute values through the uinput event interface
  • KeyboardInjector
    • Windows: wraps SendInput with scan-code translation
    • Linux: writes key events through a libevdev uinput keyboard device
  • MouseInjector
    • Windows: wraps SendInput for absolute/relative movement, button state, and scroll
    • Linux: writes relative motion and button events through a libevdev uinput mouse device

Settings & Profiles

  • SettingsSingleton wraps QSettings and exposes: executor type, TCP port, active keymap profile path, mouse sensitivity
  • KeymapProfile loads/saves INI-style profiles mapping each GamepadButtons entry to an InputKeyCode (native virtual key / Linux keycode) plus optional display names; also stores per-thumbstick behavior (direction keys vs mouse move), per-trigger mappings, and per-thumbstick mouse-move toggles

What's Included

Component Role
VirtualGamePad-PC C++/Qt6 server — GUI, TCP listener, QR display, Colfer parsing, executor dispatch, platform input injection
VirtualGamePad-Mobile Android client — Compose UI, touch gamepad, QR scanner, Colfer serialization, TCP socket management
VGP_Data_Exchange Shared Colfer schema + generated C/Java code for GamepadReading and GamepadButtons
ViGEmBus Windows kernel driver dependency (Xbox 360 virtual device) — installed separately, not bundled

Key Features

  • Cross-platform: Windows 10/11 (SendInput + ViGEmBus) and Linux (libevdev + uinput)
  • Dual execution: native virtual gamepad or keyboard/mouse fallback via customizable keymap profiles
  • Multi-instance multiplayer: each PC instance listens independently; multiple phones can connect to separate servers
  • Local-only networking: TCP over LAN or USB tethering, no cloud, no telemetry
  • QR-code pairing: one scan connects phone to server
  • Tunable per-profile mappings: buttons, triggers, thumbsticks (directional keys, smooth mouse, or raw gamepad axes)
  • Portable or installable builds (controlled by CMake option)
  • Open-source and auditable

Tech Stack

Area Technology
PC Server C++20, Qt6 (Core, Widgets, Network, Gui), CMake, Ninja
Mobile Client Kotlin, Jetpack Compose, Android Gradle Plugin
Serialization Colfer binary protocol (shared .colf schema)
Windows Input SendInput (keyboard/mouse), ViGEmBus + XUSB (virtual gamepad)
Linux Input libevdev, uinput (keyboard, mouse, gamepad)
QR Generation Nayuki's QR-Code-generator (C++)
Documentation Doxygen

Build

Prerequisites

  • Windows: Visual Studio 2022 (MSVC) or MinGW, Qt 6, CMake 3.20+, Ninja, ViGEmBus driver
  • Linux: GCC 10+, Qt 6, CMake 3.20+, Ninja, libevdev-dev, access to /dev/uinput (add user to input group or run as root)

Build the PC Server

cd VirtualGamePad-PC
cmake --preset <preset-name> -B build
cmake --build build --config Release

Build the Mobile Client

cd VirtualGamePad-Mobile
./gradlew assembleRelease

For full build instructions and preset names, see VirtualGamePad-PC/Build.md.


Current Status

JAX-V4 is in active development. The entire stack — JAX CORE, JAX FLASH, and JAX FORGE — remains experimental.

Planned work includes:

  • Stabilizing all three tiers
  • Expanded platform coverage
  • Performance tuning and latency reduction
  • Enhanced profile management and community sharing
  • USB RNDIS transport evaluation for lower latency than Wi-Fi
  • Input smoothing (stick deadzone, response curves) for touch-native thumbsticks

For usage, downloads, and troubleshooting, visit the official JAX-V4 website — the best free virtual controller.


Fixes Applied (vs. upstream kitaswas/VirtualGamePad)

These are corrections made to the JAX-V4 fork beyond the original VirtualGamePad-Mobile / VirtualGamePad-PC repositories.

✅ Linux trigger axes — ABS_HAT2X/YABS_Z/ABS_RZ (FIXED)

  • File: VirtualGamePad-PC/src/simulation/linux/gamepadSim.cpp
  • Problem: The Linux GamepadInjector registered the LT/RT triggers on ABS_HAT2X/ABS_HAT2Y (a secondary hat-switch axis), not the standard analog trigger axes. ABS_HAT2X/Y is intended for a second D-pad-style hat, not analog triggers, so games and SDL/evdev-based input libraries never read trigger data from those axes. This caused LT/RT to be silently dropped or misread cross-platform vs. Windows (ViGEm/XUSB), exactly the kind of "input clipping / cross-talk" that appears only on Linux.
  • Fix: Triggers now register (and write) on ABS_Z (LT) and ABS_RZ (RT) — the same axes the kernel xpad driver uses for Xbox-style controllers. The float→[0,255] data path is unchanged, so the fix is isolated to which axis constant is registered and is low-risk.
  • Status: Shipped. Affects only the Linux GamepadExecutor path.

✅ Mobile proportional analog triggers (FIXED)

  • File: VirtualGamePad-Mobile/.../ui/composables/Trigger.kt
  • Problem: Triggers were captured as binary on/off (gamepadState.LeftTrigger = 1f / 0f on press/release) via Compose isPressed, so a light tap reported the same value as a full press. The GamepadReading schema already carries left_trigger/right_trigger as float [0,1], so only the touch capture was missing — the transport and Linux side already supported analog.
  • Fix: Dropped Material Button/interactionSource/collectIsPressedAsState entirely (its click/ripple semantics and detectDragGestures touch-slop delay fight continuous position tracking). Replaced with awaitEachGesture + raw pointerInput, reading change.position.y directly each frame and writing a normalized coerceIn(0f, 1f) pressure vector based on vertical offset inside the trigger box. Touch-down reports immediately (no slop delay), overshoot clamps for free, and a DisposableEffect safety net zeroes the trigger if the composable leaves mid-press. Same "make an input continuous" category as the still-open stick deadzone/response-curve fix in DEV_NOTES.md; both should be tested in one on-device session.
  • Test: GamepadInputE2ETest.kt updated from the old > 0.5f binary assertion to verify both a near-max full press (> 0.9f) and a light top press (0.01f..0.4f), confirming the analog gradient end-to-end.
  • Status: Shipped.

Safety & Security

Independent scan results

Every release build is scanned before publishing. As of the current build, 1 out of 69 vendors flags VGamepadPC.exe — a generic heuristic label from one engine, not a confirmed detection. Every major vendor (Microsoft Defender, Kaspersky, ESET, Malwarebytes, BitDefender, and 60+ others) shows it clean. That single flag is a known pattern for small, unsigned, low-reputation executables that do input injection — the heuristic reacts to what the code touches (keyboard/gamepad injection APIs), not to actual malicious behavior. Check it yourself rather than taking our word for it.

VirusTotal — VGamepadPC.exe VirusTotal — GitHub repo

Why that 1/69 flag is a false positive

The flagging engine is MaxSecure, with the label Trojan.Malware.300983.susgen — the susgen suffix literally means "suspicious, generic." That's not "we identified known malware," it's "this file's behavior pattern resembles a broad category we watch." MaxSecure in particular has a well-documented reputation for aggressive heuristic flagging on small, unsigned executables.

Every engine that actually matters caught nothing — Microsoft Defender, Kaspersky, ESET, BitDefender, Malwarebytes, CrowdStrike, all 68 others: clean. If this were a real trojan, you'd expect several major vendors to agree, not one outlier.

The behavioral trigger is almost certainly your app's legitimate function: input injection. Any tool that calls SendInput, opens a raw socket to accept remote commands, and interacts with driver-level APIs looks structurally similar to a RAT (remote access trojan) to a heuristic scanner — because on a byte-pattern level, "receive network input → inject keystrokes" is exactly the shape of a RAT, even though your version does it consensually, locally, with the user driving both ends.

Zero reputation is the other half of it. Every AV vendor weights "how many people have run this exact binary hash before, and did anything bad happen" — a fresh CI build with no install history starts at zero trust, so any heuristic near-miss gets flagged rather than suppressed. This is why code signing genuinely helps here, not just for silencing SmartScreen — a signed binary from a consistent publisher identity builds exactly the reputation history that makes heuristics stop firing.

About

Best free virtual controller — turn your phone into a PC gamepad

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages