Closed LHLaurini closed 3 weeks ago
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.
Ok, so after a little research, here what I've found so far:
IInputManager.injectInputEvent
to inject KeyEvent
s and MotionEvent
s wouldn't work with multiple controllers, unless we can somehow register new InputDevice
s;InputDriver
looked promising until I realized it only works with Android Things.I'll keep looking when I have some free time.
https://github.com/Genymobile/scrcpy/blob/dce08677375dbb1b65217c6f4080b012284f4afa/server/src/main/java/com/genymobile/scrcpy/Controller.java#L210-L214 https://github.com/Genymobile/scrcpy/blob/dce08677375dbb1b65217c6f4080b012284f4afa/server/src/main/java/com/genymobile/scrcpy/Device.java#L178-L179
InputDevice.SOURCE_GAMEPAD
and pass some arbitrary deviceId
?
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.
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 !?
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.
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.
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.
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).
Hmm, opening /dev/uinput requires root permissions, right?
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.
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
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.
I decided to make some changes so that a future pull request can add support for keyboard (or even mouse) using uinput.
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.
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.
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.
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?
(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();
(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.
@LHLaurini are you still working on this PR?
@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:
ioctl
without JNA (or JNI), which may not be possible;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.
@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 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.
I'm not sure which one you meant.
My purpose is whenever I type the UTF-8 characters from physical keyboard, the system will receive it and show correctly on device.
May I ask your being able to use any keyboard (with accents and other special keys) and have it be recognized by the system as a physical device
function is implemented or not yet?
My purpose is whenever I type the UTF-8 characters from physical keyboard, the system will receive it and show correctly on device.
Ah, I see. In that case, character encoding (like UTF-8) has nothing to do with the problem. When that's implemented, scrcpy would just forward keystrokes as-is to the device as if the keyboard is connected to it.
May I ask your
being able to use any keyboard (with accents and other special keys) and have it be recognized by the system as a physical device
function is implemented or not yet?
Not yet and I don't intend to implement it in this pull request.
The best method for injecting input in any Linux based system is uinput
. Currently, this branch uses it to send just controller input. I intend to isolate uinput
stuff from the GameController
class (soon) and make it so it can eventually also be used for keyboard injection (in a future PR).
@LHLaurini thank you for your response. I'm looking forward to hearing news from your PR.
@LHLaurini Thank you for your update. I see you have separated the uinput
to UinputDevice
.
I would like to test the keyboard injection things we discussed above, so please let me know when your branch is ready and can buildable.
I have now implemented all that I wanted to. Since it seems we won't be able to use evdev with pure Java, all that's left is to decide about the native interface. We have two options:
--with[out]-jna
), making it optional;scrcpy-server
.What do you think, @rom1v ?
Hello, thanks for releasing this. I just bought an Linux Server to build this. After getting many Issues, I finally managed to build this.
I tried it on my Phone but it does not work. Not on my Homescreen, Apps with Controller Support and "Gamepad testing" apps I use an original Xbox 360 Controller with an original USB Dongle from Microsoft. Phone: A7 2018, Andoid 10 connected via USB or using adb connect. Latest build from your fork (release-v1.17-6-gf8524a2) on Windows 10
Latest build from your fork (release-v1.17-6-gf8524a2) on Windows 10
It seems you are on the wrong branch (master). You can switch to the correct one by running
git checkout game-controller
Please let me know if it works.
Latest build from your fork (release-v1.17-6-gf8524a2) on Windows 10
It seems you are on the wrong branch (master). You can switch to the correct one by running
git checkout game-controller
Please let me know if it works.
Thank you for your quick answer. I got (v1.17-74-g2b2619b) now but my Controller does not work and I can not use my mouse/keyboard either. But I do not really care about Mouse Input.
Thank you for your quick answer. I got (v1.17-74-g2b2619b) now but my Controller does not work and I can not use my mouse/keyboard either. But I do not really care about Mouse Input.
Ok, could you show me the scrcpy log?
Oh, I did not even saw the log before: https://pastebin.com/raw/nya2WvbT
What I just noticed, when my controller is not connected, my mouse and keyboard work just fine. After I connect my controller and disconnect it, I still have to restart scrcpy to use my mouse and keyboard again.
Maybe your phone doesn't allow using uinput from the adb shell. Which is weird, since I've also got a Samsung phone and it works for me.
Anyway, run these commands in the adb shell and show me the output:
uname -a
whoami
groups
ls -l /dev/uinput
Edit: also, the keyboard & mouse not working is a bug, I need to fix that.
Thank for the answer. https://pastebin.com/raw/ZXDiFP6c
Hmm, weird. I've got almost the exact same output. Try echo -n > /dev/uinput
.
I do not get any response with this command. Only /dev/uinput - /system/bin/sh: /dev/uinput: can't execute: Permission denied
Then I can only assume SELinux is setup to block it. I'm afraid nothing can be done, unless you have root access, in which case, you could run setenforce permissive
.
Thanks for your help and your time. I might try it with another phone because I cannot root/downgrade my phone.
I would love to help test this and use my controller on xcloud. I have no idea how to build stuf, and so ar I have only got problems trying. Is there a way to get a build with the game controller feature for windows?
Does this branch increase the minimum required Android version or other requirements? Building the branch, I get the following error on startup:
[server] INFO: Device: HUAWEI COR-L29 (Android 9)
[server] INFO: Supported ABIs: arm64-v8a, armeabi-v7a, armeabi
[server] ERROR: Exception on thread Thread[main,5,main]
java.lang.UnsatisfiedLinkError: Library media_jni not found; tried [/data/local/tmp/libmedia_jni.so]
at java.lang.Runtime.loadLibrary0(Runtime.java:1040)
at java.lang.System.loadLibrary(System.java:1672)
at android.media.MediaCodec.<clinit>(MediaCodec.java:3581)
at android.media.MediaCodec.createEncoderByType(MediaCodec.java:1784)
at com.genymobile.scrcpy.ScreenEncoder.createCodec(ScreenEncoder.java:179)
at com.genymobile.scrcpy.ScreenEncoder.internalStreamScreen(ScreenEncoder.java:77)
at com.genymobile.scrcpy.ScreenEncoder.streamScreen(ScreenEncoder.java:60)
at com.genymobile.scrcpy.Server.scrcpy(Server.java:110)
at com.genymobile.scrcpy.Server.main(Server.java:285)
at com.android.internal.os.RuntimeInit.nativeFinishInit(Native Method)
at com.android.internal.os.RuntimeInit.main(RuntimeInit.java:371)
Exit due to uncaughtException in main thread:
INFO: Renderer: opengl
INFO: OpenGL version: 4.6.0 NVIDIA 460.73.01
INFO: Trilinear filtering enabled
INFO: Initial texture: 1080x2336
WARN: Device disconnected
WARN: Killing the server...
Building and running master instead runs fine on my device.
I would love to help test this and use my controller on xcloud. I have no idea how to build stuf, and so ar I have only got problems trying. Is there a way to get a build with the game controller feature for windows?
Building for Windows is often difficult. I've tried using MSYS2, but no luck. I'll try cross-compiling from Linux later.
Does this branch increase the minimum required Android version or other requirements? Building the branch, I get the following error on startup:
[server] INFO: Device: HUAWEI COR-L29 (Android 9) [server] INFO: Supported ABIs: arm64-v8a, armeabi-v7a, armeabi [server] ERROR: Exception on thread Thread[main,5,main] java.lang.UnsatisfiedLinkError: Library media_jni not found; tried [/data/local/tmp/libmedia_jni.so] at java.lang.Runtime.loadLibrary0(Runtime.java:1040) at java.lang.System.loadLibrary(System.java:1672) at android.media.MediaCodec.<clinit>(MediaCodec.java:3581) at android.media.MediaCodec.createEncoderByType(MediaCodec.java:1784) at com.genymobile.scrcpy.ScreenEncoder.createCodec(ScreenEncoder.java:179) at com.genymobile.scrcpy.ScreenEncoder.internalStreamScreen(ScreenEncoder.java:77) at com.genymobile.scrcpy.ScreenEncoder.streamScreen(ScreenEncoder.java:60) at com.genymobile.scrcpy.Server.scrcpy(Server.java:110) at com.genymobile.scrcpy.Server.main(Server.java:285) at com.android.internal.os.RuntimeInit.nativeFinishInit(Native Method) at com.android.internal.os.RuntimeInit.main(RuntimeInit.java:371) Exit due to uncaughtException in main thread: INFO: Renderer: opengl INFO: OpenGL version: 4.6.0 NVIDIA 460.73.01 INFO: Trilinear filtering enabled INFO: Initial texture: 1080x2336 WARN: Device disconnected WARN: Killing the server...
Building and running master instead runs fine on my device.
Weird. AFAIK the only needed library is libjnidispatch.so, which is bundled in the jar. Maybe your phone loads an extra library? Try running echo ${LD_LIBRARY_PATH}
on the ADB shell. If it outputs anything, then the problem in probably in https://github.com/LHLaurini/scrcpy/blob/2b2619b274b5ed5329f57fa47b98f9c6c39de616/app/src/server.c#L268
Thanks for the response. LD_LIBRARY_PATH was empty, adb shell output only an empty line. I tested it on someone else's phone, also Huawei, Android version 10, and it ran just fine. Might also be a weird unknown device setting on mine.
Trying more often, sometimes the stack trace is different. If I restart my device and try it once the output is as above. mediajni is still listed as an issue though. I'm not completely sure what triggers the different output(screen on, off, apps running, etc) but I'll put it here as well in case it could help. The stack trace is cut off, I'm not sure if there is a debug flag to show them in full:
INFO: scrcpy 1.17 <https://github.com/Genymobile/scrcpy>
x/server/scrcpy-server: 1 file pushed. 11.0 MB/s (422114 bytes in 0.037s)
[server] INFO: Device: HUAWEI COR-L29 (Android 9)
[server] INFO: Supported ABIs: arm64-v8a, armeabi-v7a, armeabi
[server] ERROR: Could not invoke method
java.lang.reflect.InvocationTargetException
at java.lang.reflect.Method.invoke(Native Method)
at com.genymobile.scrcpy.wrappers.PowerManager.isScreenOn(PowerManager.java:32)
at com.genymobile.scrcpy.Device.isScreenOn(Device.java:215)
at com.genymobile.scrcpy.Controller.control(Controller.java:69)
at com.genymobile.scrcpy.Server$2.run(Server.java:130)
at java.lang.Thread.run(Thread.java:784)
Caused by: java.lang.RuntimeException: Bad file descriptor
at android.os.BinderProxy.transactNative(Native Method)
at android.os.BinderProxy.transact(Binder.java:1149)
at android.os.IPowerManager$Stub$Proxy.isInteractive(IPowerManager.java:919)
... 6 more
[server] ERROR: Exception on thread Thread[main,5,main]
java.lang.UnsatisfiedLinkError: Library media_jni not found; tried [/data/local/tmp/libmedia_jni.so]
at java.lang.Runtime.loadLibrary0(Runtime.java:1040)
at java.lang.System.loadLibrary(System.java:1672)
at android.media.MediaCodec.<clinit>(MediaCodec.java:3581)
at android.media.MediaCodec.createEncoderByType(MediaCodec.java:1784)
at com.genymobile.scrcpy.ScreenEncoder.createCodec(ScreenEncoder.java:179)
at com.genymobile.scrcpy.ScreenEncoder.internalStreamScreen(ScreenEncoder.java:77)[server] ERROR: Exception on thread Thread[Thread-1,5,main]
at com.genymobile.scrcpy.ScreenEncoder.streamScreen(ScreenEncoder.java:60)
at com.genymobile.scrcpy.Server.scrcpy(Server.java:110)
at com.genymobile.scrcpy.Server.main(Server.java:285)
at com.android.internal.os.RuntimeInit.nativeFinishInit(Native Method)
at com.android.internal.os.RuntimeInit.main(RuntimeInit.java:371)
Exit due to uncaughtException in main thread:
java.lang.AssertionError: java.lang.reflect.InvocationTargetException
at com.genymobile.scrcpy.wrappers.ServiceManager.getService(ServiceManager.java:39)
at com.genymobile.scrcpy.wrappers.ServiceManager.getInputManager(ServiceManager.java:59)
at com.genymobile.scrcpy.Device.injectEvent(Device.java:173)
at com.genymobile.scrcpy.Device.injectEvent(Device.java:181)
at com.genymobile.scrcpy.Device.injectEvent(Device.java:189)
at com.genymobile.scrcpy.Device.injectKeyEvent(Device.java:203)
at com.genymobile.scrcpy.Device.injectKeycode(Device.java:211)
at com.genymobile.scrcpy.Controller.control(Controller.java:70)
at com.genymobile.scrcpy.Server$2.run(Server.java:130)
at java.lang.Thread.run(Thread.java:784)
Caused by: java.lang.reflect.InvocationTargetException
at java.lang.reflect.Method.invoke(Native Method)
at com.genymobile.scrcpy.wrappers.ServiceManager.getService(ServiceManager.java:35)
... 9 more
Caused by: java.lang.RuntimeException: Bad file descriptor
at android.os.BinderProxy.transactNative(Native Method)
at android.os.BinderProxy.transact(Binder.java:1149)
at android.os.ServiceManagerProxy.getService(ServiceManagerNative.java:125)
at android.os.ServiceManager.rawGetService(ServiceManager.java:253)
at android.os.ServiceManager.getService(ServiceManager.java:124)
... 11 more
INFO: Renderer: opengl
INFO: OpenGL version: 4.6.0 NVIDIA 460.73.01
INFO: Trilinear filtering enabled
INFO: Initial texture: 1080x2336
WARN: Device disconnected
WARN: Killing the server...
@Cannahawk It's difficult for me to debug the issue if I'm unable to reproduce it. Maybe it has to do with Android 9. When I have some spare time (and if I don't forget) I'll test with another phone.
Also, just to make sure: did you build the server from source or use the precompiled version?
Yeah I can understand that being hard to debug. I build the server from source.
@Cannahawk I just tested with an Android 8.1 phone and I get a similar error, except it can't find javacrypto. Could be something to do with JNA, I'll have to investigate, but there's a good chance it's the same problem.
@Cannahawk Should be fixed. Seems the issue was caused by setting LD_LIBRARY_PATH
(something didn't like that, didn't figure out what exactly). Instead, I've switched to using the jna.boot.library.path
property.
Also, added a catch so if the device doesn't support uinput, "normal" input still works.
@LHLaurini Thank you. I just tested it and it starts up now. It appears the error is on my phone not supporting uinput according to your new error log, but that is on me now.
Oh, well, that's a shame. Seems like few phones support uinput without root. I'll add your test to the list.
closes #99
What it does
Important stuff
open
,ioctl
,write
andclose
). 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;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
ioctl
syscall without native code;To-do
Out of scope
I probably won't be adding some special features to this pull request, such as:
Feedback
If you have any feedback (suggestions, complaints, new info, ...), please don't hesitate to write it down below.
Tested devices
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:
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.