Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add gamepad support #5270

Merged
merged 45 commits into from
Sep 15, 2024
Merged

Add gamepad support #5270

merged 45 commits into from
Sep 15, 2024

Conversation

rom1v
Copy link
Collaborator

@rom1v rom1v commented Sep 6, 2024

This PR adds support for game controllers (gamepads).

There was a PR (#2130) implementing gamepad support using uinput on the device. However, this required to add a native dependency or to add native code (cf #2130 (comment)).

This PR uses HID instead, and provides support for AOA and UHID (like for keyboard and mouse).
It does not add any dependency, and it also works in OTG mode (via AOA), so even without USB debugging.

To enable it:

# UHID
scrcpy --gamepad=uhid
scrcpy -G  # short option, equivalent

# AOA
scrcpy --gamepad=aoa

# OTG
scrcpy --otg --gamepad=aoa
scrcpy --otg -G  # short option, equivalent

It is not enabled by default because UHID is not supported on all devices (especially with old Android versions, I don't remember which version exactly, due to a permission error).

I tested with PS4 and PS5 controllers connected by USB on my laptop, with gamepad tester apps, it seems to work.

Rumble is not implemented, because I don't know how to make it work. It probably requires to add the correct bytes to the report descriptor, and to parse the received HID outputs (the mechanism to receive them is already there). @yume-chan (or others) your help is welcome 😉

@LHLaurini I marked you as co-author of the commit named Handle SDL gamepad events (I don't give the SHA1 because it will change on every rebase), because your PR helped me to write this commit 👍

TODO

  • Rumble
  • Review/rework/fix patchset
  • User documentation

Fixes #99

@ahosker
Copy link

ahosker commented Sep 6, 2024

This would be very cool

@LHLaurini
Copy link
Contributor

LHLaurini commented Sep 6, 2024

I don't have any games with controller support installed, so I tested using https://greggman.github.io/html5-gamepad-test/ and a Gamepad Tester app.

OS: Arch Linux (up-to-date)
Phone: Samsung Galaxy M21s
Controller: Sony Dualsense (PS5 controller)

What works:

  • All digital buttons
  • Analog sticks

What kinda works:

  • Analog trigger values flicker between the correct value and half (e.g. if I press it halfway, it alternates between 0.5 and 0.25 -- kinda hard to tell exactly, it flickers fast).

What doesn't work:

  • Pressing (not just touching) the touchpad shows the following message: [server] WARN: Unknown UHID id: 5760. If repeated, scrcpy eventually crashes.
  • Pressing the mute button shows the following message: [server] WARN: Unknown UHID id: 6864. If repeated, scrcpy eventually crashes.

@rom1v
Copy link
Collaborator Author

rom1v commented Sep 6, 2024

Thank you very much!

What kinda works:

  • Analog trigger values flicker between the correct value and half (e.g. if I press it halfway, it alternates between 0.5 and 0.25 -- kinda hard to tell exactly, it flickers fast).

Oh, I wrote in the wrong endianness:

diff --git a/app/src/hid/hid_gamepad.c b/app/src/hid/hid_gamepad.c
index ca4dc67be..f78cfec73 100644
--- a/app/src/hid/hid_gamepad.c
+++ b/app/src/hid/hid_gamepad.c
@@ -322,12 +322,12 @@ sc_hid_gamepad_event_from_slot(uint16_t hid_id,
     uint8_t *data = hid_input->data;
     // Buttons are individual bits in the report descriptor, so the resulting
     // u16 must be written in little-endian
-    sc_write16be(data, slot->axis_left_x);
-    sc_write16be(data + 2, slot->axis_left_y);
-    sc_write16be(data + 4, slot->axis_right_x);
-    sc_write16be(data + 6, slot->axis_right_y);
-    sc_write16be(data + 8, slot->axis_left_trigger);
-    sc_write16be(data + 10, slot->axis_right_trigger);
+    sc_write16le(data, slot->axis_left_x);
+    sc_write16le(data + 2, slot->axis_left_y);
+    sc_write16le(data + 4, slot->axis_right_x);
+    sc_write16le(data + 6, slot->axis_right_y);
+    sc_write16le(data + 8, slot->axis_left_trigger);
+    sc_write16le(data + 10, slot->axis_right_trigger);
     sc_write16le(data + 12, slot->buttons & SC_HID_BUTTONS_MASK);
     data[14] = sc_hid_gamepad_get_dpad_value(slot->buttons);
 }

I wondered why it did not affect analog sticks, the reason is because the physical source gives a 8-bit value, so when converted to a 16-bit value by SDL it gives 2 identical 8-bit values, so big/little endian makes no difference.

(with this change, it takes values between 50% and 100%, there is still a minor fix to do to make it work between 0% and 100%)

EDIT: one more diff to fix the trigger values:

diff --git a/app/src/hid/hid_gamepad.c b/app/src/hid/hid_gamepad.c
index 97596e5bd..3dc342741 100644
--- a/app/src/hid/hid_gamepad.c
+++ b/app/src/hid/hid_gamepad.c
@@ -76,8 +76,8 @@ static const uint8_t SC_HID_GAMEPAD_REPORT_DESC[] = {
     0x09, 0xC4,
     // Logical Minimum (0)
     0x15, 0x00,
-    // Logical Maximum (65535)
-    0x27, 0xFF, 0xFF, 0x00, 0x00,
+    // Logical Maximum (32767)
+    0x26, 0xFF, 0x7F,
     // Report Size (16)
     0x75, 0x10,
     // Report Count (2)
@@ -418,26 +418,28 @@ sc_hid_gamepad_generate_input_from_axis(struct sc_hid_gamepad *hid,
 
     struct sc_hid_gamepad_slot *slot = &hid->slots[slot_idx];
 
-    // [-32768 to 32767] -> [0 to 65535]
-    uint16_t value = ((int32_t) event->value) + 0x8000;
+// [-32768 to 32767] -> [0 to 65535]
+#define AXIS_RESCALE(V) (uint16_t) (((int32_t) V) + 0x8000)
     switch (event->axis) {
         case SC_GAMEPAD_AXIS_LEFTX:
-            slot->axis_left_x = value;
+            slot->axis_left_x = AXIS_RESCALE(event->value);
             break;
         case SC_GAMEPAD_AXIS_LEFTY:
-            slot->axis_left_y = value;
+            slot->axis_left_y = AXIS_RESCALE(event->value);
             break;
         case SC_GAMEPAD_AXIS_RIGHTX:
-            slot->axis_right_x = value;
+            slot->axis_right_x = AXIS_RESCALE(event->value);
             break;
         case SC_GAMEPAD_AXIS_RIGHTY:
-            slot->axis_right_y = value;
+            slot->axis_right_y = AXIS_RESCALE(event->value);
             break;
         case SC_GAMEPAD_AXIS_LEFT_TRIGGER:
-            slot->axis_left_trigger = value;
+            // Trigger is always positive between 0 and 32767
+            slot->axis_left_trigger = MAX(0, event->value);
             break;
         case SC_GAMEPAD_AXIS_RIGHT_TRIGGER:
-            slot->axis_right_trigger = value;
+            // Trigger is always positive between 0 and 32767
+            slot->axis_right_trigger = MAX(0, event->value);
             break;
         default:
             return false;

What doesn't work:

  • Pressing (not just touching) the touchpad shows the following message: [server] WARN: Unknown UHID id: 5760. If repeated, scrcpy eventually crashes.
  • Pressing the mute button shows the following message: [server] WARN: Unknown UHID id: 6864. If repeated, scrcpy eventually crashes.

Hmm, I cannot reproduce. It looks like control message corruption.

Please run scrcpy -G --no-audio -Vverbose, and post the content before you get Unknown UHID id please.

Can you reproduce over AOA?

scrcpy eventually crashes

Does it crash like a segfault (on the client)? Or with an exception on the server?

Please build and run with:

meson setup x -Db_sanitize=address
ninja -Cx
./run x -G --no-audio

@SuperKali
Copy link

Hi @rom1v, Thank you very much for your work you are doing, I tried the binary you released but when I try to start the session it gives me permission error:

C:\Users\super\Downloads\scrcpy-win64-gamepad_draft1\scrcpy-win64-gamepad_draft1>scrcpy.exe -G
scrcpy 2.6.1 <https://github.com/Genymobile/scrcpy>
INFO: ADB device found:
INFO:     --> (tcpip)  youyeetoo-r1-v3:5555            device  redroid13_arm64
C:\Users\super\Downloads\scrcpy-win64-gamepad_draft1\scrcp... file pushed, 0 skipped. 85.9 MB/s (71328 bytes in 0.001s)
[server] INFO: Device: [redroid] redroid redroid13_arm64 (Android 13)
INFO: Renderer: direct3d
INFO: Texture: 1080x1920
[server] ERROR: Controller error
java.io.IOException: android.system.ErrnoException: open failed: EACCES (Permission denied)
        at com.genymobile.scrcpy.control.UhidManager.open(UhidManager.java:68)
        at com.genymobile.scrcpy.control.Controller.handleEvent(Controller.java:213)
        at com.genymobile.scrcpy.control.Controller.control(Controller.java:102)
        at com.genymobile.scrcpy.control.Controller.lambda$start$0$com-genymobile-scrcpy-control-Controller(Controller.java:110)
        at com.genymobile.scrcpy.control.Controller$$ExternalSyntheticLambda0.run(D8$$SyntheticClass:0)
        at java.lang.Thread.run(Thread.java:1012)
Caused by: android.system.ErrnoException: open failed: EACCES (Permission denied)
        at libcore.io.Linux.open(Native Method)
        at libcore.io.ForwardingOs.open(ForwardingOs.java:563)
        at libcore.io.BlockGuardOs.open(BlockGuardOs.java:274)
        at android.system.Os.open(Os.java:494)
        at com.genymobile.scrcpy.control.UhidManager.open(UhidManager.java:51)
        ... 5 more
WARN: Device disconnected

C:\Users\super\Downloads\scrcpy-win64-gamepad_draft1\scrcpy-win64-gamepad_draft1>

How can fix it?

@rom1v
Copy link
Collaborator Author

rom1v commented Sep 7, 2024

Oh, you don't have the permissions to open /dev/uhid on your device. Typically, this happens on old Android versions, but you are on Android 13. I have no solution for UHID. However, maybe AOA works over USB (scrcpy --gamepad=aoa).

@LHLaurini
Copy link
Contributor

Rebuilt with -Db_sanitize=address, I can't get it to crash anymore, even without the flag...

Please run scrcpy -G --no-audio -Vverbose, and post the content before you get Unknown UHID id please.

When pressing either the touchpad or the mute button, I get:

VERBOSE: input: UHID input [0]
[server] WARN: Unknown UHID id: 0
VERBOSE: input: UHID input [0]
[server] WARN: Unknown UHID id: 0

The first two lines show up on press, the other two on release.

So the crash seems to be due to trying to read from an empty buffer (just a guess, I didn't look at the code)? We should just ignore these.

Can you reproduce over AOA?

No, AOA seems fine (other than the issue with the triggers).

app/src/uhid/gamepad_uhid.c Outdated Show resolved Hide resolved
app/src/usb/gamepad_aoa.c Show resolved Hide resolved
app/src/uhid/gamepad_uhid.c Outdated Show resolved Hide resolved
app/src/hid/hid_gamepad.c Outdated Show resolved Hide resolved
@rom1v
Copy link
Collaborator Author

rom1v commented Sep 7, 2024

I reworked the PR to fix all known issues. I also updated the binaries for Windows in the first post (draft 2).

Thank you for your feedback ❤️

@rom1v rom1v mentioned this pull request Sep 7, 2024
8 tasks
@LHLaurini
Copy link
Contributor

I reworked the PR to fix all known issues.

Everything's working correctly for me with -G/--gamepad=uhid. Only thing I was unable to test was using a second controller.


With --gamepad=aoa, changes to axes seem noticeably delayed. Also, if I turn the analog sticks in circles, I get the following message (repeated):

WARN: Could not push AOA HID input (gamepad axis)

-Vverbose doesn't reveal anything interesting.

@yume-chan
Copy link
Contributor

yume-chan commented Sep 8, 2024

This is how my gamepad rumble currently works:

diff --git a/app/src/scrcpy.c b/app/src/scrcpy.c
index c45f88ad..8509b32b 100644
--- a/app/src/scrcpy.c
+++ b/app/src/scrcpy.c
@@ -756,6 +756,7 @@ aoa_complete:
         if (options->gamepad_input_mode == SC_GAMEPAD_INPUT_MODE_UHID) {
             sc_gamepad_uhid_init(&s->gamepad_uhid, &s->controller);
             gp = &s->gamepad_uhid.gamepad_processor;
+            uhid_gamepad = &s->gamepad_uhid;
         }
 
         struct sc_uhid_devices *uhid_devices = NULL;
diff --git a/app/src/uhid/gamepad_uhid.c b/app/src/uhid/gamepad_uhid.c
index 5f19a9d0..5a85816d 100644
--- a/app/src/uhid/gamepad_uhid.c
+++ b/app/src/uhid/gamepad_uhid.c
@@ -119,7 +119,15 @@ sc_gamepad_uhid_process_hid_output(struct sc_gamepad_uhid *gamepad,
         LOGI("==== HID output [%" PRIu16 "]", hid_id);
     }
 
-    // TODO
+    struct sc_hid_gamepad *gp = &gamepad->hid;
+    struct sc_hid_gamepad_slot* slot = &gp->slots[hid_id - SC_HID_ID_GAMEPAD_FIRST];
+    SDL_GameController *gc = SDL_GameControllerFromInstanceID(slot->gamepad_id);
+    // |           | HID Report | SDL Parameter |
+    // |-----------|------------|---------------|
+    // | Intensity | 0 - 100    | 0 - 65535     |
+    // | Duration  | 0 - 255    | 0 - 1000      |
+    SDL_GameControllerRumble(gc, data[4] * 0xFFFF / 100, data[5] * 0xFFFF / 100, data[6] * 1000 / 0xFF);
 }
 
 void
diff --git a/server/src/main/java/com/genymobile/scrcpy/control/UhidManager.java b/server/src/main/java/com/genymobile/scrcpy/control/UhidManager.java
index 408dbf5d..2db043bc 100644
--- a/server/src/main/java/com/genymobile/scrcpy/control/UhidManager.java
+++ b/server/src/main/java/com/genymobile/scrcpy/control/UhidManager.java
@@ -25,6 +25,7 @@ public final class UhidManager {
     private static final int UHID_INPUT2 = 12;
 
     // Linux: include/uapi/linux/input.h
+    private static final short BUS_BLUETOOTH = 0x05;
     private static final short BUS_VIRTUAL = 0x06;
 
     private static final int SIZE_OF_UHID_EVENT = 4380; // sizeof(struct uhid_event)
@@ -174,9 +175,9 @@ public final class UhidManager {
         buf.put("scrcpy".getBytes(StandardCharsets.US_ASCII));
         buf.put(empty, 0, 256 - "scrcpy".length());
         buf.putShort((short) reportDesc.length);
-        buf.putShort(BUS_VIRTUAL);
-        buf.putInt(0); // vendor id
-        buf.putInt(0); // product id
+        buf.putShort(BUS_BLUETOOTH);
+        buf.putInt(0x045e); // product id
+        buf.putInt(0x02fd); // vendor id
         buf.putInt(0); // version
         buf.putInt(0); // country;
         buf.put(reportDesc);

How it works

It uses this Linux driver: https://github.com/torvalds/linux/blob/d1f2d51b711a3b7f1ae1b46701c769c1d580fa7f/drivers/hid/hid-microsoft.c#L450-L461, so the device bus must be Bluetooth and the vender ID/product ID must match one of the list items.

I changed the server to always send those values, but this will break HID keyboards and mice, so it should be specified by the client.

Output report format

This driver hard coded the output report format: https://github.com/torvalds/linux/blob/d1f2d51b711a3b7f1ae1b46701c769c1d580fa7f/drivers/hid/hid-microsoft.c#L44-L57. So the report descriptor (the Usage Page (Physical Input Device) part in #2130 (comment)) doesn't matter.

It doesn't support trigger rumbles (byte 2 and 3 in the output report) (notice the MAGNITUDE_STRONG enum value starts at 2, so MAGNITUDE_NUM is 4, but 0 and 1 are unused).

Change to back button

One side effect of using this driver is that the Back/Select button no longer works using button number 10, it must be set using the AC Back usage (in #2130 (comment))

The patch above didn't include this change.

Other implementations

I believe there are other methods to support rumbles, for example with proper HID Set Effect Report usage. I can't find the reference documentation now, but this usage is super complicated. The host needs to first set several rumble presents (the device/Scrcpy virtual device needs to store them), then the host specifies which present and some options to play.

Also I believe wired Xbox/PlayStation controllers uses other specific drivers (and packet formats) to read inputs and play rumbles, but those can't be simulated by UHID.

@yume-chan
Copy link
Contributor

I also tested two Xbox controllers on Windows and it works:

image

@rom1v
Copy link
Collaborator Author

rom1v commented Sep 8, 2024

@LHLaurini

With --gamepad=aoa, changes to axes seem noticeably delayed. Also, if I turn the analog sticks in circles, I get the following message (repeated):

WARN: Could not push AOA HID input (gamepad axis)

OK, I cannot reproduce with my device/gamepad, but we would need an input resampler to handle this case: #3088.

@rom1v
Copy link
Collaborator Author

rom1v commented Sep 8, 2024

@yume-chan Thank you very much!

However, I fail to generate any rumble request (HID output) from Android.

I tested for example with "Game Controller Tester" (uk.co.powgames.gamecondiag), if I enable "Rumble left shoulder", it says: "no rumble support":

no_rumble

I tested from branch gamepad.rumblewip (which is the current gamepad branch + your diff, I squashed your first change with the previous commit).

(Btw, rumble will not be possible over AOA, since HID output is not supported.)

@Withoutruless
Copy link
Contributor

Im on A14, UNICA Rom (OneUI 6.1)
Windows 11, using a PDP Xbox One Controller.

Here's some feedback:

  • UHID works perfectly, everything works correctly except for vibration obv (tested using 'scrcpy -G --no-playback --mouse=disabled --keyboard=disabled')

  • On AOA, the Triggers dont do anything and the Right Stick acts as both of the Triggers instead.

Both "Triggers" start points are 0.5.

Left depresses LTRIGGER, Right presses LTRIGGER.
Up depresses RTRIGGER and Down presses RTRIGGER.

ive had this problem before on OTG but reversed (so the Triggers would act as the right stick instead) and i fixed that by modifying the "Generic.kl" in /system/usr/keylayout (ive tried vendor specific .kl many times, it just wouldn't work) so im guessing that's why its doing this.

this is the code in my modified Generic.kl if you need it:

Triggers
axis 0x02 LTRIGGER
axis 0x05 RTRIGGER

Left and right stick
axis 0x00 X flat 4096
axis 0x01 Y flat 4096
axis 0x03 Z flat 4096
axis 0x04 RZ flat 4096 
  • On AOA you cant only use the gamepad, for example: "scrcpy --otg --gamepad=aoa --mouse=disabled --keyboard=disabled" gives you "Could not disable both keyboard and mouse in OTG mode."

  • And finally, on AOA moving the mouse pins the Right Stick top left and the Left Stick as well, but the left stick moves with the mouse and teleports based on the direction you move your mouse:
    Left = Left Stick tp to the right and start moving left.
    Right = Left Stick tp to the left and start moving right.

Other than that, all buttons work fine on AOA and im pretty excited for this, thanks for dropping test binaries.

and i hope my gibberish is useful to you lol

@rom1v
Copy link
Collaborator Author

rom1v commented Sep 9, 2024

Here's some feedback:

UHID works perfectly, everything works correctly except for vibration obv (tested using 'scrcpy -G --no-playback --mouse=disabled --keyboard=disabled')

On AOA, the Triggers dont do anything and the Right Stick acts as both of the Triggers instead.
Both "Triggers" start points are 0.5.

Did you test draft1 or draft2?

These two problems you report for AOA should be fixed in draft2, but the behavior should be the same for UHID (broken in draft1, fixed in draft2).

Left depresses LTRIGGER, Right presses LTRIGGER.
Up depresses RTRIGGER and Down presses RTRIGGER.

Cannot reproduce. Probably related to the previous errors in draft1.

On AOA you cant only use the gamepad, for example: "scrcpy --otg --gamepad=aoa --mouse=disabled --keyboard=disabled" gives you "Could not disable both keyboard and mouse in OTG mode."

Thank you, fixed 👍

And finally, on AOA moving the mouse pins the Right Stick top left and the Left Stick as well, but the left stick moves with the mouse and teleports based on the direction you move your mouse:
Left = Left Stick tp to the right and start moving left.
Right = Left Stick tp to the left and start moving right.

Oh thank you, stupid mistake, I assigned the same HID_ID to the mouse and to the first controller (off-by-one error in the hardcoded values 😄).

EDIT: I just published a draft3 binary.

@yume-chan
Copy link
Contributor

yume-chan commented Sep 9, 2024

I tested for example with "Game Controller Tester" (uk.co.powgames.gamecondiag), if I enable "Rumble left shoulder", it says: "no rumble support":

Is your device running Android 12+? Controller rumble support was added in Android 12.

This app works for me on an Android 13 device, but it always vibrates both motors no matter which one I choose (it also does this when my Xbox controller is connected to the device directly over Bluetooth).

Judging from the permission prompt it shows on connection, it must use Android USB API to directly talk to controllers, so it can support all motors on all Android versions. (I think it's not possible to emulate USB devices through software)

I test my implementation using Chrome and this code I wrote: https://codepen.io/yume-chan/pen/qBzLGNd. Move the sticks to control vibration. On PC it supports all four motors, but on Android it only supports two. (again, same as when the controller is connected to the device over Bluetooth or USB). My code can vibrate left and right motors separately.

I checked again, at least for Xbox controllers, neither hid-microsoft nor xpad (https://github.com/paroj/xpad/blob/3a215825f981643d164aa4e10fd2ab584fd9987c/xpad.c#L1740-L1756) driver supports trigger rumble, so I think that's why Chrome doesn't support 4 motors no matter how the controller is connected (over USB, over Bluetooth, or virtual device using hid-microsoft driver).

@rom1v
Copy link
Collaborator Author

rom1v commented Sep 9, 2024

Is your device running Android 12+? Controller rumble support was added in Android 12.

Yes, Android 14.

I just tested, in fact it works like you say on a Pixel 8 with Android 14, but not on a Samsung Galaxy Tab with Android 14.

Btw, if I set the vid:pid to 02fd:045e, the device is shown as a Xbox One S Controller. If I don't, I get the string I set ("scrcpy" with the actual gamepad name as reported by SDL):

gamepad

Given that:

  • rumble only works on some devices
  • not all motors work
  • individual motors are not taken into account (only both vibrate)
  • the feature is probably not used very much in Android games
  • it impacts how the device is recognized

I think I will not support rumble for now. Maybe in the future if things improve.

Thank you very much for your research and documentation in this thread 👍

@rom1v
Copy link
Collaborator Author

rom1v commented Sep 9, 2024

Do you know any (free) Android game which supports 2 or more gamepads (a two-player fighting game for example)?

@Withoutruless
Copy link
Contributor

Do you know any (free) Android game which supports 2 or more gamepads (a two-player fighting game for example)?

afaik "Vita Fighters" supports 2 controllers

@rom1v
Copy link
Collaborator Author

rom1v commented Sep 9, 2024

@Withoutruless Thank you, it works 👍

@Withoutruless
Copy link
Contributor

Withoutruless commented Sep 9, 2024

On AOA, the Triggers dont do anything and the Right Stick acts as both of the Triggers instead.
Both "Triggers" start points are 0.5.

So i copied the .kl for the PDP Xbox One Controller (should be the same for X360 and the normal XOne too) and made a custom .kl called "Vendor_ffff_Product_ffff.kl" (which is what scrcpy uses on AOA)

and changed the text from:

# Triggers.
axis 0x02 LTRIGGER ##
axis 0x05 RTRIGGER ##

# Left and right stick.
# The reported value for flat is 128 out of a range from -32767 to 32768, which is absurd.
# This confuses applications that rely on the flat value because the joystick actually
# settles in a flat range of +/- 4096 or so.
axis 0x00 X flat 4096
axis 0x01 Y flat 4096
axis 0x03 Z flat 4096 ##
axis 0x04 RZ flat 4096 ##

# This works on the physical OTG (no Scrcpy)

To:

# Triggers.
axis 0x0a LTRIGGER ##
axis 0x09 RTRIGGER ##

# Left and right stick.
# The reported value for flat is 128 out of a range from -32767 to 32768, which is absurd.
# This confuses applications that rely on the flat value because the joystick actually
# settles in a flat range of +/- 4096 or so.
axis 0x00 X flat 4096
axis 0x01 Y flat 4096
axis 0x02 Z flat 4096 ##
axis 0x05 RZ flat 4096 ##

# This works on Scrcpy AOA.

so now i can use both AOA and UHID just fine.

everything else i mentioned has been fixed so thank you for that haha.

@rom1v
Copy link
Collaborator Author

rom1v commented Sep 9, 2024

So i copied the .kl for the PDP Xbox One Controller (should be the same for X360 and the normal XOne too) and made a custom .kl called "Vendor_ffff_Product_ffff.kl" (which is what scrcpy uses on AOA)

Where do you copy that file? Is there something to change in this PR so that it works the same "out-of-the-box"?

@Withoutruless
Copy link
Contributor

Where do you copy that file?

i copy that file to "/system/usr/keylayout" and it changes the way android handles input for the device with that specific vendor & product id.

Is there something to change in this PR so that it works the same "out-of-the-box"?

if its possible to change the vendor & product id so it matches something like an Xbox 360 Wired Controller
(Vendor = 045e, Product = 028e)

then that'd make android handle the received input differently and you wouldnt have to accomodate for the generic.kl's bugged RT & RStick

which im assuming you do by sending:

LTRIGGER as axis 0x0a instead of 0x02
RTRIGGER as axis 0x09 instead of 0x05

Z as axis 0x02 instead of 0x03
and
RZ as axis 0x05 instead of 0x04

so you could try changing those values and the emulated controller vendor & product id and see if it works.
but im not an expert so... sorry if im wrong.

also would it be possible to add an optional background capture for UHID & AOA so it captures input regardless if the window is in focus or not?

@rom1v
Copy link
Collaborator Author

rom1v commented Sep 9, 2024

if its possible to change the vendor & product id so it matches something like an Xbox 360 Wired Controller
(Vendor = 045e, Product = 028e)

That's what I did here: #5270 (comment)
But then, of course, the controller is shown as a Xbox 360 controller, even if the physical one is a PS4 controller.

I might use the real vid:pid of the physical device, but that might also cause inconsistencies between controllers I guess (I pass a custom report descriptor).

also would it be possible to add an optional background capture for UHID & AOA so it captures input regardless if the window is in focus or not?

The problem is the same as with mouse and keyboard: I can't receive input events from SDL without a focused window (AFAIK).

rom1v and others added 23 commits September 15, 2024 11:21
Pushing a close event from the keyboard_aoa or mouse_aoa implementation
was racy, because the AOA thread might be stopped before these events
were processed.

Instead, keep the list of open AOA devices to close them automatically
from the AOA thread before exiting.

PR #5270 <#5270>
Now that the AOA open/close are asynchronous, an open error did not make
scrcpy exit anymore.

Add a mechanism to exit if the AOA device could not be opened
asynchronously.

PR #5270 <#5270>
Fix typo and reference the latest version of "HID Usage Tables"
specifications.

PR #5270 <#5270>
Mouse and keyboard events with unknown button/keycode/scancode cannot be
handled properly. Discard them without forwarding them to the
keyboard or mouse processors.

This can happen for example if a more recent version of SDL introduces
new enum values.

PR #5270 <#5270>
Introduce a gamepad processor trait, similar to the keyboard processor
and mouse processor traits.

Handle gamepad events received from SDL, convert them to scrcpy-specific
gamepad events, and forward them to the gamepad processor.

Further commits will provide AOA and UHID implementations of the gamepad
processor trait.

PR #5270 <#5270>

Co-authored-by: Luiz Henrique Laurini <[email protected]>
This will be helpful for writing HID values.

PR #5270 <#5270>
Implement the HID protocol for gamepads, that will be used in further
commits by the AOA and UHID gamepad processor implementations.

PR #5270 <#5270>
Similar to AOA keyboard and mouse, but for gamepads.

Can be enabled with --gamepad=aoa.

PR #5270 <#5270>
Trigger SDL_CONTROLLERDEVICEADDED for all gamepads already connected
when scrcpy starts. We want to handle both the gamepads initially
connected and the gamepads connected while scrcpy is running.

This is not racy, because this event may not be trigged automatically
until SDL events are "pumped" (SDL_PumpEvents/SDL_WaitEvent).

PR #5270 <#5270>
Implement gamepad support for OTG.

PR #5270 <#5270>
This message will be sent on gamepad disconnection.

Contrary to keyboard and mouse devices, which are registered once and
unregistered when scrcpy exists, each physical gamepad is mapped with
its own HID id, and they can be plugged and unplugged dynamically.

PR #5270 <#5270>
Similar to UHID keyboard and mouse, but for gamepads.

Can be enabled with --gamepad=uhid or -G.

It is not enabled by default because not all devices support UHID
(there is a permission error on old Android versions).

PR #5270 <#5270>
For convenience, short options were added to select UHID input modes:
 - -K for --keyboard=uhid
 - -M for --mouse=uhid
 - -G for --gamepad=uhid

In OTG mode, UHID is not available, so the short options should select
AOA instead.

PR #5270 <#5270>
Make the local function write_string() accept the output buffer as a
first parameter, like the other similar functions.

PR #5270 <#5270>
Initialize UHID devices with a custom name:
 - "scrcpy: $GAMEPAD_NAME" for gamepads
 - "scrcpy" for keyboard and mouse (or if no gamepad name is available)

The name may appear in Android apps.

PR #5270 <#5270>
There was a registration mechanism to listen to HID outputs with a
specific HID id.

However, the UHID gamepad processor handles several ids, so it cannot
work. We could complexify the registration mechanism, but instead,
directly dispatch to the expected processor based on the UHID id.

Concretely, instead of passing a sc_uhid_devices instance to construct a
sc_keyboard_uhid, so that it can register itself, construct the
sc_uhid_devices with all the UHID instances (currently only
sc_keyboard_uhid) so that it can dispatch HID outputs directly.

PR #5270 <#5270>
The sc_uhid_devices instance is initialized only when there is a UHID
keyboard.

The device message receiver assumed that it could not receive HID output
reports without a sc_uhid_devices instance (i.e. without a UHID
keyboard), but in practice, a UHID driver implementation on the device
may decide to send UHID output reports for mouse or for gamepads (and we
must just ignore them).

So remove the assert().

PR #5270 <#5270>
Mainly copied and adapted from HID keyboard and mouse documentation.

PR #5270 <#5270>
Capture the gamepads even when the window is not focused.

Note: In theory, with this flag set, we could capture gamepad events
even without a window (--no-window). In practice, scrcpy still requires
a window, because --no-window implies --no-control, and the input
manager is owned by the sc_screen instance, which does not exist if
there is no window. Supporting this use case would require a lot of
refactors.

Refs <#5270 (comment)>
PR #5270 <#5270>

Suggested-by: Luiz Henrique Laurini <[email protected]>
UHID may not work on old Android versions due to permission errors.

Mention it in UHID mouse and gamepad documentation (it was already
mentioned for UHID keyboard).

Refs #4473 comment <#4473 (comment)>
PR #5270 <#5270>
Android does not support multiple HID gamepads properly over AOA.

PR #5270 <#5270>
@rom1v rom1v merged commit 4cc4abd into dev Sep 15, 2024
This was referenced Sep 15, 2024
@rom1v
Copy link
Collaborator Author

rom1v commented Oct 10, 2024

Ref issue #5362.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

6 participants