mirror of
https://github.com/Genymobile/scrcpy.git
synced 2025-04-19 19:15:08 +00:00
Merge 0ef8f64165
into 1a0d300786
This commit is contained in:
commit
b1e4c3e205
5 changed files with 128 additions and 43 deletions
|
@ -148,6 +148,7 @@ public class Controller implements AsyncProcessor, VirtualDisplayListener {
|
|||
displayDataAvailable.notify();
|
||||
}
|
||||
}
|
||||
getUhidManager().setDisplayId(virtualDisplayId);
|
||||
}
|
||||
|
||||
public void setSurfaceCapture(SurfaceCapture surfaceCapture) {
|
||||
|
|
|
@ -1,8 +1,10 @@
|
|||
package com.genymobile.scrcpy.control;
|
||||
|
||||
import com.genymobile.scrcpy.AndroidVersions;
|
||||
import com.genymobile.scrcpy.device.DisplayInfo;
|
||||
import com.genymobile.scrcpy.util.Ln;
|
||||
import com.genymobile.scrcpy.util.StringUtils;
|
||||
import com.genymobile.scrcpy.wrappers.ServiceManager;
|
||||
|
||||
import android.os.Build;
|
||||
import android.os.HandlerThread;
|
||||
|
@ -21,6 +23,32 @@ import java.nio.charset.StandardCharsets;
|
|||
|
||||
public final class UhidManager {
|
||||
|
||||
static class Device {
|
||||
String inputPort;
|
||||
FileDescriptor fd;
|
||||
|
||||
Device(String inputPort, FileDescriptor fd, int displayId) {
|
||||
this.inputPort = inputPort;
|
||||
this.fd = fd;
|
||||
setDisplayId(displayId);
|
||||
}
|
||||
|
||||
void setDisplayId(int displayId) {
|
||||
if (Build.VERSION.SDK_INT >= AndroidVersions.API_34_ANDROID_14 && displayId != 0) {
|
||||
DisplayInfo displayInfo = ServiceManager.getDisplayManager().getDisplayInfo(displayId);
|
||||
ServiceManager.getInputManager().addUniqueIdAssociationByPort(inputPort, displayInfo.getUniqueId());
|
||||
}
|
||||
}
|
||||
|
||||
void close() {
|
||||
if (Build.VERSION.SDK_INT >= AndroidVersions.API_34_ANDROID_14) {
|
||||
ServiceManager.getInputManager().removeUniqueIdAssociationByPort(inputPort);
|
||||
}
|
||||
|
||||
UhidManager.close(fd);
|
||||
}
|
||||
}
|
||||
|
||||
// Linux: include/uapi/linux/uhid.h
|
||||
private static final int UHID_OUTPUT = 6;
|
||||
private static final int UHID_CREATE2 = 11;
|
||||
|
@ -31,12 +59,14 @@ public final class UhidManager {
|
|||
|
||||
private static final int SIZE_OF_UHID_EVENT = 4380; // sizeof(struct uhid_event)
|
||||
|
||||
private final ArrayMap<Integer, FileDescriptor> fds = new ArrayMap<>();
|
||||
private final ArrayMap<Integer, Device> devices = new ArrayMap<>();
|
||||
private final ByteBuffer buffer = ByteBuffer.allocate(SIZE_OF_UHID_EVENT).order(ByteOrder.nativeOrder());
|
||||
|
||||
private final DeviceMessageSender sender;
|
||||
private final MessageQueue queue;
|
||||
|
||||
private int displayId = 0;
|
||||
|
||||
public UhidManager(DeviceMessageSender sender) {
|
||||
this.sender = sender;
|
||||
if (Build.VERSION.SDK_INT >= AndroidVersions.API_23_ANDROID_6_0) {
|
||||
|
@ -52,13 +82,15 @@ public final class UhidManager {
|
|||
try {
|
||||
FileDescriptor fd = Os.open("/dev/uhid", OsConstants.O_RDWR, 0);
|
||||
try {
|
||||
FileDescriptor old = fds.put(id, fd);
|
||||
// Must be unique across the system
|
||||
String inputPort = "scrcpy:" + Os.getpid() + ":" + id;
|
||||
Device old = devices.put(id, new Device(inputPort, fd, displayId));
|
||||
if (old != null) {
|
||||
Ln.w("Duplicate UHID id: " + id);
|
||||
close(old);
|
||||
old.close();
|
||||
}
|
||||
|
||||
byte[] req = buildUhidCreate2Req(vendorId, productId, name, reportDesc);
|
||||
byte[] req = buildUhidCreate2Req(vendorId, productId, name, inputPort, reportDesc);
|
||||
Os.write(fd, req, 0, req.length);
|
||||
|
||||
registerUhidListener(id, fd);
|
||||
|
@ -71,6 +103,13 @@ public final class UhidManager {
|
|||
}
|
||||
}
|
||||
|
||||
public void setDisplayId(int displayId) {
|
||||
this.displayId = displayId;
|
||||
for (Device device : devices.values()) {
|
||||
device.setDisplayId(displayId);
|
||||
}
|
||||
}
|
||||
|
||||
private void registerUhidListener(int id, FileDescriptor fd) {
|
||||
if (Build.VERSION.SDK_INT >= AndroidVersions.API_23_ANDROID_6_0) {
|
||||
queue.addOnFileDescriptorEventListener(fd, MessageQueue.OnFileDescriptorEventListener.EVENT_INPUT, (fd2, events) -> {
|
||||
|
@ -134,21 +173,21 @@ public final class UhidManager {
|
|||
}
|
||||
|
||||
public void writeInput(int id, byte[] data) throws IOException {
|
||||
FileDescriptor fd = fds.get(id);
|
||||
if (fd == null) {
|
||||
Device device = devices.get(id);
|
||||
if (device == null) {
|
||||
Ln.w("Unknown UHID id: " + id);
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
byte[] req = buildUhidInput2Req(data);
|
||||
Os.write(fd, req, 0, req.length);
|
||||
Os.write(device.fd, req, 0, req.length);
|
||||
} catch (ErrnoException e) {
|
||||
throw new IOException(e);
|
||||
}
|
||||
}
|
||||
|
||||
private static byte[] buildUhidCreate2Req(int vendorId, int productId, String name, byte[] reportDesc) {
|
||||
private static byte[] buildUhidCreate2Req(int vendorId, int productId, String name, String phys, byte[] reportDesc) {
|
||||
/*
|
||||
* struct uhid_event {
|
||||
* uint32_t type;
|
||||
|
@ -170,16 +209,21 @@ public final class UhidManager {
|
|||
* } __attribute__((__packed__));
|
||||
*/
|
||||
|
||||
byte[] empty = new byte[256];
|
||||
ByteBuffer buf = ByteBuffer.allocate(280 + reportDesc.length).order(ByteOrder.nativeOrder());
|
||||
buf.putInt(UHID_CREATE2);
|
||||
|
||||
String actualName = name.isEmpty() ? "scrcpy" : name;
|
||||
byte[] utf8Name = actualName.getBytes(StandardCharsets.UTF_8);
|
||||
int len = StringUtils.getUtf8TruncationIndex(utf8Name, 127);
|
||||
assert len <= 127;
|
||||
buf.put(utf8Name, 0, len);
|
||||
buf.put(empty, 0, 256 - len);
|
||||
byte[] nameBytes = actualName.getBytes(StandardCharsets.UTF_8);
|
||||
int nameLen = StringUtils.getUtf8TruncationIndex(nameBytes, 127);
|
||||
buf.put(nameBytes, 0, nameLen);
|
||||
buf.position(buf.position() + 128 - nameLen);
|
||||
|
||||
byte[] physBytes = phys.getBytes(StandardCharsets.UTF_8);
|
||||
int physLen = StringUtils.getUtf8TruncationIndex(physBytes, 63);
|
||||
buf.put(physBytes, 0, physLen);
|
||||
buf.position(buf.position() + 64 - physLen);
|
||||
|
||||
buf.position(buf.position() + 64); // uniq
|
||||
|
||||
buf.putShort((short) reportDesc.length);
|
||||
buf.putShort(BUS_VIRTUAL);
|
||||
|
@ -215,18 +259,18 @@ public final class UhidManager {
|
|||
public void close(int id) {
|
||||
// Linux: Documentation/hid/uhid.rst
|
||||
// If you close() the fd, the device is automatically unregistered and destroyed internally.
|
||||
FileDescriptor fd = fds.remove(id);
|
||||
if (fd != null) {
|
||||
unregisterUhidListener(fd);
|
||||
close(fd);
|
||||
Device device = devices.remove(id);
|
||||
if (device != null) {
|
||||
unregisterUhidListener(device.fd);
|
||||
device.close();
|
||||
} else {
|
||||
Ln.w("Closing unknown UHID device: " + id);
|
||||
}
|
||||
}
|
||||
|
||||
public void closeAll() {
|
||||
for (FileDescriptor fd : fds.values()) {
|
||||
close(fd);
|
||||
for (Device device : devices.values()) {
|
||||
device.close();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -7,16 +7,18 @@ public final class DisplayInfo {
|
|||
private final int layerStack;
|
||||
private final int flags;
|
||||
private final int dpi;
|
||||
private final String uniqueId;
|
||||
|
||||
public static final int FLAG_SUPPORTS_PROTECTED_BUFFERS = 0x00000001;
|
||||
|
||||
public DisplayInfo(int displayId, Size size, int rotation, int layerStack, int flags, int dpi) {
|
||||
public DisplayInfo(int displayId, Size size, int rotation, int layerStack, int flags, int dpi, String uniqueId) {
|
||||
this.displayId = displayId;
|
||||
this.size = size;
|
||||
this.rotation = rotation;
|
||||
this.layerStack = layerStack;
|
||||
this.flags = flags;
|
||||
this.dpi = dpi;
|
||||
this.uniqueId = uniqueId;
|
||||
}
|
||||
|
||||
public int getDisplayId() {
|
||||
|
@ -42,5 +44,9 @@ public final class DisplayInfo {
|
|||
public int getDpi() {
|
||||
return dpi;
|
||||
}
|
||||
|
||||
public String getUniqueId() {
|
||||
return uniqueId;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -81,7 +81,7 @@ public final class DisplayManager {
|
|||
int density = Integer.parseInt(m.group(5));
|
||||
int layerStack = Integer.parseInt(m.group(6));
|
||||
|
||||
return new DisplayInfo(displayId, new Size(width, height), rotation, layerStack, flags, density);
|
||||
return new DisplayInfo(displayId, new Size(width, height), rotation, layerStack, flags, density, "");
|
||||
}
|
||||
|
||||
private static DisplayInfo getDisplayInfoFromDumpsysDisplay(int displayId) {
|
||||
|
@ -129,7 +129,8 @@ public final class DisplayManager {
|
|||
int layerStack = cls.getDeclaredField("layerStack").getInt(displayInfo);
|
||||
int flags = cls.getDeclaredField("flags").getInt(displayInfo);
|
||||
int dpi = cls.getDeclaredField("logicalDensityDpi").getInt(displayInfo);
|
||||
return new DisplayInfo(displayId, new Size(width, height), rotation, layerStack, flags, dpi);
|
||||
String uniqueId = (String)cls.getDeclaredField("uniqueId").get(displayInfo);
|
||||
return new DisplayInfo(displayId, new Size(width, height), rotation, layerStack, flags, dpi, uniqueId);
|
||||
} catch (ReflectiveOperationException e) {
|
||||
throw new AssertionError(e);
|
||||
}
|
||||
|
|
|
@ -1,8 +1,10 @@
|
|||
package com.genymobile.scrcpy.wrappers;
|
||||
|
||||
import com.genymobile.scrcpy.FakeContext;
|
||||
import com.genymobile.scrcpy.util.Ln;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.content.Context;
|
||||
import android.view.InputEvent;
|
||||
import android.view.MotionEvent;
|
||||
|
||||
|
@ -16,38 +18,25 @@ public final class InputManager {
|
|||
public static final int INJECT_INPUT_EVENT_MODE_WAIT_FOR_FINISH = 2;
|
||||
|
||||
private final Object manager;
|
||||
private Method injectInputEventMethod;
|
||||
|
||||
private static Method injectInputEventMethod;
|
||||
private static Method setDisplayIdMethod;
|
||||
private static Method setActionButtonMethod;
|
||||
private static Method addUniqueIdAssociationByPortMethod;
|
||||
private static Method removeUniqueIdAssociationByPortMethod;
|
||||
|
||||
static InputManager create() {
|
||||
try {
|
||||
Class<?> inputManagerClass = getInputManagerClass();
|
||||
Method getInstanceMethod = inputManagerClass.getDeclaredMethod("getInstance");
|
||||
Object im = getInstanceMethod.invoke(null);
|
||||
return new InputManager(im);
|
||||
} catch (ReflectiveOperationException e) {
|
||||
throw new AssertionError(e);
|
||||
}
|
||||
}
|
||||
|
||||
private static Class<?> getInputManagerClass() {
|
||||
try {
|
||||
// Parts of the InputManager class have been moved to a new InputManagerGlobal class in Android 14 preview
|
||||
return Class.forName("android.hardware.input.InputManagerGlobal");
|
||||
} catch (ClassNotFoundException e) {
|
||||
return android.hardware.input.InputManager.class;
|
||||
}
|
||||
Object im = FakeContext.get().getSystemService(Context.INPUT_SERVICE);
|
||||
return new InputManager(im);
|
||||
}
|
||||
|
||||
private InputManager(Object manager) {
|
||||
this.manager = manager;
|
||||
}
|
||||
|
||||
private Method getInjectInputEventMethod() throws NoSuchMethodException {
|
||||
private static Method getInjectInputEventMethod() throws NoSuchMethodException {
|
||||
if (injectInputEventMethod == null) {
|
||||
injectInputEventMethod = manager.getClass().getMethod("injectInputEvent", InputEvent.class, int.class);
|
||||
injectInputEventMethod = android.hardware.input.InputManager.class.getMethod("injectInputEvent", InputEvent.class, int.class);
|
||||
}
|
||||
return injectInputEventMethod;
|
||||
}
|
||||
|
@ -97,4 +86,48 @@ public final class InputManager {
|
|||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private static Method getAddUniqueIdAssociationByPortMethod() throws NoSuchMethodException {
|
||||
if (addUniqueIdAssociationByPortMethod == null) {
|
||||
try {
|
||||
// Android 15
|
||||
addUniqueIdAssociationByPortMethod = android.hardware.input.InputManager.class.getMethod("addUniqueIdAssociationByPort", String.class, String.class);
|
||||
} catch (NoSuchMethodException ignored) {
|
||||
// Android 12 to 14
|
||||
addUniqueIdAssociationByPortMethod = android.hardware.input.InputManager.class.getMethod("addUniqueIdAssociation", String.class, String.class);
|
||||
}
|
||||
}
|
||||
return addUniqueIdAssociationByPortMethod;
|
||||
}
|
||||
|
||||
public void addUniqueIdAssociationByPort(String inputPort, String uniqueId) {
|
||||
try {
|
||||
Method method = getAddUniqueIdAssociationByPortMethod();
|
||||
method.invoke(manager, inputPort, uniqueId);
|
||||
} catch (ReflectiveOperationException e) {
|
||||
Ln.e("Cannot add unique id association by port", e);
|
||||
}
|
||||
}
|
||||
|
||||
private static Method getRemoveUniqueIdAssociationByPortMethod() throws NoSuchMethodException {
|
||||
if (removeUniqueIdAssociationByPortMethod == null) {
|
||||
try {
|
||||
// Android 15
|
||||
removeUniqueIdAssociationByPortMethod = android.hardware.input.InputManager.class.getMethod("removeUniqueIdAssociationByPort", String.class);
|
||||
} catch (NoSuchMethodException ignored) {
|
||||
// Android 12 to 14
|
||||
removeUniqueIdAssociationByPortMethod = android.hardware.input.InputManager.class.getMethod("removeUniqueIdAssociation", String.class);
|
||||
}
|
||||
}
|
||||
return removeUniqueIdAssociationByPortMethod;
|
||||
}
|
||||
|
||||
public void removeUniqueIdAssociationByPort(String inputPort) {
|
||||
try {
|
||||
Method method = getRemoveUniqueIdAssociationByPortMethod();
|
||||
method.invoke(manager, inputPort);
|
||||
} catch (ReflectiveOperationException e) {
|
||||
Ln.e("Cannot remove unique id association by port", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Reference in a new issue