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

Support game controllers #2130

Closed
wants to merge 19 commits into from
Closed

Conversation

LHLaurini
Copy link
Contributor

@LHLaurini LHLaurini commented Feb 18, 2021

closes #99

What it does

  • Handles events from connected game controllers;
  • Transfers game controller events to Android device;
  • Emulates an Xbox 360 game controller input device.

Important stuff

  • In order to emulate input devices, we need a few system calls (open, ioctl, write and close). Since we can't use most of these directly from Java (AFAIK), we need either native code or a library that allows us to call these functions. To avoid depending on the NDK, I'm using the Java Native Access library;
  • JNA requires a native library (libjnidispatch.so). For some reason, app_process doesn't want to load it, so right now it's extracted to /data/local/tmp on initialization and cleaned up later.

Things you can help with

  • Testing:
    • Comment whether it works on your device or not, so I can keep a list;
    • Axes behavior;
    • Multiple controllers.
  • Find a way to use the ioctl syscall without native code;
  • Load native libraries without extracting them;
  • Figure out multiple controllers with event injection.

To-do

  • Add command line option for disabling game controllers
  • Write tests
  • Isolate uinput related stuff into another class (so other types of devices can be implemented)
  • Fix input not working when uinput fails
  • Support legacy uinput
  • Look into event injection (for when uinput is unavailable)
  • Look into getting games to recognize axis movement when using event injection
  • Implement controller through event injection

Out of scope

I probably won't be adding some special features to this pull request, such as:

  • Vibration
  • Battery reporting
  • Lights
  • Touchpad

Feedback

If you have any feedback (suggestions, complaints, new info, ...), please don't hesitate to write it down below.

Tested devices

Manufacturer Model Android version Last tested on Tested by Supported Supported with root
Motorola Moto G5 Plus 8.1 2021-04-30 @LHLaurini
Motorola moto g(6) play 9 2021-04-30 @LHLaurini
Samsung Galaxy A7 2018 10 2021-04-06 @Fablo020
Samsung Galaxy M21s 11 2021-04-30 @LHLaurini ✔️
Huawei Honor Play COR-L29 9 2021-05-01 @Cannahawk
Xiaomi Pocophone F1 11 2021-05-23 @Nightm4res ✔️
Sony Xperia XZ1 Compact 11 2021-06-14 @Schlumpf7 ✔️
Sony Xperia XZ 2021-06-15 @Schlumpf7 ✔️
Blackview BV6000 2021-06-15 @Schlumpf7 ✔️
Xiaomi Mi A3 11 2021-06-17 @RuiGuilherme ✔️
Nvidia Shield TV 11 2022-02-12 @scaldav ✔️
Blackview BV6300 Pro 10 2022-05-02 @Fancy2209 ✔️
Google Pixel 6 12 2022-06-02 @AntoninHuaut ✔️
Redmi Note 9 Pro 5G 12 2022-06-18 @esec ✔️
Redmi Note 5 Pro 11* 2022-06-20 @Sigmacringe ✔️

The list of (un)supported devices can change anytime.

Reporting a new device

If you have tested with a device not on the list, please report it by writing a comment below. Please follow (more or less) the following format:

**Manufacturer**: Foo
**Model**: Bar 12
**Android version**: 123.4 (specify if not stock)
**Root**: Y/N

NEW: Windows and Linux releases

Prebuilt versions of scrcpy with the new patches can be found here. Remember to set the SCRCPY_SERVER_PATH variable.

Copy link
Collaborator

@rom1v rom1v left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you for your contribution.

In order to emulate input devices, we need a few system calls (open, ioctl, write and close). Since we can't use most of these directly from Java (AFAIK), we need either native code or a library that allows us to call these functions.

Injecting KeyEvent (already used for keyboard) should also allow to inject gamepad buttons, don't they?

For example: KEYCODE_BUTTON_L1, KEYCODE_BUTTON_START, KEYCODE_BUTTON_X

@LHLaurini
Copy link
Contributor Author

Injecting KeyEvent (already used for keyboard) should also allow to inject gamepad buttons

For some reason, that didn't even cross my mind. I'll look into it.

@LHLaurini
Copy link
Contributor Author

Ok, so after a little research, here what I've found so far:

  • Using IInputManager.injectInputEvent to inject KeyEvents and MotionEvents wouldn't work with multiple controllers, unless we can somehow register new InputDevices;
  • InputDriver looked promising until I realized it only works with Android Things.

I'll keep looking when I have some free time.

@rom1v
Copy link
Collaborator

rom1v commented Feb 19, 2021

int source = nonPrimaryButtonPressed ? InputDevice.SOURCE_MOUSE : InputDevice.SOURCE_TOUCHSCREEN;
MotionEvent event = MotionEvent
.obtain(lastTouchDown, now, action, pointerCount, pointerProperties, pointerCoords, 0, buttons, 1f, 1f, DEVICE_ID_VIRTUAL, 0, source,
0);

