Inject additional ACTION_BUTTON_* events for mouse
On mouse click events: - the first button pressed must first generate ACTION_DOWN; - all button pressed (including the first one) must generate ACTION_BUTTON_PRESS; - all button released (including the last one) must generate ACTION_BUTTON_RELEASE; - the last button released must in addition generate ACTION_UP. Otherwise, Chrome does not work properly. Fixes #3635 <https://github.com/Genymobile/scrcpy/issues/3635> Signed-off-by: Romain Vimont <rom@rom1v.com>
This commit is contained in:
parent
8c5c55f9e1
commit
9b286ec8a7
2 changed files with 80 additions and 2 deletions
|
@ -1,5 +1,7 @@
|
||||||
package com.genymobile.scrcpy;
|
package com.genymobile.scrcpy;
|
||||||
|
|
||||||
|
import com.genymobile.scrcpy.wrappers.InputManager;
|
||||||
|
|
||||||
import android.os.Build;
|
import android.os.Build;
|
||||||
import android.os.SystemClock;
|
import android.os.SystemClock;
|
||||||
import android.view.InputDevice;
|
import android.view.InputDevice;
|
||||||
|
@ -99,7 +101,7 @@ public class Controller {
|
||||||
break;
|
break;
|
||||||
case ControlMessage.TYPE_INJECT_TOUCH_EVENT:
|
case ControlMessage.TYPE_INJECT_TOUCH_EVENT:
|
||||||
if (device.supportsInputEvents()) {
|
if (device.supportsInputEvents()) {
|
||||||
injectTouch(msg.getAction(), msg.getPointerId(), msg.getPosition(), msg.getPressure(), msg.getButtons());
|
injectTouch(msg.getAction(), msg.getPointerId(), msg.getPosition(), msg.getPressure(), msg.getActionButton(), msg.getButtons());
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case ControlMessage.TYPE_INJECT_SCROLL_EVENT:
|
case ControlMessage.TYPE_INJECT_SCROLL_EVENT:
|
||||||
|
@ -179,7 +181,7 @@ public class Controller {
|
||||||
return successCount;
|
return successCount;
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean injectTouch(int action, long pointerId, Position position, float pressure, int buttons) {
|
private boolean injectTouch(int action, long pointerId, Position position, float pressure, int actionButton, int buttons) {
|
||||||
long now = SystemClock.uptimeMillis();
|
long now = SystemClock.uptimeMillis();
|
||||||
|
|
||||||
Point point = device.getPhysicalPoint(position);
|
Point point = device.getPhysicalPoint(position);
|
||||||
|
@ -226,6 +228,62 @@ public class Controller {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* If the input device is a mouse (on API >= 23):
|
||||||
|
* - the first button pressed must first generate ACTION_DOWN;
|
||||||
|
* - all button pressed (including the first one) must generate ACTION_BUTTON_PRESS;
|
||||||
|
* - all button released (including the last one) must generate ACTION_BUTTON_RELEASE;
|
||||||
|
* - the last button released must in addition generate ACTION_UP.
|
||||||
|
*
|
||||||
|
* Otherwise, Chrome does not work properly: <https://github.com/Genymobile/scrcpy/issues/3635>
|
||||||
|
*/
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && source == InputDevice.SOURCE_MOUSE) {
|
||||||
|
if (action == MotionEvent.ACTION_DOWN) {
|
||||||
|
if (actionButton == buttons) {
|
||||||
|
// First button pressed: ACTION_DOWN
|
||||||
|
MotionEvent downEvent = MotionEvent.obtain(lastTouchDown, now, MotionEvent.ACTION_DOWN, pointerCount, pointerProperties,
|
||||||
|
pointerCoords, 0, buttons, 1f, 1f, DEFAULT_DEVICE_ID, 0, source, 0);
|
||||||
|
if (!device.injectEvent(downEvent, Device.INJECT_MODE_ASYNC)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Any button pressed: ACTION_BUTTON_PRESS
|
||||||
|
MotionEvent pressEvent = MotionEvent.obtain(lastTouchDown, now, MotionEvent.ACTION_BUTTON_PRESS, pointerCount, pointerProperties,
|
||||||
|
pointerCoords, 0, buttons, 1f, 1f, DEFAULT_DEVICE_ID, 0, source, 0);
|
||||||
|
if (!InputManager.setActionButton(pressEvent, actionButton)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (!device.injectEvent(pressEvent, Device.INJECT_MODE_ASYNC)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (action == MotionEvent.ACTION_UP) {
|
||||||
|
// Any button released: ACTION_BUTTON_RELEASE
|
||||||
|
MotionEvent releaseEvent = MotionEvent.obtain(lastTouchDown, now, MotionEvent.ACTION_BUTTON_RELEASE, pointerCount, pointerProperties,
|
||||||
|
pointerCoords, 0, buttons, 1f, 1f, DEFAULT_DEVICE_ID, 0, source, 0);
|
||||||
|
if (!InputManager.setActionButton(releaseEvent, actionButton)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (!device.injectEvent(releaseEvent, Device.INJECT_MODE_ASYNC)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (buttons == 0) {
|
||||||
|
// Last button released: ACTION_UP
|
||||||
|
MotionEvent upEvent = MotionEvent.obtain(lastTouchDown, now, MotionEvent.ACTION_UP, pointerCount, pointerProperties,
|
||||||
|
pointerCoords, 0, buttons, 1f, 1f, DEFAULT_DEVICE_ID, 0, source, 0);
|
||||||
|
if (!device.injectEvent(upEvent, Device.INJECT_MODE_ASYNC)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
MotionEvent event = MotionEvent
|
MotionEvent event = MotionEvent
|
||||||
.obtain(lastTouchDown, now, action, pointerCount, pointerProperties, pointerCoords, 0, buttons, 1f, 1f, DEFAULT_DEVICE_ID, 0, source,
|
.obtain(lastTouchDown, now, action, pointerCount, pointerProperties, pointerCoords, 0, buttons, 1f, 1f, DEFAULT_DEVICE_ID, 0, source,
|
||||||
0);
|
0);
|
||||||
|
|
|
@ -3,6 +3,7 @@ package com.genymobile.scrcpy.wrappers;
|
||||||
import com.genymobile.scrcpy.Ln;
|
import com.genymobile.scrcpy.Ln;
|
||||||
|
|
||||||
import android.view.InputEvent;
|
import android.view.InputEvent;
|
||||||
|
import android.view.MotionEvent;
|
||||||
|
|
||||||
import java.lang.reflect.InvocationTargetException;
|
import java.lang.reflect.InvocationTargetException;
|
||||||
import java.lang.reflect.Method;
|
import java.lang.reflect.Method;
|
||||||
|
@ -17,6 +18,7 @@ public final class InputManager {
|
||||||
private Method injectInputEventMethod;
|
private Method injectInputEventMethod;
|
||||||
|
|
||||||
private static Method setDisplayIdMethod;
|
private static Method setDisplayIdMethod;
|
||||||
|
private static Method setActionButtonMethod;
|
||||||
|
|
||||||
public InputManager(android.hardware.input.InputManager manager) {
|
public InputManager(android.hardware.input.InputManager manager) {
|
||||||
this.manager = manager;
|
this.manager = manager;
|
||||||
|
@ -56,4 +58,22 @@ public final class InputManager {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static Method getSetActionButtonMethod() throws NoSuchMethodException {
|
||||||
|
if (setActionButtonMethod == null) {
|
||||||
|
setActionButtonMethod = MotionEvent.class.getMethod("setActionButton", int.class);
|
||||||
|
}
|
||||||
|
return setActionButtonMethod;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean setActionButton(MotionEvent motionEvent, int actionButton) {
|
||||||
|
try {
|
||||||
|
Method method = getSetActionButtonMethod();
|
||||||
|
method.invoke(motionEvent, actionButton);
|
||||||
|
return true;
|
||||||
|
} catch (InvocationTargetException | IllegalAccessException | NoSuchMethodException e) {
|
||||||
|
Ln.e("Cannot set action button on MotionEvent", e);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue