Compare commits

..

2 Commits

Author SHA1 Message Date
Romain Vimont
c2d0dcd002 Add multitouch simulation
While the Ctrl key is held, the first left-click adds an immobile
"virtual finger" at this position.

Moving the mouse moves another cursor. The left click may be released
and pressed as necessary to control the second finger.

Releasing Ctrl releases the virtual finger.
2019-10-10 21:45:22 +02:00
Romain Vimont
9448eae8a4 Initialize input manager dynamically
To simulate multitouch, we will need more fields in input_manager, which
will be initialized dynamically.
2019-10-06 21:36:06 +02:00
14 changed files with 163 additions and 294 deletions

View File

@@ -16,6 +16,7 @@
(3 + CONTROL_MSG_CLIPBOARD_TEXT_MAX_LENGTH) (3 + CONTROL_MSG_CLIPBOARD_TEXT_MAX_LENGTH)
#define POINTER_ID_MOUSE UINT64_C(-1); #define POINTER_ID_MOUSE UINT64_C(-1);
#define POINTER_ID_VIRTUAL_FINGER UINT64_C(-2);
enum control_msg_type { enum control_msg_type {
CONTROL_MSG_TYPE_INJECT_KEYCODE, CONTROL_MSG_TYPE_INJECT_KEYCODE,

View File

@@ -176,30 +176,19 @@ convert_mouse_action(SDL_EventType from, enum android_motionevent_action *to) {
} }
} }
static inline void
map_coords(int32_t *x, int32_t *y, const SDL_Rect *rect,
struct size frame_size) {
*x = (*x - rect->x) * frame_size.width / rect->w;
*y = (*y - rect->y) * frame_size.height / rect->h;
}
bool bool
convert_mouse_button(const SDL_MouseButtonEvent *from, const SDL_Rect *rect, convert_mouse_button(const SDL_MouseButtonEvent *from, struct size screen_size,
struct size frame_size, struct control_msg *to) { struct control_msg *to) {
to->type = CONTROL_MSG_TYPE_INJECT_TOUCH_EVENT; to->type = CONTROL_MSG_TYPE_INJECT_TOUCH_EVENT;
if (!convert_mouse_action(from->type, &to->inject_touch_event.action)) { if (!convert_mouse_action(from->type, &to->inject_touch_event.action)) {
return false; return false;
} }
int32_t x = from->x;
int32_t y = from->y;
map_coords(&x, &y, rect, frame_size);
to->inject_touch_event.pointer_id = POINTER_ID_MOUSE; to->inject_touch_event.pointer_id = POINTER_ID_MOUSE;
to->inject_touch_event.position.screen_size = frame_size; to->inject_touch_event.position.screen_size = screen_size;
to->inject_touch_event.position.point.x = x; to->inject_touch_event.position.point.x = from->x;
to->inject_touch_event.position.point.y = y; to->inject_touch_event.position.point.y = from->y;
to->inject_touch_event.pressure = 1.f; to->inject_touch_event.pressure = 1.f;
to->inject_touch_event.buttons = to->inject_touch_event.buttons =
convert_mouse_buttons(SDL_BUTTON(from->button)); convert_mouse_buttons(SDL_BUTTON(from->button));
@@ -208,18 +197,14 @@ convert_mouse_button(const SDL_MouseButtonEvent *from, const SDL_Rect *rect,
} }
bool bool
convert_mouse_motion(const SDL_MouseMotionEvent *from, const SDL_Rect *rect, convert_mouse_motion(const SDL_MouseMotionEvent *from, struct size screen_size,
struct size frame_size, struct control_msg *to) { struct control_msg *to) {
int32_t x = from->x;
int32_t y = from->y;
map_coords(&x, &y, rect, frame_size);
to->type = CONTROL_MSG_TYPE_INJECT_TOUCH_EVENT; to->type = CONTROL_MSG_TYPE_INJECT_TOUCH_EVENT;
to->inject_touch_event.action = AMOTION_EVENT_ACTION_MOVE; to->inject_touch_event.action = AMOTION_EVENT_ACTION_MOVE;
to->inject_touch_event.pointer_id = POINTER_ID_MOUSE; to->inject_touch_event.pointer_id = POINTER_ID_MOUSE;
to->inject_touch_event.position.screen_size = frame_size; to->inject_touch_event.position.screen_size = screen_size;
to->inject_touch_event.position.point.x = x; to->inject_touch_event.position.point.x = from->x;
to->inject_touch_event.position.point.y = y; to->inject_touch_event.position.point.y = from->y;
to->inject_touch_event.pressure = 1.f; to->inject_touch_event.pressure = 1.f;
to->inject_touch_event.buttons = convert_mouse_buttons(from->state); to->inject_touch_event.buttons = convert_mouse_buttons(from->state);
@@ -260,7 +245,6 @@ convert_mouse_wheel(const SDL_MouseWheelEvent *from, struct position position,
struct control_msg *to) { struct control_msg *to) {
to->type = CONTROL_MSG_TYPE_INJECT_SCROLL_EVENT; to->type = CONTROL_MSG_TYPE_INJECT_SCROLL_EVENT;
// TODO map coords
to->inject_scroll_event.position = position; to->inject_scroll_event.position = position;
int mul = from->direction == SDL_MOUSEWHEEL_NORMAL ? 1 : -1; int mul = from->direction == SDL_MOUSEWHEEL_NORMAL ? 1 : -1;

View File

@@ -21,14 +21,14 @@ bool
convert_input_key(const SDL_KeyboardEvent *from, struct control_msg *to); convert_input_key(const SDL_KeyboardEvent *from, struct control_msg *to);
bool bool
convert_mouse_button(const SDL_MouseButtonEvent *from, const SDL_Rect *rect, convert_mouse_button(const SDL_MouseButtonEvent *from, struct size screen_size,
struct size frame_size, struct control_msg *to); struct control_msg *to);
// the video size may be different from the real device size, so we need the // the video size may be different from the real device size, so we need the
// size to which the absolute position apply, to scale it accordingly // size to which the absolute position apply, to scale it accordingly
bool bool
convert_mouse_motion(const SDL_MouseMotionEvent *from, const SDL_Rect *rect, convert_mouse_motion(const SDL_MouseMotionEvent *from, struct size screen_size,
struct size frame_size, struct control_msg *to); struct control_msg *to);
bool bool
convert_touch(const SDL_TouchFingerEvent *from, struct size screen_size, convert_touch(const SDL_TouchFingerEvent *from, struct size screen_size,

View File

@@ -7,6 +7,19 @@
#include "lock_util.h" #include "lock_util.h"
#include "log.h" #include "log.h"
void
input_manager_init(struct input_manager *input_manager,
struct controller *controller,
struct video_buffer *video_buffer,
struct screen *screen) {
input_manager->controller = controller;
input_manager->video_buffer = video_buffer;
input_manager->screen = screen;
input_manager->ctrl_down = false;
input_manager->vfinger.down = false;
}
// Convert window coordinates (as provided by SDL_GetMouseState() to renderer // Convert window coordinates (as provided by SDL_GetMouseState() to renderer
// coordinates (as provided in SDL mouse events) // coordinates (as provided in SDL mouse events)
// //
@@ -211,6 +224,26 @@ clipboard_paste(struct controller *controller) {
} }
} }
static void
simulate_virtual_finger(struct input_manager *input_manager, bool down,
struct position *position) {
SDL_assert(input_manager->vfinger.down != down);
input_manager->vfinger.down = down;
struct control_msg msg;
msg.type = CONTROL_MSG_TYPE_INJECT_TOUCH_EVENT;
msg.inject_touch_event.action = down ? AMOTION_EVENT_ACTION_DOWN
: AMOTION_EVENT_ACTION_UP;
msg.inject_touch_event.pointer_id = POINTER_ID_VIRTUAL_FINGER;
msg.inject_touch_event.position = *position;
msg.inject_touch_event.pressure = 1.f;
msg.inject_touch_event.buttons = 0;
if (!controller_push_msg(input_manager->controller, &msg)) {
LOGW("Could not request 'inject virtual finger event'");
}
}
void void
input_manager_process_text_input(struct input_manager *input_manager, input_manager_process_text_input(struct input_manager *input_manager,
const SDL_TextInputEvent *event) { const SDL_TextInputEvent *event) {
@@ -244,6 +277,14 @@ input_manager_process_key(struct input_manager *input_manager,
bool alt = event->keysym.mod & (KMOD_LALT | KMOD_RALT); bool alt = event->keysym.mod & (KMOD_LALT | KMOD_RALT);
bool meta = event->keysym.mod & (KMOD_LGUI | KMOD_RGUI); bool meta = event->keysym.mod & (KMOD_LGUI | KMOD_RGUI);
// store the Ctrl state to modify mouse events
input_manager->ctrl_down = ctrl;
if (input_manager->vfinger.down && !ctrl) {
simulate_virtual_finger(input_manager, false,
&input_manager->vfinger.position);
}
// use Cmd on macOS, Ctrl on other platforms // use Cmd on macOS, Ctrl on other platforms
#ifdef __APPLE__ #ifdef __APPLE__
bool cmd = !ctrl && meta; bool cmd = !ctrl && meta;
@@ -394,7 +435,7 @@ input_manager_process_mouse_motion(struct input_manager *input_manager,
return; return;
} }
struct control_msg msg; struct control_msg msg;
if (convert_mouse_motion(event, &input_manager->screen->rect, input_manager->screen->frame_size, &msg)) { if (convert_mouse_motion(event, input_manager->screen->frame_size, &msg)) {
if (!controller_push_msg(input_manager->controller, &msg)) { if (!controller_push_msg(input_manager->controller, &msg)) {
LOGW("Could not request 'inject mouse motion event'"); LOGW("Could not request 'inject mouse motion event'");
} }
@@ -427,6 +468,7 @@ input_manager_process_mouse_button(struct input_manager *input_manager,
// simulated from touch events, so it's a duplicate // simulated from touch events, so it's a duplicate
return; return;
} }
if (event->type == SDL_MOUSEBUTTONDOWN) { if (event->type == SDL_MOUSEBUTTONDOWN) {
if (control && event->button == SDL_BUTTON_RIGHT) { if (control && event->button == SDL_BUTTON_RIGHT) {
press_back_or_turn_screen_on(input_manager->controller); press_back_or_turn_screen_on(input_manager->controller);
@@ -437,12 +479,24 @@ input_manager_process_mouse_button(struct input_manager *input_manager,
return; return;
} }
// double-click on black borders resize to fit the device screen // double-click on black borders resize to fit the device screen
if (event->button == SDL_BUTTON_LEFT && event->clicks == 2) { if (event->button == SDL_BUTTON_LEFT) {
bool outside = if (event->clicks >= 2) {
is_outside_device_screen(input_manager, event->x, event->y); bool outside =
if (outside) { is_outside_device_screen(input_manager, event->x, event->y);
screen_resize_to_fit(input_manager->screen); if (outside) {
return; screen_resize_to_fit(input_manager->screen);
return;
}
}
struct virtual_finger *vfinger = &input_manager->vfinger;
if (input_manager->ctrl_down && !vfinger->down) {
vfinger->position.point.x = event->x;
vfinger->position.point.y = event->y;
vfinger->position.screen_size =
input_manager->screen->frame_size,
simulate_virtual_finger(input_manager, true,
&vfinger->position);
} }
} }
// otherwise, send the click event to the device // otherwise, send the click event to the device
@@ -453,7 +507,7 @@ input_manager_process_mouse_button(struct input_manager *input_manager,
} }
struct control_msg msg; struct control_msg msg;
if (convert_mouse_button(event, &input_manager->screen->rect, input_manager->screen->frame_size, &msg)) { if (convert_mouse_button(event, input_manager->screen->frame_size, &msg)) {
if (!controller_push_msg(input_manager->controller, &msg)) { if (!controller_push_msg(input_manager->controller, &msg)) {
LOGW("Could not request 'inject mouse button event'"); LOGW("Could not request 'inject mouse button event'");
} }

View File

@@ -14,8 +14,21 @@ struct input_manager {
struct controller *controller; struct controller *controller;
struct video_buffer *video_buffer; struct video_buffer *video_buffer;
struct screen *screen; struct screen *screen;
bool ctrl_down;
struct virtual_finger {
bool down;
struct position position;
} vfinger;
}; };
void
input_manager_init(struct input_manager *input_manager,
struct controller *controller,
struct video_buffer *video_buffer,
struct screen *screen);
void void
input_manager_process_text_input(struct input_manager *input_manager, input_manager_process_text_input(struct input_manager *input_manager,
const SDL_TextInputEvent *event); const SDL_TextInputEvent *event);

View File

@@ -37,12 +37,7 @@ static struct decoder decoder;
static struct recorder recorder; static struct recorder recorder;
static struct controller controller; static struct controller controller;
static struct file_handler file_handler; static struct file_handler file_handler;
static struct input_manager input_manager;
static struct input_manager input_manager = {
.controller = &controller,
.video_buffer = &video_buffer,
.screen = &screen,
};
// init SDL and set appropriate hints // init SDL and set appropriate hints
static bool static bool
@@ -145,10 +140,8 @@ handle_event(SDL_Event *event, bool control) {
case SDL_WINDOWEVENT: case SDL_WINDOWEVENT:
switch (event->window.event) { switch (event->window.event) {
case SDL_WINDOWEVENT_EXPOSED: case SDL_WINDOWEVENT_EXPOSED:
screen_render(&screen);
break;
case SDL_WINDOWEVENT_SIZE_CHANGED: case SDL_WINDOWEVENT_SIZE_CHANGED:
screen_resized(&screen); screen_render(&screen);
break; break;
} }
break; break;
@@ -313,6 +306,8 @@ scrcpy(const struct scrcpy_options *options) {
goto end; goto end;
} }
input_manager_init(&input_manager, &controller, &video_buffer, &screen);
if (!server_connect_to(&server)) { if (!server_connect_to(&server)) {
goto end; goto end;
} }

View File

@@ -122,32 +122,6 @@ get_initial_optimal_size(struct size frame_size) {
return get_optimal_size(frame_size, frame_size); return get_optimal_size(frame_size, frame_size);
} }
static void
update_frame_rect(struct screen *screen) {
struct size window_size = get_window_size(screen);
// 32 bits because we need to multiply two 16 bits values
uint32_t ww = window_size.width;
uint32_t wh = window_size.height;
uint32_t fw = screen->frame_size.width;
uint32_t fh = screen->frame_size.height;
SDL_Rect *rect = &screen->rect;
bool keep_width = fw * wh > fh * ww;
if (keep_width) {
rect->x = 0;
rect->w = ww;
rect->h = ww * fh / fw;
rect->y = (wh - rect->h) / 2;
} else {
rect->y = 0;
rect->h = wh;
rect->w = wh * fw / fh;
rect->x = (ww - rect->w) / 2;
}
}
void void
screen_init(struct screen *screen) { screen_init(struct screen *screen) {
*screen = (struct screen) SCREEN_INITIALIZER; *screen = (struct screen) SCREEN_INITIALIZER;
@@ -196,6 +170,13 @@ screen_init_rendering(struct screen *screen, const char *window_title,
return false; return false;
} }
if (SDL_RenderSetLogicalSize(screen->renderer, frame_size.width,
frame_size.height)) {
LOGE("Could not set renderer logical size: %s", SDL_GetError());
screen_destroy(screen);
return false;
}
SDL_Surface *icon = read_xpm(icon_xpm); SDL_Surface *icon = read_xpm(icon_xpm);
if (icon) { if (icon) {
SDL_SetWindowIcon(screen->window, icon); SDL_SetWindowIcon(screen->window, icon);
@@ -239,6 +220,12 @@ static bool
prepare_for_frame(struct screen *screen, struct size new_frame_size) { prepare_for_frame(struct screen *screen, struct size new_frame_size) {
if (screen->frame_size.width != new_frame_size.width if (screen->frame_size.width != new_frame_size.width
|| screen->frame_size.height != new_frame_size.height) { || screen->frame_size.height != new_frame_size.height) {
if (SDL_RenderSetLogicalSize(screen->renderer, new_frame_size.width,
new_frame_size.height)) {
LOGE("Could not set renderer logical size: %s", SDL_GetError());
return false;
}
// frame dimension changed, destroy texture // frame dimension changed, destroy texture
SDL_DestroyTexture(screen->texture); SDL_DestroyTexture(screen->texture);
@@ -284,7 +271,6 @@ screen_update_frame(struct screen *screen, struct video_buffer *vb) {
mutex_unlock(vb->mutex); mutex_unlock(vb->mutex);
return false; return false;
} }
update_frame_rect(screen);
update_texture(screen, frame); update_texture(screen, frame);
mutex_unlock(vb->mutex); mutex_unlock(vb->mutex);
@@ -292,16 +278,10 @@ screen_update_frame(struct screen *screen, struct video_buffer *vb) {
return true; return true;
} }
void
screen_resized(struct screen *screen) {
update_frame_rect(screen);
screen_render(screen);
}
void void
screen_render(struct screen *screen) { screen_render(struct screen *screen) {
SDL_RenderClear(screen->renderer); SDL_RenderClear(screen->renderer);
SDL_RenderCopy(screen->renderer, screen->texture, NULL, &screen->rect); SDL_RenderCopy(screen->renderer, screen->texture, NULL, NULL);
SDL_RenderPresent(screen->renderer); SDL_RenderPresent(screen->renderer);
} }

View File

@@ -17,7 +17,6 @@ struct screen {
struct size frame_size; struct size frame_size;
//used only in fullscreen mode to know the windowed window size //used only in fullscreen mode to know the windowed window size
struct size windowed_window_size; struct size windowed_window_size;
struct SDL_Rect rect; // frame location and size inside the window
bool has_frame; bool has_frame;
bool fullscreen; bool fullscreen;
bool no_window; bool no_window;
@@ -35,15 +34,9 @@ struct screen {
.width = 0, \ .width = 0, \
.height = 0, \ .height = 0, \
}, \ }, \
.rect = { \ .has_frame = false, \
.x = 0, \ .fullscreen = false, \
.y = 0, \ .no_window = false, \
.w = 0, \
.h = 0, \
}, \
.has_frame = false, \
.fullscreen = false, \
.no_window = false, \
} }
// initialize default values // initialize default values
@@ -67,9 +60,6 @@ screen_destroy(struct screen *screen);
bool bool
screen_update_frame(struct screen *screen, struct video_buffer *vb); screen_update_frame(struct screen *screen, struct video_buffer *vb);
void
screen_resized(struct screen *screen);
// render the texture to the renderer // render the texture to the renderer
void void
screen_render(struct screen *screen); screen_render(struct screen *screen);

View File

@@ -161,11 +161,7 @@ public final class Device {
* @param mode one of the {@code SCREEN_POWER_MODE_*} constants * @param mode one of the {@code SCREEN_POWER_MODE_*} constants
*/ */
public void setScreenPowerMode(int mode) { public void setScreenPowerMode(int mode) {
IBinder d = SurfaceControl.getBuiltInDisplay(); IBinder d = SurfaceControl.getBuiltInDisplay(0);
if (d == null) {
Ln.e("Could not get built-in display");
return;
}
SurfaceControl.setDisplayPowerMode(d, mode); SurfaceControl.setDisplayPowerMode(d, mode);
Ln.i("Device screen turned " + (mode == Device.POWER_MODE_OFF ? "off" : "on")); Ln.i("Device screen turned " + (mode == Device.POWER_MODE_OFF ? "off" : "on"));
} }

View File

@@ -1,102 +1,44 @@
package com.genymobile.scrcpy.wrappers; package com.genymobile.scrcpy.wrappers;
import com.genymobile.scrcpy.Ln;
import android.content.ClipData; import android.content.ClipData;
import android.os.Build;
import android.os.IInterface; import android.os.IInterface;
import java.lang.reflect.InvocationTargetException; import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method; import java.lang.reflect.Method;
public class ClipboardManager { public class ClipboardManager {
private static final String PACKAGE_NAME = "com.android.shell";
private static final int USER_ID = 0;
private final IInterface manager; private final IInterface manager;
private Method getPrimaryClipMethod; private final Method getPrimaryClipMethod;
private Method setPrimaryClipMethod; private final Method setPrimaryClipMethod;
public ClipboardManager(IInterface manager) { public ClipboardManager(IInterface manager) {
this.manager = manager; this.manager = manager;
} try {
getPrimaryClipMethod = manager.getClass().getMethod("getPrimaryClip", String.class);
private Method getGetPrimaryClipMethod() { setPrimaryClipMethod = manager.getClass().getMethod("setPrimaryClip", ClipData.class, String.class);
if (getPrimaryClipMethod == null) { } catch (NoSuchMethodException e) {
try { throw new AssertionError(e);
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) {
getPrimaryClipMethod = manager.getClass().getMethod("getPrimaryClip", String.class);
} else {
getPrimaryClipMethod = manager.getClass().getMethod("getPrimaryClip", String.class, int.class);
}
} catch (NoSuchMethodException e) {
Ln.e("Could not find method", e);
}
}
return getPrimaryClipMethod;
}
private Method getSetPrimaryClipMethod() {
if (setPrimaryClipMethod == null) {
try {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) {
setPrimaryClipMethod = manager.getClass().getMethod("setPrimaryClip", ClipData.class, String.class);
} else {
setPrimaryClipMethod = manager.getClass().getMethod("setPrimaryClip", ClipData.class,
String.class, int.class);
}
} catch (NoSuchMethodException e) {
Ln.e("Could not find method", e);
}
}
return setPrimaryClipMethod;
}
private static ClipData getPrimaryClip(Method method, IInterface manager) throws InvocationTargetException,
IllegalAccessException {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) {
return (ClipData) method.invoke(manager, PACKAGE_NAME);
}
return (ClipData) method.invoke(manager, PACKAGE_NAME, USER_ID);
}
private static void setPrimaryClip(Method method, IInterface manager, ClipData clipData) throws InvocationTargetException,
IllegalAccessException {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) {
method.invoke(manager, clipData, PACKAGE_NAME);
} else {
method.invoke(manager, clipData, PACKAGE_NAME, USER_ID);
} }
} }
public CharSequence getText() { public CharSequence getText() {
Method method = getGetPrimaryClipMethod();
if (method == null) {
return null;
}
try { try {
ClipData clipData = getPrimaryClip(method, manager); ClipData clipData = (ClipData) getPrimaryClipMethod.invoke(manager, "com.android.shell");
if (clipData == null || clipData.getItemCount() == 0) { if (clipData == null || clipData.getItemCount() == 0) {
return null; return null;
} }
return clipData.getItemAt(0).getText(); return clipData.getItemAt(0).getText();
} catch (InvocationTargetException | IllegalAccessException e) { } catch (InvocationTargetException | IllegalAccessException e) {
Ln.e("Could not invoke " + method.getName(), e); throw new AssertionError(e);
return null;
} }
} }
public void setText(CharSequence text) { public void setText(CharSequence text) {
Method method = getSetPrimaryClipMethod();
if (method == null) {
return;
}
ClipData clipData = ClipData.newPlainText(null, text); ClipData clipData = ClipData.newPlainText(null, text);
try { try {
setPrimaryClip(method, manager, clipData); setPrimaryClipMethod.invoke(manager, clipData, "com.android.shell");
} catch (InvocationTargetException | IllegalAccessException e) { } catch (InvocationTargetException | IllegalAccessException e) {
Ln.e("Could not invoke " + method.getName(), e); throw new AssertionError(e);
} }
} }
} }

View File

@@ -1,7 +1,5 @@
package com.genymobile.scrcpy.wrappers; package com.genymobile.scrcpy.wrappers;
import com.genymobile.scrcpy.Ln;
import android.os.IInterface; import android.os.IInterface;
import android.view.InputEvent; import android.view.InputEvent;
@@ -15,33 +13,22 @@ public final class InputManager {
public static final int INJECT_INPUT_EVENT_MODE_WAIT_FOR_FINISH = 2; public static final int INJECT_INPUT_EVENT_MODE_WAIT_FOR_FINISH = 2;
private final IInterface manager; private final IInterface manager;
private Method injectInputEventMethod; private final Method injectInputEventMethod;
public InputManager(IInterface manager) { public InputManager(IInterface manager) {
this.manager = manager; this.manager = manager;
} try {
injectInputEventMethod = manager.getClass().getMethod("injectInputEvent", InputEvent.class, int.class);
private Method getInjectInputEventMethod() { } catch (NoSuchMethodException e) {
if (injectInputEventMethod == null) { throw new AssertionError(e);
try {
injectInputEventMethod = manager.getClass().getMethod("injectInputEvent", InputEvent.class, int.class);
} catch (NoSuchMethodException e) {
Ln.e("Could not find method", e);
}
} }
return injectInputEventMethod;
} }
public boolean injectInputEvent(InputEvent inputEvent, int mode) { public boolean injectInputEvent(InputEvent inputEvent, int mode) {
Method method = getInjectInputEventMethod();
if (method == null) {
return false;
}
try { try {
return (Boolean) method.invoke(manager, inputEvent, mode); return (Boolean) injectInputEventMethod.invoke(manager, inputEvent, mode);
} catch (InvocationTargetException | IllegalAccessException e) { } catch (InvocationTargetException | IllegalAccessException e) {
Ln.e("Could not invoke " + method.getName(), e); throw new AssertionError(e);
return false;
} }
} }
} }

View File

@@ -1,7 +1,5 @@
package com.genymobile.scrcpy.wrappers; package com.genymobile.scrcpy.wrappers;
import com.genymobile.scrcpy.Ln;
import android.annotation.SuppressLint; import android.annotation.SuppressLint;
import android.os.Build; import android.os.Build;
import android.os.IInterface; import android.os.IInterface;
@@ -11,35 +9,24 @@ import java.lang.reflect.Method;
public final class PowerManager { public final class PowerManager {
private final IInterface manager; private final IInterface manager;
private Method isScreenOnMethod; private final Method isScreenOnMethod;
public PowerManager(IInterface manager) { public PowerManager(IInterface manager) {
this.manager = manager; this.manager = manager;
} try {
@SuppressLint("ObsoleteSdkInt") // we may lower minSdkVersion in the future
private Method getIsScreenOnMethod() { String methodName = Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT_WATCH ? "isInteractive" : "isScreenOn";
if (isScreenOnMethod == null) { isScreenOnMethod = manager.getClass().getMethod(methodName);
try { } catch (NoSuchMethodException e) {
@SuppressLint("ObsoleteSdkInt") // we may lower minSdkVersion in the future throw new AssertionError(e);
String methodName = Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT_WATCH ? "isInteractive" : "isScreenOn";
isScreenOnMethod = manager.getClass().getMethod(methodName);
} catch (NoSuchMethodException e) {
Ln.e("Could not find method", e);
}
} }
return isScreenOnMethod;
} }
public boolean isScreenOn() { public boolean isScreenOn() {
Method method = getIsScreenOnMethod();
if (method == null) {
return false;
}
try { try {
return (Boolean) method.invoke(manager); return (Boolean) isScreenOnMethod.invoke(manager);
} catch (InvocationTargetException | IllegalAccessException e) { } catch (InvocationTargetException | IllegalAccessException e) {
Ln.e("Could not invoke " + method.getName(), e); throw new AssertionError(e);
return false;
} }
} }
} }

View File

@@ -17,49 +17,35 @@ public class StatusBarManager {
this.manager = manager; this.manager = manager;
} }
private Method getExpandNotificationsPanelMethod() { public void expandNotificationsPanel() {
if (expandNotificationsPanelMethod == null) { if (expandNotificationsPanelMethod == null) {
try { try {
expandNotificationsPanelMethod = manager.getClass().getMethod("expandNotificationsPanel"); expandNotificationsPanelMethod = manager.getClass().getMethod("expandNotificationsPanel");
} catch (NoSuchMethodException e) { } catch (NoSuchMethodException e) {
Ln.e("Could not find method", e); Ln.e("ServiceBarManager.expandNotificationsPanel() is not available on this device");
return;
} }
} }
return expandNotificationsPanelMethod;
}
private Method getCollapsePanelsMethod() {
if (collapsePanelsMethod == null) {
try {
collapsePanelsMethod = manager.getClass().getMethod("collapsePanels");
} catch (NoSuchMethodException e) {
Ln.e("Could not find method", e);
}
}
return collapsePanelsMethod;
}
public void expandNotificationsPanel() {
Method method = getExpandNotificationsPanelMethod();
if (method == null) {
return;
}
try { try {
method.invoke(manager); expandNotificationsPanelMethod.invoke(manager);
} catch (InvocationTargetException | IllegalAccessException e) { } catch (InvocationTargetException | IllegalAccessException e) {
Ln.e("Could not invoke " + method.getName(), e); Ln.e("Could not invoke ServiceBarManager.expandNotificationsPanel()", e);
} }
} }
public void collapsePanels() { public void collapsePanels() {
Method method = getCollapsePanelsMethod(); if (collapsePanelsMethod == null) {
if (method == null) { try {
return; collapsePanelsMethod = manager.getClass().getMethod("collapsePanels");
} catch (NoSuchMethodException e) {
Ln.e("ServiceBarManager.collapsePanels() is not available on this device");
return;
}
} }
try { try {
method.invoke(manager); collapsePanelsMethod.invoke(manager);
} catch (InvocationTargetException | IllegalAccessException e) { } catch (InvocationTargetException | IllegalAccessException e) {
Ln.e("Could not invoke " + method.getName(), e); Ln.e("Could not invoke ServiceBarManager.collapsePanels()", e);
} }
} }
} }

View File

@@ -1,16 +1,11 @@
package com.genymobile.scrcpy.wrappers; package com.genymobile.scrcpy.wrappers;
import com.genymobile.scrcpy.Ln;
import android.annotation.SuppressLint; import android.annotation.SuppressLint;
import android.graphics.Rect; import android.graphics.Rect;
import android.os.Build; import android.os.Build;
import android.os.IBinder; import android.os.IBinder;
import android.view.Surface; import android.view.Surface;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
@SuppressLint("PrivateApi") @SuppressLint("PrivateApi")
public final class SurfaceControl { public final class SurfaceControl {
@@ -28,9 +23,6 @@ public final class SurfaceControl {
} }
} }
private static Method getBuiltInDisplayMethod;
private static Method setDisplayPowerModeMethod;
private SurfaceControl() { private SurfaceControl() {
// only static methods // only static methods
} }
@@ -84,62 +76,24 @@ public final class SurfaceControl {
} }
} }
private static Method getGetBuiltInDisplayMethod() { public static IBinder getBuiltInDisplay(int builtInDisplayId) {
if (getBuiltInDisplayMethod == null) {
try {
// the method signature has changed in Android Q
// <https://github.com/Genymobile/scrcpy/issues/586>
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) {
getBuiltInDisplayMethod = CLASS.getMethod("getBuiltInDisplay", int.class);
} else {
getBuiltInDisplayMethod = CLASS.getMethod("getInternalDisplayToken");
}
} catch (NoSuchMethodException e) {
Ln.e("Could not find method", e);
}
}
return getBuiltInDisplayMethod;
}
public static IBinder getBuiltInDisplay() {
Method method = getGetBuiltInDisplayMethod();
if (method == null) {
return null;
}
try { try {
// the method signature has changed in Android Q
// <https://github.com/Genymobile/scrcpy/issues/586>
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) { if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) {
// call getBuiltInDisplay(0) return (IBinder) CLASS.getMethod("getBuiltInDisplay", int.class).invoke(null, builtInDisplayId);
return (IBinder) method.invoke(null, 0);
} }
return (IBinder) CLASS.getMethod("getPhysicalDisplayToken", long.class).invoke(null, builtInDisplayId);
// call getInternalDisplayToken() } catch (Exception e) {
return (IBinder) method.invoke(null); throw new AssertionError(e);
} catch (InvocationTargetException | IllegalAccessException e) {
Ln.e("Could not invoke " + method.getName(), e);
return null;
} }
} }
private static Method getSetDisplayPowerModeMethod() {
if (setDisplayPowerModeMethod == null) {
try {
setDisplayPowerModeMethod = CLASS.getMethod("setDisplayPowerMode", IBinder.class, int.class);
} catch (NoSuchMethodException e) {
Ln.e("Could not find method", e);
}
}
return setDisplayPowerModeMethod;
}
public static void setDisplayPowerMode(IBinder displayToken, int mode) { public static void setDisplayPowerMode(IBinder displayToken, int mode) {
Method method = getSetDisplayPowerModeMethod();
if (method == null) {
return;
}
try { try {
method.invoke(null, displayToken, mode); CLASS.getMethod("setDisplayPowerMode", IBinder.class, int.class).invoke(null, displayToken, mode);
} catch (InvocationTargetException | IllegalAccessException e) { } catch (Exception e) {
Ln.e("Could not invoke " + method.getName(), e); throw new AssertionError(e);
} }
} }