KeyEvent event = new KeyEvent(now, now, action, keyCode, repeat, metaState, KeyCharacterMap.VIRTUAL_KEYBOARD, 0, 0,
InputDevice.SOURCE_KEYBOARD);

InputDevice.SOURCE_GAMEPAD and pass some arbitrary deviceId?

@LHLaurini
Copy link
Contributor Author

LHLaurini commented Feb 19, 2021

InputDevice.SOURCE_GAMEPAD

Yes, we'd need to use InputDevice.SOURCE_GAMEPAD, InputDevice.SOURCE_JOYSTICK and InputDevice.SOURCE_DPAD, depending on the kind of event.

and pass some arbitrary deviceId?

That was my first idea, but it still won't allow apps to query info about controllers. I'm going to implement and test it, maybe it works better than I think.

@Looneybinke903
Copy link

Looneybinke903 commented Feb 19, 2021 via email

@LHLaurini
Copy link
Contributor Author

LHLaurini commented Feb 19, 2021

But in this case it is only working through code or already has an executable to download? because if it is a code you could tell me where to put it and how !?

No, there's no binary release yet. You can try compiling my branch. Take a look at the build instructions, they're pretty easy to follow. Just don't use the prebuilt server.

Note: all of this is still work in progress, so don't expect it to be perfect.

@LHLaurini
Copy link
Contributor Author

I implemented event injection, as suggested, but I can't seem to find a way to set axes from MotionEvent.obtain and there's no setAxis method either, so I just tested button events for now.

  • Using the Gamepad Tester app, I can see the correct events for buttons, but, of course, it reports "Name: Virtual";
  • gamepad-tester.com on Google Chrome doesn't work anymore. Just says "No gamepad detected.". That's because the HTML5 Gamepad API needs to detect controllers for them to work;
  • I've tried the game "Into the Dead", buttons work;
  • I'll try other games. I don't expect this approach to work with Unity games due to the way it handles controllers.

Even if it works, I still need to figure out MotionEvent.

Now, to be honest, I still think the uinput approach is the best we got, as long as we find a way to get app_process to properly load native libraries.

@LHLaurini
Copy link
Contributor Author

