Compare commits
4 Commits
logical_si
...
mipmaps
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
91c5e7e626 | ||
|
|
7c851cdb2d | ||
|
|
08a175b7f1 | ||
|
|
9f029da2f9 |
4
FAQ.md
4
FAQ.md
@@ -142,13 +142,13 @@ screen, then you might get poor quality, especially visible on text (see [#40]).
|
|||||||
To improve downscaling quality, trilinear filtering is enabled automatically
|
To improve downscaling quality, trilinear filtering is enabled automatically
|
||||||
if the renderer is OpenGL and if it supports mipmapping.
|
if the renderer is OpenGL and if it supports mipmapping.
|
||||||
|
|
||||||
On Windows, you might want to force OpenGL:
|
On Windows or macOS, you might need to force OpenGL:
|
||||||
|
|
||||||
```
|
```
|
||||||
scrcpy --render-driver=opengl
|
scrcpy --render-driver=opengl
|
||||||
```
|
```
|
||||||
|
|
||||||
You may also need to configure the [scaling behavior]:
|
On Windows, you may also need to configure the [scaling behavior].
|
||||||
|
|
||||||
> `scrcpy.exe` > Properties > Compatibility > Change high DPI settings >
|
> `scrcpy.exe` > Properties > Compatibility > Change high DPI settings >
|
||||||
> Override high DPI scaling behavior > Scaling performed by: _Application_.
|
> Override high DPI scaling behavior > Scaling performed by: _Application_.
|
||||||
|
|||||||
@@ -363,7 +363,7 @@ Note that _scrcpy_ manages 3 different rotations:
|
|||||||
current running app may refuse, if it does support the requested
|
current running app may refuse, if it does support the requested
|
||||||
orientation).
|
orientation).
|
||||||
- `--lock-video-orientation` changes the mirroring orientation (the orientation
|
- `--lock-video-orientation` changes the mirroring orientation (the orientation
|
||||||
of the video sent from the device to the computer). This affects the
|
of the video sent from the device to the computer. This affects the
|
||||||
recording.
|
recording.
|
||||||
- `--rotation` (or `Ctrl`+`←`/`Ctrl`+`→`) rotates only the window content. This
|
- `--rotation` (or `Ctrl`+`←`/`Ctrl`+`→`) rotates only the window content. This
|
||||||
affects only the display, not the recording.
|
affects only the display, not the recording.
|
||||||
|
|||||||
@@ -45,9 +45,8 @@ control_msg_serialize(const struct control_msg *msg, unsigned char *buf) {
|
|||||||
buffer_write32be(&buf[6], msg->inject_keycode.metastate);
|
buffer_write32be(&buf[6], msg->inject_keycode.metastate);
|
||||||
return 10;
|
return 10;
|
||||||
case CONTROL_MSG_TYPE_INJECT_TEXT: {
|
case CONTROL_MSG_TYPE_INJECT_TEXT: {
|
||||||
size_t len =
|
size_t len = write_string(msg->inject_text.text,
|
||||||
write_string(msg->inject_text.text,
|
CONTROL_MSG_TEXT_MAX_LENGTH, &buf[1]);
|
||||||
CONTROL_MSG_INJECT_TEXT_MAX_LENGTH, &buf[1]);
|
|
||||||
return 1 + len;
|
return 1 + len;
|
||||||
}
|
}
|
||||||
case CONTROL_MSG_TYPE_INJECT_TOUCH_EVENT:
|
case CONTROL_MSG_TYPE_INJECT_TOUCH_EVENT:
|
||||||
|
|||||||
@@ -10,7 +10,7 @@
|
|||||||
#include "android/keycodes.h"
|
#include "android/keycodes.h"
|
||||||
#include "common.h"
|
#include "common.h"
|
||||||
|
|
||||||
#define CONTROL_MSG_INJECT_TEXT_MAX_LENGTH 300
|
#define CONTROL_MSG_TEXT_MAX_LENGTH 300
|
||||||
#define CONTROL_MSG_CLIPBOARD_TEXT_MAX_LENGTH 4093
|
#define CONTROL_MSG_CLIPBOARD_TEXT_MAX_LENGTH 4093
|
||||||
#define CONTROL_MSG_SERIALIZED_MAX_SIZE \
|
#define CONTROL_MSG_SERIALIZED_MAX_SIZE \
|
||||||
(3 + CONTROL_MSG_CLIPBOARD_TEXT_MAX_LENGTH)
|
(3 + CONTROL_MSG_CLIPBOARD_TEXT_MAX_LENGTH)
|
||||||
|
|||||||
@@ -7,6 +7,33 @@
|
|||||||
#include "util/lock.h"
|
#include "util/lock.h"
|
||||||
#include "util/log.h"
|
#include "util/log.h"
|
||||||
|
|
||||||
|
// Convert window coordinates (as provided by SDL_GetMouseState() to renderer
|
||||||
|
// coordinates (as provided in SDL mouse events)
|
||||||
|
//
|
||||||
|
// See my question:
|
||||||
|
// <https://stackoverflow.com/questions/49111054/how-to-get-mouse-position-on-mouse-wheel-event>
|
||||||
|
static void
|
||||||
|
convert_to_renderer_coordinates(SDL_Renderer *renderer, int *x, int *y) {
|
||||||
|
SDL_Rect viewport;
|
||||||
|
float scale_x, scale_y;
|
||||||
|
SDL_RenderGetViewport(renderer, &viewport);
|
||||||
|
SDL_RenderGetScale(renderer, &scale_x, &scale_y);
|
||||||
|
*x = (int) (*x / scale_x) - viewport.x;
|
||||||
|
*y = (int) (*y / scale_y) - viewport.y;
|
||||||
|
}
|
||||||
|
|
||||||
|
static struct point
|
||||||
|
get_mouse_point(struct screen *screen) {
|
||||||
|
int x;
|
||||||
|
int y;
|
||||||
|
SDL_GetMouseState(&x, &y);
|
||||||
|
convert_to_renderer_coordinates(screen->renderer, &x, &y);
|
||||||
|
return (struct point) {
|
||||||
|
.x = x,
|
||||||
|
.y = y,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
static const int ACTION_DOWN = 1;
|
static const int ACTION_DOWN = 1;
|
||||||
static const int ACTION_UP = 1 << 1;
|
static const int ACTION_UP = 1 << 1;
|
||||||
|
|
||||||
@@ -415,6 +442,36 @@ input_manager_process_key(struct input_manager *im,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static struct point
|
||||||
|
rotate_position(struct screen *screen, int32_t x, int32_t y) {
|
||||||
|
unsigned rotation = screen->rotation;
|
||||||
|
assert(rotation < 4);
|
||||||
|
|
||||||
|
int32_t w = screen->content_size.width;
|
||||||
|
int32_t h = screen->content_size.height;
|
||||||
|
struct point result;
|
||||||
|
switch (rotation) {
|
||||||
|
case 0:
|
||||||
|
result.x = x;
|
||||||
|
result.y = y;
|
||||||
|
break;
|
||||||
|
case 1:
|
||||||
|
result.x = h - y;
|
||||||
|
result.y = x;
|
||||||
|
break;
|
||||||
|
case 2:
|
||||||
|
result.x = w - x;
|
||||||
|
result.y = h - y;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
assert(rotation == 3);
|
||||||
|
result.x = y;
|
||||||
|
result.y = w - x;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
static bool
|
static bool
|
||||||
convert_mouse_motion(const SDL_MouseMotionEvent *from, struct screen *screen,
|
convert_mouse_motion(const SDL_MouseMotionEvent *from, struct screen *screen,
|
||||||
struct control_msg *to) {
|
struct control_msg *to) {
|
||||||
@@ -423,7 +480,7 @@ convert_mouse_motion(const SDL_MouseMotionEvent *from, struct screen *screen,
|
|||||||
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 = screen->frame_size;
|
to->inject_touch_event.position.screen_size = screen->frame_size;
|
||||||
to->inject_touch_event.position.point =
|
to->inject_touch_event.position.point =
|
||||||
screen_convert_to_frame_coords(screen, from->x, from->y);
|
rotate_position(screen, from->x, 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);
|
||||||
|
|
||||||
@@ -458,19 +515,14 @@ convert_touch(const SDL_TouchFingerEvent *from, struct screen *screen,
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
struct size frame_size = screen->frame_size;
|
||||||
|
|
||||||
to->inject_touch_event.pointer_id = from->fingerId;
|
to->inject_touch_event.pointer_id = from->fingerId;
|
||||||
to->inject_touch_event.position.screen_size = screen->frame_size;
|
to->inject_touch_event.position.screen_size = frame_size;
|
||||||
|
|
||||||
int ww;
|
|
||||||
int wh;
|
|
||||||
SDL_GL_GetDrawableSize(screen->window, &ww, &wh);
|
|
||||||
|
|
||||||
// SDL touch event coordinates are normalized in the range [0; 1]
|
// SDL touch event coordinates are normalized in the range [0; 1]
|
||||||
int32_t x = from->x * ww;
|
float x = from->x * frame_size.width;
|
||||||
int32_t y = from->y * wh;
|
float y = from->y * frame_size.height;
|
||||||
to->inject_touch_event.position.point =
|
to->inject_touch_event.position.point = rotate_position(screen, x, y);
|
||||||
screen_convert_to_frame_coords(screen, x, y);
|
|
||||||
|
|
||||||
to->inject_touch_event.pressure = from->pressure;
|
to->inject_touch_event.pressure = from->pressure;
|
||||||
to->inject_touch_event.buttons = 0;
|
to->inject_touch_event.buttons = 0;
|
||||||
return true;
|
return true;
|
||||||
@@ -487,6 +539,13 @@ input_manager_process_touch(struct input_manager *im,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static bool
|
||||||
|
is_outside_device_screen(struct input_manager *im, int x, int y)
|
||||||
|
{
|
||||||
|
return x < 0 || x >= im->screen->content_size.width ||
|
||||||
|
y < 0 || y >= im->screen->content_size.height;
|
||||||
|
}
|
||||||
|
|
||||||
static bool
|
static bool
|
||||||
convert_mouse_button(const SDL_MouseButtonEvent *from, struct screen *screen,
|
convert_mouse_button(const SDL_MouseButtonEvent *from, struct screen *screen,
|
||||||
struct control_msg *to) {
|
struct control_msg *to) {
|
||||||
@@ -499,7 +558,7 @@ convert_mouse_button(const SDL_MouseButtonEvent *from, struct screen *screen,
|
|||||||
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 = screen->frame_size;
|
to->inject_touch_event.position.screen_size = screen->frame_size;
|
||||||
to->inject_touch_event.position.point =
|
to->inject_touch_event.position.point =
|
||||||
screen_convert_to_frame_coords(screen, from->x, from->y);
|
rotate_position(screen, from->x, 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));
|
||||||
@@ -524,14 +583,10 @@ input_manager_process_mouse_button(struct input_manager *im,
|
|||||||
action_home(im->controller, ACTION_DOWN | ACTION_UP);
|
action_home(im->controller, ACTION_DOWN | ACTION_UP);
|
||||||
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 && event->clicks == 2) {
|
||||||
int x = event->x;
|
bool outside =
|
||||||
int y = event->y;
|
is_outside_device_screen(im, event->x, event->y);
|
||||||
SDL_Rect *r = &im->screen->rect;
|
|
||||||
bool outside = x < r->x || x >= r->x + r->w
|
|
||||||
|| y < r->y || y >= r->y + r->h;
|
|
||||||
if (outside) {
|
if (outside) {
|
||||||
screen_resize_to_fit(im->screen);
|
screen_resize_to_fit(im->screen);
|
||||||
return;
|
return;
|
||||||
@@ -555,15 +610,9 @@ input_manager_process_mouse_button(struct input_manager *im,
|
|||||||
static bool
|
static bool
|
||||||
convert_mouse_wheel(const SDL_MouseWheelEvent *from, struct screen *screen,
|
convert_mouse_wheel(const SDL_MouseWheelEvent *from, struct screen *screen,
|
||||||
struct control_msg *to) {
|
struct control_msg *to) {
|
||||||
|
|
||||||
// mouse_x and mouse_y are expressed in pixels relative to the window
|
|
||||||
int mouse_x;
|
|
||||||
int mouse_y;
|
|
||||||
SDL_GetMouseState(&mouse_x, &mouse_y);
|
|
||||||
|
|
||||||
struct position position = {
|
struct position position = {
|
||||||
.screen_size = screen->frame_size,
|
.screen_size = screen->frame_size,
|
||||||
.point = screen_convert_to_frame_coords(screen, mouse_x, mouse_y),
|
.point = get_mouse_point(screen),
|
||||||
};
|
};
|
||||||
|
|
||||||
to->type = CONTROL_MSG_TYPE_INJECT_SCROLL_EVENT;
|
to->type = CONTROL_MSG_TYPE_INJECT_SCROLL_EVENT;
|
||||||
|
|||||||
174
app/src/screen.c
174
app/src/screen.c
@@ -87,26 +87,6 @@ get_preferred_display_bounds(struct size *bounds) {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Indicate if the width should be kept on computing the optimal rectangle size
|
|
||||||
// to display the content in the window.
|
|
||||||
// Return true if the width should be kept, false if the height should be kept.
|
|
||||||
static bool
|
|
||||||
should_keep_width(struct size window_size, struct size content_size) {
|
|
||||||
// 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 cw = content_size.width;
|
|
||||||
uint32_t ch = content_size.height;
|
|
||||||
|
|
||||||
// To avoid keeping alternatively width and height on successive resizes
|
|
||||||
// due to rounding to integer, always prefer height (arbitrarily) if in the
|
|
||||||
// error range.
|
|
||||||
|
|
||||||
// err = ceil of content aspect ratio
|
|
||||||
unsigned err = (cw + ch - 1) / ch;
|
|
||||||
return cw * wh > ch * (ww + err);
|
|
||||||
}
|
|
||||||
|
|
||||||
// return the optimal size of the window, with the following constraints:
|
// return the optimal size of the window, with the following constraints:
|
||||||
// - it attempts to keep at least one dimension of the current_size (i.e. it
|
// - it attempts to keep at least one dimension of the current_size (i.e. it
|
||||||
// crops the black borders)
|
// crops the black borders)
|
||||||
@@ -119,33 +99,33 @@ get_optimal_size(struct size current_size, struct size content_size) {
|
|||||||
return current_size;
|
return current_size;
|
||||||
}
|
}
|
||||||
|
|
||||||
struct size window_size;
|
|
||||||
|
|
||||||
struct size display_size;
|
struct size display_size;
|
||||||
|
// 32 bits because we need to multiply two 16 bits values
|
||||||
|
uint32_t w;
|
||||||
|
uint32_t h;
|
||||||
|
|
||||||
if (!get_preferred_display_bounds(&display_size)) {
|
if (!get_preferred_display_bounds(&display_size)) {
|
||||||
// could not get display bounds, do not constraint the size
|
// could not get display bounds, do not constraint the size
|
||||||
window_size.width = current_size.width;
|
w = current_size.width;
|
||||||
window_size.height = current_size.height;
|
h = current_size.height;
|
||||||
} else {
|
} else {
|
||||||
window_size.width = MIN(current_size.width, display_size.width);
|
w = MIN(current_size.width, display_size.width);
|
||||||
window_size.height = MIN(current_size.height, display_size.height);
|
h = MIN(current_size.height, display_size.height);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool keep_width = should_keep_width(window_size, content_size);
|
bool keep_width = content_size.width * h > content_size.height * w;
|
||||||
if (keep_width) {
|
if (keep_width) {
|
||||||
// remove black borders on top and bottom
|
// remove black borders on top and bottom
|
||||||
window_size.height = content_size.height * window_size.width
|
h = content_size.height * w / content_size.width;
|
||||||
/ content_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)
|
||||||
window_size.width = content_size.width * window_size.height
|
w = content_size.width * h / content_size.height;
|
||||||
/ content_size.height;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// width and height must fit into 16 bits
|
// w and h must fit into 16 bits
|
||||||
assert(window_size.width < 0x10000 && window_size.height < 0x10000);
|
assert(w < 0x10000 && h < 0x10000);
|
||||||
return window_size;
|
return (struct size) {w, h};
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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
|
||||||
@@ -182,34 +162,6 @@ get_initial_optimal_size(struct size content_size, uint16_t req_width,
|
|||||||
return window_size;
|
return window_size;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void
|
|
||||||
update_content_rect(struct screen *screen) {
|
|
||||||
int w;
|
|
||||||
int h;
|
|
||||||
SDL_GL_GetDrawableSize(screen->window, &w, &h);
|
|
||||||
|
|
||||||
struct size window_size = {w, h};
|
|
||||||
uint16_t ww = window_size.width;
|
|
||||||
uint16_t wh = window_size.height;
|
|
||||||
uint16_t cw = screen->content_size.width;
|
|
||||||
uint16_t ch = screen->content_size.height;
|
|
||||||
|
|
||||||
SDL_Rect *rect = &screen->rect;
|
|
||||||
|
|
||||||
bool keep_width = should_keep_width(window_size, screen->content_size);
|
|
||||||
if (keep_width) {
|
|
||||||
rect->x = 0;
|
|
||||||
rect->w = ww;
|
|
||||||
rect->h = ww * ch / cw;
|
|
||||||
rect->y = (wh - rect->h) / 2;
|
|
||||||
} else {
|
|
||||||
rect->y = 0;
|
|
||||||
rect->h = wh;
|
|
||||||
rect->w = wh * cw / ch;
|
|
||||||
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;
|
||||||
@@ -299,6 +251,13 @@ screen_init_rendering(struct screen *screen, const char *window_title,
|
|||||||
const char *renderer_name = r ? NULL : renderer_info.name;
|
const char *renderer_name = r ? NULL : renderer_info.name;
|
||||||
LOGI("Renderer: %s", renderer_name ? renderer_name : "(unknown)");
|
LOGI("Renderer: %s", renderer_name ? renderer_name : "(unknown)");
|
||||||
|
|
||||||
|
if (SDL_RenderSetLogicalSize(screen->renderer, content_size.width,
|
||||||
|
content_size.height)) {
|
||||||
|
LOGE("Could not set renderer logical size: %s", SDL_GetError());
|
||||||
|
screen_destroy(screen);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
// stats with "opengl"
|
// stats with "opengl"
|
||||||
screen->use_opengl = renderer_name && !strncmp(renderer_name, "opengl", 6);
|
screen->use_opengl = renderer_name && !strncmp(renderer_name, "opengl", 6);
|
||||||
if (screen->use_opengl) {
|
if (screen->use_opengl) {
|
||||||
@@ -342,8 +301,6 @@ screen_init_rendering(struct screen *screen, const char *window_title,
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
update_content_rect(screen);
|
|
||||||
|
|
||||||
screen->windowed_window_size = window_size;
|
screen->windowed_window_size = window_size;
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
@@ -378,6 +335,13 @@ screen_set_rotation(struct screen *screen, unsigned rotation) {
|
|||||||
struct size new_content_size =
|
struct size new_content_size =
|
||||||
get_rotated_size(screen->frame_size, rotation);
|
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 windowed_size = get_windowed_window_size(screen);
|
||||||
struct size target_size = {
|
struct size target_size = {
|
||||||
.width = (uint32_t) windowed_size.width * new_content_size.width
|
.width = (uint32_t) windowed_size.width * new_content_size.width
|
||||||
@@ -392,7 +356,6 @@ screen_set_rotation(struct screen *screen, unsigned rotation) {
|
|||||||
screen->rotation = rotation;
|
screen->rotation = rotation;
|
||||||
LOGI("Display rotation set to %u", rotation);
|
LOGI("Display rotation set to %u", rotation);
|
||||||
|
|
||||||
update_content_rect(screen);
|
|
||||||
screen_render(screen);
|
screen_render(screen);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -401,11 +364,18 @@ 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 =
|
||||||
|
get_rotated_size(new_frame_size, screen->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 false;
|
||||||
|
}
|
||||||
|
|
||||||
// frame dimension changed, destroy texture
|
// frame dimension changed, destroy texture
|
||||||
SDL_DestroyTexture(screen->texture);
|
SDL_DestroyTexture(screen->texture);
|
||||||
|
|
||||||
struct size new_content_size =
|
|
||||||
get_rotated_size(new_frame_size, screen->rotation);
|
|
||||||
struct size content_size = screen->content_size;
|
struct size content_size = screen->content_size;
|
||||||
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 = {
|
||||||
@@ -419,7 +389,6 @@ prepare_for_frame(struct screen *screen, struct size new_frame_size) {
|
|||||||
|
|
||||||
screen->frame_size = new_frame_size;
|
screen->frame_size = new_frame_size;
|
||||||
screen->content_size = new_content_size;
|
screen->content_size = new_content_size;
|
||||||
update_content_rect(screen);
|
|
||||||
|
|
||||||
LOGI("New texture: %" PRIu16 "x%" PRIu16,
|
LOGI("New texture: %" PRIu16 "x%" PRIu16,
|
||||||
screen->frame_size.width, screen->frame_size.height);
|
screen->frame_size.width, screen->frame_size.height);
|
||||||
@@ -465,17 +434,11 @@ screen_update_frame(struct screen *screen, struct video_buffer *vb) {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
|
||||||
screen_window_resized(struct screen *screen) {
|
|
||||||
update_content_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);
|
||||||
if (screen->rotation == 0) {
|
if (screen->rotation == 0) {
|
||||||
SDL_RenderCopy(screen->renderer, screen->texture, NULL, &screen->rect);
|
SDL_RenderCopy(screen->renderer, screen->texture, NULL, NULL);
|
||||||
} else {
|
} else {
|
||||||
// rotation in RenderCopyEx() is clockwise, while screen->rotation is
|
// rotation in RenderCopyEx() is clockwise, while screen->rotation is
|
||||||
// counterclockwise (to be consistent with --lock-video-orientation)
|
// counterclockwise (to be consistent with --lock-video-orientation)
|
||||||
@@ -485,14 +448,12 @@ screen_render(struct screen *screen) {
|
|||||||
SDL_Rect *dstrect = NULL;
|
SDL_Rect *dstrect = NULL;
|
||||||
SDL_Rect rect;
|
SDL_Rect rect;
|
||||||
if (screen->rotation & 1) {
|
if (screen->rotation & 1) {
|
||||||
rect.x = screen->rect.x + (screen->rect.w - screen->rect.h) / 2;
|
struct size size = screen->content_size;
|
||||||
rect.y = screen->rect.y + (screen->rect.h - screen->rect.w) / 2;
|
rect.x = (size.width - size.height) / 2;
|
||||||
rect.w = screen->rect.h;
|
rect.y = (size.height - size.width) / 2;
|
||||||
rect.h = screen->rect.w;
|
rect.w = size.height;
|
||||||
|
rect.h = size.width;
|
||||||
dstrect = ▭
|
dstrect = ▭
|
||||||
} else {
|
|
||||||
assert(screen->rotation == 2);
|
|
||||||
dstrect = &screen->rect;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
SDL_RenderCopyEx(screen->renderer, screen->texture, NULL, dstrect,
|
SDL_RenderCopyEx(screen->renderer, screen->texture, NULL, dstrect,
|
||||||
@@ -530,8 +491,7 @@ screen_resize_to_fit(struct screen *screen) {
|
|||||||
struct size optimal_size =
|
struct size optimal_size =
|
||||||
get_optimal_window_size(screen, screen->content_size);
|
get_optimal_window_size(screen, screen->content_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: %ux%u", optimal_size.width,
|
LOGD("Resized to optimal size");
|
||||||
optimal_size.height);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
@@ -547,8 +507,7 @@ screen_resize_to_pixel_perfect(struct screen *screen) {
|
|||||||
|
|
||||||
struct size content_size = screen->content_size;
|
struct size content_size = screen->content_size;
|
||||||
SDL_SetWindowSize(screen->window, content_size.width, content_size.height);
|
SDL_SetWindowSize(screen->window, content_size.width, content_size.height);
|
||||||
LOGD("Resized to pixel-perfect: %ux%u", content_size.width,
|
LOGD("Resized to pixel-perfect");
|
||||||
content_size.height);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
@@ -571,7 +530,7 @@ screen_handle_window_event(struct screen *screen,
|
|||||||
// window is maximized or fullscreen is enabled.
|
// window is maximized or fullscreen is enabled.
|
||||||
screen->windowed_window_size = get_window_size(screen->window);
|
screen->windowed_window_size = get_window_size(screen->window);
|
||||||
}
|
}
|
||||||
screen_window_resized(screen);
|
screen_render(screen);
|
||||||
break;
|
break;
|
||||||
case SDL_WINDOWEVENT_MAXIMIZED:
|
case SDL_WINDOWEVENT_MAXIMIZED:
|
||||||
// The backup size must be non-nul.
|
// The backup size must be non-nul.
|
||||||
@@ -592,44 +551,3 @@ screen_handle_window_event(struct screen *screen,
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
struct point
|
|
||||||
screen_convert_to_frame_coords(struct screen *screen, int32_t x, int32_t y) {
|
|
||||||
unsigned rotation = screen->rotation;
|
|
||||||
assert(rotation < 4);
|
|
||||||
|
|
||||||
int32_t w = screen->content_size.width;
|
|
||||||
int32_t h = screen->content_size.height;
|
|
||||||
|
|
||||||
// take the HiDPI scaling (dw/ww and dh/wh) into account
|
|
||||||
int ww, wh, dw, dh;
|
|
||||||
SDL_GetWindowSize(screen->window, &ww, &wh);
|
|
||||||
SDL_GL_GetDrawableSize(screen->window, &dw, &dh);
|
|
||||||
|
|
||||||
// scale (64 bits for intermediate multiplications)
|
|
||||||
x = (int64_t) (x - screen->rect.x) * w * dw / (screen->rect.w * ww);
|
|
||||||
y = (int64_t) (y - screen->rect.y) * h * dh / (screen->rect.h * wh);
|
|
||||||
|
|
||||||
// rotate
|
|
||||||
struct point result;
|
|
||||||
switch (rotation) {
|
|
||||||
case 0:
|
|
||||||
result.x = x;
|
|
||||||
result.y = y;
|
|
||||||
break;
|
|
||||||
case 1:
|
|
||||||
result.x = h - y;
|
|
||||||
result.y = x;
|
|
||||||
break;
|
|
||||||
case 2:
|
|
||||||
result.x = w - x;
|
|
||||||
result.y = h - y;
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
assert(rotation == 3);
|
|
||||||
result.x = y;
|
|
||||||
result.y = w - x;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -28,8 +28,6 @@ struct screen {
|
|||||||
struct size windowed_window_size_backup;
|
struct size windowed_window_size_backup;
|
||||||
// client rotation: 0, 1, 2 or 3 (x90 degrees counterclockwise)
|
// client rotation: 0, 1, 2 or 3 (x90 degrees counterclockwise)
|
||||||
unsigned rotation;
|
unsigned rotation;
|
||||||
// rectangle of the content (excluding black borders)
|
|
||||||
struct SDL_Rect rect;
|
|
||||||
bool has_frame;
|
bool has_frame;
|
||||||
bool fullscreen;
|
bool fullscreen;
|
||||||
bool maximized;
|
bool maximized;
|
||||||
@@ -60,12 +58,6 @@ struct screen {
|
|||||||
.height = 0, \
|
.height = 0, \
|
||||||
}, \
|
}, \
|
||||||
.rotation = 0, \
|
.rotation = 0, \
|
||||||
.rect = { \
|
|
||||||
.x = 0, \
|
|
||||||
.y = 0, \
|
|
||||||
.w = 0, \
|
|
||||||
.h = 0, \
|
|
||||||
}, \
|
|
||||||
.has_frame = false, \
|
.has_frame = false, \
|
||||||
.fullscreen = false, \
|
.fullscreen = false, \
|
||||||
.maximized = false, \
|
.maximized = false, \
|
||||||
@@ -98,10 +90,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);
|
||||||
|
|
||||||
// update content after window resizing
|
|
||||||
void
|
|
||||||
screen_window_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);
|
||||||
@@ -126,9 +114,4 @@ screen_set_rotation(struct screen *screen, unsigned rotation);
|
|||||||
void
|
void
|
||||||
screen_handle_window_event(struct screen *screen, const SDL_WindowEvent *event);
|
screen_handle_window_event(struct screen *screen, const SDL_WindowEvent *event);
|
||||||
|
|
||||||
// convert point from window coordinates to frame coordinates
|
|
||||||
// x and y are expressed in pixels
|
|
||||||
struct point
|
|
||||||
screen_convert_to_frame_coords(struct screen *screen, int32_t x, int32_t y);
|
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@@ -49,20 +49,20 @@ static void test_serialize_inject_text(void) {
|
|||||||
static void test_serialize_inject_text_long(void) {
|
static void test_serialize_inject_text_long(void) {
|
||||||
struct control_msg msg;
|
struct control_msg msg;
|
||||||
msg.type = CONTROL_MSG_TYPE_INJECT_TEXT;
|
msg.type = CONTROL_MSG_TYPE_INJECT_TEXT;
|
||||||
char text[CONTROL_MSG_INJECT_TEXT_MAX_LENGTH + 1];
|
char text[CONTROL_MSG_TEXT_MAX_LENGTH + 1];
|
||||||
memset(text, 'a', sizeof(text));
|
memset(text, 'a', sizeof(text));
|
||||||
text[CONTROL_MSG_INJECT_TEXT_MAX_LENGTH] = '\0';
|
text[CONTROL_MSG_TEXT_MAX_LENGTH] = '\0';
|
||||||
msg.inject_text.text = text;
|
msg.inject_text.text = text;
|
||||||
|
|
||||||
unsigned char buf[CONTROL_MSG_SERIALIZED_MAX_SIZE];
|
unsigned char buf[CONTROL_MSG_SERIALIZED_MAX_SIZE];
|
||||||
int size = control_msg_serialize(&msg, buf);
|
int size = control_msg_serialize(&msg, buf);
|
||||||
assert(size == 3 + CONTROL_MSG_INJECT_TEXT_MAX_LENGTH);
|
assert(size == 3 + CONTROL_MSG_TEXT_MAX_LENGTH);
|
||||||
|
|
||||||
unsigned char expected[3 + CONTROL_MSG_INJECT_TEXT_MAX_LENGTH];
|
unsigned char expected[3 + CONTROL_MSG_TEXT_MAX_LENGTH];
|
||||||
expected[0] = CONTROL_MSG_TYPE_INJECT_TEXT;
|
expected[0] = CONTROL_MSG_TYPE_INJECT_TEXT;
|
||||||
expected[1] = 0x01;
|
expected[1] = 0x01;
|
||||||
expected[2] = 0x2c; // text length (16 bits)
|
expected[2] = 0x2c; // text length (16 bits)
|
||||||
memset(&expected[3], 'a', CONTROL_MSG_INJECT_TEXT_MAX_LENGTH);
|
memset(&expected[3], 'a', CONTROL_MSG_TEXT_MAX_LENGTH);
|
||||||
|
|
||||||
assert(!memcmp(buf, expected, sizeof(expected)));
|
assert(!memcmp(buf, expected, sizeof(expected)));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
project('scrcpy', 'c',
|
project('scrcpy', 'c',
|
||||||
version: '1.12.1',
|
version: '1.12.1',
|
||||||
meson_version: '>= 0.48',
|
meson_version: '>= 0.37',
|
||||||
default_options: [
|
default_options: [
|
||||||
'c_std=c11',
|
'c_std=c11',
|
||||||
'warning_level=2',
|
'warning_level=2',
|
||||||
|
|||||||
@@ -3,9 +3,7 @@
|
|||||||
prebuilt_server = get_option('prebuilt_server')
|
prebuilt_server = get_option('prebuilt_server')
|
||||||
if prebuilt_server == ''
|
if prebuilt_server == ''
|
||||||
custom_target('scrcpy-server',
|
custom_target('scrcpy-server',
|
||||||
# gradle is responsible for tracking source changes
|
build_always: true, # gradle is responsible for tracking source changes
|
||||||
build_by_default: true,
|
|
||||||
build_always_stale: true,
|
|
||||||
output: 'scrcpy-server',
|
output: 'scrcpy-server',
|
||||||
command: [find_program('./scripts/build-wrapper.sh'), meson.current_source_dir(), '@OUTPUT@', get_option('buildtype')],
|
command: [find_program('./scripts/build-wrapper.sh'), meson.current_source_dir(), '@OUTPUT@', get_option('buildtype')],
|
||||||
console: true,
|
console: true,
|
||||||
|
|||||||
@@ -13,8 +13,8 @@ public class ControlMessageReader {
|
|||||||
static final int INJECT_SCROLL_EVENT_PAYLOAD_LENGTH = 20;
|
static final int INJECT_SCROLL_EVENT_PAYLOAD_LENGTH = 20;
|
||||||
static final int SET_SCREEN_POWER_MODE_PAYLOAD_LENGTH = 1;
|
static final int SET_SCREEN_POWER_MODE_PAYLOAD_LENGTH = 1;
|
||||||
|
|
||||||
|
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;
|
||||||
public static final int INJECT_TEXT_MAX_LENGTH = 300;
|
|
||||||
private static final int RAW_BUFFER_SIZE = 1024;
|
private static final int RAW_BUFFER_SIZE = 1024;
|
||||||
|
|
||||||
private final byte[] rawBuffer = new byte[RAW_BUFFER_SIZE];
|
private final byte[] rawBuffer = new byte[RAW_BUFFER_SIZE];
|
||||||
|
|||||||
@@ -36,7 +36,10 @@ public final class Device {
|
|||||||
*/
|
*/
|
||||||
private final int layerStack;
|
private final int layerStack;
|
||||||
|
|
||||||
private final boolean supportsInputEvents;
|
/**
|
||||||
|
* The FLAG_PRESENTATION from the DisplayInfo
|
||||||
|
*/
|
||||||
|
private final boolean isPresentationDisplay;
|
||||||
|
|
||||||
public Device(Options options) {
|
public Device(Options options) {
|
||||||
displayId = options.getDisplayId();
|
displayId = options.getDisplayId();
|
||||||
@@ -50,6 +53,7 @@ public final class Device {
|
|||||||
|
|
||||||
screenInfo = ScreenInfo.computeScreenInfo(displayInfo, options.getCrop(), options.getMaxSize(), options.getLockedVideoOrientation());
|
screenInfo = ScreenInfo.computeScreenInfo(displayInfo, options.getCrop(), options.getMaxSize(), options.getLockedVideoOrientation());
|
||||||
layerStack = displayInfo.getLayerStack();
|
layerStack = displayInfo.getLayerStack();
|
||||||
|
isPresentationDisplay = (displayInfoFlags & DisplayInfo.FLAG_PRESENTATION) != 0;
|
||||||
|
|
||||||
registerRotationWatcher(new IRotationWatcher.Stub() {
|
registerRotationWatcher(new IRotationWatcher.Stub() {
|
||||||
@Override
|
@Override
|
||||||
@@ -69,10 +73,8 @@ public final class Device {
|
|||||||
Ln.w("Display doesn't have FLAG_SUPPORTS_PROTECTED_BUFFERS flag, mirroring can be restricted");
|
Ln.w("Display doesn't have FLAG_SUPPORTS_PROTECTED_BUFFERS flag, mirroring can be restricted");
|
||||||
}
|
}
|
||||||
|
|
||||||
// main display or any display on Android >= Q
|
if (!supportsInputEvents()) {
|
||||||
supportsInputEvents = displayId == 0 || Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q;
|
Ln.w("Input events are not supported for displays with FLAG_PRESENTATION enabled for devices with API lower than 29");
|
||||||
if (!supportsInputEvents) {
|
|
||||||
Ln.w("Input events are not supported for secondary displays before Android 10");
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -114,7 +116,10 @@ public final class Device {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public boolean supportsInputEvents() {
|
public boolean supportsInputEvents() {
|
||||||
return 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) {
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ public final class DisplayInfo {
|
|||||||
private final int flags;
|
private final int flags;
|
||||||
|
|
||||||
public static final int FLAG_SUPPORTS_PROTECTED_BUFFERS = 0x00000001;
|
public static final int FLAG_SUPPORTS_PROTECTED_BUFFERS = 0x00000001;
|
||||||
|
public static final int FLAG_PRESENTATION = 0x00000008;
|
||||||
|
|
||||||
public DisplayInfo(int displayId, Size size, int rotation, int layerStack, int flags) {
|
public DisplayInfo(int displayId, Size size, int rotation, int layerStack, int flags) {
|
||||||
this.displayId = displayId;
|
this.displayId = displayId;
|
||||||
|
|||||||
@@ -136,12 +136,12 @@ public class ScreenEncoder implements Device.RotationListener {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private static MediaCodec createCodec() throws IOException {
|
private static MediaCodec createCodec() throws IOException {
|
||||||
return MediaCodec.createEncoderByType(MediaFormat.MIMETYPE_VIDEO_AVC);
|
return MediaCodec.createEncoderByType("video/avc");
|
||||||
}
|
}
|
||||||
|
|
||||||
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, MediaFormat.MIMETYPE_VIDEO_AVC);
|
format.setString(MediaFormat.KEY_MIME, "video/avc");
|
||||||
format.setInteger(MediaFormat.KEY_BIT_RATE, bitRate);
|
format.setInteger(MediaFormat.KEY_BIT_RATE, bitRate);
|
||||||
// must be present to configure the encoder, but does not impact the actual frame rate, which is variable
|
// must be present to configure the encoder, but does not impact the actual frame rate, which is variable
|
||||||
format.setInteger(MediaFormat.KEY_FRAME_RATE, 60);
|
format.setInteger(MediaFormat.KEY_FRAME_RATE, 60);
|
||||||
|
|||||||
@@ -53,7 +53,8 @@ public final class InputManager {
|
|||||||
method.invoke(inputEvent, displayId);
|
method.invoke(inputEvent, displayId);
|
||||||
return true;
|
return true;
|
||||||
} catch (InvocationTargetException | IllegalAccessException | NoSuchMethodException e) {
|
} catch (InvocationTargetException | IllegalAccessException | NoSuchMethodException e) {
|
||||||
Ln.e("Cannot associate a display id to the input event", e);
|
// just a warning, it might happen on old devices
|
||||||
|
Ln.w("Cannot associate a display id to the input event");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -66,7 +66,7 @@ public class ControlMessageReaderTest {
|
|||||||
ByteArrayOutputStream bos = new ByteArrayOutputStream();
|
ByteArrayOutputStream bos = new ByteArrayOutputStream();
|
||||||
DataOutputStream dos = new DataOutputStream(bos);
|
DataOutputStream dos = new DataOutputStream(bos);
|
||||||
dos.writeByte(ControlMessage.TYPE_INJECT_TEXT);
|
dos.writeByte(ControlMessage.TYPE_INJECT_TEXT);
|
||||||
byte[] text = new byte[ControlMessageReader.INJECT_TEXT_MAX_LENGTH];
|
byte[] text = new byte[ControlMessageReader.TEXT_MAX_LENGTH];
|
||||||
Arrays.fill(text, (byte) 'a');
|
Arrays.fill(text, (byte) 'a');
|
||||||
dos.writeShort(text.length);
|
dos.writeShort(text.length);
|
||||||
dos.write(text);
|
dos.write(text);
|
||||||
|
|||||||
Reference in New Issue
Block a user