# How scrcpy Maps Gamepad Input to Android: UHID vs AOA Architecture Explained

> Learn how scrcpy maps gamepad input to Android using UHID or AOA. Understand the architecture translating PC controller events to an Android physical gamepad.

- Repository: [Genymobile/scrcpy](https://github.com/Genymobile/scrcpy)
- Tags: architecture
- Published: 2026-02-25

---

**scrcpy forwards gamepad input from your PC to an Android device by translating SDL2 events into HID reports using either UHID (Linux kernel virtual device) or AOA (Android Open Accessory protocol) backends, making the host controller appear as a physical gamepad on the Android side.**

The Genymobile/scrcpy project implements gamepad forwarding through a modular architecture that supports two distinct transport mechanisms. When you launch scrcpy with the `--gamepad` (`-G`) flag, the application captures local gamepad events and routes them through either the UHID or AOA processor, depending on your host operating system and connection method.

## scrcpy Gamepad Input Architecture Overview

### SDL2 Event Capture

scrcpy relies on SDL2 to discover and monitor gamepads connected to the host PC. The [`app/src/input_manager.c`](https://github.com/Genymobile/scrcpy/blob/main/app/src/input_manager.c) file contains the event handling logic that intercepts device additions, axis movements, and button presses.

When SDL generates a gamepad event, `sc_input_manager_process_gamepad_*` functions dispatch the event to the active processor:

```c
// input_manager.c – routing SDL events to the gamepad processor
im->gp->ops->process_gamepad_added(im->gp, &evt);   // device connected
im->gp->ops->process_gamepad_axis(im->gp, &evt);     // analog stick/trigger
im->gp->ops->process_gamepad_button(im->gp, &evt);    // button press/release

```

### The Gamepad Processor Trait

Both UHID and AOA implementations conform to the `sc_gamepad_processor` trait defined in [`app/src/trait/gamepad_processor.h`](https://github.com/Genymobile/scrcpy/blob/main/app/src/trait/gamepad_processor.h). This interface standardizes four callbacks:

- `process_gamepad_added`
- `process_gamepad_removed`
- `process_gamepad_axis`
- `process_gamepad_button`

This trait-based design allows scrcpy to swap between UHID and AOA backends at runtime based on the `--gamepad` command-line argument.

## UHID Backend: Linux Kernel Virtual Gamepad

The **UHID** (`--gamepad=uhid`) mode creates a virtual HID device on Linux hosts using the kernel's UHID interface. This method requires the `uhid` kernel module but provides the most transparent gamepad input mapping to Android.

In [`app/src/uhid/gamepad_uhid.c`](https://github.com/Genymobile/scrcpy/blob/main/app/src/uhid/gamepad_uhid.c), the implementation translates SDL events into standard HID gamepad reports:

```c
// gamepad_uhid.c – generating HID reports from button events
sc_hid_gamepad_generate_input_from_button(&gamepad->hid, &hid_input, event);
uhid_output_write(&gamepad->uhid, &hid_input);

```

The `uhid_output_write` function writes the report to the virtual device, which then appears to Android as a physical Bluetooth or USB HID gamepad. This approach works over both USB and Wi-Fi connections because the HID device is created on the host side and forwarded through scrcpy's standard transport.

## AOA Backend: Android Open Accessory Protocol

The **AOA** (`--gamepad=aoa`) mode uses the Android Open Accessory protocol to send HID reports directly over the USB accessory channel. This backend is implemented in [`app/src/usb/gamepad_aoa.c`](https://github.com/Genymobile/scrcpy/blob/main/app/src/usb/gamepad_aoa.c) and works on any host OS that supports AOA, though it requires a USB connection (it does not work over Wi-Fi).

The AOA processor builds HID input reports and pushes them through the AOA channel:

```c
// gamepad_aoa.c – pushing axis events over AOA
sc_hid_gamepad_generate_input_from_axis(&gamepad->hid, &hid_input, event);
sc_aoa_push_input(gamepad->aoa, &hid_input);

```

In OTG mode (`--otg`), scrcpy uses [`app/src/usb/screen_otg.c`](https://github.com/Genymobile/scrcpy/blob/main/app/src/usb/screen_otg.c) to route SDL events to the AOA gamepad processor. The [`screen_otg.c`](https://github.com/Genymobile/scrcpy/blob/main/screen_otg.c) file acts as the bridge between the input manager and the specific backend, calling `gp->ops->process_gamepad_axis(gp, &evt)` to forward events.

## Enabling and Configuring Gamepad Support in scrcpy

Activate gamepad forwarding using the `--gamepad` or `-G` flag. You can explicitly select the backend or let scrcpy choose based on your platform.

| Command | Backend | Requirements | Use Case |
|---------|---------|--------------|----------|
| `scrcpy --gamepad=uhid` | UHID | Linux host, `uhid` kernel module | Best compatibility, works over Wi-Fi |
| `scrcpy --gamepad=aoa` | AOA | USB connection | Cross-platform, requires USB cable |
| `scrcpy --otg --gamepad=aoa` | AOA | USB-OTG adapter | Direct accessory mode, no ADB needed |
| `scrcpy --no-video --no-audio --gamepad=uhid` | UHID | Linux host | Input-only mode for remote gaming |

When using UHID mode, ensure your Linux kernel has the `uhid` module loaded. AOA mode automatically negotiates the accessory protocol when the device connects, but requires the Android device to support AOA (most modern devices do).

## Summary

- scrcpy implements **gamepad input mapping** through two distinct backends: **UHID** (Linux kernel virtual device) and **AOA** (Android Open Accessory protocol).
- The **UHID backend** in [`app/src/uhid/gamepad_uhid.c`](https://github.com/Genymobile/scrcpy/blob/main/app/src/uhid/gamepad_uhid.c) creates a virtual HID device that appears as a physical gamepad to Android, supporting both USB and Wi-Fi connections.
- The **AOA backend** in [`app/src/usb/gamepad_aoa.c`](https://github.com/Genymobile/scrcpy/blob/main/app/src/usb/gamepad_aoa.c) sends HID reports directly over the USB accessory channel, working cross-platform but requiring a physical USB connection.
- SDL2 events are captured in [`app/src/input_manager.c`](https://github.com/Genymobile/scrcpy/blob/main/app/src/input_manager.c) and routed through the `sc_gamepad_processor` trait interface to the active backend.
- Enable the feature with `--gamepad=uhid` or `--gamepad=aoa` depending on your host OS and connection type.

## Frequently Asked Questions

### Does scrcpy support Xbox and PlayStation controllers?

Yes. scrcpy uses SDL2 for gamepad discovery, which supports standard USB and Bluetooth controllers including Xbox, PlayStation, and Nintendo Switch Pro controllers. The SDL events are normalized and translated to standard HID gamepad reports in [`app/src/uhid/gamepad_uhid.c`](https://github.com/Genymobile/scrcpy/blob/main/app/src/uhid/gamepad_uhid.c) or [`app/src/usb/gamepad_aoa.c`](https://github.com/Genymobile/scrcpy/blob/main/app/src/usb/gamepad_aoa.c), ensuring compatibility with Android's standard gamepad input stack regardless of the host controller brand.

### What is the difference between UHID and AOA gamepad modes?

**UHID** (`--gamepad=uhid`) creates a virtual HID device on Linux hosts using the kernel's UHID interface. The device appears to Android as a physical Bluetooth or USB gamepad and works over both USB and Wi-Fi connections. **AOA** (`--gamepad=aoa`) uses the Android Open Accessory protocol to push HID reports directly through the USB accessory channel. AOA works on any host OS but requires a USB cable and does not function over Wi-Fi. Choose UHID for flexibility on Linux; use AOA for cross-platform USB-only connections.

### Can I use scrcpy gamepad forwarding over Wi-Fi?

Yes, but only when using the **UHID** backend. The UHID implementation in [`app/src/uhid/gamepad_uhid.c`](https://github.com/Genymobile/scrcpy/blob/main/app/src/uhid/gamepad_uhid.c) creates the virtual HID device on the Linux host, and scrcpy's standard ADB transport forwards the input events to the Android device. This works over TCP/IP (Wi-Fi) because the HID device is emulated on the host side. The **AOA** backend requires a direct USB connection to negotiate the Android Open Accessory protocol and cannot function over Wi-Fi.

### How do I troubleshoot gamepad input lag in scrcpy?

First, verify you are using the appropriate backend for your connection type. For USB connections, ensure you are using `--gamepad=uhid` on Linux or `--gamepad=aoa` on other platforms. Over Wi-Fi, only UHID mode is available. Check that your controller is properly recognized by SDL2 by running `scrcpy --gamepad=uhid --verbosity=debug` and monitoring for `sc_input_manager_process_gamepad_*` events in the logs. If using UHID, confirm the `uhid` kernel module is loaded with `lsmod | grep uhid`. For AOA lag, ensure your USB cable supports data transfer (not charge-only) and try a direct USB 3.0 port rather than a hub.