Tested both methods with "Dead Trigger" (an Unity game). With uinput, controls work out of the box. By injecting events, every single button needs to be rebound (I'm surprised it works at all).

@rom1v
Copy link
Collaborator

rom1v commented Feb 20, 2021

Hmm, opening /dev/uinput requires root permissions, right?

@LHLaurini
Copy link
Contributor Author

LHLaurini commented Feb 20, 2021

Hmm, opening /dev/uinput requires root permissions, right?

Nope. Just limited to the ADB shell (at least on my phone).

Here, look:

f41:/dev $ ls -l /dev/uinput                                                                                           
crw-rw---- 1 uhid uhid 10, 223 2021-02-12 13:43 /dev/uinput
f41:/dev $ whoami
shell
f41:/dev $ groups
shell input log adb sdcard_rw sdcard_r ext_data_rw ext_obb_rw net_bt_admin net_bt inet net_bw_stats readproc uhid

shell is a member of the uhid group.

@rom1v
Copy link
Collaborator

rom1v commented Feb 20, 2021

Hmm, opening /dev/uinput requires root permissions, right?

Nope. Just limited to the ADB shell (at least on my phone).

Does it mean it can also be used to inject key/touch events and avoid (in some way) the ASCII-limitation of Android text injection?

https://github.com/Genymobile/scrcpy/blob/master/FAQ.md#special-characters-do-not-work

@LHLaurini
Copy link
Contributor Author

Does it mean it can also be used to inject key/touch events and avoid (in some way) the ASCII-limitation of Android text injection?

Yes, it can. When I started writing my patch, I tried injecting key presses and it worked.

Also, since Android would recognize the virtual device as if it were real, it should also allow the user to select a text box and not have the virtual keyboard pop up. In fact, I'm pretty sure I'd seen the "Configure physical keyboard" notification.

@LHLaurini
Copy link
Contributor Author

I decided to make some changes so that a future pull request can add support for keyboard (or even mouse) using uinput.

@rom1v
Copy link
Collaborator

rom1v commented Feb 20, 2021

You might be interested in android.system.Os. The documentation does not show all the methods, because some are hidden, but you can probably still access them. For example ioctlInt().

@rom1v
Copy link
Collaborator

rom1v commented Feb 20, 2021

Or even Libcore.java + Linux.java.

With that, you might not need JNA.

EDIT: to be adapted on older versions of Android, for example on Android 6 there is no field rawOs, but on Android 10 I have access to it and all the methods.

@LHLaurini
Copy link
Contributor Author

You might be interested in android.system.Os. The documentation does not show all the methods, because some are hidden, but you can probably still access them. For example ioctlInt().

Thanks. Will look into it.

@LHLaurini
Copy link
Contributor Author

LHLaurini commented Feb 20, 2021

You might be interested in android.system.Os. The documentation does not show all the methods, because some are hidden, but you can probably still access them. For example ioctlInt().

Or even Libcore.java + Linux.java.

With that, you might not need JNA.

EDIT: to be adapted on older versions of Android, for example on Android 6 there is no field rawOs, but on Android 10 I have access to it and all the methods.

Good find.

We could use fcntlInt for UI_SET_EVBIT, UI_SET_KEYBIT and UI_SET_ABSBIT, and use fcntlVoid for UI_DEV_CREATE and UI_DEV_DESTROY.

We could use ioctlInt for UI_SET_EVBIT, UI_SET_KEYBIT and UI_SET_ABSBIT, and it may also work for UI_DEV_CREATE and UI_DEV_DESTROY (I had mixed up ioctl and fcntl).

Sadly, UI_DEV_SETUP and UI_ABS_SETUP require pointers, so these methods won't work. If there were a version that received a byte[] we could use it.

Not all hope is lost, however. We should be able to use an older setup method, in which we write a struct uinput_user_dev. Hopefully it still works.

Another problem is that we'll need to be careful with the structs, since we'll have to assemble them manually. At least, if we do something wrong, it shouldn't crash, just won't work.

Finally, this is going to take me a while to implement, but at least we won't need JNA.

@LHLaurini
Copy link
Contributor Author

Hmm, I can't seem figure out how to use ioctlInt.

import android.system.Os;
Os.ioctlInt(fd, UI_SET_EVBIT, EV_KEY);

fails with

error: cannot find symbol
        Os.ioctlInt(fd, UI_SET_EVBIT, EV_KEY);
          ^
  symbol:   method ioctlInt(FileDescriptor,int,int)
  location: class Os
Method ioctlInt = Os.class.getDeclaredMethod("ioctlInt", int.class/*, int.class*/);

throws

java.lang.NoSuchMethodException: android.system.Os.ioctlInt [int]
        at java.lang.Class.getMethod(Class.java:2072)
        at java.lang.Class.getDeclaredMethod(Class.java:2050)
        at com.genymobile.scrcpy.GameController.getIoctlInt(GameController.java:134)
        at com.genymobile.scrcpy.GameController.<clinit>(GameController.java:143)
        at com.genymobile.scrcpy.Controller.handleEvent(Controller.java:177)
        at com.genymobile.scrcpy.Controller.control(Controller.java:75)
        at com.genymobile.scrcpy.Server$2.run(Server.java:130)
        at java.lang.Thread.run(Thread.java:923)
import libcore.io.Libcore;

fails with

error: package libcore.io does not exist
import libcore.io.Libcore;
                 ^

open, write and close seem to be working just fine. Any ideas?

@rom1v
Copy link
Collaborator

rom1v commented Feb 20, 2021

(Quick msg just to answer about your blocking points)

Before this commit the signature was:

public int ioctlInt(FileDescriptor fd, int cmd, Int32Ref arg) …

So:

Method m = Os.class.getDeclaredMethod("ioctlInt", FileDescriptor.class, int.class, Class.forName("android.system.Int32Ref"));

For LibCore/Linux:

Object linux = Class.forName("libcore.io.Libcore").getDeclaredField("rawOs").get(null);
Class<?> linuxClass = linux.getClass();
@LHLaurini
Copy link
Contributor Author

LHLaurini commented Feb 20, 2021

(Quick msg just to answer about your blocking points)

Before this commit the signature was:

public int ioctlInt(FileDescriptor fd, int cmd, Int32Ref arg) …

So:

Method m = Os.class.getDeclaredMethod("ioctlInt", FileDescriptor.class, int.class, Class.forName("android.system.Int32Ref"));

For LibCore/Linux:

Object linux = Class.forName("libcore.io.Libcore").getDeclaredField("rawOs").get(null);
Class<?> linuxClass = linux.getClass();

Ah, I see, I completely forgot the first parameter and was using the wrong one for the third. Thank you for the help.

So now I'm able to call ioctlInt, but there's a problem. Int32Ref is a pointer to an integer, because ioctlInt uses the third parameter to "return" an integer. That's why they've now changed the signature. Even if I try to initialize it with the correct value, I just get ioctl failed: EINVAL (Invalid argument).

I only found ioctlInt used with SIOCINQ, SIOCOUTQ and FIONREAD which are all "getters". ioctlInetAddress is also used with getters, only they use struct ifreq internally.

I'll keep looking.

PS: The pointer is internal, so we can't try to manipulate it somehow. Also, I found nothing new by searching for "ioctl java". Native may really be the only way.

@quyleanh
Copy link
Contributor

@LHLaurini are you still working on this PR?

@LHLaurini
Copy link
Contributor Author

@LHLaurini are you still working on this PR?

Not right now, but it works well in its current state, AFAIK. What I have left to do is:

  • make some changes so this can also be used for proper key injection (maybe even mouse actions);
  • investigate some more about a way to call ioctl without JNA (or JNI), which may not be possible;
  • add an option for disabling controller input.

I'll work some more on this when I have some free time, maybe tomorrow. If you want to use this branch, you'll have to compile it yourself. Feel free to ask any questions.

@quyleanh
Copy link
Contributor

@LHLaurini thank you for your response.
Actually, I would like to use this branch with UTF-8 text injection. So I think I will wait until you implement the first item.

@LHLaurini
Copy link
Contributor Author

@LHLaurini thank you for your response.
Actually, I would like to use this branch with UTF-8 text injection. So I think I will wait until you implement the first item.

I don't think my changes will allow for UTF-8 text injection (at least not directly), if you need to have a function that injects a string.

But if you mean being able to use any keyboard (with accents and other special keys) and have it be recognized by the system as a physical device, then sure.

I'm not sure which one you meant.

@yume-chan
Copy link
Contributor

yume-chan commented Feb 7, 2023

I rebased this onto the latest dev branch: https://github.com/Genymobile/scrcpy/compare/dev...yume-chan:scrcpy:feat/game-controller?expand=1

Next will try to create a virtual keyboard and using \uef00 for Unicode injection (even though it might only support 0-FFFF UTF-16 characters)

@yume-chan
Copy link
Contributor

yume-chan commented Feb 7, 2023

2023-02-07.17-32-59.mp4

It works, slowly. After all, this feature has been deprecated since Android 3. Also no idea how to show IME candidate window in SDL when there isn't any text input (tried SDL_SetTextInputRect and SDL_StartTextInput). IME candidate window added (obviously it won't follow the text input position on Android side)

