Add shortcuts to rotate display

Add Ctrl+Left and Ctrl+Right shortcuts to rotate the display (the
content of the scrcpy window).

Contrary to --lock-video-orientation, the rotation has no impact on
recording, and can be changed dynamically (and immediately).

Fixes #218 <https://github.com/Genymobile/scrcpy/issues/218>
This commit is contained in:
Romain Vimont 2020-04-07 23:03:23 +02:00
parent fd63e7eb5a
commit d48b375a1d
6 changed files with 152 additions and 27 deletions

View file

@ -491,6 +491,8 @@ 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 display left | `Ctrl`+`←` _(left)_ | `Cmd`+`←` _(left)_
| Rotate display 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

@ -166,6 +166,14 @@ Default is 0 (automatic).\n
.B Ctrl+f .B Ctrl+f
Switch fullscreen mode Switch fullscreen mode
.TP
.B Ctrl+Left
Rotate display left
.TP
.B Ctrl+Right
Rotate display 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

@ -145,6 +145,12 @@ 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 display left\n"
"\n"
" " CTRL_OR_CMD "+Right\n"
" Rotate display 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"

View file

@ -221,6 +221,18 @@ rotate_device(struct controller *controller) {
} }
} }
static void
rotate_client_left(struct screen *screen) {
unsigned new_rotation = (screen->rotation + 1) % 4;
screen_set_rotation(screen, new_rotation);
}
static void
rotate_client_right(struct screen *screen) {
unsigned new_rotation = (screen->rotation + 3) % 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) {
@ -351,6 +363,16 @@ 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

@ -15,6 +15,19 @@
#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) {
@ -80,8 +93,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 frame_size) { get_optimal_size(struct size current_size, struct size content_size) {
if (frame_size.width == 0 || frame_size.height == 0) { if (content_size.width == 0 || content_size.height == 0) {
// avoid division by 0 // avoid division by 0
return current_size; return current_size;
} }
@ -100,14 +113,14 @@ get_optimal_size(struct size current_size, struct size frame_size) {
h = MIN(current_size.height, display_size.height); h = MIN(current_size.height, display_size.height);
} }
bool keep_width = frame_size.width * h > frame_size.height * w; 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
h = frame_size.height * w / frame_size.width; h = content_size.height * w / 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)
w = frame_size.width * h / frame_size.height; w = content_size.width * h / content_size.height;
} }
// w and h must fit into 16 bits // w and h must fit into 16 bits
@ -117,33 +130,33 @@ get_optimal_size(struct size current_size, struct size frame_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 frame_size) { get_optimal_window_size(const struct screen *screen, struct size content_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, frame_size); return get_optimal_size(windowed_size, content_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 frame_size, uint16_t req_width, get_initial_optimal_size(struct size content_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(frame_size, frame_size); window_size = get_optimal_size(content_size, content_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 * frame_size.width window_size.width = (uint32_t) req_height * content_size.width
/ frame_size.height; / content_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 * frame_size.height window_size.height = (uint32_t) req_width * content_size.height
/ frame_size.width; / content_size.width;
} }
} }
return window_size; return window_size;
@ -167,9 +180,11 @@ 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(frame_size, window_width, window_height); get_initial_optimal_size(content_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;
@ -206,8 +221,8 @@ screen_init_rendering(struct screen *screen, const char *window_title,
return false; return false;
} }
if (SDL_RenderSetLogicalSize(screen->renderer, frame_size.width, if (SDL_RenderSetLogicalSize(screen->renderer, content_size.width,
frame_size.height)) { content_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;
@ -253,13 +268,51 @@ 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("Display 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) {
if (SDL_RenderSetLogicalSize(screen->renderer, new_frame_size.width, struct size new_content_size =
new_frame_size.height)) { 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()); LOGE("Could not set renderer logical size: %s", SDL_GetError());
return false; return false;
} }
@ -267,14 +320,16 @@ 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_frame_size.width (uint32_t) windowed_size.width * new_content_size.width
/ screen->frame_size.width, / content_size.width,
(uint32_t) windowed_size.height * new_frame_size.height (uint32_t) windowed_size.height * new_content_size.height
/ screen->frame_size.height, / content_size.height,
}; };
target_size = get_optimal_size(target_size, new_frame_size); target_size = get_optimal_size(target_size, new_content_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;
@ -319,7 +374,29 @@ 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 {
// rotation in RenderCopyEx() is clockwise, while screen->rotation is
// counterclockwise (to be consistent with --lock-video-orientation)
int cw_rotation = (4 - screen->rotation) % 4;
double angle = 90 * cw_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;
}
SDL_RenderCopyEx(screen->renderer, screen->texture, NULL, dstrect,
angle, NULL, 0);
}
SDL_RenderPresent(screen->renderer); SDL_RenderPresent(screen->renderer);
} }
@ -349,8 +426,10 @@ 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, screen->frame_size); get_optimal_window_size(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"); LOGD("Resized to optimal size");
} }
@ -366,8 +445,9 @@ screen_resize_to_pixel_perfect(struct screen *screen) {
screen->maximized = false; screen->maximized = false;
} }
SDL_SetWindowSize(screen->window, screen->frame_size.width, struct size content_size =
screen->frame_size.height); get_rotated_size(screen->frame_size, screen->rotation);
SDL_SetWindowSize(screen->window, content_size.width, content_size.height);
LOGD("Resized to pixel-perfect"); LOGD("Resized to pixel-perfect");
} }

View file

@ -22,6 +22,8 @@ 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 counterclockwise)
unsigned rotation;
bool has_frame; bool has_frame;
bool fullscreen; bool fullscreen;
bool maximized; bool maximized;
@ -44,6 +46,7 @@ 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, \
@ -90,6 +93,10 @@ 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);
// set the display rotation (0, 1, 2 or 3, x90 degrees counterclockwise)
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);