Compare commits

..

1 Commits

Author SHA1 Message Date
Romain Vimont
a12b459746 Accept negative window position
It seems to work on some window managers.

Fixes #1242 <https://github.com/Genymobile/scrcpy/issues/1242>
2020-03-26 18:36:00 +01:00
37 changed files with 134 additions and 534 deletions

1
.gitignore vendored
View File

@@ -2,4 +2,3 @@ build/
/dist/ /dist/
.idea/ .idea/
.gradle/ .gradle/
/x/

View File

@@ -353,21 +353,6 @@ scrcpy --no-control
scrcpy -n scrcpy -n
``` ```
#### Display
If several displays are available, it is possible to select the display to
mirror:
```bash
scrcpy --display 1
```
The list of display ids can be retrieved by:
```
adb shell dumpsys display # search "mDisplayId=" in the output
```
#### Turn screen off #### Turn screen off
It is possible to turn the device screen off while mirroring on start with a It is possible to turn the device screen off while mirroring on start with a
@@ -491,8 +476,6 @@ Also see [issue #14].
| Action | Shortcut | Shortcut (macOS) | Action | Shortcut | Shortcut (macOS)
| -------------------------------------- |:----------------------------- |:----------------------------- | -------------------------------------- |:----------------------------- |:-----------------------------
| Switch fullscreen mode | `Ctrl`+`f` | `Cmd`+`f` | Switch fullscreen mode | `Ctrl`+`f` | `Cmd`+`f`
| Rotate window left | `Ctrl`+`←` _(left)_ | `Cmd`+`←` _(left)_
| Rotate window right | `Ctrl`+`→` _(right)_ | `Cmd`+`→` _(right)_
| Resize window to 1:1 (pixel-perfect) | `Ctrl`+`g` | `Cmd`+`g` | Resize window to 1:1 (pixel-perfect) | `Ctrl`+`g` | `Cmd`+`g`
| Resize window to remove black borders | `Ctrl`+`x` \| _Double-click¹_ | `Cmd`+`x` \| _Double-click¹_ | Resize window to remove black borders | `Ctrl`+`x` \| _Double-click¹_ | `Cmd`+`x` \| _Double-click¹_
| Click on `HOME` | `Ctrl`+`h` \| _Middle-click_ | `Ctrl`+`h` \| _Middle-click_ | Click on `HOME` | `Ctrl`+`h` \| _Middle-click_ | `Ctrl`+`h` \| _Middle-click_

View File

@@ -33,15 +33,6 @@ The values are expressed in the device natural orientation (typically, portrait
.B \-\-max\-size .B \-\-max\-size
value is computed on the cropped size. value is computed on the cropped size.
.TP
.BI "\-\-display " id
Specify the display id to mirror.
The list of possible display ids can be listed by "adb shell dumpsys display"
(search "mDisplayId=" in the output).
Default is 0.
.TP .TP
.B \-f, \-\-fullscreen .B \-f, \-\-fullscreen
Start in fullscreen. Start in fullscreen.
@@ -166,14 +157,6 @@ Default is 0 (automatic).\n
.B Ctrl+f .B Ctrl+f
switch fullscreen mode switch fullscreen mode
.TP
.B Ctrl+Left
rotate window left
.TP
.B Ctrl+Right
rotate window right
.TP .TP
.B Ctrl+g .B Ctrl+g
resize window to 1:1 (pixel\-perfect) resize window to 1:1 (pixel\-perfect)

View File

@@ -36,15 +36,6 @@ scrcpy_print_usage(const char *arg0) {
" (typically, portrait for a phone, landscape for a tablet).\n" " (typically, portrait for a phone, landscape for a tablet).\n"
" Any --max-size value is computed on the cropped size.\n" " Any --max-size value is computed on the cropped size.\n"
"\n" "\n"
" --display id\n"
" Specify the display id to mirror.\n"
"\n"
" The list of possible display ids can be listed by:\n"
" adb shell dumpsys display\n"
" (search \"mDisplayId=\" in the output)\n"
"\n"
" Default is 0.\n"
"\n"
" -f, --fullscreen\n" " -f, --fullscreen\n"
" Start in fullscreen.\n" " Start in fullscreen.\n"
"\n" "\n"
@@ -145,12 +136,6 @@ scrcpy_print_usage(const char *arg0) {
" " CTRL_OR_CMD "+f\n" " " CTRL_OR_CMD "+f\n"
" switch fullscreen mode\n" " switch fullscreen mode\n"
"\n" "\n"
" " CTRL_OR_CMD "+Left\n"
" rotate window left\n"
"\n"
" " CTRL_OR_CMD "+Right\n"
" rotate window right\n"
"\n"
" " CTRL_OR_CMD "+g\n" " " CTRL_OR_CMD "+g\n"
" resize window to 1:1 (pixel-perfect)\n" " resize window to 1:1 (pixel-perfect)\n"
"\n" "\n"
@@ -319,7 +304,7 @@ parse_lock_video_orientation(const char *s, int8_t *lock_video_orientation) {
static bool static bool
parse_window_position(const char *s, int16_t *position) { parse_window_position(const char *s, int16_t *position) {
// special value for "auto" // special value for "auto"
static_assert(WINDOW_POSITION_UNDEFINED == -0x8000, "unexpected value"); static_assert(WINDOW_POSITION_UNDEFINED == -0x8000);
if (!strcmp(s, "auto")) { if (!strcmp(s, "auto")) {
*position = WINDOW_POSITION_UNDEFINED; *position = WINDOW_POSITION_UNDEFINED;
@@ -378,18 +363,6 @@ parse_port_range(const char *s, struct port_range *port_range) {
return true; return true;
} }
static bool
parse_display_id(const char *s, uint16_t *display_id) {
long value;
bool ok = parse_integer_arg(s, &value, false, 0, 0xFFFF, "display id");
if (!ok) {
return false;
}
*display_id = (uint16_t) value;
return true;
}
static bool static bool
parse_record_format(const char *optarg, enum recorder_format *format) { parse_record_format(const char *optarg, enum recorder_format *format) {
if (!strcmp(optarg, "mp4")) { if (!strcmp(optarg, "mp4")) {
@@ -434,7 +407,6 @@ guess_record_format(const char *filename) {
#define OPT_WINDOW_BORDERLESS 1011 #define OPT_WINDOW_BORDERLESS 1011
#define OPT_MAX_FPS 1012 #define OPT_MAX_FPS 1012
#define OPT_LOCK_VIDEO_ORIENTATION 1013 #define OPT_LOCK_VIDEO_ORIENTATION 1013
#define OPT_DISPLAY_ID 1014
bool bool
scrcpy_parse_args(struct scrcpy_cli_args *args, int argc, char *argv[]) { scrcpy_parse_args(struct scrcpy_cli_args *args, int argc, char *argv[]) {
@@ -442,7 +414,6 @@ scrcpy_parse_args(struct scrcpy_cli_args *args, int argc, char *argv[]) {
{"always-on-top", no_argument, NULL, OPT_ALWAYS_ON_TOP}, {"always-on-top", no_argument, NULL, OPT_ALWAYS_ON_TOP},
{"bit-rate", required_argument, NULL, 'b'}, {"bit-rate", required_argument, NULL, 'b'},
{"crop", required_argument, NULL, OPT_CROP}, {"crop", required_argument, NULL, OPT_CROP},
{"display", required_argument, NULL, OPT_DISPLAY_ID},
{"fullscreen", no_argument, NULL, 'f'}, {"fullscreen", no_argument, NULL, 'f'},
{"help", no_argument, NULL, 'h'}, {"help", no_argument, NULL, 'h'},
{"lock-video-orientation", required_argument, NULL, {"lock-video-orientation", required_argument, NULL,
@@ -491,11 +462,6 @@ scrcpy_parse_args(struct scrcpy_cli_args *args, int argc, char *argv[]) {
case OPT_CROP: case OPT_CROP:
opts->crop = optarg; opts->crop = optarg;
break; break;
case OPT_DISPLAY_ID:
if (!parse_display_id(optarg, &opts->display_id)) {
return false;
}
break;
case 'f': case 'f':
opts->fullscreen = true; opts->fullscreen = true;
break; break;

View File

@@ -23,7 +23,7 @@ fps_counter_init(struct fps_counter *counter) {
} }
counter->thread = NULL; counter->thread = NULL;
atomic_init(&counter->started, 0); SDL_AtomicSet(&counter->started, 0);
// no need to initialize the other fields, they are unused until started // no need to initialize the other fields, they are unused until started
return true; return true;
@@ -35,16 +35,6 @@ fps_counter_destroy(struct fps_counter *counter) {
SDL_DestroyMutex(counter->mutex); SDL_DestroyMutex(counter->mutex);
} }
static inline bool
is_started(struct fps_counter *counter) {
return atomic_load_explicit(&counter->started, memory_order_acquire);
}
static inline void
set_started(struct fps_counter *counter, bool started) {
atomic_store_explicit(&counter->started, started, memory_order_release);
}
// must be called with mutex locked // must be called with mutex locked
static void static void
display_fps(struct fps_counter *counter) { display_fps(struct fps_counter *counter) {
@@ -80,10 +70,10 @@ run_fps_counter(void *data) {
mutex_lock(counter->mutex); mutex_lock(counter->mutex);
while (!counter->interrupted) { while (!counter->interrupted) {
while (!counter->interrupted && !is_started(counter)) { while (!counter->interrupted && !SDL_AtomicGet(&counter->started)) {
cond_wait(counter->state_cond, counter->mutex); cond_wait(counter->state_cond, counter->mutex);
} }
while (!counter->interrupted && is_started(counter)) { while (!counter->interrupted && SDL_AtomicGet(&counter->started)) {
uint32_t now = SDL_GetTicks(); uint32_t now = SDL_GetTicks();
check_interval_expired(counter, now); check_interval_expired(counter, now);
@@ -106,7 +96,7 @@ fps_counter_start(struct fps_counter *counter) {
counter->nr_skipped = 0; counter->nr_skipped = 0;
mutex_unlock(counter->mutex); mutex_unlock(counter->mutex);
set_started(counter, true); SDL_AtomicSet(&counter->started, 1);
cond_signal(counter->state_cond); cond_signal(counter->state_cond);
// counter->thread is always accessed from the same thread, no need to lock // counter->thread is always accessed from the same thread, no need to lock
@@ -124,13 +114,13 @@ fps_counter_start(struct fps_counter *counter) {
void void
fps_counter_stop(struct fps_counter *counter) { fps_counter_stop(struct fps_counter *counter) {
set_started(counter, false); SDL_AtomicSet(&counter->started, 0);
cond_signal(counter->state_cond); cond_signal(counter->state_cond);
} }
bool bool
fps_counter_is_started(struct fps_counter *counter) { fps_counter_is_started(struct fps_counter *counter) {
return is_started(counter); return SDL_AtomicGet(&counter->started);
} }
void void
@@ -155,7 +145,7 @@ fps_counter_join(struct fps_counter *counter) {
void void
fps_counter_add_rendered_frame(struct fps_counter *counter) { fps_counter_add_rendered_frame(struct fps_counter *counter) {
if (!is_started(counter)) { if (!SDL_AtomicGet(&counter->started)) {
return; return;
} }
@@ -168,7 +158,7 @@ fps_counter_add_rendered_frame(struct fps_counter *counter) {
void void
fps_counter_add_skipped_frame(struct fps_counter *counter) { fps_counter_add_skipped_frame(struct fps_counter *counter) {
if (!is_started(counter)) { if (!SDL_AtomicGet(&counter->started)) {
return; return;
} }

View File

@@ -1,9 +1,9 @@
#ifndef FPSCOUNTER_H #ifndef FPSCOUNTER_H
#define FPSCOUNTER_H #define FPSCOUNTER_H
#include <stdatomic.h>
#include <stdbool.h> #include <stdbool.h>
#include <stdint.h> #include <stdint.h>
#include <SDL2/SDL_atomic.h>
#include <SDL2/SDL_mutex.h> #include <SDL2/SDL_mutex.h>
#include <SDL2/SDL_thread.h> #include <SDL2/SDL_thread.h>
@@ -16,7 +16,7 @@ struct fps_counter {
// atomic so that we can check without locking the mutex // atomic so that we can check without locking the mutex
// if the FPS counter is disabled, we don't want to lock unnecessarily // if the FPS counter is disabled, we don't want to lock unnecessarily
atomic_bool started; SDL_atomic_t started;
// the following fields are protected by the mutex // the following fields are protected by the mutex
bool interrupted; bool interrupted;

View File

@@ -221,18 +221,6 @@ rotate_device(struct controller *controller) {
} }
} }
static void
rotate_client_left(struct screen *screen) {
unsigned new_rotation = (screen->rotation + 3) % 4;
screen_set_rotation(screen, new_rotation);
}
static void
rotate_client_right(struct screen *screen) {
unsigned new_rotation = (screen->rotation + 1) % 4;
screen_set_rotation(screen, new_rotation);
}
void void
input_manager_process_text_input(struct input_manager *im, input_manager_process_text_input(struct input_manager *im,
const SDL_TextInputEvent *event) { const SDL_TextInputEvent *event) {
@@ -363,16 +351,6 @@ input_manager_process_key(struct input_manager *im,
action_volume_up(controller, action); action_volume_up(controller, action);
} }
return; return;
case SDLK_LEFT:
if (cmd && !shift && down) {
rotate_client_left(im->screen);
}
return;
case SDLK_RIGHT:
if (cmd && !shift && down) {
rotate_client_right(im->screen);
}
return;
case SDLK_c: case SDLK_c:
if (control && cmd && !shift && !repeat && down) { if (control && cmd && !shift && !repeat && down) {
request_device_clipboard(controller); request_device_clipboard(controller);

View File

@@ -286,7 +286,6 @@ scrcpy(const struct scrcpy_options *options) {
.max_fps = options->max_fps, .max_fps = options->max_fps,
.lock_video_orientation = options->lock_video_orientation, .lock_video_orientation = options->lock_video_orientation,
.control = options->control, .control = options->control,
.display_id = options->display_id,
}; };
if (!server_start(&server, options->serial, &params)) { if (!server_start(&server, options->serial, &params)) {
return false; return false;

View File

@@ -25,7 +25,6 @@ struct scrcpy_options {
int16_t window_y; // WINDOW_POSITION_UNDEFINED for "auto" int16_t window_y; // WINDOW_POSITION_UNDEFINED for "auto"
uint16_t window_width; uint16_t window_width;
uint16_t window_height; uint16_t window_height;
uint16_t display_id;
bool show_touches; bool show_touches;
bool fullscreen; bool fullscreen;
bool always_on_top; bool always_on_top;
@@ -56,7 +55,6 @@ struct scrcpy_options {
.window_y = WINDOW_POSITION_UNDEFINED, \ .window_y = WINDOW_POSITION_UNDEFINED, \
.window_width = 0, \ .window_width = 0, \
.window_height = 0, \ .window_height = 0, \
.display_id = 0, \
.show_touches = false, \ .show_touches = false, \
.fullscreen = false, \ .fullscreen = false, \
.always_on_top = false, \ .always_on_top = false, \

View File

@@ -15,19 +15,6 @@
#define DISPLAY_MARGINS 96 #define DISPLAY_MARGINS 96
static inline struct size
get_rotated_size(struct size size, int rotation) {
struct size rotated_size;
if (rotation & 1) {
rotated_size.width = size.height;
rotated_size.height = size.width;
} else {
rotated_size.width = size.width;
rotated_size.height = size.height;
}
return rotated_size;
}
// get the window size in a struct size // get the window size in a struct size
static struct size static struct size
get_window_size(SDL_Window *window) { get_window_size(SDL_Window *window) {
@@ -93,8 +80,8 @@ get_preferred_display_bounds(struct size *bounds) {
// - it keeps the aspect ratio // - it keeps the aspect ratio
// - it scales down to make it fit in the display_size // - it scales down to make it fit in the display_size
static struct size static struct size
get_optimal_size(struct size current_size, struct size content_size) { get_optimal_size(struct size current_size, struct size frame_size) {
if (content_size.width == 0 || content_size.height == 0) { if (frame_size.width == 0 || frame_size.height == 0) {
// avoid division by 0 // avoid division by 0
return current_size; return current_size;
} }
@@ -113,14 +100,14 @@ get_optimal_size(struct size current_size, struct size content_size) {
h = MIN(current_size.height, display_size.height); h = MIN(current_size.height, display_size.height);
} }
bool keep_width = content_size.width * h > content_size.height * w; bool keep_width = frame_size.width * h > frame_size.height * w;
if (keep_width) { if (keep_width) {
// remove black borders on top and bottom // remove black borders on top and bottom
h = content_size.height * w / content_size.width; h = frame_size.height * w / frame_size.width;
} else { } else {
// remove black borders on left and right (or none at all if it already // remove black borders on left and right (or none at all if it already
// fits) // fits)
w = content_size.width * h / content_size.height; w = frame_size.width * h / frame_size.height;
} }
// w and h must fit into 16 bits // w and h must fit into 16 bits
@@ -130,33 +117,33 @@ get_optimal_size(struct size current_size, struct size content_size) {
// same as get_optimal_size(), but read the current size from the window // same as get_optimal_size(), but read the current size from the window
static inline struct size static inline struct size
get_optimal_window_size(const struct screen *screen, struct size content_size) { get_optimal_window_size(const struct screen *screen, struct size frame_size) {
struct size windowed_size = get_windowed_window_size(screen); struct size windowed_size = get_windowed_window_size(screen);
return get_optimal_size(windowed_size, content_size); return get_optimal_size(windowed_size, frame_size);
} }
// initially, there is no current size, so use the frame size as current size // initially, there is no current size, so use the frame size as current size
// req_width and req_height, if not 0, are the sizes requested by the user // req_width and req_height, if not 0, are the sizes requested by the user
static inline struct size static inline struct size
get_initial_optimal_size(struct size content_size, uint16_t req_width, get_initial_optimal_size(struct size frame_size, uint16_t req_width,
uint16_t req_height) { uint16_t req_height) {
struct size window_size; struct size window_size;
if (!req_width && !req_height) { if (!req_width && !req_height) {
window_size = get_optimal_size(content_size, content_size); window_size = get_optimal_size(frame_size, frame_size);
} else { } else {
if (req_width) { if (req_width) {
window_size.width = req_width; window_size.width = req_width;
} else { } else {
// compute from the requested height // compute from the requested height
window_size.width = (uint32_t) req_height * content_size.width window_size.width = (uint32_t) req_height * frame_size.width
/ content_size.height; / frame_size.height;
} }
if (req_height) { if (req_height) {
window_size.height = req_height; window_size.height = req_height;
} else { } else {
// compute from the requested width // compute from the requested width
window_size.height = (uint32_t) req_width * content_size.height window_size.height = (uint32_t) req_width * frame_size.height
/ content_size.width; / frame_size.width;
} }
} }
return window_size; return window_size;
@@ -180,11 +167,9 @@ screen_init_rendering(struct screen *screen, const char *window_title,
int16_t window_x, int16_t window_y, uint16_t window_width, int16_t window_x, int16_t window_y, uint16_t window_width,
uint16_t window_height, bool window_borderless) { uint16_t window_height, bool window_borderless) {
screen->frame_size = frame_size; screen->frame_size = frame_size;
struct size content_size =
get_rotated_size(frame_size, screen->rotation);
struct size window_size = struct size window_size =
get_initial_optimal_size(content_size, window_width, window_height); get_initial_optimal_size(frame_size, window_width, window_height);
uint32_t window_flags = SDL_WINDOW_HIDDEN | SDL_WINDOW_RESIZABLE; uint32_t window_flags = SDL_WINDOW_HIDDEN | SDL_WINDOW_RESIZABLE;
#ifdef HIDPI_SUPPORT #ifdef HIDPI_SUPPORT
window_flags |= SDL_WINDOW_ALLOW_HIGHDPI; window_flags |= SDL_WINDOW_ALLOW_HIGHDPI;
@@ -221,8 +206,8 @@ screen_init_rendering(struct screen *screen, const char *window_title,
return false; return false;
} }
if (SDL_RenderSetLogicalSize(screen->renderer, content_size.width, if (SDL_RenderSetLogicalSize(screen->renderer, frame_size.width,
content_size.height)) { frame_size.height)) {
LOGE("Could not set renderer logical size: %s", SDL_GetError()); LOGE("Could not set renderer logical size: %s", SDL_GetError());
screen_destroy(screen); screen_destroy(screen);
return false; return false;
@@ -268,51 +253,13 @@ screen_destroy(struct screen *screen) {
} }
} }
void
screen_set_rotation(struct screen *screen, unsigned rotation) {
assert(rotation < 4);
if (rotation == screen->rotation) {
return;
}
struct size old_content_size =
get_rotated_size(screen->frame_size, screen->rotation);
struct size new_content_size =
get_rotated_size(screen->frame_size, rotation);
if (SDL_RenderSetLogicalSize(screen->renderer,
new_content_size.width,
new_content_size.height)) {
LOGE("Could not set renderer logical size: %s", SDL_GetError());
return;
}
struct size windowed_size = get_windowed_window_size(screen);
struct size target_size = {
.width = (uint32_t) windowed_size.width * new_content_size.width
/ old_content_size.width,
.height = (uint32_t) windowed_size.height * new_content_size.height
/ old_content_size.height,
};
target_size = get_optimal_size(target_size, new_content_size);
set_window_size(screen, target_size);
screen->rotation = rotation;
LOGI("Client rotation set to %u", rotation);
screen_render(screen);
}
// recreate the texture and resize the window if the frame size has changed // recreate the texture and resize the window if the frame size has changed
static bool 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) {
struct size new_content_size = if (SDL_RenderSetLogicalSize(screen->renderer, new_frame_size.width,
get_rotated_size(new_frame_size, screen->rotation); new_frame_size.height)) {
if (SDL_RenderSetLogicalSize(screen->renderer,
new_content_size.width,
new_content_size.height)) {
LOGE("Could not set renderer logical size: %s", SDL_GetError()); LOGE("Could not set renderer logical size: %s", SDL_GetError());
return false; return false;
} }
@@ -320,16 +267,14 @@ prepare_for_frame(struct screen *screen, struct size new_frame_size) {
// frame dimension changed, destroy texture // frame dimension changed, destroy texture
SDL_DestroyTexture(screen->texture); SDL_DestroyTexture(screen->texture);
struct size content_size =
get_rotated_size(screen->frame_size, screen->rotation);
struct size windowed_size = get_windowed_window_size(screen); struct size windowed_size = get_windowed_window_size(screen);
struct size target_size = { struct size target_size = {
(uint32_t) windowed_size.width * new_content_size.width (uint32_t) windowed_size.width * new_frame_size.width
/ content_size.width, / screen->frame_size.width,
(uint32_t) windowed_size.height * new_content_size.height (uint32_t) windowed_size.height * new_frame_size.height
/ content_size.height, / screen->frame_size.height,
}; };
target_size = get_optimal_size(target_size, new_content_size); target_size = get_optimal_size(target_size, new_frame_size);
set_window_size(screen, target_size); set_window_size(screen, target_size);
screen->frame_size = new_frame_size; screen->frame_size = new_frame_size;
@@ -374,27 +319,7 @@ screen_update_frame(struct screen *screen, struct video_buffer *vb) {
void void
screen_render(struct screen *screen) { screen_render(struct screen *screen) {
SDL_RenderClear(screen->renderer); SDL_RenderClear(screen->renderer);
if (screen->rotation == 0) {
SDL_RenderCopy(screen->renderer, screen->texture, NULL, NULL); SDL_RenderCopy(screen->renderer, screen->texture, NULL, NULL);
} else {
double angle = 90 * screen->rotation;
SDL_Rect *dstrect = NULL;
SDL_Rect rect;
if (screen->rotation & 1) {
struct size size =
get_rotated_size(screen->frame_size, screen->rotation);
rect.x = (size.width - size.height) / 2;
rect.y = (size.height - size.width) / 2;
rect.w = size.height;
rect.h = size.width;
dstrect = &rect;
}
// rotation in RenderCopyEx() is clockwise
SDL_RenderCopyEx(screen->renderer, screen->texture, NULL, dstrect,
angle, NULL, 0);
}
SDL_RenderPresent(screen->renderer); SDL_RenderPresent(screen->renderer);
} }
@@ -424,10 +349,8 @@ screen_resize_to_fit(struct screen *screen) {
screen->maximized = false; screen->maximized = false;
} }
struct size content_size =
get_rotated_size(screen->frame_size, screen->rotation);
struct size optimal_size = struct size optimal_size =
get_optimal_window_size(screen, content_size); get_optimal_window_size(screen, screen->frame_size);
SDL_SetWindowSize(screen->window, optimal_size.width, optimal_size.height); SDL_SetWindowSize(screen->window, optimal_size.width, optimal_size.height);
LOGD("Resized to optimal size"); LOGD("Resized to optimal size");
} }
@@ -443,9 +366,8 @@ screen_resize_to_pixel_perfect(struct screen *screen) {
screen->maximized = false; screen->maximized = false;
} }
struct size content_size = SDL_SetWindowSize(screen->window, screen->frame_size.width,
get_rotated_size(screen->frame_size, screen->rotation); screen->frame_size.height);
SDL_SetWindowSize(screen->window, content_size.width, content_size.height);
LOGD("Resized to pixel-perfect"); LOGD("Resized to pixel-perfect");
} }

View File

@@ -22,8 +22,6 @@ struct screen {
// Since we receive the event SIZE_CHANGED before MAXIMIZED, we must be // Since we receive the event SIZE_CHANGED before MAXIMIZED, we must be
// able to revert the size to its non-maximized value. // able to revert the size to its non-maximized value.
struct size windowed_window_size_backup; struct size windowed_window_size_backup;
// client rotation: 0, 1, 2 or 3 (x90 degrees clockwise)
unsigned rotation;
bool has_frame; bool has_frame;
bool fullscreen; bool fullscreen;
bool maximized; bool maximized;
@@ -46,7 +44,6 @@ struct screen {
.width = 0, \ .width = 0, \
.height = 0, \ .height = 0, \
}, \ }, \
.rotation = 0, \
.has_frame = false, \ .has_frame = false, \
.fullscreen = false, \ .fullscreen = false, \
.maximized = false, \ .maximized = false, \
@@ -93,10 +90,6 @@ screen_resize_to_fit(struct screen *screen);
void void
screen_resize_to_pixel_perfect(struct screen *screen); screen_resize_to_pixel_perfect(struct screen *screen);
// change the client rotation (0, 1, 2 or 3, x90 degrees clockwise)
void
screen_set_rotation(struct screen *screen, unsigned rotation);
// react to window events // react to window events
void void
screen_handle_window_event(struct screen *screen, const SDL_WindowEvent *event); screen_handle_window_event(struct screen *screen, const SDL_WindowEvent *event);

View File

@@ -5,7 +5,6 @@
#include <inttypes.h> #include <inttypes.h>
#include <libgen.h> #include <libgen.h>
#include <stdio.h> #include <stdio.h>
#include <SDL2/SDL_thread.h>
#include <SDL2/SDL_timer.h> #include <SDL2/SDL_timer.h>
#include <SDL2/SDL_platform.h> #include <SDL2/SDL_platform.h>
@@ -235,12 +234,10 @@ execute_server(struct server *server, const struct server_params *params) {
char bit_rate_string[11]; char bit_rate_string[11];
char max_fps_string[6]; char max_fps_string[6];
char lock_video_orientation_string[3]; char lock_video_orientation_string[3];
char display_id_string[6];
sprintf(max_size_string, "%"PRIu16, params->max_size); sprintf(max_size_string, "%"PRIu16, params->max_size);
sprintf(bit_rate_string, "%"PRIu32, params->bit_rate); sprintf(bit_rate_string, "%"PRIu32, params->bit_rate);
sprintf(max_fps_string, "%"PRIu16, params->max_fps); sprintf(max_fps_string, "%"PRIu16, params->max_fps);
sprintf(lock_video_orientation_string, "%"PRIi8, params->lock_video_orientation); sprintf(lock_video_orientation_string, "%"PRIi8, params->lock_video_orientation);
sprintf(display_id_string, "%"PRIu16, params->display_id);
const char *const cmd[] = { const char *const cmd[] = {
"shell", "shell",
"CLASSPATH=" DEVICE_SERVER_PATH, "CLASSPATH=" DEVICE_SERVER_PATH,
@@ -267,7 +264,6 @@ execute_server(struct server *server, const struct server_params *params) {
params->crop ? params->crop : "-", params->crop ? params->crop : "-",
"true", // always send frame meta (packet boundaries + timestamp) "true", // always send frame meta (packet boundaries + timestamp)
params->control ? "true" : "false", params->control ? "true" : "false",
display_id_string,
}; };
#ifdef SERVER_DEBUGGER #ifdef SERVER_DEBUGGER
LOGI("Server debugger waiting for a client on device port " LOGI("Server debugger waiting for a client on device port "
@@ -318,12 +314,14 @@ connect_to_server(uint16_t port, uint32_t attempts, uint32_t delay) {
} }
static void static void
close_socket(socket_t socket) { close_socket(socket_t *socket) {
assert(socket != INVALID_SOCKET); assert(*socket != INVALID_SOCKET);
net_shutdown(socket, SHUT_RDWR); net_shutdown(*socket, SHUT_RDWR);
if (!net_close(socket)) { if (!net_close(*socket)) {
LOGW("Could not close socket"); LOGW("Could not close socket");
return;
} }
*socket = INVALID_SOCKET;
} }
void void
@@ -331,22 +329,6 @@ server_init(struct server *server) {
*server = (struct server) SERVER_INITIALIZER; *server = (struct server) SERVER_INITIALIZER;
} }
static int
run_wait_server(void *data) {
struct server *server = data;
cmd_simple_wait(server->process, NULL); // ignore exit code
// no need for synchronization, server_socket is initialized before this
// thread was created
if (server->server_socket != INVALID_SOCKET
&& !atomic_flag_test_and_set(&server->server_socket_closed)) {
// On Linux, accept() is unblocked by shutdown(), but on Windows, it is
// unblocked by closesocket(). Therefore, call both (close_socket()).
close_socket(server->server_socket);
}
LOGD("Server terminated");
return 0;
}
bool bool
server_start(struct server *server, const char *serial, server_start(struct server *server, const char *serial,
const struct server_params *params) { const struct server_params *params) {
@@ -360,50 +342,30 @@ server_start(struct server *server, const char *serial,
} }
if (!push_server(serial)) { if (!push_server(serial)) {
goto error1; SDL_free(server->serial);
return false;
} }
if (!enable_tunnel_any_port(server, params->port_range)) { if (!enable_tunnel_any_port(server, params->port_range)) {
goto error1; SDL_free(server->serial);
return false;
} }
// server will connect to our server socket // server will connect to our server socket
server->process = execute_server(server, params); server->process = execute_server(server, params);
if (server->process == PROCESS_NONE) {
goto error2;
}
// If the server process dies before connecting to the server socket, then if (server->process == PROCESS_NONE) {
// the client will be stuck forever on accept(). To avoid the problem, we if (!server->tunnel_forward) {
// must be able to wake up the accept() call when the server dies. To keep close_socket(&server->server_socket);
// things simple and multiplatform, just spawn a new thread waiting for the }
// server process and calling shutdown()/close() on the server socket if disable_tunnel(server);
// necessary to wake up any accept() blocking call. SDL_free(server->serial);
server->wait_server_thread = return false;
SDL_CreateThread(run_wait_server, "wait-server", server);
if (!server->wait_server_thread) {
cmd_terminate(server->process);
cmd_simple_wait(server->process, NULL); // ignore exit code
goto error2;
} }
server->tunnel_enabled = true; server->tunnel_enabled = true;
return true; return true;
error2:
if (!server->tunnel_forward) {
bool was_closed =
atomic_flag_test_and_set(&server->server_socket_closed);
// the thread is not started, the flag could not be already set
assert(!was_closed);
(void) was_closed;
close_socket(server->server_socket);
}
disable_tunnel(server);
error1:
SDL_free(server->serial);
return false;
} }
bool bool
@@ -421,11 +383,7 @@ server_connect_to(struct server *server) {
} }
// we don't need the server socket anymore // we don't need the server socket anymore
if (!atomic_flag_test_and_set(&server->server_socket_closed)) { close_socket(&server->server_socket);
// close it from here
close_socket(server->server_socket);
// otherwise, it is closed by run_wait_server()
}
} else { } else {
uint32_t attempts = 100; uint32_t attempts = 100;
uint32_t delay = 100; // ms uint32_t delay = 100; // ms
@@ -452,27 +410,29 @@ server_connect_to(struct server *server) {
void void
server_stop(struct server *server) { server_stop(struct server *server) {
if (server->server_socket != INVALID_SOCKET if (server->server_socket != INVALID_SOCKET) {
&& !atomic_flag_test_and_set(&server->server_socket_closed)) { close_socket(&server->server_socket);
close_socket(server->server_socket);
} }
if (server->video_socket != INVALID_SOCKET) { if (server->video_socket != INVALID_SOCKET) {
close_socket(server->video_socket); close_socket(&server->video_socket);
} }
if (server->control_socket != INVALID_SOCKET) { if (server->control_socket != INVALID_SOCKET) {
close_socket(server->control_socket); close_socket(&server->control_socket);
} }
assert(server->process != PROCESS_NONE); assert(server->process != PROCESS_NONE);
cmd_terminate(server->process); if (!cmd_terminate(server->process)) {
LOGW("Could not terminate server");
}
cmd_simple_wait(server->process, NULL); // ignore exit code
LOGD("Server terminated");
if (server->tunnel_enabled) { if (server->tunnel_enabled) {
// ignore failure // ignore failure
disable_tunnel(server); disable_tunnel(server);
} }
SDL_WaitThread(server->wait_server_thread, NULL);
} }
void void

View File

@@ -1,10 +1,8 @@
#ifndef SERVER_H #ifndef SERVER_H
#define SERVER_H #define SERVER_H
#include <stdatomic.h>
#include <stdbool.h> #include <stdbool.h>
#include <stdint.h> #include <stdint.h>
#include <SDL2/SDL_thread.h>
#include "config.h" #include "config.h"
#include "command.h" #include "command.h"
@@ -14,8 +12,6 @@
struct server { struct server {
char *serial; char *serial;
process_t process; process_t process;
SDL_Thread *wait_server_thread;
atomic_flag server_socket_closed;
socket_t server_socket; // only used if !tunnel_forward socket_t server_socket; // only used if !tunnel_forward
socket_t video_socket; socket_t video_socket;
socket_t control_socket; socket_t control_socket;
@@ -28,8 +24,6 @@ struct server {
#define SERVER_INITIALIZER { \ #define SERVER_INITIALIZER { \
.serial = NULL, \ .serial = NULL, \
.process = PROCESS_NONE, \ .process = PROCESS_NONE, \
.wait_server_thread = NULL, \
.server_socket_closed = ATOMIC_FLAG_INIT, \
.server_socket = INVALID_SOCKET, \ .server_socket = INVALID_SOCKET, \
.video_socket = INVALID_SOCKET, \ .video_socket = INVALID_SOCKET, \
.control_socket = INVALID_SOCKET, \ .control_socket = INVALID_SOCKET, \
@@ -50,7 +44,6 @@ struct server_params {
uint16_t max_fps; uint16_t max_fps;
int8_t lock_video_orientation; int8_t lock_video_orientation;
bool control; bool control;
uint16_t display_id;
}; };
// init default values // init default values

View File

@@ -7,7 +7,7 @@ buildscript {
jcenter() jcenter()
} }
dependencies { dependencies {
classpath 'com.android.tools.build:gradle:3.6.2' classpath 'com.android.tools.build:gradle:3.4.2'
// NOTE: Do not place your application dependencies here; they belong // NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files // in the individual module build.gradle files

View File

@@ -129,6 +129,11 @@ page at http://checkstyle.sourceforge.net/config.html -->
</module> </module>
<module name="IllegalInstantiation" /> <module name="IllegalInstantiation" />
<module name="InnerAssignment" /> <module name="InnerAssignment" />
<module name="MagicNumber">
<property name="severity" value="info" />
<property name="ignoreHashCodeMethod" value="true" />
<property name="ignoreAnnotation" value="true" />
</module>
<module name="MissingSwitchDefault" /> <module name="MissingSwitchDefault" />
<module name="SimplifyBooleanExpression" /> <module name="SimplifyBooleanExpression" />
<module name="SimplifyBooleanReturn" /> <module name="SimplifyBooleanReturn" />

Binary file not shown.

View File

@@ -1,5 +1,5 @@
distributionBase=GRADLE_USER_HOME distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-6.3-bin.zip distributionUrl=https\://services.gradle.org/distributions/gradle-5.5.1-all.zip
zipStoreBase=GRADLE_USER_HOME zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists zipStorePath=wrapper/dists

33
gradlew vendored
View File

@@ -125,8 +125,8 @@ if $darwin; then
GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
fi fi
# For Cygwin or MSYS, switch paths to Windows format before running java # For Cygwin, switch paths to Windows format before running java
if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then if $cygwin ; then
APP_HOME=`cygpath --path --mixed "$APP_HOME"` APP_HOME=`cygpath --path --mixed "$APP_HOME"`
CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
JAVACMD=`cygpath --unix "$JAVACMD"` JAVACMD=`cygpath --unix "$JAVACMD"`
@@ -154,19 +154,19 @@ if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then
else else
eval `echo args$i`="\"$arg\"" eval `echo args$i`="\"$arg\""
fi fi
i=`expr $i + 1` i=$((i+1))
done done
case $i in case $i in
0) set -- ;; (0) set -- ;;
1) set -- "$args0" ;; (1) set -- "$args0" ;;
2) set -- "$args0" "$args1" ;; (2) set -- "$args0" "$args1" ;;
3) set -- "$args0" "$args1" "$args2" ;; (3) set -- "$args0" "$args1" "$args2" ;;
4) set -- "$args0" "$args1" "$args2" "$args3" ;; (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
esac esac
fi fi
@@ -175,9 +175,14 @@ save () {
for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
echo " " echo " "
} }
APP_ARGS=`save "$@"` APP_ARGS=$(save "$@")
# Collect all arguments for the java command, following the shell quoting and substitution rules # Collect all arguments for the java command, following the shell quoting and substitution rules
eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong
if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then
cd "$(dirname "$0")"
fi
exec "$JAVACMD" "$@" exec "$JAVACMD" "$@"

3
gradlew.bat vendored
View File

@@ -29,9 +29,6 @@ if "%DIRNAME%" == "" set DIRNAME=.
set APP_BASE_NAME=%~n0 set APP_BASE_NAME=%~n0
set APP_HOME=%DIRNAME% set APP_HOME=%DIRNAME%
@rem Resolve any "." and ".." in APP_HOME to make it shorter.
for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"

View File

@@ -8,10 +8,11 @@ import java.nio.charset.StandardCharsets;
public class ControlMessageReader { public class ControlMessageReader {
static final int INJECT_KEYCODE_PAYLOAD_LENGTH = 9; private static final int INJECT_KEYCODE_PAYLOAD_LENGTH = 9;
static final int INJECT_TOUCH_EVENT_PAYLOAD_LENGTH = 27; private static final int INJECT_MOUSE_EVENT_PAYLOAD_LENGTH = 17;
static final int INJECT_SCROLL_EVENT_PAYLOAD_LENGTH = 20; private static final int INJECT_TOUCH_EVENT_PAYLOAD_LENGTH = 21;
static final int SET_SCREEN_POWER_MODE_PAYLOAD_LENGTH = 1; private static final int INJECT_SCROLL_EVENT_PAYLOAD_LENGTH = 20;
private static final int SET_SCREEN_POWER_MODE_PAYLOAD_LENGTH = 1;
public static final int TEXT_MAX_LENGTH = 300; public static final int TEXT_MAX_LENGTH = 300;
public static final int CLIPBOARD_TEXT_MAX_LENGTH = 4093; public static final int CLIPBOARD_TEXT_MAX_LENGTH = 4093;
@@ -121,6 +122,7 @@ public class ControlMessageReader {
return ControlMessage.createInjectText(text); return ControlMessage.createInjectText(text);
} }
@SuppressWarnings("checkstyle:MagicNumber")
private ControlMessage parseInjectTouchEvent() { private ControlMessage parseInjectTouchEvent() {
if (buffer.remaining() < INJECT_TOUCH_EVENT_PAYLOAD_LENGTH) { if (buffer.remaining() < INJECT_TOUCH_EVENT_PAYLOAD_LENGTH) {
return null; return null;
@@ -170,10 +172,12 @@ public class ControlMessageReader {
return new Position(x, y, screenWidth, screenHeight); return new Position(x, y, screenWidth, screenHeight);
} }
@SuppressWarnings("checkstyle:MagicNumber")
private static int toUnsigned(short value) { private static int toUnsigned(short value) {
return value & 0xffff; return value & 0xffff;
} }
@SuppressWarnings("checkstyle:MagicNumber")
private static int toUnsigned(byte value) { private static int toUnsigned(byte value) {
return value & 0xff; return value & 0xff;
} }

View File

@@ -47,6 +47,7 @@ public class Controller {
} }
} }
@SuppressWarnings("checkstyle:MagicNumber")
public void control() throws IOException { public void control() throws IOException {
// on start, power on the device // on start, power on the device
if (!device.isScreenOn()) { if (!device.isScreenOn()) {
@@ -75,29 +76,19 @@ public class Controller {
ControlMessage msg = connection.receiveControlMessage(); ControlMessage msg = connection.receiveControlMessage();
switch (msg.getType()) { switch (msg.getType()) {
case ControlMessage.TYPE_INJECT_KEYCODE: case ControlMessage.TYPE_INJECT_KEYCODE:
if (device.supportsInputEvents()) {
injectKeycode(msg.getAction(), msg.getKeycode(), msg.getMetaState()); injectKeycode(msg.getAction(), msg.getKeycode(), msg.getMetaState());
}
break; break;
case ControlMessage.TYPE_INJECT_TEXT: case ControlMessage.TYPE_INJECT_TEXT:
if (device.supportsInputEvents()) {
injectText(msg.getText()); injectText(msg.getText());
}
break; break;
case ControlMessage.TYPE_INJECT_TOUCH_EVENT: case ControlMessage.TYPE_INJECT_TOUCH_EVENT:
if (device.supportsInputEvents()) {
injectTouch(msg.getAction(), msg.getPointerId(), msg.getPosition(), msg.getPressure(), msg.getButtons()); injectTouch(msg.getAction(), msg.getPointerId(), msg.getPosition(), msg.getPressure(), msg.getButtons());
}
break; break;
case ControlMessage.TYPE_INJECT_SCROLL_EVENT: case ControlMessage.TYPE_INJECT_SCROLL_EVENT:
if (device.supportsInputEvents()) {
injectScroll(msg.getPosition(), msg.getHScroll(), msg.getVScroll()); injectScroll(msg.getPosition(), msg.getHScroll(), msg.getVScroll());
}
break; break;
case ControlMessage.TYPE_BACK_OR_SCREEN_ON: case ControlMessage.TYPE_BACK_OR_SCREEN_ON:
if (device.supportsInputEvents()) {
pressBackOrTurnScreenOn(); pressBackOrTurnScreenOn();
}
break; break;
case ControlMessage.TYPE_EXPAND_NOTIFICATION_PANEL: case ControlMessage.TYPE_EXPAND_NOTIFICATION_PANEL:
device.expandNotificationPanel(); device.expandNotificationPanel();
@@ -113,9 +104,7 @@ public class Controller {
device.setClipboardText(msg.getText()); device.setClipboardText(msg.getText());
break; break;
case ControlMessage.TYPE_SET_SCREEN_POWER_MODE: case ControlMessage.TYPE_SET_SCREEN_POWER_MODE:
if (device.supportsInputEvents()) {
device.setScreenPowerMode(msg.getAction()); device.setScreenPowerMode(msg.getAction());
}
break; break;
case ControlMessage.TYPE_ROTATE_DEVICE: case ControlMessage.TYPE_ROTATE_DEVICE:
device.rotateDevice(); device.rotateDevice();

View File

@@ -84,6 +84,7 @@ public final class DesktopConnection implements Closeable {
controlSocket.close(); controlSocket.close();
} }
@SuppressWarnings("checkstyle:MagicNumber")
private void send(String deviceName, int width, int height) throws IOException { private void send(String deviceName, int width, int height) throws IOException {
byte[] buffer = new byte[DEVICE_NAME_FIELD_LENGTH + 4]; byte[] buffer = new byte[DEVICE_NAME_FIELD_LENGTH + 4];

View File

@@ -1,6 +1,5 @@
package com.genymobile.scrcpy; package com.genymobile.scrcpy;
import com.genymobile.scrcpy.wrappers.InputManager;
import com.genymobile.scrcpy.wrappers.ServiceManager; import com.genymobile.scrcpy.wrappers.ServiceManager;
import com.genymobile.scrcpy.wrappers.SurfaceControl; import com.genymobile.scrcpy.wrappers.SurfaceControl;
import com.genymobile.scrcpy.wrappers.WindowManager; import com.genymobile.scrcpy.wrappers.WindowManager;
@@ -26,35 +25,9 @@ public final class Device {
private ScreenInfo screenInfo; private ScreenInfo screenInfo;
private RotationListener rotationListener; private RotationListener rotationListener;
/**
* Logical display identifier
*/
private final int displayId;
/**
* The surface flinger layer stack associated with this logical display
*/
private final int layerStack;
/**
* The FLAG_PRESENTATION from the DisplayInfo
*/
private final boolean isPresentationDisplay;
public Device(Options options) { public Device(Options options) {
displayId = options.getDisplayId(); DisplayInfo displayInfo = serviceManager.getDisplayManager().getDisplayInfo();
DisplayInfo displayInfo = serviceManager.getDisplayManager().getDisplayInfo(displayId);
if (displayInfo == null) {
int[] displayIds = serviceManager.getDisplayManager().getDisplayIds();
throw new InvalidDisplayIdException(displayId, displayIds);
}
int displayInfoFlags = displayInfo.getFlags();
screenInfo = ScreenInfo.computeScreenInfo(displayInfo, options.getCrop(), options.getMaxSize(), options.getLockedVideoOrientation()); screenInfo = ScreenInfo.computeScreenInfo(displayInfo, options.getCrop(), options.getMaxSize(), options.getLockedVideoOrientation());
layerStack = displayInfo.getLayerStack();
isPresentationDisplay = (displayInfoFlags & DisplayInfo.FLAG_PRESENTATION) != 0;
registerRotationWatcher(new IRotationWatcher.Stub() { registerRotationWatcher(new IRotationWatcher.Stub() {
@Override @Override
public void onRotationChanged(int rotation) throws RemoteException { public void onRotationChanged(int rotation) throws RemoteException {
@@ -68,24 +41,12 @@ public final class Device {
} }
} }
}); });
if ((displayInfoFlags & DisplayInfo.FLAG_SUPPORTS_PROTECTED_BUFFERS) == 0) {
Ln.w("Display doesn't have FLAG_SUPPORTS_PROTECTED_BUFFERS flag, mirroring can be restricted");
}
if (!supportsInputEvents()) {
Ln.w("Input events are not supported for displays with FLAG_PRESENTATION enabled for devices with API lower than 29");
}
} }
public synchronized ScreenInfo getScreenInfo() { public synchronized ScreenInfo getScreenInfo() {
return screenInfo; return screenInfo;
} }
public int getLayerStack() {
return layerStack;
}
public Point getPhysicalPoint(Position position) { public Point getPhysicalPoint(Position position) {
// it hides the field on purpose, to read it with a lock // it hides the field on purpose, to read it with a lock
@SuppressWarnings("checkstyle:HiddenField") @SuppressWarnings("checkstyle:HiddenField")
@@ -115,22 +76,7 @@ public final class Device {
return Build.MODEL; return Build.MODEL;
} }
public boolean supportsInputEvents() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
return true;
}
return !isPresentationDisplay;
}
public boolean injectInputEvent(InputEvent inputEvent, int mode) { public boolean injectInputEvent(InputEvent inputEvent, int mode) {
if (!supportsInputEvents()) {
throw new AssertionError("Could not inject input event if !supportsInputEvents()");
}
if (displayId != 0 && !InputManager.setDisplayId(inputEvent, displayId)) {
return false;
}
return serviceManager.getInputManager().injectInputEvent(inputEvent, mode); return serviceManager.getInputManager().injectInputEvent(inputEvent, mode);
} }
@@ -163,11 +109,9 @@ public final class Device {
} }
public void setClipboardText(String text) { public void setClipboardText(String text) {
boolean ok = serviceManager.getClipboardManager().setText(text); serviceManager.getClipboardManager().setText(text);
if (ok) {
Ln.i("Device clipboard set"); Ln.i("Device clipboard set");
} }
}
/** /**
* @param mode one of the {@code SCREEN_POWER_MODE_*} constants * @param mode one of the {@code SCREEN_POWER_MODE_*} constants
@@ -178,11 +122,9 @@ public final class Device {
Ln.e("Could not get built-in display"); Ln.e("Could not get built-in display");
return; return;
} }
boolean ok = SurfaceControl.setDisplayPowerMode(d, mode); SurfaceControl.setDisplayPowerMode(d, mode);
if (ok) {
Ln.i("Device screen turned " + (mode == Device.POWER_MODE_OFF ? "off" : "on")); Ln.i("Device screen turned " + (mode == Device.POWER_MODE_OFF ? "off" : "on"));
} }
}
/** /**
* Disable auto-rotation (if enabled), set the screen rotation and re-enable auto-rotation (if it was enabled). * Disable auto-rotation (if enabled), set the screen rotation and re-enable auto-rotation (if it was enabled).

View File

@@ -13,6 +13,7 @@ public class DeviceMessageWriter {
private final byte[] rawBuffer = new byte[MAX_EVENT_SIZE]; private final byte[] rawBuffer = new byte[MAX_EVENT_SIZE];
private final ByteBuffer buffer = ByteBuffer.wrap(rawBuffer); private final ByteBuffer buffer = ByteBuffer.wrap(rawBuffer);
@SuppressWarnings("checkstyle:MagicNumber")
public void writeTo(DeviceMessage msg, OutputStream output) throws IOException { public void writeTo(DeviceMessage msg, OutputStream output) throws IOException {
buffer.clear(); buffer.clear();
buffer.put((byte) DeviceMessage.TYPE_CLIPBOARD); buffer.put((byte) DeviceMessage.TYPE_CLIPBOARD);

View File

@@ -1,25 +1,12 @@
package com.genymobile.scrcpy; package com.genymobile.scrcpy;
public final class DisplayInfo { public final class DisplayInfo {
private final int displayId;
private final Size size; private final Size size;
private final int rotation; private final int rotation;
private final int layerStack;
private final int flags;
public static final int FLAG_SUPPORTS_PROTECTED_BUFFERS = 0x00000001; public DisplayInfo(Size size, int rotation) {
public static final int FLAG_PRESENTATION = 0x00000008;
public DisplayInfo(int displayId, Size size, int rotation, int layerStack, int flags) {
this.displayId = displayId;
this.size = size; this.size = size;
this.rotation = rotation; this.rotation = rotation;
this.layerStack = layerStack;
this.flags = flags;
}
public int getDisplayId() {
return displayId;
} }
public Size getSize() { public Size getSize() {
@@ -29,13 +16,5 @@ public final class DisplayInfo {
public int getRotation() { public int getRotation() {
return rotation; return rotation;
} }
public int getLayerStack() {
return layerStack;
}
public int getFlags() {
return flags;
}
} }

View File

@@ -1,21 +0,0 @@
package com.genymobile.scrcpy;
public class InvalidDisplayIdException extends RuntimeException {
private final int displayId;
private final int[] availableDisplayIds;
public InvalidDisplayIdException(int displayId, int[] availableDisplayIds) {
super("There is no display having id " + displayId);
this.displayId = displayId;
this.availableDisplayIds = availableDisplayIds;
}
public int getDisplayId() {
return displayId;
}
public int[] getAvailableDisplayIds() {
return availableDisplayIds;
}
}

View File

@@ -11,7 +11,6 @@ public class Options {
private Rect crop; private Rect crop;
private boolean sendFrameMeta; // send PTS so that the client may record properly private boolean sendFrameMeta; // send PTS so that the client may record properly
private boolean control; private boolean control;
private int displayId;
public int getMaxSize() { public int getMaxSize() {
return maxSize; return maxSize;
@@ -76,12 +75,4 @@ public class Options {
public void setControl(boolean control) { public void setControl(boolean control) {
this.control = control; this.control = control;
} }
public int getDisplayId() {
return displayId;
}
public void setDisplayId(int displayId) {
this.displayId = displayId;
}
} }

View File

@@ -71,12 +71,10 @@ public class ScreenEncoder implements Device.RotationListener {
// does not include the locked video orientation // does not include the locked video orientation
Rect unlockedVideoRect = screenInfo.getUnlockedVideoSize().toRect(); Rect unlockedVideoRect = screenInfo.getUnlockedVideoSize().toRect();
int videoRotation = screenInfo.getVideoRotation(); int videoRotation = screenInfo.getVideoRotation();
int layerStack = device.getLayerStack();
setSize(format, videoRect.width(), videoRect.height()); setSize(format, videoRect.width(), videoRect.height());
configure(codec, format); configure(codec, format);
Surface surface = codec.createInputSurface(); Surface surface = codec.createInputSurface();
setDisplaySurface(display, surface, videoRotation, contentRect, unlockedVideoRect, layerStack); setDisplaySurface(display, surface, videoRotation, contentRect, unlockedVideoRect);
codec.start(); codec.start();
try { try {
alive = encode(codec, fd); alive = encode(codec, fd);
@@ -147,6 +145,7 @@ public class ScreenEncoder implements Device.RotationListener {
return MediaCodec.createEncoderByType("video/avc"); return MediaCodec.createEncoderByType("video/avc");
} }
@SuppressWarnings("checkstyle:MagicNumber")
private static MediaFormat createFormat(int bitRate, int maxFps, int iFrameInterval) { private static MediaFormat createFormat(int bitRate, int maxFps, int iFrameInterval) {
MediaFormat format = new MediaFormat(); MediaFormat format = new MediaFormat();
format.setString(MediaFormat.KEY_MIME, "video/avc"); format.setString(MediaFormat.KEY_MIME, "video/avc");
@@ -179,12 +178,12 @@ public class ScreenEncoder implements Device.RotationListener {
format.setInteger(MediaFormat.KEY_HEIGHT, height); format.setInteger(MediaFormat.KEY_HEIGHT, height);
} }
private static void setDisplaySurface(IBinder display, Surface surface, int orientation, Rect deviceRect, Rect displayRect, int layerStack) { private static void setDisplaySurface(IBinder display, Surface surface, int orientation, Rect deviceRect, Rect displayRect) {
SurfaceControl.openTransaction(); SurfaceControl.openTransaction();
try { try {
SurfaceControl.setDisplaySurface(display, surface); SurfaceControl.setDisplaySurface(display, surface);
SurfaceControl.setDisplayProjection(display, orientation, deviceRect, displayRect); SurfaceControl.setDisplayProjection(display, orientation, deviceRect, displayRect);
SurfaceControl.setDisplayLayerStack(display, layerStack); SurfaceControl.setDisplayLayerStack(display, 0);
} finally { } finally {
SurfaceControl.closeTransaction(); SurfaceControl.closeTransaction();
} }

View File

@@ -104,6 +104,7 @@ public final class ScreenInfo {
return rect.width() + ":" + rect.height() + ":" + rect.left + ":" + rect.top; return rect.width() + ":" + rect.height() + ":" + rect.left + ":" + rect.top;
} }
@SuppressWarnings("checkstyle:MagicNumber")
private static Size computeVideoSize(int w, int h, int maxSize) { private static Size computeVideoSize(int w, int h, int maxSize) {
// Compute the video size and the padding of the content inside this video. // Compute the video size and the padding of the content inside this video.
// Principle: // Principle:

View File

@@ -16,7 +16,6 @@ public final class Server {
} }
private static void scrcpy(Options options) throws IOException { private static void scrcpy(Options options) throws IOException {
Ln.i("Device: " + Build.MANUFACTURER + " " + Build.MODEL + " (Android " + Build.VERSION.RELEASE + ")");
final Device device = new Device(options); final Device device = new Device(options);
boolean tunnelForward = options.isTunnelForward(); boolean tunnelForward = options.isTunnelForward();
try (DesktopConnection connection = DesktopConnection.open(device, tunnelForward)) { try (DesktopConnection connection = DesktopConnection.open(device, tunnelForward)) {
@@ -69,6 +68,7 @@ public final class Server {
}).start(); }).start();
} }
@SuppressWarnings("checkstyle:MagicNumber")
private static Options createOptions(String... args) { private static Options createOptions(String... args) {
if (args.length < 1) { if (args.length < 1) {
throw new IllegalArgumentException("Missing client version"); throw new IllegalArgumentException("Missing client version");
@@ -80,8 +80,8 @@ public final class Server {
"The server version (" + BuildConfig.VERSION_NAME + ") does not match the client " + "(" + clientVersion + ")"); "The server version (" + BuildConfig.VERSION_NAME + ") does not match the client " + "(" + clientVersion + ")");
} }
if (args.length != 10) { if (args.length != 9) {
throw new IllegalArgumentException("Expecting 10 parameters"); throw new IllegalArgumentException("Expecting 9 parameters");
} }
Options options = new Options(); Options options = new Options();
@@ -111,12 +111,10 @@ public final class Server {
boolean control = Boolean.parseBoolean(args[8]); boolean control = Boolean.parseBoolean(args[8]);
options.setControl(control); options.setControl(control);
int displayId = Integer.parseInt(args[9]);
options.setDisplayId(displayId);
return options; return options;
} }
@SuppressWarnings("checkstyle:MagicNumber")
private static Rect parseCrop(String crop) { private static Rect parseCrop(String crop) {
if ("-".equals(crop)) { if ("-".equals(crop)) {
return null; return null;
@@ -141,6 +139,7 @@ public final class Server {
} }
} }
@SuppressWarnings("checkstyle:MagicNumber")
private static void suggestFix(Throwable e) { private static void suggestFix(Throwable e) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
if (e instanceof MediaCodec.CodecException) { if (e instanceof MediaCodec.CodecException) {
@@ -152,16 +151,6 @@ public final class Server {
} }
} }
} }
if (e instanceof InvalidDisplayIdException) {
InvalidDisplayIdException idie = (InvalidDisplayIdException) e;
int[] displayIds = idie.getAvailableDisplayIds();
if (displayIds != null && displayIds.length > 0) {
Ln.e("Try to use one of the available display ids:");
for (int id : displayIds) {
Ln.e(" scrcpy --display " + id);
}
}
}
} }
public static void main(String... args) throws Exception { public static void main(String... args) throws Exception {

View File

@@ -5,6 +5,7 @@ public final class StringUtils {
// not instantiable // not instantiable
} }
@SuppressWarnings("checkstyle:MagicNumber")
public static int getUtf8TruncationIndex(byte[] utf8, int maxLength) { public static int getUtf8TruncationIndex(byte[] utf8, int maxLength) {
int len = utf8.length; int len = utf8.length;
if (len <= maxLength) { if (len <= maxLength) {

View File

@@ -74,15 +74,13 @@ public class ClipboardManager {
} }
} }
public boolean setText(CharSequence text) { public void setText(CharSequence text) {
try { try {
Method method = getSetPrimaryClipMethod(); Method method = getSetPrimaryClipMethod();
ClipData clipData = ClipData.newPlainText(null, text); ClipData clipData = ClipData.newPlainText(null, text);
setPrimaryClip(method, manager, clipData); setPrimaryClip(method, manager, clipData);
return true;
} catch (InvocationTargetException | IllegalAccessException | NoSuchMethodException e) { } catch (InvocationTargetException | IllegalAccessException | NoSuchMethodException e) {
Ln.e("Could not invoke method", e); Ln.e("Could not invoke method", e);
return false;
} }
} }
} }

View File

@@ -12,28 +12,15 @@ public final class DisplayManager {
this.manager = manager; this.manager = manager;
} }
public DisplayInfo getDisplayInfo(int displayId) { public DisplayInfo getDisplayInfo() {
try { try {
Object displayInfo = manager.getClass().getMethod("getDisplayInfo", int.class).invoke(manager, displayId); Object displayInfo = manager.getClass().getMethod("getDisplayInfo", int.class).invoke(manager, 0);
if (displayInfo == null) {
return null;
}
Class<?> cls = displayInfo.getClass(); Class<?> cls = displayInfo.getClass();
// width and height already take the rotation into account // width and height already take the rotation into account
int width = cls.getDeclaredField("logicalWidth").getInt(displayInfo); int width = cls.getDeclaredField("logicalWidth").getInt(displayInfo);
int height = cls.getDeclaredField("logicalHeight").getInt(displayInfo); int height = cls.getDeclaredField("logicalHeight").getInt(displayInfo);
int rotation = cls.getDeclaredField("rotation").getInt(displayInfo); int rotation = cls.getDeclaredField("rotation").getInt(displayInfo);
int layerStack = cls.getDeclaredField("layerStack").getInt(displayInfo); return new DisplayInfo(new Size(width, height), rotation);
int flags = cls.getDeclaredField("flags").getInt(displayInfo);
return new DisplayInfo(displayId, new Size(width, height), rotation, layerStack, flags);
} catch (Exception e) {
throw new AssertionError(e);
}
}
public int[] getDisplayIds() {
try {
return (int[]) manager.getClass().getMethod("getDisplayIds").invoke(manager);
} catch (Exception e) { } catch (Exception e) {
throw new AssertionError(e); throw new AssertionError(e);
} }

View File

@@ -17,8 +17,6 @@ public final class InputManager {
private final IInterface manager; private final IInterface manager;
private Method injectInputEventMethod; private Method injectInputEventMethod;
private static Method setDisplayIdMethod;
public InputManager(IInterface manager) { public InputManager(IInterface manager) {
this.manager = manager; this.manager = manager;
} }
@@ -39,23 +37,4 @@ public final class InputManager {
return false; return false;
} }
} }
private static Method getSetDisplayIdMethod() throws NoSuchMethodException {
if (setDisplayIdMethod == null) {
setDisplayIdMethod = InputEvent.class.getMethod("setDisplayId", int.class);
}
return setDisplayIdMethod;
}
public static boolean setDisplayId(InputEvent inputEvent, int displayId) {
try {
Method method = getSetDisplayIdMethod();
method.invoke(inputEvent, displayId);
return true;
} catch (InvocationTargetException | IllegalAccessException | NoSuchMethodException e) {
// just a warning, it might happen on old devices
Ln.w("Cannot associate a display id to the input event");
return false;
}
}
} }

View File

@@ -121,14 +121,12 @@ public final class SurfaceControl {
return setDisplayPowerModeMethod; return setDisplayPowerModeMethod;
} }
public static boolean setDisplayPowerMode(IBinder displayToken, int mode) { public static void setDisplayPowerMode(IBinder displayToken, int mode) {
try { try {
Method method = getSetDisplayPowerModeMethod(); Method method = getSetDisplayPowerModeMethod();
method.invoke(null, displayToken, mode); method.invoke(null, displayToken, mode);
return true;
} catch (InvocationTargetException | IllegalAccessException | NoSuchMethodException e) { } catch (InvocationTargetException | IllegalAccessException | NoSuchMethodException e) {
Ln.e("Could not invoke method", e); Ln.e("Could not invoke method", e);
return false;
} }
} }

View File

@@ -28,9 +28,6 @@ public class ControlMessageReaderTest {
dos.writeInt(KeyEvent.META_CTRL_ON); dos.writeInt(KeyEvent.META_CTRL_ON);
byte[] packet = bos.toByteArray(); byte[] packet = bos.toByteArray();
// The message type (1 byte) does not count
Assert.assertEquals(ControlMessageReader.INJECT_KEYCODE_PAYLOAD_LENGTH, packet.length - 1);
reader.readFrom(new ByteArrayInputStream(packet)); reader.readFrom(new ByteArrayInputStream(packet));
ControlMessage event = reader.next(); ControlMessage event = reader.next();
@@ -80,6 +77,7 @@ public class ControlMessageReaderTest {
} }
@Test @Test
@SuppressWarnings("checkstyle:MagicNumber")
public void testParseTouchEvent() throws IOException { public void testParseTouchEvent() throws IOException {
ControlMessageReader reader = new ControlMessageReader(); ControlMessageReader reader = new ControlMessageReader();
@@ -97,9 +95,6 @@ public class ControlMessageReaderTest {
byte[] packet = bos.toByteArray(); byte[] packet = bos.toByteArray();
// The message type (1 byte) does not count
Assert.assertEquals(ControlMessageReader.INJECT_TOUCH_EVENT_PAYLOAD_LENGTH, packet.length - 1);
reader.readFrom(new ByteArrayInputStream(packet)); reader.readFrom(new ByteArrayInputStream(packet));
ControlMessage event = reader.next(); ControlMessage event = reader.next();
@@ -115,6 +110,7 @@ public class ControlMessageReaderTest {
} }
@Test @Test
@SuppressWarnings("checkstyle:MagicNumber")
public void testParseScrollEvent() throws IOException { public void testParseScrollEvent() throws IOException {
ControlMessageReader reader = new ControlMessageReader(); ControlMessageReader reader = new ControlMessageReader();
@@ -130,9 +126,6 @@ public class ControlMessageReaderTest {
byte[] packet = bos.toByteArray(); byte[] packet = bos.toByteArray();
// The message type (1 byte) does not count
Assert.assertEquals(ControlMessageReader.INJECT_SCROLL_EVENT_PAYLOAD_LENGTH, packet.length - 1);
reader.readFrom(new ByteArrayInputStream(packet)); reader.readFrom(new ByteArrayInputStream(packet));
ControlMessage event = reader.next(); ControlMessage event = reader.next();
@@ -240,9 +233,6 @@ public class ControlMessageReaderTest {
byte[] packet = bos.toByteArray(); byte[] packet = bos.toByteArray();
// The message type (1 byte) does not count
Assert.assertEquals(ControlMessageReader.SET_SCREEN_POWER_MODE_PAYLOAD_LENGTH, packet.length - 1);
reader.readFrom(new ByteArrayInputStream(packet)); reader.readFrom(new ByteArrayInputStream(packet));
ControlMessage event = reader.next(); ControlMessage event = reader.next();

View File

@@ -8,6 +8,7 @@ import java.nio.charset.StandardCharsets;
public class StringUtilsTest { public class StringUtilsTest {
@Test @Test
@SuppressWarnings("checkstyle:MagicNumber")
public void testUtf8Truncate() { public void testUtf8Truncate() {
String s = "aÉbÔc"; String s = "aÉbÔc";
byte[] utf8 = s.getBytes(StandardCharsets.UTF_8); byte[] utf8 = s.getBytes(StandardCharsets.UTF_8);