image

https://github.com/yume-chan/scrcpy/compare/feat/game-controller...yume-chan:scrcpy:feat/uinput-hex-input?expand=1

EDIT: This method also requires no IME on Android, otherwise the injected keys will be intercepted by it. Which means either physical keyboard attached or Scrcpy HID keyboard mode activated. Can't make the virtual keyboard from uinput be classified as physical keyboard.

EDIT2: If I add two virtual keyboards, the second one will be classified as physical keyboard. This is because the first one is recognized as built-in keyboard, then got rejected due to its device id not being zero. Android is such an interesting OS. However, still need to disable Android IME for Unicode injection to work.

@Shoko84
Copy link

Shoko84 commented May 19, 2023

Hello @LHLaurini! Is the current state of your repository fork works under scrcpy 1.19? I recently switch to Android 12 and was sad to see it could not run anymore with your release.

I tried to setup and configurate a clean Linux environment to compile your fork, scrcpy is running again but controller inputs doesn't seem to be recognized, any quick ideas?

If an easy fix is possible, might be possible I do an update to my old video explaining quickly how to setup your scrcpy gamepad-compatible (https://www.youtube.com/watch?v=zeJiB4IUBdc, credited in the description ofc)

Have a good week-end, cheers!

@LHLaurini
Copy link
Contributor Author

LHLaurini commented May 20, 2023

Hello @LHLaurini! Is the current state of your repository fork works under scrcpy 1.19? I recently switch to Android 12 and was sad to see it could not run anymore with your release.

I tried to setup and configurate a clean Linux environment to compile your fork, scrcpy is running again but controller inputs doesn't seem to be recognized, any quick ideas?

If an easy fix is possible, might be possible I do an update to my old video explaining quickly how to setup your scrcpy gamepad-compatible (https://www.youtube.com/watch?v=zeJiB4IUBdc, credited in the description ofc)

Have a good week-end, cheers!

Hi, there. I stopped working on this, but the fork should still be functional (but out-of-date). You can try following the troubleshooting steps I mentioned in other comments or try out @yume-chan 's fork.

@WuDi-ZhanShen
Copy link

(Quick msg just to answer about your blocking points)

Before this commit the signature was:

public int ioctlInt(FileDescriptor fd, int cmd, Int32Ref arg) …

So:

Method m = Os.class.getDeclaredMethod("ioctlInt", FileDescriptor.class, int.class, Class.forName("android.system.Int32Ref"));

For LibCore/Linux:

Object linux = Class.forName("libcore.io.Libcore").getDeclaredField("rawOs").get(null);
Class<?> linuxClass = linux.getClass();

I will try this method on my uhid demo now. It would be fantastic if it works!

@WuDi-ZhanShen
Copy link

I implemented event injection, as suggested, but I can't seem to find a way to set axes from MotionEvent.obtain and there's no setAxis method either, so I just tested button events for now.

  • Using the Gamepad Tester app, I can see the correct events for buttons, but, of course, it reports "Name: Virtual";
  • gamepad-tester.com on Google Chrome doesn't work anymore. Just says "No gamepad detected.". That's because the HTML5 Gamepad API needs to detect controllers for them to work;
  • I've tried the game "Into the Dead", buttons work;
  • I'll try other games. I don't expect this approach to work with Unity games due to the way it handles controllers.

Even if it works, I still need to figure out MotionEvent.

Now, to be honest, I still think the uinput approach is the best we got, as long as we find a way to get app_process to properly load native libraries.

https://github.com/WuDi-ZhanShen/Android-Gyroscope-MC/blob/main/app/src/main/java/com/tile/tuoluoyi/GamePadNative.java

This is the way to set axis values for a MotionEvent. Using PointerProperties and PointerCoords.

@WuDi-ZhanShen
Copy link

Hello! The "uinputcommand_jni" library is already built into Android 12 and higher versions of the Android system. We can directly load the "uinputcommand_jni" library and use its JNI functions to register and use uinput devices with pure Java code.

However, if you need better compatibility with lower versions of Android systems, I suggest using the "hidcommand_jni" library, which has been built into the system since Android 6.0.1.

You can find information on how to use the JNI functions in "uinputcommand_jni" library and "hidcommand_jni" library in the Android source code links below:
https://cs.android.com/android/platform/superproject/+/master:frameworks/base/cmds/uinput/src/com/android/commands/uinput/Device.java

https://cs.android.com/android/platform/superproject/+/master:frameworks/base/cmds/hid/src/com/android/commands/hid/Device.java;l=68-87;drc=f532c911262bc97bea7d419ce14c6dc151d0d3a5

Uinput and uhid can achieve almost the same effect, but there is a subtle difference, which is that uinput can ignore the logical maximum and minimum value limits of axis events. Therefore, I think adding both of these methods to the source code of scrcpy would be more comprehensive.

In addition, I already have a simple demo that uses pure Java to register and use uhid devices by loading the "hidcommand_jni" library. You can check it out here: https://github.com/WuDi-ZhanShen/AndroidUHidPureJava

Finally, in the initial version of my Android app, I also used uinput to implement the function. However, according to user feedback, on some devices, there was no "/dev/uinput" node file, so uinput did not work at all. After switching to uhid, there was no such problem. Because uhid is responsible for registering all Bluetooth gamepads, Bluetooth keyboards, and Bluetooth mice, all Android devices that support Bluetooth must also support uhid.

@xAffan
Copy link

xAffan commented Jul 25, 2023

Hello! The "uinputcommand_jni" library is already built into Android 12 and higher versions of the Android system. We can directly load the "uinputcommand_jni" library and use its JNI functions to register and use uinput devices with pure Java code.

However, if you need better compatibility with lower versions of Android systems, I suggest using the "hidcommand_jni" library, which has been built into the system since Android 6.0.1.

You can find information on how to use the JNI functions in "uinputcommand_jni" library and "hidcommand_jni" library in the Android source code links below: https://cs.android.com/android/platform/superproject/+/master:frameworks/base/cmds/uinput/src/com/android/commands/uinput/Device.java

https://cs.android.com/android/platform/superproject/+/master:frameworks/base/cmds/hid/src/com/android/commands/hid/Device.java;l=68-87;drc=f532c911262bc97bea7d419ce14c6dc151d0d3a5

Uinput and uhid can achieve almost the same effect, but there is a subtle difference, which is that uinput can ignore the logical maximum and minimum value limits of axis events. Therefore, I think adding both of these methods to the source code of scrcpy would be more comprehensive.

In addition, I already have a simple demo that uses pure Java to register and use uhid devices by loading the "hidcommand_jni" library. You can check it out here: https://github.com/WuDi-ZhanShen/AndroidUHidPureJava

Finally, in the initial version of my Android app, I also used uinput to implement the function. However, according to user feedback, on some devices, there was no "/dev/uinput" node file, so uinput did not work at all. After switching to uhid, there was no such problem. Because uhid is responsible for registering all Bluetooth gamepads, Bluetooth keyboards, and Bluetooth mice, all Android devices that support Bluetooth must also support uhid.

I hope someone can pick up the work for scrcpy.

@awsms
Copy link

awsms commented Sep 10, 2023

Manufacturer: POCO (Xiaomi)
Model: X3 Pro
Android version: 12 (LineageOS 19.1)
Root: Y
Works perfectly!

@Nightm4res
Copy link

Manufacturer: Google
Model: Pixel 6 pro
Android version: 14 Beta
Root: N

Everything works fine on all builds of 14 Beta. Playing call of duty warzone mobile with no problem using my xinput controller. Will update when android 14 is out.

@Withoutruless
Copy link

Withoutruless commented Oct 22, 2023

Manufacturer: Samsung
Model: Galaxy A52s 5G
Android version: 13/14 (Stock OneUI 5.1)/(Stock OneUI 6) works on both.
Root: No

Every button works perfectly on both wifi and usb, It stutters over 2.4GHz wifi but works perfectly on a direct connection over hotspot.

just a "feature" i found: if you start this while focusing on another window, you can use your gamepad without having to be focused on the main scrcpy window.

also, by creating: C:\usr\x86_64-w64-mingw32\share\scrcpy
and putting the old scrcpy-server in there you can use this and newer versions of scrcpy together.

I would love to see this updated with the latest scrcpy version for newer features.

@datsoy
Copy link

datsoy commented Nov 29, 2023

Manufacturer: Oneplus
Model: 6T
Android version: 13 (CrDroid 9.11)
Root: Y KernelSU

Its working, I do have ds4windows installed. I dont know if it helps.
Tested to control a game from yuzu android. I hope this will get implemented on latest release.

@GamerJD75
Copy link

GamerJD75 commented Dec 1, 2023

Manufacturer: Samsung
Model: Galaxy S22 Ultra
Android version: 14 - OneUI 6
Root: No

EDIT: Gamepad works fine with this build. I get no sound though. I have to spin up a newer instance of scrcpy in the background to get audio from my PC.

Would be nice for them to merge the gamepad controls into the main release.

@Withoutruless
Copy link

Manufacturer: Samsung Model: Galaxy S22 Ultra Android version: 14 - OneUI 6 Root: No

EDIT: Gamepad works fine with this build. I get no sound though. I have to spin up a newer instance of scrcpy in the background to get audio from my PC.

Would be nice for them to merge the gamepad controls into the main release.

Yeah, would love to see this upstreamed to the latest version.

@rom1v
Copy link
Collaborator

rom1v commented May 3, 2024

Now that UHID (keyboard and mouse) is available (#4473), can this work be rebased on the latest version?

@LHLaurini
Copy link
Contributor Author

Now that UHID (keyboard and mouse) is available (#4473), can this work be rebased on the latest version?

This implementation uses uinput -- which is relatively simple to use -- to create a virtual input device and directly emit input events.

#4473, on the other hand, works by using uhid. If I understand correctly, the scrcpy app manually constructs HID events and sends them to the server, which is quite a bit more complex. Please correct me if I'm mistaken.

So yes and no. Some of the code could be reused, but a decent portion would have to be rewritten. To put it bluntly, that's a non-trivial amount of work I currently have neither the time nor the interest for. That may change one day but, in the meantime, if someone is willing to put in the work, please feel free to use my code as a starting point.

I'm closing this pull request for now. Thank you to everybody who provided feedback.

@LHLaurini LHLaurini closed this May 5, 2024
@BinaryQuantumSoul
Copy link

So is this abandoned @LHLaurini ?

I have a remote controller from parsec/sunshine connected on my pc whose inputs need to be passed to my mirrored android phone running the game

@rom1v
Copy link
Collaborator

rom1v commented Aug 8, 2024

@LHLaurini

If I understand correctly, the scrcpy app manually constructs HID events and sends them to the server, which is quite a bit more complex. Please correct me if I'm mistaken.

The main difference is that it injects events in UHID format to /dev/uhid, while this PR injects events in the UINPUT format to /dev/uinput.

The reason why it is problematic to use uinput is that it requires a call to ioctl(), which cannot be easily performed in Java, so it requires either a native dependency or to add native code, which I would like to avoid in scrcpy.

What is missing to implement it in UHID is a report descriptor for a gamepad and the matching HID events format for simulating a gamepad on Android.

Here is how it is done for keyboard and mouse for reference:

Adapting it to gamepads requires to read and understand some HID specs:

the scrcpy app manually constructs HID events and sends them to the server

That's an implementation detail: the HID events are generated on the client side to share the code with AOA (to send HID events directly over USB).

If you (or anyone else) can adapt your PR to inject UHID events from Java (basically your GameController class, but using UHID rather than UINPUT), that is with a correct report descriptor and correct UHID events recognized by Android, then problem solved (I'll take care of the work to construct the events from the client side).

@rom1v
Copy link
Collaborator

rom1v commented Aug 9, 2024

Adapting it to gamepads requires to read and understand some HID specs:

I read the specs, and wrote (blindly) a first draft for the report descriptor:

// 2 bytes for buttons + padding, 4 bytes for X, Y, Rx, Ry
#define SC_HID_GAMEPAD_EVENT_SIZE 6

const uint8_t SC_HID_GAMEPAD_REPORT_DESC[] = {
    // Usage Page (Generic Desktop)
    0x05, 0x01,
    // Usage (Gamepad)
    0x09, 0x05,

    // Collection (Application)
    0xA1, 0x01,

    // Collection (Physical)
    0xA1, 0x00,

    // Usage Page (Buttons)
    0x05, 0x09,

    // Usage Minimum (1)
    0x19, 0x01,
    // Usage Maximum (15)
    0x29, 0x0E,
    // Logical Minimum (0)
    0x15, 0x00,
    // Logical Maximum (1)
    0x25, 0x01,
    // Report Count (15)
    0x95, 0x05,
    // Report Size (1)
    0x75, 0x01,
    // Input (Data, Variable, Absolute): 15 buttons bits
    0x81, 0x02,

    // Report Count (1)
    0x95, 0x01,
    // Report Size (1)
    0x75, 0x01,
    // Input (Constant): 1 bit padding
    0x81, 0x01,

    // Usage Page (Generic Desktop)
    0x05, 0x01,
    // Usage (X)
    0x09, 0x30,
    // Usage (Y)
    0x09, 0x31,
    // Usage (Rx)
    0x09, 0x33,
    // Usage (Ry)
    0x09, 0x34,
    // Logical Minimum (0)
    0x15, 0x81,
    // Logical Maximum (255)
    0x25, 0xFF,
    // Report Size (8)
    0x75, 0x08,
    // Report Count (4)
    0x95, 0x03,
    // Input (Data, Variable, Absolute): 4 bytes (X, Y, Rx, Ry)
    0x81, 0x02,

    // End Collection
    0xC0,

    // End Collection
    0xC0,
};

It assumes that the thumbsticks use X, Y, Rx, Ry (but I'm not sure this is correct).
Also, I don't know how the (15) buttons are actually mapped.

I plan to implement what's needed to handle HID game controllers (AOA and UHID), so that I can test and debug. I don't know when though, but that's probably my next subject on scrcpy.

@yume-chan
Copy link
Contributor

This simulates an Xbox One controller:

Descriptor:

[
  0x05, 0x01,                    // Usage Page (Generic Desktop)
  0x09, 0x05,                    // Usage (Game Pad)
  0xa1, 0x01,                    // Collection (Application)
  0x09, 0x30,                    //   Usage (X)
  0x09, 0x31,                    //   Usage (Y)
  0x09, 0x32,                    //   Usage (Z)
  0x09, 0x35,                    //   Usage (Rz)
  0x15, 0x00,                    //   Logical Minimum (0)
  0x27, 0xFF, 0xFF, 0x00, 0x00,  //   Logical Maximum (65535)
  0x75, 0x10,                    //   Report Size (16)
  0x95, 0x04,                    //   Report Count (4)
  0x81, 0x02,                    //   Input (Data,Var,Abs)

  0x05, 0x02,                    //   Usage Page (Sim Controls)
  0x09, 0xC5,                    //   Usage (Brake)
  0x09, 0xC4,                    //   Usage (Accelerator)
  0x15, 0x00,                    //   Logical Minimum (0)
  0x27, 0xFF, 0xFF, 0x00, 0x00,  //   Logical Maximum (65535)
  0x75, 0x10,                    //   Report Size (16)
  0x95, 0x02,                    //   Report Count (2)
  0x81, 0x02,                    //   Input (Data,Var,Abs)

  0x05, 0x01,                    //   Usage Page (Generic Desktop Controls)
  0x09, 0x39,                    //   Usage (Hat switch)
  0x15, 0x01,                    //   Logical Minimum (1)
  0x25, 0x08,                    //   Logical Maximum (8)
  0x35, 0x00,                    //   Physical Minimum (0)
  0x46, 0x3B, 0x01,              //   Physical Maximum (315)
  0x66, 0x14, 0x00,              //   Unit (System: English Rotation, Length: Centimeter)
  0x75, 0x04,                    //   Report Size (4)
  0x95, 0x01,                    //   Report Count (1)
  0x81, 0x42,                    //   Input (Data,Var,Abs,No Wrap,Linear,Preferred State,Null State)
  0x75, 0x04,                    //   Report Size (4)
  0x95, 0x01,                    //   Report Count (1)
  0x15, 0x00,                    //   Logical Minimum (0)
  0x25, 0x00,                    //   Logical Maximum (0)
  0x35, 0x00,                    //   Physical Minimum (0)
  0x45, 0x00,                    //   Physical Maximum (0)
  0x65, 0x00,                    //   Unit (None)
  0x81, 0x03,                    //   Input (Const,Var,Abs,No Wrap,Linear,Preferred State,No Null Position)

  0x05, 0x09,                    //   Usage Page (Button)
  0x19, 0x01,                    //   Usage Minimum (Button 1)
  0x29, 0x0F,                    //   Usage Maximum (Button 15)
  0x15, 0x00,                    //   Logical Minium (0)
  0x25, 0x01,                    //   Logical Maximum (1)
  0x75, 0x01,                    //   Report Size (1)
  0x95, 0x0F,                    //   Report Count (15)
  0x81, 0x02,                    //   Input (Data,Var,Abs)
  0x75, 0x01,                    //   Report Size (1)
  0x95, 0x01,                    //   Report Count (1)
  0x81, 0x03,                    //   Input (Const,Var,Abs,No Wrap,Linear,Preferred State,No Null Position)

  0x05, 0x0C,                    //   Usage Page (Consumer)
  0x0A, 0x24, 0x02,              //   Usage (AC Back)
  0x0A, 0x23, 0x02,              //   Usage (AC Home)
  0x15, 0x00,                    //   Logical Minimum (0)
  0x25, 0x01,                    //   Logical Maximum (1)
  0x75, 0x01,                    //   Report Size (1)
  0x95, 0x02,                    //   Report Count (2)
  0x81, 0x02,                    //   Input (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position)
  0x15, 0x00,                    //   Logical Minimum (0)
  0x25, 0x00,                    //   Logical Maximum (0)
  0x75, 0x06,                    //   Report Size (6)
  0x95, 0x01,                    //   Report Count (1)
  0x81, 0x03,                    //   Input (Const,Var,Abs,No Wrap,Linear,Preferred State,No Null Position)

  0x05, 0x0F,                    //   Usage Page (Physical Input Device)
  0x09, 0x21,                    //   Usage (Set Effect Report)
  0xA1, 0x02,                    //   Collection (Logical)
  0x09, 0x97,                    //     Usage (DC Enable Actuators)
  0x15, 0x00,                    //     Logical Minimum (0)
  0x25, 0x01,                    //     Logical Maximum (1)
  0x75, 0x04,                    //     Report Size (4)
  0x95, 0x01,                    //     Report Count (1)
  0x91, 0x02,                    //     Output (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position,Non-volatile)
  0x15, 0x00,                    //     Logical Minimum (0)
  0x25, 0x00,                    //     Logical Maximum (0)
  0x75, 0x04,                    //     Report Size (4)
  0x95, 0x01,                    //     Report Count (1)
  0x91, 0x03,                    //     Output (Const,Var,Abs,No Wrap,Linear,Preferred State,No Null Position,Non-volatile)
  0x09, 0x70,                    //     Usage (Magnitude)
  0x15, 0x00,                    //     Logical Minimum (0)
  0x25, 0x64,                    //     Logical Maximum (100)
  0x75, 0x08,                    //     Report Size (8)
  0x95, 0x04,                    //     Report Count (4)
  0x91, 0x02,                    //     Output (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position,Non-volatile)
  0x09, 0x50,                    //     Usage (Duration)
  0x66, 0x01, 0x10,              //     Unit (System: SI Linear, Time: Seconds)
  0x55, 0x0E,                    //     Unit Exponent (-2)
  0x15, 0x00,                    //     Logical Minimum (0)
  0x26, 0xFF, 0x00,              //     Logical Maximum (255)
  0x75, 0x08,                    //     Report Size (8)
  0x95, 0x01,                    //     Report Count (1)
  0x91, 0x02,                    //     Output (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position,Non-volatile)
  0x09, 0xA7,                    //     Usage (Start Delay)
  0x15, 0x00,                    //     Logical Minimum (0)
  0x26, 0xFF, 0x00,              //     Logical Maximum (255)
  0x75, 0x08,                    //     Report Size (8)
  0x95, 0x01,                    //     Report Count (1)
  0x91, 0x02,                    //     Output (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position,Non-volatile)
  0x65, 0x00,                    //     Unit (None)
  0x55, 0x00,                    //     Unit Exponent (0)
  0x09, 0x7C,                    //     Usage (Loop Count)
  0x15, 0x00,                    //     Logical Minimum (0)
  0x26, 0xFF, 0x00,              //     Logical Maximum (255)
  0x75, 0x08,                    //     Report Size (8)
  0x95, 0x01,                    //     Report Count (1)
  0x91, 0x02,                    //     Output (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position,Non-volatile)
  0xC0,                          //   End Collection

  0xc0,                          // End Collection
]

Input report format:

image

To enable rumble (vibration), the vendor ID and product ID must be 0x045e and 0x02fd for a special driver to load, it sends output reports for rumble:

image

@rom1v
Copy link
Collaborator

rom1v commented Aug 10, 2024

Thank you for the documentation! Will be useful 👍

  0x09, 0x30,                    //   Usage (X)
  0x09, 0x31,                    //   Usage (Y)
  0x09, 0x32,                    //   Usage (Z)
  0x09, 0x35,                    //   Usage (Rz)

So the XBoxOne controller uses X, Y for the left stick and Z, Rz for the right stick. This is also what I have seen here (also the report descriptor is a bit different).

Report descriptors for other controllers also seem to use the same 👍

@rom1v rom1v mentioned this pull request Sep 6, 2024
3 tasks
@rom1v
Copy link
Collaborator

rom1v commented Sep 7, 2024

Implemented in #5270. Feedback welcome 😉

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment