From be3d357a6dcdf0f3fccabd795b869a30e1e5bee1 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Sat, 30 Mar 2024 11:23:41 +0100 Subject: [PATCH 01/44] Use source repo tarball for libusb Legitimate or not, we should not use sources that do not match the repository. Refs Refs Refs #4713 --- app/deps/libusb.sh | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/app/deps/libusb.sh b/app/deps/libusb.sh index 97fc3c72..26f0140b 100755 --- a/app/deps/libusb.sh +++ b/app/deps/libusb.sh @@ -5,9 +5,9 @@ cd "$DEPS_DIR" . common VERSION=1.0.27 -FILENAME=libusb-$VERSION.tar.bz2 +FILENAME=libusb-$VERSION.tar.gz PROJECT_DIR=libusb-$VERSION -SHA256SUM=ffaa41d741a8a3bee244ac8e54a72ea05bf2879663c098c82fc5757853441575 +SHA256SUM=e8f18a7a36ecbb11fb820bd71540350d8f61bcd9db0d2e8c18a6fb80b214a3de cd "$SOURCES_DIR" @@ -15,7 +15,7 @@ if [[ -d "$PROJECT_DIR" ]] then echo "$PWD/$PROJECT_DIR" found else - get_file "https://github.com/libusb/libusb/releases/download/v$VERSION/libusb-$VERSION.tar.bz2" "$FILENAME" "$SHA256SUM" + get_file "https://github.com/libusb/libusb/archive/refs/tags/v$VERSION.tar.gz" "$FILENAME" "$SHA256SUM" tar xf "$FILENAME" # First level directory is "$PROJECT_DIR" fi @@ -33,6 +33,7 @@ else mkdir "$HOST" cd "$HOST" + "$SOURCES_DIR/$PROJECT_DIR"/bootstrap.sh "$SOURCES_DIR/$PROJECT_DIR"/configure \ --prefix="$INSTALL_DIR/$HOST" \ --host="$HOST_TRIPLET" \ From 1c3801a0b1624258e5004e651c9dbcff7eafe324 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Sun, 10 Mar 2024 22:04:17 +0100 Subject: [PATCH 02/44] Add a shortcut to pause/unpause display Pause/unpause display on MOD+z and MOD+Shift+z. It only impacts rendering, the device is still captured, the video stream continues to be transmitted to the device and recorded (if recording is enabled). Fixes #1632 PR #4748 --- app/scrcpy.1 | 8 ++++++ app/src/cli.c | 8 ++++++ app/src/input_manager.c | 54 +++++++++++++++++++++----------------- app/src/screen.c | 57 +++++++++++++++++++++++++++++++++++++---- app/src/screen.h | 7 +++++ doc/shortcuts.md | 2 ++ 6 files changed, 107 insertions(+), 29 deletions(-) diff --git a/app/scrcpy.1 b/app/scrcpy.1 index 1e3c91b1..eb09f530 100644 --- a/app/scrcpy.1 +++ b/app/scrcpy.1 @@ -577,6 +577,14 @@ Flip display horizontally .B MOD+Shift+Up, MOD+Shift+Down Flip display vertically +.TP +.B MOD+z +Pause or re-pause display + +.TP +.B MOD+Shift+z +Unpause display + .TP .B MOD+g Resize window to 1:1 (pixel\-perfect) diff --git a/app/src/cli.c b/app/src/cli.c index daa041cf..92807947 100644 --- a/app/src/cli.c +++ b/app/src/cli.c @@ -900,6 +900,14 @@ static const struct sc_shortcut shortcuts[] = { .shortcuts = { "MOD+Shift+Up", "MOD+Shift+Down" }, .text = "Flip display vertically", }, + { + .shortcuts = { "MOD+z" }, + .text = "Pause or re-pause display", + }, + { + .shortcuts = { "MOD+Shift+z" }, + .text = "Unpause display", + }, { .shortcuts = { "MOD+g" }, .text = "Resize window to 1:1 (pixel-perfect)", diff --git a/app/src/input_manager.c b/app/src/input_manager.c index f26c4164..c7a758f4 100644 --- a/app/src/input_manager.c +++ b/app/src/input_manager.c @@ -402,6 +402,7 @@ sc_input_manager_process_key(struct sc_input_manager *im, const SDL_KeyboardEvent *event) { // controller is NULL if --no-control is requested bool control = im->controller; + bool paused = im->screen->paused; SDL_Keycode keycode = event->keysym.sym; uint16_t mod = event->keysym.mod; @@ -427,46 +428,51 @@ sc_input_manager_process_key(struct sc_input_manager *im, enum sc_action action = down ? SC_ACTION_DOWN : SC_ACTION_UP; switch (keycode) { case SDLK_h: - if (im->kp && !shift && !repeat) { + if (im->kp && !shift && !repeat && !paused) { action_home(im, action); } return; case SDLK_b: // fall-through case SDLK_BACKSPACE: - if (im->kp && !shift && !repeat) { + if (im->kp && !shift && !repeat && !paused) { action_back(im, action); } return; case SDLK_s: - if (im->kp && !shift && !repeat) { + if (im->kp && !shift && !repeat && !paused) { action_app_switch(im, action); } return; case SDLK_m: - if (im->kp && !shift && !repeat) { + if (im->kp && !shift && !repeat && !paused) { action_menu(im, action); } return; case SDLK_p: - if (im->kp && !shift && !repeat) { + if (im->kp && !shift && !repeat && !paused) { action_power(im, action); } return; case SDLK_o: - if (control && !repeat && down) { + if (control && !repeat && down && !paused) { enum sc_screen_power_mode mode = shift ? SC_SCREEN_POWER_MODE_NORMAL : SC_SCREEN_POWER_MODE_OFF; set_screen_power_mode(im, mode); } return; + case SDLK_z: + if (down && !repeat) { + sc_screen_set_paused(im->screen, !shift); + } + return; case SDLK_DOWN: if (shift) { if (!repeat & down) { apply_orientation_transform(im, SC_ORIENTATION_FLIP_180); } - } else if (im->kp) { + } else if (im->kp && !paused) { // forward repeated events action_volume_down(im, action); } @@ -477,7 +483,7 @@ sc_input_manager_process_key(struct sc_input_manager *im, apply_orientation_transform(im, SC_ORIENTATION_FLIP_180); } - } else if (im->kp) { + } else if (im->kp && !paused) { // forward repeated events action_volume_up(im, action); } @@ -505,17 +511,17 @@ sc_input_manager_process_key(struct sc_input_manager *im, } return; case SDLK_c: - if (im->kp && !shift && !repeat && down) { + if (im->kp && !shift && !repeat && down && !paused) { get_device_clipboard(im, SC_COPY_KEY_COPY); } return; case SDLK_x: - if (im->kp && !shift && !repeat && down) { + if (im->kp && !shift && !repeat && down && !paused) { get_device_clipboard(im, SC_COPY_KEY_CUT); } return; case SDLK_v: - if (im->kp && !repeat && down) { + if (im->kp && !repeat && down && !paused) { if (shift || im->legacy_paste) { // inject the text as input events clipboard_paste(im); @@ -547,7 +553,7 @@ sc_input_manager_process_key(struct sc_input_manager *im, } return; case SDLK_n: - if (control && !repeat && down) { + if (control && !repeat && down && !paused) { if (shift) { collapse_panels(im); } else if (im->key_repeat == 0) { @@ -558,12 +564,12 @@ sc_input_manager_process_key(struct sc_input_manager *im, } return; case SDLK_r: - if (control && !shift && !repeat && down) { + if (control && !shift && !repeat && down && !paused) { rotate_device(im); } return; case SDLK_k: - if (control && !shift && !repeat && down + if (control && !shift && !repeat && down && !paused && im->kp && im->kp->hid) { // Only if the current keyboard is hid open_hard_keyboard_settings(im); @@ -574,7 +580,7 @@ sc_input_manager_process_key(struct sc_input_manager *im, return; } - if (!im->kp) { + if (!im->kp || paused) { return; } @@ -622,7 +628,6 @@ sc_input_manager_process_key(struct sc_input_manager *im, static void sc_input_manager_process_mouse_motion(struct sc_input_manager *im, const SDL_MouseMotionEvent *event) { - if (event->which == SDL_TOUCH_MOUSEID) { // simulated from touch events, so it's a duplicate return; @@ -695,16 +700,16 @@ sc_input_manager_process_touch(struct sc_input_manager *im, static void sc_input_manager_process_mouse_button(struct sc_input_manager *im, const SDL_MouseButtonEvent *event) { - bool control = im->controller; - if (event->which == SDL_TOUCH_MOUSEID) { // simulated from touch events, so it's a duplicate return; } + bool control = im->controller; + bool paused = im->screen->paused; bool down = event->type == SDL_MOUSEBUTTONDOWN; if (!im->forward_all_clicks) { - if (control) { + if (control && !paused) { enum sc_action action = down ? SC_ACTION_DOWN : SC_ACTION_UP; if (im->kp && event->button == SDL_BUTTON_X1) { @@ -747,7 +752,7 @@ sc_input_manager_process_mouse_button(struct sc_input_manager *im, // otherwise, send the click event to the device } - if (!im->mp) { + if (!im->mp || paused) { return; } @@ -885,9 +890,10 @@ void sc_input_manager_handle_event(struct sc_input_manager *im, const SDL_Event *event) { bool control = im->controller; + bool paused = im->screen->paused; switch (event->type) { case SDL_TEXTINPUT: - if (!im->kp) { + if (!im->kp || paused) { break; } sc_input_manager_process_text_input(im, &event->text); @@ -899,13 +905,13 @@ sc_input_manager_handle_event(struct sc_input_manager *im, sc_input_manager_process_key(im, &event->key); break; case SDL_MOUSEMOTION: - if (!im->mp) { + if (!im->mp || paused) { break; } sc_input_manager_process_mouse_motion(im, &event->motion); break; case SDL_MOUSEWHEEL: - if (!im->mp) { + if (!im->mp || paused) { break; } sc_input_manager_process_mouse_wheel(im, &event->wheel); @@ -919,7 +925,7 @@ sc_input_manager_handle_event(struct sc_input_manager *im, case SDL_FINGERMOTION: case SDL_FINGERDOWN: case SDL_FINGERUP: - if (!im->mp) { + if (!im->mp || paused) { break; } sc_input_manager_process_touch(im, &event->tfinger); diff --git a/app/src/screen.c b/app/src/screen.c index 091001bc..351eb3fb 100644 --- a/app/src/screen.c +++ b/app/src/screen.c @@ -362,6 +362,8 @@ sc_screen_init(struct sc_screen *screen, screen->maximized = false; screen->minimized = false; screen->mouse_capture_key_pressed = 0; + screen->paused = false; + screen->resume_frame = NULL; screen->req.x = params->window_x; screen->req.y = params->window_y; @@ -614,13 +616,10 @@ prepare_for_frame(struct sc_screen *screen, struct sc_size new_frame_size) { } static bool -sc_screen_update_frame(struct sc_screen *screen) { - av_frame_unref(screen->frame); - sc_frame_buffer_consume(&screen->fb, screen->frame); - AVFrame *frame = screen->frame; - +sc_screen_apply_frame(struct sc_screen *screen) { sc_fps_counter_add_rendered_frame(&screen->fps_counter); + AVFrame *frame = screen->frame; struct sc_size new_frame_size = {frame->width, frame->height}; enum sc_display_result res = prepare_for_frame(screen, new_frame_size); if (res == SC_DISPLAY_RESULT_ERROR) { @@ -655,6 +654,54 @@ sc_screen_update_frame(struct sc_screen *screen) { return true; } +static bool +sc_screen_update_frame(struct sc_screen *screen) { + if (screen->paused) { + if (!screen->resume_frame) { + screen->resume_frame = av_frame_alloc(); + if (!screen->resume_frame) { + LOG_OOM(); + return false; + } + } else { + av_frame_unref(screen->resume_frame); + } + sc_frame_buffer_consume(&screen->fb, screen->resume_frame); + return true; + } + + av_frame_unref(screen->frame); + sc_frame_buffer_consume(&screen->fb, screen->frame); + return sc_screen_apply_frame(screen); +} + +void +sc_screen_set_paused(struct sc_screen *screen, bool paused) { + if (!paused && !screen->paused) { + // nothing to do + return; + } + + if (screen->paused && screen->resume_frame) { + // If display screen was paused, refresh the frame immediately, even if + // the new state is also paused. + av_frame_free(&screen->frame); + screen->frame = screen->resume_frame; + screen->resume_frame = NULL; + sc_screen_apply_frame(screen); + } + + if (!paused) { + LOGI("Display screen unpaused"); + } else if (!screen->paused) { + LOGI("Display screen paused"); + } else { + LOGI("Display screen re-paused"); + } + + screen->paused = paused; +} + void sc_screen_switch_fullscreen(struct sc_screen *screen) { uint32_t new_mode = screen->fullscreen ? 0 : SDL_WINDOW_FULLSCREEN_DESKTOP; diff --git a/app/src/screen.h b/app/src/screen.h index 46591be5..361ce455 100644 --- a/app/src/screen.h +++ b/app/src/screen.h @@ -64,6 +64,9 @@ struct sc_screen { SDL_Keycode mouse_capture_key_pressed; AVFrame *frame; + + bool paused; + AVFrame *resume_frame; }; struct sc_screen_params { @@ -135,6 +138,10 @@ void sc_screen_set_orientation(struct sc_screen *screen, enum sc_orientation orientation); +// set the display pause state +void +sc_screen_set_paused(struct sc_screen *screen, bool paused); + // react to SDL events // If this function returns false, scrcpy must exit with an error. bool diff --git a/doc/shortcuts.md b/doc/shortcuts.md index 8c402855..d0f6ebec 100644 --- a/doc/shortcuts.md +++ b/doc/shortcuts.md @@ -28,6 +28,8 @@ _[Super] is typically the Windows or Cmd key._ | Rotate display right | MOD+ _(right)_ | Flip display horizontally | MOD+Shift+ _(left)_ \| MOD+Shift+ _(right)_ | Flip display vertically | MOD+Shift+ _(up)_ \| MOD+Shift+ _(down)_ + | Pause or re-pause display | MOD+z + | Unpause display | MOD+Shift+z | Resize window to 1:1 (pixel-perfect) | MOD+g | Resize window to remove black borders | MOD+w \| _Double-left-click¹_ | Click on `HOME` | MOD+h \| _Middle-click_ From db55edb196134ab39f14c76edfd35225055f1227 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Sat, 30 Mar 2024 15:18:44 +0100 Subject: [PATCH 03/44] Fix YUV conversion for full color range Take the color range (full vs limited) into account to render the picture. Note that with the current version of SDL, it has no impact with the SDL opengl render driver. Fixes #4756 Refs Refs libusb/#9311 Suggested-by: Simon Chan <1330321+yume-chan@users.noreply.github.com> --- app/src/display.c | 18 ++++++++++++++++++ app/src/display.h | 2 ++ 2 files changed, 20 insertions(+) diff --git a/app/src/display.c b/app/src/display.c index c8df615d..25c23265 100644 --- a/app/src/display.c +++ b/app/src/display.c @@ -1,6 +1,7 @@ #include "display.h" #include +#include #include "util/log.h" @@ -65,6 +66,7 @@ sc_display_init(struct sc_display *display, SDL_Window *window, bool mipmaps) { display->texture = NULL; display->pending.flags = 0; display->pending.frame = NULL; + display->has_frame = false; return true; } @@ -196,9 +198,25 @@ sc_display_set_texture_size(struct sc_display *display, struct sc_size size) { return SC_DISPLAY_RESULT_OK; } +static SDL_YUV_CONVERSION_MODE +sc_display_to_sdl_color_range(enum AVColorRange color_range) { + return color_range == AVCOL_RANGE_JPEG ? SDL_YUV_CONVERSION_JPEG + : SDL_YUV_CONVERSION_AUTOMATIC; +} + static bool sc_display_update_texture_internal(struct sc_display *display, const AVFrame *frame) { + if (!display->has_frame) { + // First frame + display->has_frame = true; + + // Configure YUV color range conversion + SDL_YUV_CONVERSION_MODE sdl_color_range = + sc_display_to_sdl_color_range(frame->color_range); + SDL_SetYUVConversionMode(sdl_color_range); + } + int ret = SDL_UpdateYUVTexture(display->texture, NULL, frame->data[0], frame->linesize[0], frame->data[1], frame->linesize[1], diff --git a/app/src/display.h b/app/src/display.h index 643ce73c..590715ee 100644 --- a/app/src/display.h +++ b/app/src/display.h @@ -33,6 +33,8 @@ struct sc_display { struct sc_size size; AVFrame *frame; } pending; + + bool has_frame; }; enum sc_display_result { From bf625790faccd86b5cdf89234ee405825f76b662 Mon Sep 17 00:00:00 2001 From: Simon Chan <1330321+yume-chan@users.noreply.github.com> Date: Sat, 30 Mar 2024 15:25:14 +0100 Subject: [PATCH 04/44] Request limited color range by default Most devices currently use limited color range, but some recent devices encode in full color range, which is currently not supported by the SDL opengl render driver. Fixes #4756 Refs Refs libusb/#9311 Signed-off-by: Romain Vimont --- .../src/main/java/com/genymobile/scrcpy/SurfaceEncoder.java | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/server/src/main/java/com/genymobile/scrcpy/SurfaceEncoder.java b/server/src/main/java/com/genymobile/scrcpy/SurfaceEncoder.java index 28435c09..8eda8231 100644 --- a/server/src/main/java/com/genymobile/scrcpy/SurfaceEncoder.java +++ b/server/src/main/java/com/genymobile/scrcpy/SurfaceEncoder.java @@ -3,6 +3,7 @@ package com.genymobile.scrcpy; import android.media.MediaCodec; import android.media.MediaCodecInfo; import android.media.MediaFormat; +import android.os.Build; import android.os.Looper; import android.os.SystemClock; import android.view.Surface; @@ -220,6 +221,9 @@ public class SurfaceEncoder implements AsyncProcessor { // 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_COLOR_FORMAT, MediaCodecInfo.CodecCapabilities.COLOR_FormatSurface); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { + format.setInteger(MediaFormat.KEY_COLOR_RANGE, MediaFormat.COLOR_RANGE_LIMITED); + } format.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, DEFAULT_I_FRAME_INTERVAL); // display the very first frame, and recover from bad quality when no new frames format.setLong(MediaFormat.KEY_REPEAT_PREVIOUS_FRAME_AFTER, REPEAT_FRAME_DELAY_US); // µs From aa34d63171c86a23942d575ac62410a0f8765a4d Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Wed, 3 Apr 2024 08:57:18 +0200 Subject: [PATCH 05/44] Fix segfault on close with --no-video Do not call sc_screen_hide_window() if screen is not initialized. To reproduce: scrcpy --no-video --record=file.mp4 This only segfaults in debug mode since commit fd0f432e877153d83ed435474fb7b04e41de4269. --- app/src/scrcpy.c | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/app/src/scrcpy.c b/app/src/scrcpy.c index f43af35e..537562f4 100644 --- a/app/src/scrcpy.c +++ b/app/src/scrcpy.c @@ -805,9 +805,12 @@ scrcpy(struct scrcpy_options *options) { ret = event_loop(s); LOGD("quit..."); - // Close the window immediately on closing, because screen_destroy() may - // only be called once the video demuxer thread is joined (it may take time) - sc_screen_hide_window(&s->screen); + if (options->video_playback) { + // Close the window immediately on closing, because screen_destroy() + // may only be called once the video demuxer thread is joined (it may + // take time) + sc_screen_hide_window(&s->screen); + } end: if (timeout_started) { From ee6620d123e87d4af8e51cd272de5eafb677122a Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Mon, 1 Apr 2024 15:17:31 +0200 Subject: [PATCH 06/44] Refactor WindowManager methods Select the available method to invoke the same way as in other wrappers (using a version field). Refs d894e270a7719b92e38b4f5e0294b9d55e90a6df Refs #4740 --- .../scrcpy/wrappers/WindowManager.java | 125 +++++++++--------- 1 file changed, 64 insertions(+), 61 deletions(-) diff --git a/server/src/main/java/com/genymobile/scrcpy/wrappers/WindowManager.java b/server/src/main/java/com/genymobile/scrcpy/wrappers/WindowManager.java index d9654b1b..2fc7ee02 100644 --- a/server/src/main/java/com/genymobile/scrcpy/wrappers/WindowManager.java +++ b/server/src/main/java/com/genymobile/scrcpy/wrappers/WindowManager.java @@ -12,12 +12,15 @@ import java.lang.reflect.Method; public final class WindowManager { private final IInterface manager; private Method getRotationMethod; - private Method freezeRotationMethod; + private Method freezeDisplayRotationMethod; - private Method isRotationFrozenMethod; + private int freezeDisplayRotationMethodVersion; + private Method isDisplayRotationFrozenMethod; - private Method thawRotationMethod; + private int isDisplayRotationFrozenMethodVersion; + private Method thawDisplayRotationMethod; + private int thawDisplayRotationMethodVersion; static WindowManager create() { IInterface manager = ServiceManager.getService("window", "android.view.IWindowManager"); @@ -43,50 +46,47 @@ public final class WindowManager { return getRotationMethod; } - private Method getFreezeRotationMethod() throws NoSuchMethodException { - if (freezeRotationMethod == null) { - freezeRotationMethod = manager.getClass().getMethod("freezeRotation", int.class); - } - return freezeRotationMethod; - } - - // New method added by this commit: - // private Method getFreezeDisplayRotationMethod() throws NoSuchMethodException { if (freezeDisplayRotationMethod == null) { - freezeDisplayRotationMethod = manager.getClass().getMethod("freezeDisplayRotation", int.class, int.class); + try { + freezeDisplayRotationMethod = manager.getClass().getMethod("freezeRotation", int.class); + freezeDisplayRotationMethodVersion = 0; + } catch (NoSuchMethodException e) { + // New method added by this commit: + // + freezeDisplayRotationMethod = manager.getClass().getMethod("freezeDisplayRotation", int.class, int.class); + freezeDisplayRotationMethodVersion = 1; + } } return freezeDisplayRotationMethod; } - private Method getIsRotationFrozenMethod() throws NoSuchMethodException { - if (isRotationFrozenMethod == null) { - isRotationFrozenMethod = manager.getClass().getMethod("isRotationFrozen"); - } - return isRotationFrozenMethod; - } - - // New method added by this commit: - // private Method getIsDisplayRotationFrozenMethod() throws NoSuchMethodException { if (isDisplayRotationFrozenMethod == null) { - isDisplayRotationFrozenMethod = manager.getClass().getMethod("isDisplayRotationFrozen", int.class); + try { + isDisplayRotationFrozenMethod = manager.getClass().getMethod("isRotationFrozen"); + isDisplayRotationFrozenMethodVersion = 0; + } catch (NoSuchMethodException e) { + // New method added by this commit: + // + isDisplayRotationFrozenMethod = manager.getClass().getMethod("isDisplayRotationFrozen", int.class); + isDisplayRotationFrozenMethodVersion = 1; + } } return isDisplayRotationFrozenMethod; } - private Method getThawRotationMethod() throws NoSuchMethodException { - if (thawRotationMethod == null) { - thawRotationMethod = manager.getClass().getMethod("thawRotation"); - } - return thawRotationMethod; - } - - // New method added by this commit: - // private Method getThawDisplayRotationMethod() throws NoSuchMethodException { if (thawDisplayRotationMethod == null) { - thawDisplayRotationMethod = manager.getClass().getMethod("thawDisplayRotation", int.class); + try { + thawDisplayRotationMethod = manager.getClass().getMethod("thawRotation"); + thawDisplayRotationMethodVersion = 0; + } catch (NoSuchMethodException e) { + // New method added by this commit: + // + thawDisplayRotationMethod = manager.getClass().getMethod("thawDisplayRotation", int.class); + thawDisplayRotationMethodVersion = 1; + } } return thawDisplayRotationMethod; } @@ -103,16 +103,18 @@ public final class WindowManager { public void freezeRotation(int displayId, int rotation) { try { - try { - Method method = getFreezeDisplayRotationMethod(); - method.invoke(manager, displayId, rotation); - } catch (ReflectiveOperationException e) { - if (displayId == 0) { - Method method = getFreezeRotationMethod(); + Method method = getFreezeDisplayRotationMethod(); + switch (freezeDisplayRotationMethodVersion) { + case 0: + if (displayId != 0) { + Ln.e("Secondary display rotation not supported on this device"); + return; + } method.invoke(manager, rotation); - } else { - Ln.e("Could not invoke method", e); - } + break; + default: + method.invoke(manager, displayId, rotation); + break; } } catch (ReflectiveOperationException e) { Ln.e("Could not invoke method", e); @@ -121,17 +123,16 @@ public final class WindowManager { public boolean isRotationFrozen(int displayId) { try { - try { - Method method = getIsDisplayRotationFrozenMethod(); - return (boolean) method.invoke(manager, displayId); - } catch (ReflectiveOperationException e) { - if (displayId == 0) { - Method method = getIsRotationFrozenMethod(); + Method method = getIsDisplayRotationFrozenMethod(); + switch (isDisplayRotationFrozenMethodVersion) { + case 0: + if (displayId != 0) { + Ln.e("Secondary display rotation not supported on this device"); + return false; + } return (boolean) method.invoke(manager); - } else { - Ln.e("Could not invoke method", e); - return false; - } + default: + return (boolean) method.invoke(manager, displayId); } } catch (ReflectiveOperationException e) { Ln.e("Could not invoke method", e); @@ -141,16 +142,18 @@ public final class WindowManager { public void thawRotation(int displayId) { try { - try { - Method method = getThawDisplayRotationMethod(); - method.invoke(manager, displayId); - } catch (ReflectiveOperationException e) { - if (displayId == 0) { - Method method = getThawRotationMethod(); + Method method = getThawDisplayRotationMethod(); + switch (thawDisplayRotationMethodVersion) { + case 0: + if (displayId != 0) { + Ln.e("Secondary display rotation not supported on this device"); + return; + } method.invoke(manager); - } else { - Ln.e("Could not invoke method", e); - } + break; + default: + method.invoke(manager, displayId); + break; } } catch (ReflectiveOperationException e) { Ln.e("Could not invoke method", e); From 7011dd1ef0fe0f745387251c0a97bac338ce767d Mon Sep 17 00:00:00 2001 From: Stepan Salenikovich Date: Thu, 7 Mar 2024 14:13:22 -0500 Subject: [PATCH 07/44] Fix freeze and thaw rotation for Android 14 Changed since AOSP/framework_base commit 670fb7f5c0d23cf51ead25538bcb017e03ed73ac, included in tag android-14.0.0_r29. Refs PR #4740 Signed-off-by: Romain Vimont --- .../scrcpy/wrappers/WindowManager.java | 40 ++++++++++++++----- 1 file changed, 30 insertions(+), 10 deletions(-) diff --git a/server/src/main/java/com/genymobile/scrcpy/wrappers/WindowManager.java b/server/src/main/java/com/genymobile/scrcpy/wrappers/WindowManager.java index 2fc7ee02..e1a3340a 100644 --- a/server/src/main/java/com/genymobile/scrcpy/wrappers/WindowManager.java +++ b/server/src/main/java/com/genymobile/scrcpy/wrappers/WindowManager.java @@ -52,10 +52,17 @@ public final class WindowManager { freezeDisplayRotationMethod = manager.getClass().getMethod("freezeRotation", int.class); freezeDisplayRotationMethodVersion = 0; } catch (NoSuchMethodException e) { - // New method added by this commit: - // - freezeDisplayRotationMethod = manager.getClass().getMethod("freezeDisplayRotation", int.class, int.class); - freezeDisplayRotationMethodVersion = 1; + try { + // New method added by this commit: + // + freezeDisplayRotationMethod = manager.getClass().getMethod("freezeDisplayRotation", int.class, int.class); + freezeDisplayRotationMethodVersion = 1; + } catch (NoSuchMethodException e1) { + // Android 15 preview and 14 QPR3 Beta added a String caller parameter for debugging: + // + freezeDisplayRotationMethod = manager.getClass().getMethod("freezeDisplayRotation", int.class, int.class, String.class); + freezeDisplayRotationMethodVersion = 2; + } } } return freezeDisplayRotationMethod; @@ -82,10 +89,17 @@ public final class WindowManager { thawDisplayRotationMethod = manager.getClass().getMethod("thawRotation"); thawDisplayRotationMethodVersion = 0; } catch (NoSuchMethodException e) { - // New method added by this commit: - // - thawDisplayRotationMethod = manager.getClass().getMethod("thawDisplayRotation", int.class); - thawDisplayRotationMethodVersion = 1; + try { + // New method added by this commit: + // + thawDisplayRotationMethod = manager.getClass().getMethod("thawDisplayRotation", int.class); + thawDisplayRotationMethodVersion = 1; + } catch (NoSuchMethodException e1) { + // Android 15 preview and 14 QPR3 Beta added a String caller parameter for debugging: + // + thawDisplayRotationMethod = manager.getClass().getMethod("thawDisplayRotation", int.class, String.class); + thawDisplayRotationMethodVersion = 2; + } } } return thawDisplayRotationMethod; @@ -112,9 +126,12 @@ public final class WindowManager { } method.invoke(manager, rotation); break; - default: + case 1: method.invoke(manager, displayId, rotation); break; + default: + method.invoke(manager, displayId, rotation, "scrcpy#freezeRotation"); + break; } } catch (ReflectiveOperationException e) { Ln.e("Could not invoke method", e); @@ -151,9 +168,12 @@ public final class WindowManager { } method.invoke(manager); break; - default: + case 1: method.invoke(manager, displayId); break; + default: + method.invoke(manager, displayId, "scrcpy#thawRotation"); + break; } } catch (ReflectiveOperationException e) { Ln.e("Could not invoke method", e); From a73bf932d61a1677f8898ba28adbffafacc97886 Mon Sep 17 00:00:00 2001 From: Kaiming Hu Date: Fri, 12 Apr 2024 14:16:42 +0800 Subject: [PATCH 08/44] Fix could not rotate secondary display The version of the methods with the display id parameter must be tried first, otherwise they will never be used (since the old versions without the display id are still present). Regression introduced by ee6620d123e87d4af8e51cd272de5eafb677122a. Refs #4740 PR #4841 Signed-off-by: Romain Vimont --- .../scrcpy/wrappers/WindowManager.java | 50 +++++++++---------- 1 file changed, 25 insertions(+), 25 deletions(-) diff --git a/server/src/main/java/com/genymobile/scrcpy/wrappers/WindowManager.java b/server/src/main/java/com/genymobile/scrcpy/wrappers/WindowManager.java index e1a3340a..ae1468f4 100644 --- a/server/src/main/java/com/genymobile/scrcpy/wrappers/WindowManager.java +++ b/server/src/main/java/com/genymobile/scrcpy/wrappers/WindowManager.java @@ -49,7 +49,9 @@ public final class WindowManager { private Method getFreezeDisplayRotationMethod() throws NoSuchMethodException { if (freezeDisplayRotationMethod == null) { try { - freezeDisplayRotationMethod = manager.getClass().getMethod("freezeRotation", int.class); + // Android 15 preview and 14 QPR3 Beta added a String caller parameter for debugging: + // + freezeDisplayRotationMethod = manager.getClass().getMethod("freezeDisplayRotation", int.class, int.class, String.class); freezeDisplayRotationMethodVersion = 0; } catch (NoSuchMethodException e) { try { @@ -58,9 +60,7 @@ public final class WindowManager { freezeDisplayRotationMethod = manager.getClass().getMethod("freezeDisplayRotation", int.class, int.class); freezeDisplayRotationMethodVersion = 1; } catch (NoSuchMethodException e1) { - // Android 15 preview and 14 QPR3 Beta added a String caller parameter for debugging: - // - freezeDisplayRotationMethod = manager.getClass().getMethod("freezeDisplayRotation", int.class, int.class, String.class); + freezeDisplayRotationMethod = manager.getClass().getMethod("freezeRotation", int.class); freezeDisplayRotationMethodVersion = 2; } } @@ -71,12 +71,12 @@ public final class WindowManager { private Method getIsDisplayRotationFrozenMethod() throws NoSuchMethodException { if (isDisplayRotationFrozenMethod == null) { try { - isDisplayRotationFrozenMethod = manager.getClass().getMethod("isRotationFrozen"); - isDisplayRotationFrozenMethodVersion = 0; - } catch (NoSuchMethodException e) { // New method added by this commit: // isDisplayRotationFrozenMethod = manager.getClass().getMethod("isDisplayRotationFrozen", int.class); + isDisplayRotationFrozenMethodVersion = 0; + } catch (NoSuchMethodException e) { + isDisplayRotationFrozenMethod = manager.getClass().getMethod("isRotationFrozen"); isDisplayRotationFrozenMethodVersion = 1; } } @@ -86,7 +86,9 @@ public final class WindowManager { private Method getThawDisplayRotationMethod() throws NoSuchMethodException { if (thawDisplayRotationMethod == null) { try { - thawDisplayRotationMethod = manager.getClass().getMethod("thawRotation"); + // Android 15 preview and 14 QPR3 Beta added a String caller parameter for debugging: + // + thawDisplayRotationMethod = manager.getClass().getMethod("thawDisplayRotation", int.class, String.class); thawDisplayRotationMethodVersion = 0; } catch (NoSuchMethodException e) { try { @@ -95,9 +97,7 @@ public final class WindowManager { thawDisplayRotationMethod = manager.getClass().getMethod("thawDisplayRotation", int.class); thawDisplayRotationMethodVersion = 1; } catch (NoSuchMethodException e1) { - // Android 15 preview and 14 QPR3 Beta added a String caller parameter for debugging: - // - thawDisplayRotationMethod = manager.getClass().getMethod("thawDisplayRotation", int.class, String.class); + thawDisplayRotationMethod = manager.getClass().getMethod("thawRotation"); thawDisplayRotationMethodVersion = 2; } } @@ -120,17 +120,17 @@ public final class WindowManager { Method method = getFreezeDisplayRotationMethod(); switch (freezeDisplayRotationMethodVersion) { case 0: - if (displayId != 0) { - Ln.e("Secondary display rotation not supported on this device"); - return; - } - method.invoke(manager, rotation); + method.invoke(manager, displayId, rotation, "scrcpy#freezeRotation"); break; case 1: method.invoke(manager, displayId, rotation); break; default: - method.invoke(manager, displayId, rotation, "scrcpy#freezeRotation"); + if (displayId != 0) { + Ln.e("Secondary display rotation not supported on this device"); + return; + } + method.invoke(manager, rotation); break; } } catch (ReflectiveOperationException e) { @@ -143,13 +143,13 @@ public final class WindowManager { Method method = getIsDisplayRotationFrozenMethod(); switch (isDisplayRotationFrozenMethodVersion) { case 0: + return (boolean) method.invoke(manager, displayId); + default: if (displayId != 0) { Ln.e("Secondary display rotation not supported on this device"); return false; } return (boolean) method.invoke(manager); - default: - return (boolean) method.invoke(manager, displayId); } } catch (ReflectiveOperationException e) { Ln.e("Could not invoke method", e); @@ -162,17 +162,17 @@ public final class WindowManager { Method method = getThawDisplayRotationMethod(); switch (thawDisplayRotationMethodVersion) { case 0: - if (displayId != 0) { - Ln.e("Secondary display rotation not supported on this device"); - return; - } - method.invoke(manager); + method.invoke(manager, displayId, "scrcpy#thawRotation"); break; case 1: method.invoke(manager, displayId); break; default: - method.invoke(manager, displayId, "scrcpy#thawRotation"); + if (displayId != 0) { + Ln.e("Secondary display rotation not supported on this device"); + return; + } + method.invoke(manager); break; } } catch (ReflectiveOperationException e) { From bd8b945bb321ac73b00353c7bea74b0a5292d9a3 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Fri, 12 Apr 2024 17:22:45 +0200 Subject: [PATCH 09/44] Register rotation watcher only when possible Old Android versions may not be able to register a rotation watcher for a secondary display. In that case, report the error instead of registering a rotation watcher for the default display. Refs Suggested by: Kaiming Hu --- .../java/com/genymobile/scrcpy/wrappers/WindowManager.java | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/server/src/main/java/com/genymobile/scrcpy/wrappers/WindowManager.java b/server/src/main/java/com/genymobile/scrcpy/wrappers/WindowManager.java index ae1468f4..44394ba9 100644 --- a/server/src/main/java/com/genymobile/scrcpy/wrappers/WindowManager.java +++ b/server/src/main/java/com/genymobile/scrcpy/wrappers/WindowManager.java @@ -189,6 +189,10 @@ public final class WindowManager { cls.getMethod("watchRotation", IRotationWatcher.class, int.class).invoke(manager, rotationWatcher, displayId); } catch (NoSuchMethodException e) { // old version + if (displayId != 0) { + Ln.e("Secondary display rotation not supported on this device"); + return; + } cls.getMethod("watchRotation", IRotationWatcher.class).invoke(manager, rotationWatcher); } } catch (Exception e) { From 54e08b4eaef3fb6b603332e6aa67f95a9627ea51 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Sun, 7 Apr 2024 15:56:49 +0200 Subject: [PATCH 10/44] Fix code style Limit to 80 columns. --- app/src/cli.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/app/src/cli.c b/app/src/cli.c index 92807947..3f65b5f0 100644 --- a/app/src/cli.c +++ b/app/src/cli.c @@ -2556,9 +2556,9 @@ parse_args_with_getopt(struct scrcpy_cli_args *args, int argc, char *argv[], if (opts->audio_playback && opts->audio_buffer == -1) { if (opts->audio_codec == SC_CODEC_FLAC) { - // Use 50 ms audio buffer by default, but use a higher value for FLAC, - // which is not low latency (the default encoder produces blocks of - // 4096 samples, which represent ~85.333ms). + // Use 50 ms audio buffer by default, but use a higher value for + // FLAC, which is not low latency (the default encoder produces + // blocks of 4096 samples, which represent ~85.333ms). LOGI("FLAC audio: audio buffer increased to 120 ms (use " "--audio-buffer to set a custom value)"); opts->audio_buffer = SC_TICK_FROM_MS(120); From 9aa6cc71be3116be4195e3df0bc0b13ae038a944 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Sun, 7 Apr 2024 15:58:55 +0200 Subject: [PATCH 11/44] Forbid --no-control in OTG mode The whole purpose of OTG is to only control the device. --- app/src/cli.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/app/src/cli.c b/app/src/cli.c index 3f65b5f0..89347651 100644 --- a/app/src/cli.c +++ b/app/src/cli.c @@ -2598,6 +2598,11 @@ parse_args_with_getopt(struct scrcpy_cli_args *args, int argc, char *argv[], } if (otg) { + if (!opts->control) { + LOGE("--no-control is not allowed in OTG mode"); + return false; + } + enum sc_keyboard_input_mode kmode = opts->keyboard_input_mode; if (kmode != SC_KEYBOARD_INPUT_MODE_AOA && kmode != SC_KEYBOARD_INPUT_MODE_DISABLED) { From bcb8503b261969c1ec176c7b852f9e0a4924db7b Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Wed, 17 Apr 2024 10:43:48 +0200 Subject: [PATCH 12/44] Handle reported camera sizes array is null The array of sizes may be null. Handle this case gracefully. Fixes #4852 --- .../main/java/com/genymobile/scrcpy/CameraCapture.java | 4 ++++ .../src/main/java/com/genymobile/scrcpy/LogUtils.java | 10 +++++++--- 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/server/src/main/java/com/genymobile/scrcpy/CameraCapture.java b/server/src/main/java/com/genymobile/scrcpy/CameraCapture.java index a1003829..df3cf7c4 100644 --- a/server/src/main/java/com/genymobile/scrcpy/CameraCapture.java +++ b/server/src/main/java/com/genymobile/scrcpy/CameraCapture.java @@ -127,6 +127,10 @@ public class CameraCapture extends SurfaceCapture { StreamConfigurationMap configs = characteristics.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP); android.util.Size[] sizes = highSpeed ? configs.getHighSpeedVideoSizes() : configs.getOutputSizes(MediaCodec.class); + if (sizes == null) { + return null; + } + Stream stream = Arrays.stream(sizes); if (maxSize > 0) { stream = stream.filter(it -> it.getWidth() <= maxSize && it.getHeight() <= maxSize); diff --git a/server/src/main/java/com/genymobile/scrcpy/LogUtils.java b/server/src/main/java/com/genymobile/scrcpy/LogUtils.java index efa0672b..1ffb19d3 100644 --- a/server/src/main/java/com/genymobile/scrcpy/LogUtils.java +++ b/server/src/main/java/com/genymobile/scrcpy/LogUtils.java @@ -118,12 +118,16 @@ public final class LogUtils { StreamConfigurationMap configs = characteristics.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP); android.util.Size[] sizes = configs.getOutputSizes(MediaCodec.class); - for (android.util.Size size : sizes) { - builder.append("\n - ").append(size.getWidth()).append('x').append(size.getHeight()); + if (sizes == null || sizes.length == 0) { + builder.append("\n (none)"); + } else { + for (android.util.Size size : sizes) { + builder.append("\n - ").append(size.getWidth()).append('x').append(size.getHeight()); + } } android.util.Size[] highSpeedSizes = configs.getHighSpeedVideoSizes(); - if (highSpeedSizes.length > 0) { + if (highSpeedSizes != null && highSpeedSizes.length > 0) { builder.append("\n High speed capture (--camera-high-speed):"); for (android.util.Size size : highSpeedSizes) { Range[] highFpsRanges = configs.getHighSpeedVideoFpsRanges(); From 22d78e8a82bc3d6d1c18a1cc419be536201a003c Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Fri, 19 Apr 2024 12:49:03 +0200 Subject: [PATCH 13/44] Fix boolean condition Use the short-circuit operator && between booleans. --- app/src/input_manager.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/src/input_manager.c b/app/src/input_manager.c index c7a758f4..cb606d40 100644 --- a/app/src/input_manager.c +++ b/app/src/input_manager.c @@ -468,7 +468,7 @@ sc_input_manager_process_key(struct sc_input_manager *im, return; case SDLK_DOWN: if (shift) { - if (!repeat & down) { + if (!repeat && down) { apply_orientation_transform(im, SC_ORIENTATION_FLIP_180); } @@ -479,7 +479,7 @@ sc_input_manager_process_key(struct sc_input_manager *im, return; case SDLK_UP: if (shift) { - if (!repeat & down) { + if (!repeat && down) { apply_orientation_transform(im, SC_ORIENTATION_FLIP_180); } From cca2c9ffb7a7d400c20ee3a9962462ed4631d6db Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Fri, 19 Apr 2024 12:57:04 +0200 Subject: [PATCH 14/44] Disable FPS counter when no video playback There is no frame rate to count. --- app/src/cli.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/app/src/cli.c b/app/src/cli.c index 89347651..b1cc62ac 100644 --- a/app/src/cli.c +++ b/app/src/cli.c @@ -2811,6 +2811,11 @@ parse_args_with_getopt(struct scrcpy_cli_args *args, int argc, char *argv[], } # endif + if (opts->start_fps_counter && !opts->video_playback) { + LOGW("--print-fps has no effect without video playback"); + opts->start_fps_counter = false; + } + if (otg) { // OTG mode is compatible with only very few options. // Only report obvious errors. From 45fe6b602b4c050c5b1fba87cec7160093052af3 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Sun, 7 Apr 2024 16:01:26 +0200 Subject: [PATCH 15/44] Add scrcpy window without video playback Add the possibility to solely control the device without screen mirroring: scrcpy --no-video --no-audio This is different from OTG mode, which does not require USB debugging at all. Here, the standard mode is used but with the possibility to disable video playback. By default, always open a window (even without video playback), and add an option --no-window. Fixes #4727 Fixes #4793 PR #4868 --- app/scrcpy.1 | 4 ++ app/src/cli.c | 51 ++++++++++++++--- app/src/display.c | 36 +++++++++++- app/src/display.h | 3 +- app/src/input_manager.c | 59 ++++++++++--------- app/src/options.c | 1 + app/src/options.h | 1 + app/src/scrcpy.c | 26 +++++---- app/src/screen.c | 123 ++++++++++++++++++++++++++++++++++------ app/src/screen.h | 4 ++ 10 files changed, 243 insertions(+), 65 deletions(-) diff --git a/app/scrcpy.1 b/app/scrcpy.1 index eb09f530..f9ef3498 100644 --- a/app/scrcpy.1 +++ b/app/scrcpy.1 @@ -316,6 +316,10 @@ Disable video forwarding. .B \-\-no\-video\-playback Disable video playback on the computer. +.TP +.B \-\-no\-window +Disable scrcpy window. Implies --no-video-playback and --no-control. + .TP .BI "\-\-orientation " value Same as --display-orientation=value --record-orientation=value. diff --git a/app/src/cli.c b/app/src/cli.c index b1cc62ac..0caeea5c 100644 --- a/app/src/cli.c +++ b/app/src/cli.c @@ -97,6 +97,7 @@ enum { OPT_MOUSE, OPT_HID_KEYBOARD_DEPRECATED, OPT_HID_MOUSE_DEPRECATED, + OPT_NO_WINDOW, }; struct sc_option { @@ -566,6 +567,12 @@ static const struct sc_option options[] = { .longopt = "no-video-playback", .text = "Disable video playback on the computer.", }, + { + .longopt_id = OPT_NO_WINDOW, + .longopt = "no-window", + .text = "Disable scrcpy window. Implies --no-video-playback and " + "--no-control.", + }, { .longopt_id = OPT_ORIENTATION, .longopt = "orientation", @@ -2486,6 +2493,9 @@ parse_args_with_getopt(struct scrcpy_cli_args *args, int argc, char *argv[], case OPT_CAMERA_HIGH_SPEED: opts->camera_high_speed = true; break; + case OPT_NO_WINDOW: + opts->window = false; + break; default: // getopt prints the error message on stderr return false; @@ -2523,6 +2533,12 @@ parse_args_with_getopt(struct scrcpy_cli_args *args, int argc, char *argv[], v4l2 = !!opts->v4l2_device; #endif + if (!opts->window) { + // Without window, there cannot be any video playback or control + opts->video_playback = false; + opts->control = false; + } + if (!opts->video) { opts->video_playback = false; // Do not power on the device on start if video capture is disabled @@ -2544,8 +2560,8 @@ parse_args_with_getopt(struct scrcpy_cli_args *args, int argc, char *argv[], opts->audio = false; } - if (!opts->video && !opts->audio && !otg) { - LOGE("No video, no audio, no OTG: nothing to do"); + if (!opts->video && !opts->audio && !opts->control && !otg) { + LOGE("No video, no audio, no control, no OTG: nothing to do"); return false; } @@ -2569,6 +2585,11 @@ parse_args_with_getopt(struct scrcpy_cli_args *args, int argc, char *argv[], #ifdef HAVE_V4L2 if (v4l2) { + if (!opts->video) { + LOGE("V4L2 sink requires video capture, but --no-video was set."); + return false; + } + if (opts->lock_video_orientation == SC_LOCK_VIDEO_ORIENTATION_UNLOCKED) { LOGI("Video orientation is locked for v4l2 sink. " @@ -2588,13 +2609,25 @@ parse_args_with_getopt(struct scrcpy_cli_args *args, int argc, char *argv[], } #endif - if (opts->keyboard_input_mode == SC_KEYBOARD_INPUT_MODE_AUTO) { - opts->keyboard_input_mode = otg ? SC_KEYBOARD_INPUT_MODE_AOA - : SC_KEYBOARD_INPUT_MODE_SDK; - } - if (opts->mouse_input_mode == SC_MOUSE_INPUT_MODE_AUTO) { - opts->mouse_input_mode = otg ? SC_MOUSE_INPUT_MODE_AOA - : SC_MOUSE_INPUT_MODE_SDK; + if (opts->control) { + if (opts->keyboard_input_mode == SC_KEYBOARD_INPUT_MODE_AUTO) { + opts->keyboard_input_mode = otg ? SC_KEYBOARD_INPUT_MODE_AOA + : SC_KEYBOARD_INPUT_MODE_SDK; + } + if (opts->mouse_input_mode == SC_MOUSE_INPUT_MODE_AUTO) { + if (otg) { + opts->mouse_input_mode = SC_MOUSE_INPUT_MODE_AOA; + } else if (!opts->video_playback) { + LOGI("No video mirroring, mouse mode switched to UHID"); + opts->mouse_input_mode = SC_MOUSE_INPUT_MODE_UHID; + } else { + opts->mouse_input_mode = SC_MOUSE_INPUT_MODE_SDK; + } + } else if (opts->mouse_input_mode == SC_MOUSE_INPUT_MODE_SDK + && !opts->video_playback) { + LOGE("SDK mouse mode requires video playback. Try --mouse=uhid."); + return false; + } } if (otg) { diff --git a/app/src/display.c b/app/src/display.c index 25c23265..9f5fb0c6 100644 --- a/app/src/display.c +++ b/app/src/display.c @@ -5,8 +5,30 @@ #include "util/log.h" +static bool +sc_display_init_novideo_icon(struct sc_display *display, + SDL_Surface *icon_novideo) { + assert(icon_novideo); + + if (SDL_RenderSetLogicalSize(display->renderer, + icon_novideo->w, icon_novideo->h)) { + LOGW("Could not set renderer logical size: %s", SDL_GetError()); + // don't fail + } + + display->texture = SDL_CreateTextureFromSurface(display->renderer, + icon_novideo); + if (!display->texture) { + LOGE("Could not create texture: %s", SDL_GetError()); + return false; + } + + return true; +} + bool -sc_display_init(struct sc_display *display, SDL_Window *window, bool mipmaps) { +sc_display_init(struct sc_display *display, SDL_Window *window, + SDL_Surface *icon_novideo, bool mipmaps) { display->renderer = SDL_CreateRenderer(window, -1, SDL_RENDERER_ACCELERATED); if (!display->renderer) { @@ -68,6 +90,18 @@ sc_display_init(struct sc_display *display, SDL_Window *window, bool mipmaps) { display->pending.frame = NULL; display->has_frame = false; + if (icon_novideo) { + // Without video, set a static scrcpy icon as window content + bool ok = sc_display_init_novideo_icon(display, icon_novideo); + if (!ok) { +#ifdef SC_DISPLAY_FORCE_OPENGL_CORE_PROFILE + SDL_GL_DeleteContext(display->gl_context); +#endif + SDL_DestroyRenderer(display->renderer); + return false; + } + } + return true; } diff --git a/app/src/display.h b/app/src/display.h index 590715ee..064bb7bf 100644 --- a/app/src/display.h +++ b/app/src/display.h @@ -44,7 +44,8 @@ enum sc_display_result { }; bool -sc_display_init(struct sc_display *display, SDL_Window *window, bool mipmaps); +sc_display_init(struct sc_display *display, SDL_Window *window, + SDL_Surface *icon_novideo, bool mipmaps); void sc_display_destroy(struct sc_display *display); diff --git a/app/src/input_manager.c b/app/src/input_manager.c index cb606d40..3a5fc6ed 100644 --- a/app/src/input_manager.c +++ b/app/src/input_manager.c @@ -403,6 +403,7 @@ sc_input_manager_process_key(struct sc_input_manager *im, // controller is NULL if --no-control is requested bool control = im->controller; bool paused = im->screen->paused; + bool video = im->screen->video; SDL_Keycode keycode = event->keysym.sym; uint16_t mod = event->keysym.mod; @@ -462,13 +463,13 @@ sc_input_manager_process_key(struct sc_input_manager *im, } return; case SDLK_z: - if (down && !repeat) { + if (video && down && !repeat) { sc_screen_set_paused(im->screen, !shift); } return; case SDLK_DOWN: if (shift) { - if (!repeat && down) { + if (video && !repeat && down) { apply_orientation_transform(im, SC_ORIENTATION_FLIP_180); } @@ -479,7 +480,7 @@ sc_input_manager_process_key(struct sc_input_manager *im, return; case SDLK_UP: if (shift) { - if (!repeat && down) { + if (video && !repeat && down) { apply_orientation_transform(im, SC_ORIENTATION_FLIP_180); } @@ -489,7 +490,7 @@ sc_input_manager_process_key(struct sc_input_manager *im, } return; case SDLK_LEFT: - if (!repeat && down) { + if (video && !repeat && down) { if (shift) { apply_orientation_transform(im, SC_ORIENTATION_FLIP_0); @@ -500,7 +501,7 @@ sc_input_manager_process_key(struct sc_input_manager *im, } return; case SDLK_RIGHT: - if (!repeat && down) { + if (video && !repeat && down) { if (shift) { apply_orientation_transform(im, SC_ORIENTATION_FLIP_0); @@ -533,22 +534,22 @@ sc_input_manager_process_key(struct sc_input_manager *im, } return; case SDLK_f: - if (!shift && !repeat && down) { + if (video && !shift && !repeat && down) { sc_screen_switch_fullscreen(im->screen); } return; case SDLK_w: - if (!shift && !repeat && down) { + if (video && !shift && !repeat && down) { sc_screen_resize_to_fit(im->screen); } return; case SDLK_g: - if (!shift && !repeat && down) { + if (video && !shift && !repeat && down) { sc_screen_resize_to_pixel_perfect(im->screen); } return; case SDLK_i: - if (!shift && !repeat && down) { + if (video && !shift && !repeat && down) { switch_fps_counter_state(im); } return; @@ -625,6 +626,23 @@ sc_input_manager_process_key(struct sc_input_manager *im, im->kp->ops->process_key(im->kp, &evt, ack_to_wait); } +static struct sc_position +sc_input_manager_get_position(struct sc_input_manager *im, int32_t x, + int32_t y) { + if (im->mp->relative_mode) { + // No absolute position + return (struct sc_position) { + .screen_size = {0, 0}, + .point = {0, 0}, + }; + } + + return (struct sc_position) { + .screen_size = im->screen->frame_size, + .point = sc_screen_convert_window_to_frame_coords(im->screen, x, y), + }; +} + static void sc_input_manager_process_mouse_motion(struct sc_input_manager *im, const SDL_MouseMotionEvent *event) { @@ -634,12 +652,7 @@ sc_input_manager_process_mouse_motion(struct sc_input_manager *im, } struct sc_mouse_motion_event evt = { - .position = { - .screen_size = im->screen->frame_size, - .point = sc_screen_convert_window_to_frame_coords(im->screen, - event->x, - event->y), - }, + .position = sc_input_manager_get_position(im, event->x, event->y), .pointer_id = im->forward_all_clicks ? POINTER_ID_MOUSE : POINTER_ID_GENERIC_FINGER, .xrel = event->xrel, @@ -735,7 +748,8 @@ sc_input_manager_process_mouse_button(struct sc_input_manager *im, } // double-click on black borders resize to fit the device screen - if (event->button == SDL_BUTTON_LEFT && event->clicks == 2) { + bool video = im->screen->video; + if (video && event->button == SDL_BUTTON_LEFT && event->clicks == 2) { int32_t x = event->x; int32_t y = event->y; sc_screen_hidpi_scale_coords(im->screen, &x, &y); @@ -759,12 +773,7 @@ sc_input_manager_process_mouse_button(struct sc_input_manager *im, uint32_t sdl_buttons_state = SDL_GetMouseState(NULL, NULL); struct sc_mouse_click_event evt = { - .position = { - .screen_size = im->screen->frame_size, - .point = sc_screen_convert_window_to_frame_coords(im->screen, - event->x, - event->y), - }, + .position = sc_input_manager_get_position(im, event->x, event->y), .action = sc_action_from_sdl_mousebutton_type(event->type), .button = sc_mouse_button_from_sdl(event->button), .pointer_id = im->forward_all_clicks ? POINTER_ID_MOUSE @@ -839,11 +848,7 @@ sc_input_manager_process_mouse_wheel(struct sc_input_manager *im, uint32_t buttons = SDL_GetMouseState(&mouse_x, &mouse_y); struct sc_mouse_scroll_event evt = { - .position = { - .screen_size = im->screen->frame_size, - .point = sc_screen_convert_window_to_frame_coords(im->screen, - mouse_x, mouse_y), - }, + .position = sc_input_manager_get_position(im, mouse_x, mouse_y), #if SDL_VERSION_ATLEAST(2, 0, 18) .hscroll = CLAMP(event->preciseX, -1.0f, 1.0f), .vscroll = CLAMP(event->preciseY, -1.0f, 1.0f), diff --git a/app/src/options.c b/app/src/options.c index 7a885aa5..d6bf9158 100644 --- a/app/src/options.c +++ b/app/src/options.c @@ -89,6 +89,7 @@ const struct scrcpy_options scrcpy_options_default = { .kill_adb_on_close = false, .camera_high_speed = false, .list = 0, + .window = true, }; enum sc_orientation diff --git a/app/src/options.h b/app/src/options.h index 5445e7c8..1fb61ddf 100644 --- a/app/src/options.h +++ b/app/src/options.h @@ -279,6 +279,7 @@ struct scrcpy_options { #define SC_OPTION_LIST_CAMERAS 0x4 #define SC_OPTION_LIST_CAMERA_SIZES 0x8 uint8_t list; + bool window; }; extern const struct scrcpy_options scrcpy_options_default; diff --git a/app/src/scrcpy.c b/app/src/scrcpy.c index 537562f4..5e7b19fd 100644 --- a/app/src/scrcpy.c +++ b/app/src/scrcpy.c @@ -408,7 +408,7 @@ scrcpy(struct scrcpy_options *options) { return SCRCPY_EXIT_FAILURE; } - if (options->video_playback) { + if (options->window) { // Set hints before starting the server thread to avoid race conditions // in SDL sdl_set_hints(options->render_driver); @@ -430,7 +430,7 @@ scrcpy(struct scrcpy_options *options) { assert(!options->video_playback || options->video); assert(!options->audio_playback || options->audio); - if (options->video_playback || + if (options->window || (options->control && options->clipboard_autosync)) { // Initialize the video subsystem even if --no-video or // --no-video-playback is passed so that clipboard synchronization @@ -684,11 +684,12 @@ scrcpy(struct scrcpy_options *options) { // There is a controller if and only if control is enabled assert(options->control == !!controller); - if (options->video_playback) { + if (options->window) { const char *window_title = options->window_title ? options->window_title : info->device_name; struct sc_screen_params screen_params = { + .video = options->video_playback, .controller = controller, .fp = fp, .kp = kp, @@ -710,12 +711,15 @@ scrcpy(struct scrcpy_options *options) { .start_fps_counter = options->start_fps_counter, }; - struct sc_frame_source *src = &s->video_decoder.frame_source; - if (options->display_buffer) { - sc_delay_buffer_init(&s->display_buffer, options->display_buffer, - true); - sc_frame_source_add_sink(src, &s->display_buffer.frame_sink); - src = &s->display_buffer.frame_source; + struct sc_frame_source *src; + if (options->video_playback) { + src = &s->video_decoder.frame_source; + if (options->display_buffer) { + sc_delay_buffer_init(&s->display_buffer, + options->display_buffer, true); + sc_frame_source_add_sink(src, &s->display_buffer.frame_sink); + src = &s->display_buffer.frame_source; + } } if (!sc_screen_init(&s->screen, &screen_params)) { @@ -723,7 +727,9 @@ scrcpy(struct scrcpy_options *options) { } screen_initialized = true; - sc_frame_source_add_sink(src, &s->screen.frame_sink); + if (options->video_playback) { + sc_frame_source_add_sink(src, &s->screen.frame_sink); + } } if (options->audio_playback) { diff --git a/app/src/screen.c b/app/src/screen.c index 351eb3fb..56f13f99 100644 --- a/app/src/screen.c +++ b/app/src/screen.c @@ -205,6 +205,8 @@ sc_screen_toggle_mouse_capture(struct sc_screen *screen) { static void sc_screen_update_content_rect(struct sc_screen *screen) { + assert(screen->video); + int dw; int dh; SDL_GL_GetDrawableSize(screen->window, &dw, &dh); @@ -246,6 +248,8 @@ sc_screen_update_content_rect(struct sc_screen *screen) { // changed, so that the content rectangle is recomputed static void sc_screen_render(struct sc_screen *screen, bool update_content_rect) { + assert(screen->video); + if (update_content_rect) { sc_screen_update_content_rect(screen); } @@ -255,6 +259,13 @@ sc_screen_render(struct sc_screen *screen, bool update_content_rect) { (void) res; // any error already logged } +static void +sc_screen_render_novideo(struct sc_screen *screen) { + enum sc_display_result res = + sc_display_render(&screen->display, NULL, SC_ORIENTATION_0); + (void) res; // any error already logged +} + #if defined(__APPLE__) || defined(__WINDOWS__) # define CONTINUOUS_RESIZING_WORKAROUND #endif @@ -268,6 +279,8 @@ sc_screen_render(struct sc_screen *screen, bool update_content_rect) { static int event_watcher(void *data, SDL_Event *event) { struct sc_screen *screen = data; + assert(screen->video); + if (event->type == SDL_WINDOWEVENT && event->window.event == SDL_WINDOWEVENT_RESIZED) { // In practice, it seems to always be called from the same thread in @@ -326,6 +339,7 @@ sc_screen_frame_sink_close(struct sc_frame_sink *sink) { static bool sc_screen_frame_sink_push(struct sc_frame_sink *sink, const AVFrame *frame) { struct sc_screen *screen = DOWNCAST(sink); + assert(screen->video); bool previous_skipped; bool ok = sc_frame_buffer_push(&screen->fb, frame, &previous_skipped); @@ -364,6 +378,9 @@ sc_screen_init(struct sc_screen *screen, screen->mouse_capture_key_pressed = 0; screen->paused = false; screen->resume_frame = NULL; + screen->orientation = SC_ORIENTATION_0; + + screen->video = params->video; screen->req.x = params->window_x; screen->req.y = params->window_y; @@ -381,41 +398,75 @@ sc_screen_init(struct sc_screen *screen, goto error_destroy_frame_buffer; } - screen->orientation = params->orientation; - if (screen->orientation != SC_ORIENTATION_0) { - LOGI("Initial display orientation set to %s", - sc_orientation_get_name(screen->orientation)); + if (screen->video) { + screen->orientation = params->orientation; + if (screen->orientation != SC_ORIENTATION_0) { + LOGI("Initial display orientation set to %s", + sc_orientation_get_name(screen->orientation)); + } } - uint32_t window_flags = SDL_WINDOW_HIDDEN - | SDL_WINDOW_RESIZABLE - | SDL_WINDOW_ALLOW_HIGHDPI; + uint32_t window_flags = SDL_WINDOW_ALLOW_HIGHDPI; if (params->always_on_top) { window_flags |= SDL_WINDOW_ALWAYS_ON_TOP; } if (params->window_borderless) { window_flags |= SDL_WINDOW_BORDERLESS; } + if (params->video) { + // The window will be shown on first frame + window_flags |= SDL_WINDOW_HIDDEN + | SDL_WINDOW_RESIZABLE; + } + + const char *title = params->window_title; + assert(title); + + int x = SDL_WINDOWPOS_UNDEFINED; + int y = SDL_WINDOWPOS_UNDEFINED; + int width = 256; + int height = 256; + if (params->window_x != SC_WINDOW_POSITION_UNDEFINED) { + x = params->window_x; + } + if (params->window_y != SC_WINDOW_POSITION_UNDEFINED) { + y = params->window_y; + } + if (params->window_width) { + width = params->window_width; + } + if (params->window_height) { + height = params->window_height; + } // The window will be positioned and sized on first video frame - screen->window = - SDL_CreateWindow(params->window_title, 0, 0, 0, 0, window_flags); + screen->window = SDL_CreateWindow(title, x, y, width, height, window_flags); if (!screen->window) { LOGE("Could not create window: %s", SDL_GetError()); goto error_destroy_fps_counter; } - ok = sc_display_init(&screen->display, screen->window, params->mipmaps); - if (!ok) { - goto error_destroy_window; - } - SDL_Surface *icon = scrcpy_icon_load(); if (icon) { SDL_SetWindowIcon(screen->window, icon); - scrcpy_icon_destroy(icon); - } else { + } else if (params->video) { + // just a warning LOGW("Could not load icon"); + } else { + // without video, the icon is used as window content, it must be present + LOGE("Could not load icon"); + goto error_destroy_fps_counter; + } + + SDL_Surface *icon_novideo = params->video ? NULL : icon; + bool mipmaps = params->video && params->mipmaps; + ok = sc_display_init(&screen->display, screen->window, icon_novideo, + mipmaps); + if (icon) { + scrcpy_icon_destroy(icon); + } + if (!ok) { + goto error_destroy_window; } screen->frame = av_frame_alloc(); @@ -439,7 +490,9 @@ sc_screen_init(struct sc_screen *screen, sc_input_manager_init(&screen->im, &im_params); #ifdef CONTINUOUS_RESIZING_WORKAROUND - SDL_AddEventWatch(event_watcher, screen); + if (screen->video) { + SDL_AddEventWatch(event_watcher, screen); + } #endif static const struct sc_frame_sink_ops ops = { @@ -454,6 +507,11 @@ sc_screen_init(struct sc_screen *screen, screen->open = false; #endif + if (!screen->video && sc_screen_is_relative_mode(screen)) { + // Capture mouse immediately if video mirroring is disabled + sc_screen_set_mouse_capture(screen, true); + } + return true; error_destroy_display: @@ -524,6 +582,8 @@ sc_screen_destroy(struct sc_screen *screen) { static void resize_for_content(struct sc_screen *screen, struct sc_size old_content_size, struct sc_size new_content_size) { + assert(screen->video); + struct sc_size window_size = get_window_size(screen); struct sc_size target_size = { .width = (uint32_t) window_size.width * new_content_size.width @@ -537,6 +597,8 @@ resize_for_content(struct sc_screen *screen, struct sc_size old_content_size, static void set_content_size(struct sc_screen *screen, struct sc_size new_content_size) { + assert(screen->video); + if (!screen->fullscreen && !screen->maximized && !screen->minimized) { resize_for_content(screen, screen->content_size, new_content_size); } else if (!screen->resize_pending) { @@ -551,6 +613,8 @@ set_content_size(struct sc_screen *screen, struct sc_size new_content_size) { static void apply_pending_resize(struct sc_screen *screen) { + assert(screen->video); + assert(!screen->fullscreen); assert(!screen->maximized); assert(!screen->minimized); @@ -564,6 +628,8 @@ apply_pending_resize(struct sc_screen *screen) { void sc_screen_set_orientation(struct sc_screen *screen, enum sc_orientation orientation) { + assert(screen->video); + if (orientation == screen->orientation) { return; } @@ -598,6 +664,8 @@ sc_screen_init_size(struct sc_screen *screen) { // recreate the texture and resize the window if the frame size has changed static enum sc_display_result prepare_for_frame(struct sc_screen *screen, struct sc_size new_frame_size) { + assert(screen->video); + if (screen->frame_size.width == new_frame_size.width && screen->frame_size.height == new_frame_size.height) { return SC_DISPLAY_RESULT_OK; @@ -617,6 +685,8 @@ prepare_for_frame(struct sc_screen *screen, struct sc_size new_frame_size) { static bool sc_screen_apply_frame(struct sc_screen *screen) { + assert(screen->video); + sc_fps_counter_add_rendered_frame(&screen->fps_counter); AVFrame *frame = screen->frame; @@ -656,6 +726,8 @@ sc_screen_apply_frame(struct sc_screen *screen) { static bool sc_screen_update_frame(struct sc_screen *screen) { + assert(screen->video); + if (screen->paused) { if (!screen->resume_frame) { screen->resume_frame = av_frame_alloc(); @@ -677,6 +749,8 @@ sc_screen_update_frame(struct sc_screen *screen) { void sc_screen_set_paused(struct sc_screen *screen, bool paused) { + assert(screen->video); + if (!paused && !screen->paused) { // nothing to do return; @@ -704,6 +778,8 @@ sc_screen_set_paused(struct sc_screen *screen, bool paused) { void sc_screen_switch_fullscreen(struct sc_screen *screen) { + assert(screen->video); + uint32_t new_mode = screen->fullscreen ? 0 : SDL_WINDOW_FULLSCREEN_DESKTOP; if (SDL_SetWindowFullscreen(screen->window, new_mode)) { LOGW("Could not switch fullscreen mode: %s", SDL_GetError()); @@ -721,6 +797,8 @@ sc_screen_switch_fullscreen(struct sc_screen *screen) { void sc_screen_resize_to_fit(struct sc_screen *screen) { + assert(screen->video); + if (screen->fullscreen || screen->maximized || screen->minimized) { return; } @@ -745,6 +823,8 @@ sc_screen_resize_to_fit(struct sc_screen *screen) { void sc_screen_resize_to_pixel_perfect(struct sc_screen *screen) { + assert(screen->video); + if (screen->fullscreen || screen->minimized) { return; } @@ -788,6 +868,13 @@ sc_screen_handle_event(struct sc_screen *screen, const SDL_Event *event) { return true; } case SDL_WINDOWEVENT: + if (!screen->video + && event->window.event == SDL_WINDOWEVENT_EXPOSED) { + sc_screen_render_novideo(screen); + } + + // !video implies !has_frame + assert(screen->video || !screen->has_frame); if (!screen->has_frame) { // Do nothing return true; @@ -891,6 +978,8 @@ sc_screen_handle_event(struct sc_screen *screen, const SDL_Event *event) { struct sc_point sc_screen_convert_drawable_to_frame_coords(struct sc_screen *screen, int32_t x, int32_t y) { + assert(screen->video); + enum sc_orientation orientation = screen->orientation; int32_t w = screen->content_size.width; diff --git a/app/src/screen.h b/app/src/screen.h index 361ce455..3e205cdc 100644 --- a/app/src/screen.h +++ b/app/src/screen.h @@ -26,6 +26,8 @@ struct sc_screen { bool open; // track the open/close state to assert correct behavior #endif + bool video; + struct sc_display display; struct sc_input_manager im; struct sc_frame_buffer fb; @@ -70,6 +72,8 @@ struct sc_screen { }; struct sc_screen_params { + bool video; + struct sc_controller *controller; struct sc_file_pusher *fp; struct sc_key_processor *kp; From b5c8de08e0439b1d6c5827a36faf341fad8986af Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Sat, 20 Apr 2024 18:12:20 +0200 Subject: [PATCH 16/44] Update documentation for --no-window PR #4868 --- doc/audio.md | 11 +++++++++-- doc/control.md | 25 +++++++++++++++++++++++++ doc/otg.md | 43 ++++++++++++++++++++++++++++++++----------- doc/recording.md | 13 +++++++++---- doc/window.md | 9 +++++++++ 5 files changed, 84 insertions(+), 17 deletions(-) diff --git a/doc/audio.md b/doc/audio.md index ecae4468..30dd0f97 100644 --- a/doc/audio.md +++ b/doc/audio.md @@ -28,10 +28,17 @@ To disable only the audio playback, see [no playback](video.md#no-playback). ## Audio only -To play audio only, disable the video: +To play audio only, disable video and control: ```bash -scrcpy --no-video +scrcpy --no-video --no-control +``` + +To play audio without a window: + +```bash +# --no-video and --no-control are implied by --no-window +scrcpy --no-window # interrupt with Ctrl+C ``` diff --git a/doc/control.md b/doc/control.md index e9fd9e9b..87897894 100644 --- a/doc/control.md +++ b/doc/control.md @@ -15,6 +15,31 @@ scrcpy -n # short version Read [keyboard](keyboard.md) and [mouse](mouse.md). +## Control only + +To control the device without mirroring: + +```bash +scrcpy --no-video --no-audio +``` + +By default, mouse mode is switched to UHID if video mirroring is disabled (a +relative mouse mode is required). + +To also use a UHID keyboard, set it explicitly: + +```bash +scrcpy --no-video --no-audio --keyboard=uhid +scrcpy --no-video --no-audio -K # short version +``` + +To use AOA instead (over USB only): + +```bash +scrcpy --no-video --no-audio --keyboard=aoa --mouse=aoa +``` + + ## Copy-paste Any time the Android clipboard changes, it is automatically synchronized to the diff --git a/doc/otg.md b/doc/otg.md index 3c7ed467..5f42ac9c 100644 --- a/doc/otg.md +++ b/doc/otg.md @@ -1,19 +1,21 @@ # OTG By default, _scrcpy_ injects input events at the Android API level. As an -alternative, when connected over USB, it is possible to send HID events, so that -scrcpy behaves as if it was a physical keyboard and/or mouse connected to the -Android device. +alternative, it is possible to send HID events, so that scrcpy behaves as if it +was a [physical keyboard] and/or a [physical mouse] connected to the Android +device (see [keyboard](keyboard.md) and [mouse](mouse.md)). -A special mode allows to control the device without mirroring, using AOA -[keyboard](keyboard.md#aoa) and [mouse](mouse.md#aoa). Therefore, it is possible -to run _scrcpy_ with only physical keyboard and mouse simulation (HID), as if -the computer keyboard and mouse were plugged directly to the device via an OTG -cable. +[physical keyboard]: keyboard.md#physical-keyboard-simulation +[physical mouse]: physical-keyboard-simulation -In this mode, `adb` (USB debugging) is not necessary, and mirroring is disabled. +A special mode (OTG) allows to control the device using AOA +[keyboard](keyboard.md#aoa) and [mouse](mouse.md#aoa), without using _adb_ at +all (so USB debugging is not necessary). In this mode, video and audio are +disabled, and `--keyboard=aoa and `--mouse=aoa` are implicitly set. -This is similar to `--keyboard=aoa --mouse=aoa`, but without mirroring. +Therefore, it is possible to run _scrcpy_ with only physical keyboard and mouse +simulation, as if the computer keyboard and mouse were plugged directly to the +device via an OTG cable. To enable OTG mode: @@ -23,7 +25,7 @@ scrcpy --otg scrcpy --otg -s 0123456789abcdef ``` -It is possible to disable HID keyboard or HID mouse: +It is possible to disable keyboard or mouse: ```bash scrcpy --otg --keyboard=disabled @@ -35,3 +37,22 @@ It only works if the device is connected over USB. ## OTG issues on Windows See [FAQ](/FAQ.md#otg-issues-on-windows). + + +## Control only + +Note that the purpose of OTG is to control the device without USB debugging +(adb). + +If you want to solely control the device without mirroring while USB debugging +is enabled, then OTG mode is not necessary. + +Instead, disable video and audio, and select UHID (or AOA): + +```bash +scrcpy --no-video --no-audio --keyboard=uhid --mouse=uhid +scrcpy --no-video --no-audio -KM # short version +scrcpy --no-video --no-audio --keyboard=aoa --mouse=aoa +``` + +One benefit of UHID is that it also works wirelessly. diff --git a/doc/recording.md b/doc/recording.md index 216542e9..f1a5a6e7 100644 --- a/doc/recording.md +++ b/doc/recording.md @@ -58,12 +58,10 @@ orientation](video.md#orientation). ## No playback -To disable playback while recording: +To disable playback and control while recording: ```bash -scrcpy --no-playback --record=file.mp4 -scrcpy -Nr file.mkv -# interrupt recording with Ctrl+C +scrcpy --no-playback --no-control --record=file.mp4 ``` It is also possible to disable video and audio playback separately: @@ -73,6 +71,13 @@ It is also possible to disable video and audio playback separately: scrcpy --record=file.mkv --no-audio-playback ``` +To also disable the window: + +```bash +scrcpy --no-playback --no-window --record=file.mp4 +# interrupt recording with Ctrl+C +``` + ## Time limit To limit the recording time: diff --git a/doc/window.md b/doc/window.md index b5b73921..b72c716c 100644 --- a/doc/window.md +++ b/doc/window.md @@ -1,5 +1,14 @@ # Window +## Disable window + +To disable window (may be useful for recording or for playing audio only): + +```bash +scrcpy --no-window --record=file.mp4 +# Ctrl+C to interrupt +``` + ## Title By default, the window title is the device model. It can be changed: From 063a8339ed27b94a8fe1e53a284507eb2d044e15 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Sat, 11 May 2024 16:40:22 +0200 Subject: [PATCH 17/44] Terminate on controller error This is particularly important to react to server socket disconnection since video and audio may be disabled. PR #4868 --- app/src/controller.c | 32 +++++++++++++++++++++++++++++--- app/src/controller.h | 11 ++++++++++- app/src/events.h | 1 + app/src/receiver.c | 9 ++++++++- app/src/receiver.h | 10 +++++++++- app/src/scrcpy.c | 20 +++++++++++++++++++- 6 files changed, 76 insertions(+), 7 deletions(-) diff --git a/app/src/controller.c b/app/src/controller.c index 499cfd3c..edd767eb 100644 --- a/app/src/controller.c +++ b/app/src/controller.c @@ -6,8 +6,19 @@ #define SC_CONTROL_MSG_QUEUE_MAX 64 +static void +sc_controller_receiver_on_error(struct sc_receiver *receiver, void *userdata) { + (void) receiver; + + struct sc_controller *controller = userdata; + // Forward the event to the controller listener + controller->cbs->on_error(controller, controller->cbs_userdata); +} + bool -sc_controller_init(struct sc_controller *controller, sc_socket control_socket) { +sc_controller_init(struct sc_controller *controller, sc_socket control_socket, + const struct sc_controller_callbacks *cbs, + void *cbs_userdata) { sc_vecdeque_init(&controller->queue); bool ok = sc_vecdeque_reserve(&controller->queue, SC_CONTROL_MSG_QUEUE_MAX); @@ -15,7 +26,12 @@ sc_controller_init(struct sc_controller *controller, sc_socket control_socket) { return false; } - ok = sc_receiver_init(&controller->receiver, control_socket); + static const struct sc_receiver_callbacks receiver_cbs = { + .on_error = sc_controller_receiver_on_error, + }; + + ok = sc_receiver_init(&controller->receiver, control_socket, &receiver_cbs, + controller); if (!ok) { sc_vecdeque_destroy(&controller->queue); return false; @@ -39,6 +55,10 @@ sc_controller_init(struct sc_controller *controller, sc_socket control_socket) { controller->control_socket = control_socket; controller->stopped = false; + assert(cbs && cbs->on_error); + controller->cbs = cbs; + controller->cbs_userdata = cbs_userdata; + return true; } @@ -125,10 +145,16 @@ run_controller(void *data) { sc_control_msg_destroy(&msg); if (!ok) { LOGD("Could not write msg to socket"); - break; + goto error; } } + return 0; + +error: + controller->cbs->on_error(controller, controller->cbs_userdata); + + return 1; // ignored } bool diff --git a/app/src/controller.h b/app/src/controller.h index 1e44427e..353d4d0d 100644 --- a/app/src/controller.h +++ b/app/src/controller.h @@ -22,10 +22,19 @@ struct sc_controller { bool stopped; struct sc_control_msg_queue queue; struct sc_receiver receiver; + + const struct sc_controller_callbacks *cbs; + void *cbs_userdata; +}; + +struct sc_controller_callbacks { + void (*on_error)(struct sc_controller *controller, void *userdata); }; bool -sc_controller_init(struct sc_controller *controller, sc_socket control_socket); +sc_controller_init(struct sc_controller *controller, sc_socket control_socket, + const struct sc_controller_callbacks *cbs, + void *cbs_userdata); void sc_controller_configure(struct sc_controller *controller, diff --git a/app/src/events.h b/app/src/events.h index 8bfa2582..3cf2b1dd 100644 --- a/app/src/events.h +++ b/app/src/events.h @@ -7,3 +7,4 @@ #define SC_EVENT_RECORDER_ERROR (SDL_USEREVENT + 6) #define SC_EVENT_SCREEN_INIT_SIZE (SDL_USEREVENT + 7) #define SC_EVENT_TIME_LIMIT_REACHED (SDL_USEREVENT + 8) +#define SC_EVENT_CONTROLLER_ERROR (SDL_USEREVENT + 9) diff --git a/app/src/receiver.c b/app/src/receiver.c index f4ebd3f8..fb923ac4 100644 --- a/app/src/receiver.c +++ b/app/src/receiver.c @@ -10,7 +10,8 @@ #include "util/str.h" bool -sc_receiver_init(struct sc_receiver *receiver, sc_socket control_socket) { +sc_receiver_init(struct sc_receiver *receiver, sc_socket control_socket, + const struct sc_receiver_callbacks *cbs, void *cbs_userdata) { bool ok = sc_mutex_init(&receiver->mutex); if (!ok) { return false; @@ -20,6 +21,10 @@ sc_receiver_init(struct sc_receiver *receiver, sc_socket control_socket) { receiver->acksync = NULL; receiver->uhid_devices = NULL; + assert(cbs && cbs->on_error); + receiver->cbs = cbs; + receiver->cbs_userdata = cbs_userdata; + return true; } @@ -152,6 +157,8 @@ run_receiver(void *data) { } } + receiver->cbs->on_error(receiver, receiver->cbs_userdata); + return 0; } diff --git a/app/src/receiver.h b/app/src/receiver.h index ba84c0ab..ef83978f 100644 --- a/app/src/receiver.h +++ b/app/src/receiver.h @@ -19,10 +19,18 @@ struct sc_receiver { struct sc_acksync *acksync; struct sc_uhid_devices *uhid_devices; + + const struct sc_receiver_callbacks *cbs; + void *cbs_userdata; +}; + +struct sc_receiver_callbacks { + void (*on_error)(struct sc_receiver *receiver, void *userdata); }; bool -sc_receiver_init(struct sc_receiver *receiver, sc_socket control_socket); +sc_receiver_init(struct sc_receiver *receiver, sc_socket control_socket, + const struct sc_receiver_callbacks *cbs, void *cbs_userdata); void sc_receiver_destroy(struct sc_receiver *receiver); diff --git a/app/src/scrcpy.c b/app/src/scrcpy.c index 5e7b19fd..b07611f1 100644 --- a/app/src/scrcpy.c +++ b/app/src/scrcpy.c @@ -174,6 +174,9 @@ event_loop(struct scrcpy *s) { case SC_EVENT_DEMUXER_ERROR: LOGE("Demuxer error"); return SCRCPY_EXIT_FAILURE; + case SC_EVENT_CONTROLLER_ERROR: + LOGE("Controller error"); + return SCRCPY_EXIT_FAILURE; case SC_EVENT_RECORDER_ERROR: LOGE("Recorder error"); return SCRCPY_EXIT_FAILURE; @@ -265,6 +268,16 @@ sc_audio_demuxer_on_ended(struct sc_demuxer *demuxer, } } +static void +sc_controller_on_error(struct sc_controller *controller, void *userdata) { + // Note: this function may be called twice, once from the controller thread + // and once from the receiver thread + (void) controller; + (void) userdata; + + PUSH_EVENT(SC_EVENT_CONTROLLER_ERROR); +} + static void sc_server_on_connection_failed(struct sc_server *server, void *userdata) { (void) server; @@ -553,7 +566,12 @@ scrcpy(struct scrcpy_options *options) { struct sc_mouse_processor *mp = NULL; if (options->control) { - if (!sc_controller_init(&s->controller, s->server.control_socket)) { + static const struct sc_controller_callbacks controller_cbs = { + .on_error = sc_controller_on_error, + }; + + if (!sc_controller_init(&s->controller, s->server.control_socket, + &controller_cbs, NULL)) { goto end; } controller_initialized = true; From da484b7ab9904beae0128aa2066f5d04a9c9e840 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Sun, 12 May 2024 10:44:27 +0200 Subject: [PATCH 18/44] Reject recording with control only If video and audio are disabled, there is nothing to record. --- app/src/cli.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/app/src/cli.c b/app/src/cli.c index 0caeea5c..a180c0e6 100644 --- a/app/src/cli.c +++ b/app/src/cli.c @@ -2738,6 +2738,11 @@ parse_args_with_getopt(struct scrcpy_cli_args *args, int argc, char *argv[], } if (opts->record_filename) { + if (!opts->video && !opts->audio) { + LOGE("Video and audio disabled, nothing to record"); + return false; + } + if (!opts->record_format) { opts->record_format = guess_record_format(opts->record_filename); if (!opts->record_format) { From 09e8c20168a7d608fa850aabada4f404e1c698b4 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Tue, 14 May 2024 08:23:18 +0200 Subject: [PATCH 19/44] Rename streamScreen() to streamCapture() The capture source may be either the screen or the camera. --- .../src/main/java/com/genymobile/scrcpy/SurfaceEncoder.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/server/src/main/java/com/genymobile/scrcpy/SurfaceEncoder.java b/server/src/main/java/com/genymobile/scrcpy/SurfaceEncoder.java index 8eda8231..4a0fdf4e 100644 --- a/server/src/main/java/com/genymobile/scrcpy/SurfaceEncoder.java +++ b/server/src/main/java/com/genymobile/scrcpy/SurfaceEncoder.java @@ -48,7 +48,7 @@ public class SurfaceEncoder implements AsyncProcessor { this.downsizeOnError = downsizeOnError; } - private void streamScreen() throws IOException, ConfigurationException { + private void streamCapture() throws IOException, ConfigurationException { Codec codec = streamer.getCodec(); MediaCodec mediaCodec = createMediaCodec(codec, encoderName); MediaFormat format = createFormat(codec.getMimeType(), videoBitRate, maxFps, codecOptions); @@ -254,7 +254,7 @@ public class SurfaceEncoder implements AsyncProcessor { Looper.prepare(); try { - streamScreen(); + streamCapture(); } catch (ConfigurationException e) { // Do not print stack trace, a user-friendly error-message has already been logged } catch (IOException e) { From fd9498e07c949828a6aedfc17340641b6ec56c0c Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Thu, 30 May 2024 15:49:07 +0200 Subject: [PATCH 20/44] Avoid zero-length copies Return early if there is nothing to read/write. --- app/src/util/audiobuf.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/app/src/util/audiobuf.c b/app/src/util/audiobuf.c index 3597f7ee..3cc5cad1 100644 --- a/app/src/util/audiobuf.c +++ b/app/src/util/audiobuf.c @@ -46,6 +46,9 @@ sc_audiobuf_read(struct sc_audiobuf *buf, void *to_, uint32_t samples_count) { uint32_t head = atomic_load_explicit(&buf->head, memory_order_acquire); uint32_t can_read = (buf->alloc_size + head - tail) % buf->alloc_size; + if (!can_read) { + return 0; + } if (samples_count > can_read) { samples_count = can_read; } @@ -86,6 +89,9 @@ sc_audiobuf_write(struct sc_audiobuf *buf, const void *from_, uint32_t tail = atomic_load_explicit(&buf->tail, memory_order_acquire); uint32_t can_write = (buf->alloc_size + tail - head - 1) % buf->alloc_size; + if (!can_write) { + return 0; + } if (samples_count > can_write) { samples_count = can_write; } From 5d1d5bdc169fdc1ef836a8c04f794fabe363f44b Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Sun, 9 Jun 2024 18:27:30 +0200 Subject: [PATCH 21/44] Fix thread leak on Windows Fixes #4973 --- app/src/sys/win/process.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/app/src/sys/win/process.c b/app/src/sys/win/process.c index 6e9da09c..6ae33d86 100644 --- a/app/src/sys/win/process.c +++ b/app/src/sys/win/process.c @@ -176,6 +176,8 @@ sc_process_execute_p(const char *const argv[], HANDLE *handle, unsigned flags, free(lpAttributeList); } + CloseHandle(pi.hThread); + // These handles are used by the child process, close them for this process if (pin) { CloseHandle(stdin_read_handle); From 9ea4446369e53936032668d483aede39e49c84c1 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Sun, 9 Jun 2024 19:25:32 +0200 Subject: [PATCH 22/44] Release the audio lock early The final write from the writer thread does not require a lock: it is guaranteed that enough space is available since the reader thread never writes. --- app/src/audio_player.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/app/src/audio_player.c b/app/src/audio_player.c index bd799c51..dac85bf9 100644 --- a/app/src/audio_player.c +++ b/app/src/audio_player.c @@ -194,7 +194,11 @@ sc_audio_player_frame_sink_push(struct sc_frame_sink *sink, // Still insufficient, drop old samples to make space skipped_samples = sc_audiobuf_read(&ap->buf, NULL, remaining); assert(skipped_samples == remaining); + } + SDL_UnlockAudioDevice(ap->device); + + if (written < samples) { // Now there is enough space uint32_t w = sc_audiobuf_write(&ap->buf, swr_buf + TO_BYTES(written), @@ -202,8 +206,6 @@ sc_audio_player_frame_sink_push(struct sc_frame_sink *sink, assert(w == remaining); (void) w; } - - SDL_UnlockAudioDevice(ap->device); } uint32_t underflow = 0; From 24b9e0a9705ab0b283fd2796493a25c6e4d7db42 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Tue, 11 Jun 2024 08:58:19 +0200 Subject: [PATCH 23/44] Retrieve icon decoder directly The call to av_find_best_stream() gives the decoder directly, this avoids to retrieve it afterwards in a separate step. --- app/src/icon.c | 20 +++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/app/src/icon.c b/app/src/icon.c index a9aad875..0dddefa3 100644 --- a/app/src/icon.c +++ b/app/src/icon.c @@ -78,7 +78,19 @@ decode_image(const char *path) { goto close_input; } - int stream = av_find_best_stream(ctx, AVMEDIA_TYPE_VIDEO, -1, -1, NULL, 0); + +// In ffmpeg/doc/APIchanges: +// 2021-04-27 - 46dac8cf3d - lavf 59.0.100 - avformat.h +// av_find_best_stream now uses a const AVCodec ** parameter +// for the returned decoder. +#if LIBAVFORMAT_VERSION_INT >= AV_VERSION_INT(59, 0, 100) + const AVCodec *codec; +#else + AVCodec *codec; +#endif + + int stream = + av_find_best_stream(ctx, AVMEDIA_TYPE_VIDEO, -1, -1, &codec, 0); if (stream < 0 ) { LOGE("Could not find best image stream"); goto close_input; @@ -86,12 +98,6 @@ decode_image(const char *path) { AVCodecParameters *params = ctx->streams[stream]->codecpar; - const AVCodec *codec = avcodec_find_decoder(params->codec_id); - if (!codec) { - LOGE("Could not find image decoder"); - goto close_input; - } - AVCodecContext *codec_ctx = avcodec_alloc_context3(codec); if (!codec_ctx) { LOG_OOM(); From 576e7552a29e30b40205f81f2ff4d461f018313f Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Thu, 13 Jun 2024 09:11:32 +0200 Subject: [PATCH 24/44] Mention that the Debian package is obsolete It cannot be updated until the android-framework-XX Debian package is fixed. Refs --- doc/linux.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/linux.md b/doc/linux.md index 68b4ee10..6bfe3454 100644 --- a/doc/linux.md +++ b/doc/linux.md @@ -6,7 +6,7 @@ Scrcpy is packaged in several distributions and package managers: - - Debian/Ubuntu: `apt install scrcpy` + - Debian/Ubuntu: ~~`apt install scrcpy`~~ _(obsolete version)_ - Arch Linux: `pacman -S scrcpy` - Fedora: `dnf copr enable zeno/scrcpy && dnf install scrcpy` - Gentoo: `emerge scrcpy` From 9030bd8be434ee54abbcd7aad0d4e37c699a362b Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Fri, 21 Jun 2024 12:12:13 +0200 Subject: [PATCH 25/44] Upgrade AGP from 8.1.3 to 8.3.0 --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index b27befb6..f81f7d27 100644 --- a/build.gradle +++ b/build.gradle @@ -7,7 +7,7 @@ buildscript { mavenCentral() } dependencies { - classpath 'com.android.tools.build:gradle:8.1.3' + classpath 'com.android.tools.build:gradle:8.3.0' // NOTE: Do not place your application dependencies here; they belong // in the individual module build.gradle files From 30e42af2d4ca660b06bce2ada7b1a0d303c73239 Mon Sep 17 00:00:00 2001 From: wuderek Date: Fri, 21 Jun 2024 13:41:15 +0800 Subject: [PATCH 26/44] Add missing virtual display release() PR #5008 Signed-off-by: Romain Vimont --- .../src/main/java/com/genymobile/scrcpy/ScreenCapture.java | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/server/src/main/java/com/genymobile/scrcpy/ScreenCapture.java b/server/src/main/java/com/genymobile/scrcpy/ScreenCapture.java index 95214188..1d878d78 100644 --- a/server/src/main/java/com/genymobile/scrcpy/ScreenCapture.java +++ b/server/src/main/java/com/genymobile/scrcpy/ScreenCapture.java @@ -68,6 +68,11 @@ public class ScreenCapture extends SurfaceCapture implements Device.RotationList device.setFoldListener(null); if (display != null) { SurfaceControl.destroyDisplay(display); + display = null; + } + if (virtualDisplay != null) { + virtualDisplay.release(); + virtualDisplay = null; } } From 592ca0b59b3f57e6d721a57554e0efc5a52bb70f Mon Sep 17 00:00:00 2001 From: wuderek Date: Fri, 21 Jun 2024 13:41:15 +0800 Subject: [PATCH 27/44] Try newer display API first The old createDisplay() API has been removed from Android. Try the newer API first, since more and more devices will use that version. PR #5008 --- .../com/genymobile/scrcpy/ScreenCapture.java | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/server/src/main/java/com/genymobile/scrcpy/ScreenCapture.java b/server/src/main/java/com/genymobile/scrcpy/ScreenCapture.java index 1d878d78..090c96f0 100644 --- a/server/src/main/java/com/genymobile/scrcpy/ScreenCapture.java +++ b/server/src/main/java/com/genymobile/scrcpy/ScreenCapture.java @@ -45,18 +45,18 @@ public class ScreenCapture extends SurfaceCapture implements Device.RotationList } try { - display = createDisplay(); - setDisplaySurface(display, surface, videoRotation, contentRect, unlockedVideoRect, layerStack); - Ln.d("Display: using SurfaceControl API"); - } catch (Exception surfaceControlException) { Rect videoRect = screenInfo.getVideoSize().toRect(); + virtualDisplay = ServiceManager.getDisplayManager() + .createVirtualDisplay("scrcpy", videoRect.width(), videoRect.height(), device.getDisplayId(), surface); + Ln.d("Display: using DisplayManager API"); + } catch (Exception displayManagerException) { try { - virtualDisplay = ServiceManager.getDisplayManager() - .createVirtualDisplay("scrcpy", videoRect.width(), videoRect.height(), device.getDisplayId(), surface); - Ln.d("Display: using DisplayManager API"); - } catch (Exception displayManagerException) { - Ln.e("Could not create display using SurfaceControl", surfaceControlException); + display = createDisplay(); + setDisplaySurface(display, surface, videoRotation, contentRect, unlockedVideoRect, layerStack); + Ln.d("Display: using SurfaceControl API"); + } catch (Exception surfaceControlException) { Ln.e("Could not create display using DisplayManager", displayManagerException); + Ln.e("Could not create display using SurfaceControl", surfaceControlException); throw new AssertionError("Could not create display"); } } From 24bcc3fa2b4323091a487ad1a2568d02ad0d2042 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Thu, 7 Mar 2024 23:07:01 +0100 Subject: [PATCH 28/44] Simplify shortcut modifiers Restrict shortcut modifiers to be composed of only one item each. Before, it was possible to select a list of multiple combinations of modifier keys, like --shortcut-mod='lctrl+lalt,rctrl+rsuper', meaning that shortcuts would be triggered either by LCtrl+LAlt+key or RCtrl+RSuper+key. This was overly generic, probably not used very much, and it prevents to solve inconsistencies between UP and DOWN events of modifier keys sent to the device. Refs #4732 PR #4741 --- app/scrcpy.1 | 4 +- app/src/cli.c | 100 ++++++++++++++++------------------------ app/src/cli.h | 2 +- app/src/input_manager.c | 23 ++------- app/src/input_manager.h | 7 +-- app/src/options.c | 5 +- app/src/options.h | 9 +--- app/src/scrcpy.c | 2 +- app/src/screen.h | 2 +- app/tests/test_cli.c | 24 +++------- doc/shortcuts.md | 4 +- 11 files changed, 62 insertions(+), 120 deletions(-) diff --git a/app/scrcpy.1 b/app/scrcpy.1 index f9ef3498..2be9ef59 100644 --- a/app/scrcpy.1 +++ b/app/scrcpy.1 @@ -424,9 +424,9 @@ Turn the device screen off immediately. .BI "\-\-shortcut\-mod " key\fR[+...]][,...] Specify the modifiers to use for scrcpy shortcuts. Possible keys are "lctrl", "rctrl", "lalt", "ralt", "lsuper" and "rsuper". -A shortcut can consist in several keys, separated by '+'. Several shortcuts can be specified, separated by ','. +Several shortcut modifiers can be specified, separated by ','. -For example, to use either LCtrl+LAlt or LSuper for scrcpy shortcuts, pass "lctrl+lalt,lsuper". +For example, to use either LCtrl or LSuper for scrcpy shortcuts, pass "lctrl,lsuper". Default is "lalt,lsuper" (left-Alt or left-Super). diff --git a/app/src/cli.c b/app/src/cli.c index a180c0e6..a0c0b338 100644 --- a/app/src/cli.c +++ b/app/src/cli.c @@ -716,10 +716,10 @@ static const struct sc_option options[] = { .text = "Specify the modifiers to use for scrcpy shortcuts.\n" "Possible keys are \"lctrl\", \"rctrl\", \"lalt\", \"ralt\", " "\"lsuper\" and \"rsuper\".\n" - "A shortcut can consist in several keys, separated by '+'. " - "Several shortcuts can be specified, separated by ','.\n" - "For example, to use either LCtrl+LAlt or LSuper for scrcpy " - "shortcuts, pass \"lctrl+lalt,lsuper\".\n" + "Several shortcut modifiers can be specified, separated by " + "','.\n" + "For example, to use either LCtrl or LSuper for scrcpy " + "shortcuts, pass \"lctrl,lsuper\".\n" "Default is \"lalt,lsuper\" (left-Alt or left-Super).", }, { @@ -1687,82 +1687,62 @@ parse_log_level(const char *s, enum sc_log_level *log_level) { return false; } -// item is a list of mod keys separated by '+' (e.g. "lctrl+lalt") -// returns a bitwise-or of SC_SHORTCUT_MOD_* constants (or 0 on error) -static unsigned +static enum sc_shortcut_mod parse_shortcut_mods_item(const char *item, size_t len) { - unsigned mod = 0; - - for (;;) { - char *plus = strchr(item, '+'); - // strchr() does not consider the "len" parameter, to it could find an - // occurrence too far in the string (there is no strnchr()) - bool has_plus = plus && plus < item + len; - - assert(!has_plus || plus > item); - size_t key_len = has_plus ? (size_t) (plus - item) : len; - #define STREQ(literal, s, len) \ ((sizeof(literal)-1 == len) && !memcmp(literal, s, len)) - if (STREQ("lctrl", item, key_len)) { - mod |= SC_SHORTCUT_MOD_LCTRL; - } else if (STREQ("rctrl", item, key_len)) { - mod |= SC_SHORTCUT_MOD_RCTRL; - } else if (STREQ("lalt", item, key_len)) { - mod |= SC_SHORTCUT_MOD_LALT; - } else if (STREQ("ralt", item, key_len)) { - mod |= SC_SHORTCUT_MOD_RALT; - } else if (STREQ("lsuper", item, key_len)) { - mod |= SC_SHORTCUT_MOD_LSUPER; - } else if (STREQ("rsuper", item, key_len)) { - mod |= SC_SHORTCUT_MOD_RSUPER; - } else { - LOGE("Unknown modifier key: %.*s " - "(must be one of: lctrl, rctrl, lalt, ralt, lsuper, rsuper)", - (int) key_len, item); - return 0; - } + if (STREQ("lctrl", item, len)) { + return SC_SHORTCUT_MOD_LCTRL; + } + if (STREQ("rctrl", item, len)) { + return SC_SHORTCUT_MOD_RCTRL; + } + if (STREQ("lalt", item, len)) { + return SC_SHORTCUT_MOD_LALT; + } + if (STREQ("ralt", item, len)) { + return SC_SHORTCUT_MOD_RALT; + } + if (STREQ("lsuper", item, len)) { + return SC_SHORTCUT_MOD_LSUPER; + } + if (STREQ("rsuper", item, len)) { + return SC_SHORTCUT_MOD_RSUPER; + } #undef STREQ - if (!has_plus) { - break; - } - - item = plus + 1; - assert(len >= key_len + 1); - len -= key_len + 1; + bool has_plus = strchr(item, '+'); + if (has_plus) { + LOGE("Shortcut mod combination with '+' is not supported anymore: " + "'%.*s' (see #4741)", (int) len, item); + return 0; } - return mod; + LOGE("Unknown modifier key: %.*s " + "(must be one of: lctrl, rctrl, lalt, ralt, lsuper, rsuper)", + (int) len, item); + + return 0; } static bool -parse_shortcut_mods(const char *s, struct sc_shortcut_mods *mods) { - unsigned count = 0; - unsigned current = 0; +parse_shortcut_mods(const char *s, uint8_t *shortcut_mods) { + uint8_t mods = 0; - // LCtrl+LAlt or RCtrl or LCtrl+RSuper: "lctrl+lalt,rctrl,lctrl+rsuper" + // A list of shortcut modifiers, for example "lctrl,rctrl,rsuper" for (;;) { char *comma = strchr(s, ','); - if (comma && count == SC_MAX_SHORTCUT_MODS - 1) { - assert(count < SC_MAX_SHORTCUT_MODS); - LOGW("Too many shortcut modifiers alternatives"); - return false; - } - assert(!comma || comma > s); size_t limit = comma ? (size_t) (comma - s) : strlen(s); - unsigned mod = parse_shortcut_mods_item(s, limit); + enum sc_shortcut_mod mod = parse_shortcut_mods_item(s, limit); if (!mod) { - LOGE("Invalid modifier keys: %.*s", (int) limit, s); return false; } - mods->data[current++] = mod; - ++count; + mods |= mod; if (!comma) { break; @@ -1771,7 +1751,7 @@ parse_shortcut_mods(const char *s, struct sc_shortcut_mods *mods) { s = comma + 1; } - mods->count = count; + *shortcut_mods = mods; return true; } @@ -1779,7 +1759,7 @@ parse_shortcut_mods(const char *s, struct sc_shortcut_mods *mods) { #ifdef SC_TEST // expose the function to unit-tests bool -sc_parse_shortcut_mods(const char *s, struct sc_shortcut_mods *mods) { +sc_parse_shortcut_mods(const char *s, uint8_t *mods) { return parse_shortcut_mods(s, mods); } #endif diff --git a/app/src/cli.h b/app/src/cli.h index 23d34fcd..6fd579a4 100644 --- a/app/src/cli.h +++ b/app/src/cli.h @@ -28,7 +28,7 @@ scrcpy_parse_args(struct scrcpy_cli_args *args, int argc, char *argv[]); #ifdef SC_TEST bool -sc_parse_shortcut_mods(const char *s, struct sc_shortcut_mods *mods); +sc_parse_shortcut_mods(const char *s, uint8_t *shortcut_mods); #endif #endif diff --git a/app/src/input_manager.c b/app/src/input_manager.c index 3a5fc6ed..91e65bfd 100644 --- a/app/src/input_manager.c +++ b/app/src/input_manager.c @@ -10,7 +10,7 @@ #define SC_SDL_SHORTCUT_MODS_MASK (KMOD_CTRL | KMOD_ALT | KMOD_GUI) static inline uint16_t -to_sdl_mod(unsigned shortcut_mod) { +to_sdl_mod(uint8_t shortcut_mod) { uint16_t sdl_mod = 0; if (shortcut_mod & SC_SHORTCUT_MOD_LCTRL) { sdl_mod |= KMOD_LCTRL; @@ -38,15 +38,8 @@ is_shortcut_mod(struct sc_input_manager *im, uint16_t sdl_mod) { // keep only the relevant modifier keys sdl_mod &= SC_SDL_SHORTCUT_MODS_MASK; - assert(im->sdl_shortcut_mods.count); - assert(im->sdl_shortcut_mods.count < SC_MAX_SHORTCUT_MODS); - for (unsigned i = 0; i < im->sdl_shortcut_mods.count; ++i) { - if (im->sdl_shortcut_mods.data[i] == sdl_mod) { - return true; - } - } - - return false; + // at least one shortcut mod pressed? + return sdl_mod & im->sdl_shortcut_mods; } void @@ -68,15 +61,7 @@ sc_input_manager_init(struct sc_input_manager *im, im->legacy_paste = params->legacy_paste; im->clipboard_autosync = params->clipboard_autosync; - const struct sc_shortcut_mods *shortcut_mods = params->shortcut_mods; - assert(shortcut_mods->count); - assert(shortcut_mods->count < SC_MAX_SHORTCUT_MODS); - for (unsigned i = 0; i < shortcut_mods->count; ++i) { - uint16_t sdl_mod = to_sdl_mod(shortcut_mods->data[i]); - assert(sdl_mod); - im->sdl_shortcut_mods.data[i] = sdl_mod; - } - im->sdl_shortcut_mods.count = shortcut_mods->count; + im->sdl_shortcut_mods = to_sdl_mod(params->shortcut_mods); im->vfinger_down = false; im->vfinger_invert_x = false; diff --git a/app/src/input_manager.h b/app/src/input_manager.h index 2ce11b03..8c45c165 100644 --- a/app/src/input_manager.h +++ b/app/src/input_manager.h @@ -26,10 +26,7 @@ struct sc_input_manager { bool legacy_paste; bool clipboard_autosync; - struct { - unsigned data[SC_MAX_SHORTCUT_MODS]; - unsigned count; - } sdl_shortcut_mods; + uint16_t sdl_shortcut_mods; bool vfinger_down; bool vfinger_invert_x; @@ -55,7 +52,7 @@ struct sc_input_manager_params { bool forward_all_clicks; bool legacy_paste; bool clipboard_autosync; - const struct sc_shortcut_mods *shortcut_mods; + uint8_t shortcut_mods; // OR of enum sc_shortcut_mod values }; void diff --git a/app/src/options.c b/app/src/options.c index d6bf9158..4b75ed6a 100644 --- a/app/src/options.c +++ b/app/src/options.c @@ -30,10 +30,7 @@ const struct scrcpy_options scrcpy_options_default = { }, .tunnel_host = 0, .tunnel_port = 0, - .shortcut_mods = { - .data = {SC_SHORTCUT_MOD_LALT, SC_SHORTCUT_MOD_LSUPER}, - .count = 2, - }, + .shortcut_mods = SC_SHORTCUT_MOD_LALT | SC_SHORTCUT_MOD_LSUPER, .max_size = 0, .video_bit_rate = 0, .audio_bit_rate = 0, diff --git a/app/src/options.h b/app/src/options.h index 1fb61ddf..85817341 100644 --- a/app/src/options.h +++ b/app/src/options.h @@ -169,8 +169,6 @@ enum sc_key_inject_mode { SC_KEY_INJECT_MODE_RAW, }; -#define SC_MAX_SHORTCUT_MODS 8 - enum sc_shortcut_mod { SC_SHORTCUT_MOD_LCTRL = 1 << 0, SC_SHORTCUT_MOD_RCTRL = 1 << 1, @@ -180,11 +178,6 @@ enum sc_shortcut_mod { SC_SHORTCUT_MOD_RSUPER = 1 << 5, }; -struct sc_shortcut_mods { - unsigned data[SC_MAX_SHORTCUT_MODS]; - unsigned count; -}; - struct sc_port_range { uint16_t first; uint16_t last; @@ -219,7 +212,7 @@ struct scrcpy_options { struct sc_port_range port_range; uint32_t tunnel_host; uint16_t tunnel_port; - struct sc_shortcut_mods shortcut_mods; + uint8_t shortcut_mods; // OR of enum sc_shortcut_mod values uint16_t max_size; uint32_t video_bit_rate; uint32_t audio_bit_rate; diff --git a/app/src/scrcpy.c b/app/src/scrcpy.c index b07611f1..5f13ee53 100644 --- a/app/src/scrcpy.c +++ b/app/src/scrcpy.c @@ -715,7 +715,7 @@ scrcpy(struct scrcpy_options *options) { .forward_all_clicks = options->forward_all_clicks, .legacy_paste = options->legacy_paste, .clipboard_autosync = options->clipboard_autosync, - .shortcut_mods = &options->shortcut_mods, + .shortcut_mods = options->shortcut_mods, .window_title = window_title, .always_on_top = options->always_on_top, .window_x = options->window_x, diff --git a/app/src/screen.h b/app/src/screen.h index 3e205cdc..437e7633 100644 --- a/app/src/screen.h +++ b/app/src/screen.h @@ -82,7 +82,7 @@ struct sc_screen_params { bool forward_all_clicks; bool legacy_paste; bool clipboard_autosync; - const struct sc_shortcut_mods *shortcut_mods; + uint8_t shortcut_mods; // OR of enum sc_shortcut_mod values const char *window_title; bool always_on_top; diff --git a/app/tests/test_cli.c b/app/tests/test_cli.c index f2a17272..cef8df3e 100644 --- a/app/tests/test_cli.c +++ b/app/tests/test_cli.c @@ -124,32 +124,22 @@ static void test_options2(void) { } static void test_parse_shortcut_mods(void) { - struct sc_shortcut_mods mods; + uint8_t mods; bool ok; ok = sc_parse_shortcut_mods("lctrl", &mods); assert(ok); - assert(mods.count == 1); - assert(mods.data[0] == SC_SHORTCUT_MOD_LCTRL); - - ok = sc_parse_shortcut_mods("lctrl+lalt", &mods); - assert(ok); - assert(mods.count == 1); - assert(mods.data[0] == (SC_SHORTCUT_MOD_LCTRL | SC_SHORTCUT_MOD_LALT)); + assert(mods == SC_SHORTCUT_MOD_LCTRL); ok = sc_parse_shortcut_mods("rctrl,lalt", &mods); assert(ok); - assert(mods.count == 2); - assert(mods.data[0] == SC_SHORTCUT_MOD_RCTRL); - assert(mods.data[1] == SC_SHORTCUT_MOD_LALT); + assert(mods == (SC_SHORTCUT_MOD_RCTRL | SC_SHORTCUT_MOD_LALT)); - ok = sc_parse_shortcut_mods("lsuper,rsuper+lalt,lctrl+rctrl+ralt", &mods); + ok = sc_parse_shortcut_mods("lsuper,rsuper,lctrl", &mods); assert(ok); - assert(mods.count == 3); - assert(mods.data[0] == SC_SHORTCUT_MOD_LSUPER); - assert(mods.data[1] == (SC_SHORTCUT_MOD_RSUPER | SC_SHORTCUT_MOD_LALT)); - assert(mods.data[2] == (SC_SHORTCUT_MOD_LCTRL | SC_SHORTCUT_MOD_RCTRL | - SC_SHORTCUT_MOD_RALT)); + assert(mods == (SC_SHORTCUT_MOD_LSUPER + | SC_SHORTCUT_MOD_RSUPER + | SC_SHORTCUT_MOD_LCTRL)); ok = sc_parse_shortcut_mods("", &mods); assert(!ok); diff --git a/doc/shortcuts.md b/doc/shortcuts.md index d0f6ebec..841ceaa6 100644 --- a/doc/shortcuts.md +++ b/doc/shortcuts.md @@ -13,8 +13,8 @@ It can be changed using `--shortcut-mod`. Possible keys are `lctrl`, `rctrl`, # use RCtrl for shortcuts scrcpy --shortcut-mod=rctrl -# use either LCtrl+LAlt or LSuper for shortcuts -scrcpy --shortcut-mod=lctrl+lalt,lsuper +# use either LCtrl or LSuper for shortcuts +scrcpy --shortcut-mod=lctrl,lsuper ``` _[Super] is typically the Windows or Cmd key._ From 0b926922bc169eab704f9805e6d35f79f7a1aa95 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Thu, 7 Mar 2024 23:08:27 +0100 Subject: [PATCH 29/44] Ignore shortcut keycodes Never inject keycodes used as shortcut modifiers. Refs #4732 PR #4741 --- app/src/input_manager.c | 20 +++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) diff --git a/app/src/input_manager.c b/app/src/input_manager.c index 91e65bfd..1e46b30e 100644 --- a/app/src/input_manager.c +++ b/app/src/input_manager.c @@ -42,6 +42,16 @@ is_shortcut_mod(struct sc_input_manager *im, uint16_t sdl_mod) { return sdl_mod & im->sdl_shortcut_mods; } +static bool +is_shortcut_key(struct sc_input_manager *im, SDL_Keycode keycode) { + return (im->sdl_shortcut_mods & KMOD_LCTRL && keycode == SDLK_LCTRL) + || (im->sdl_shortcut_mods & KMOD_RCTRL && keycode == SDLK_RCTRL) + || (im->sdl_shortcut_mods & KMOD_LALT && keycode == SDLK_LALT) + || (im->sdl_shortcut_mods & KMOD_RALT && keycode == SDLK_RALT) + || (im->sdl_shortcut_mods & KMOD_LGUI && keycode == SDLK_LGUI) + || (im->sdl_shortcut_mods & KMOD_RGUI && keycode == SDLK_RGUI); +} + void sc_input_manager_init(struct sc_input_manager *im, const struct sc_input_manager_params *params) { @@ -397,7 +407,12 @@ sc_input_manager_process_key(struct sc_input_manager *im, bool shift = event->keysym.mod & KMOD_SHIFT; bool repeat = event->repeat; - bool smod = is_shortcut_mod(im, mod); + // Either the modifier includes a shortcut modifier, or the key + // press/release is a modifier key. + // The second condition is necessary to ignore the release of the modifier + // key (because in this case mod is 0). + bool is_shortcut = is_shortcut_mod(im, mod) + || is_shortcut_key(im, keycode); if (down && !repeat) { if (keycode == im->last_keycode && mod == im->last_mod) { @@ -409,8 +424,7 @@ sc_input_manager_process_key(struct sc_input_manager *im, } } - // The shortcut modifier is pressed - if (smod) { + if (is_shortcut) { enum sc_action action = down ? SC_ACTION_DOWN : SC_ACTION_UP; switch (keycode) { case SDLK_h: From 9fa30ab1aeeb5cff32d2f6ad603a3a426bc04d69 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Mon, 24 Jun 2024 22:55:24 +0200 Subject: [PATCH 30/44] Fix error message parameter Use the local argument value, not the global optarg variable (even if it has the same value in practice, as it's passed as argument). --- app/src/cli.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/cli.c b/app/src/cli.c index a0c0b338..a2eb4254 100644 --- a/app/src/cli.c +++ b/app/src/cli.c @@ -2038,7 +2038,7 @@ parse_pause_on_exit(const char *s, enum sc_pause_on_exit *pause_on_exit) { } LOGE("Unsupported pause on exit mode: %s " - "(expected true, false or if-error)", optarg); + "(expected true, false or if-error)", s); return false; } From 09ce0307feb239487982bfd62f57bd762f2a9165 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Mon, 24 Jun 2024 22:56:49 +0200 Subject: [PATCH 31/44] Fix zsh completion script An '=' was missing for some options with an argument. --- app/data/zsh-completion/_scrcpy | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/data/zsh-completion/_scrcpy b/app/data/zsh-completion/_scrcpy index a23240ec..db04ca10 100644 --- a/app/data/zsh-completion/_scrcpy +++ b/app/data/zsh-completion/_scrcpy @@ -35,7 +35,7 @@ arguments=( '--forward-all-clicks[Forward clicks to device]' {-h,--help}'[Print the help]' '-K[Use UHID keyboard (same as --keyboard=uhid)]' - '--keyboard[Set the keyboard input mode]:mode:(disabled sdk uhid aoa)' + '--keyboard=[Set the keyboard input mode]:mode:(disabled sdk uhid aoa)' '--kill-adb-on-close[Kill adb when scrcpy terminates]' '--legacy-paste[Inject computer clipboard text as a sequence of key events on Ctrl+v]' '--list-camera-sizes[List the valid camera capture sizes]' @@ -46,7 +46,7 @@ arguments=( {-m,--max-size=}'[Limit both the width and height of the video to value]' '-M[Use UHID mouse (same as --mouse=uhid)]' '--max-fps=[Limit the frame rate of screen capture]' - '--mouse[Set the mouse input mode]:mode:(disabled sdk uhid aoa)' + '--mouse=[Set the mouse input mode]:mode:(disabled sdk uhid aoa)' {-n,--no-control}'[Disable device control \(mirror the device in read only\)]' {-N,--no-playback}'[Disable video and audio playback]' '--no-audio[Disable audio forwarding]' From 40493dff608cda7f0f37c166a2d8fef92581acf0 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Mon, 24 Jun 2024 23:00:33 +0200 Subject: [PATCH 32/44] Fix "resize to fit" when all clicks are forwarded To resize the window to fit the device screen, it is possible to double-click in the "black bars". This feature was mistakenly disabled when --forward-all-clicks was set. Instead, disable it only if mouse relative mode is enabled (AOA or UHID), because in that case the mouse cursor is on the device. --- app/src/input_manager.c | 75 ++++++++++++++++++++--------------------- 1 file changed, 37 insertions(+), 38 deletions(-) diff --git a/app/src/input_manager.c b/app/src/input_manager.c index 1e46b30e..f5f7992a 100644 --- a/app/src/input_manager.c +++ b/app/src/input_manager.c @@ -720,49 +720,48 @@ sc_input_manager_process_mouse_button(struct sc_input_manager *im, bool control = im->controller; bool paused = im->screen->paused; bool down = event->type == SDL_MOUSEBUTTONDOWN; - if (!im->forward_all_clicks) { - if (control && !paused) { - enum sc_action action = down ? SC_ACTION_DOWN : SC_ACTION_UP; + if (control && !paused && !im->forward_all_clicks) { + enum sc_action action = down ? SC_ACTION_DOWN : SC_ACTION_UP; - if (im->kp && event->button == SDL_BUTTON_X1) { - action_app_switch(im, action); - return; - } - if (event->button == SDL_BUTTON_X2 && down) { - if (event->clicks < 2) { - expand_notification_panel(im); - } else { - expand_settings_panel(im); - } - return; - } - if (im->kp && event->button == SDL_BUTTON_RIGHT) { - press_back_or_turn_screen_on(im, action); - return; - } - if (im->kp && event->button == SDL_BUTTON_MIDDLE) { - action_home(im, action); - return; - } + if (im->kp && event->button == SDL_BUTTON_X1) { + action_app_switch(im, action); + return; } + if (event->button == SDL_BUTTON_X2 && down) { + if (event->clicks < 2) { + expand_notification_panel(im); + } else { + expand_settings_panel(im); + } + return; + } + if (im->kp && event->button == SDL_BUTTON_RIGHT) { + press_back_or_turn_screen_on(im, action); + return; + } + if (im->kp && event->button == SDL_BUTTON_MIDDLE) { + action_home(im, action); + return; + } + } - // double-click on black borders resize to fit the device screen - bool video = im->screen->video; - if (video && event->button == SDL_BUTTON_LEFT && event->clicks == 2) { - int32_t x = event->x; - int32_t y = event->y; - sc_screen_hidpi_scale_coords(im->screen, &x, &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 (down) { - sc_screen_resize_to_fit(im->screen); - } - return; + // double-click on black borders resizes to fit the device screen + bool video = im->screen->video; + bool mouse_relative_mode = im->mp && im->mp->relative_mode; + if (video && !mouse_relative_mode && event->button == SDL_BUTTON_LEFT + && event->clicks == 2) { + int32_t x = event->x; + int32_t y = event->y; + sc_screen_hidpi_scale_coords(im->screen, &x, &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 (down) { + sc_screen_resize_to_fit(im->screen); } + return; } - // otherwise, send the click event to the device } if (!im->mp || paused) { From 035d60cf5d3f4c83d48735b4cb4cd108a5b5f413 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Mon, 24 Jun 2024 23:07:08 +0200 Subject: [PATCH 33/44] Add option to configure mouse bindings Add a new option --mouse-bind=xxxx. The argument must be exactly 4 characters, one for each secondary click: --mouse-bind=xxxx ^^^^ |||| ||| `- 5th click || `-- 4th click | `--- middle click `---- right click Each character must be one of the following: - `+`: forward the click to the device - `-`: ignore the click - `b`: trigger shortcut BACK (or turn screen on if off) - `h`: trigger shortcut HOME - `s`: trigger shortcut APP_SWITCH - `n`: trigger shortcut "expand notification panel" This deprecates --forward-all-clicks (use --mouse-bind=++++ instead). Refs PR #5022 --- app/data/bash-completion/scrcpy | 2 +- app/data/zsh-completion/_scrcpy | 2 +- app/scrcpy.1 | 21 ++++-- app/src/cli.c | 88 ++++++++++++++++++++++++-- app/src/input_events.h | 19 ++++-- app/src/input_manager.c | 109 ++++++++++++++++++++++---------- app/src/input_manager.h | 5 +- app/src/options.c | 7 +- app/src/options.h | 18 +++++- app/src/scrcpy.c | 2 +- app/src/screen.c | 2 +- app/src/screen.h | 2 +- app/src/usb/screen_otg.c | 6 +- doc/control.md | 9 --- doc/mouse.md | 38 +++++++++++ 15 files changed, 261 insertions(+), 69 deletions(-) diff --git a/app/data/bash-completion/scrcpy b/app/data/bash-completion/scrcpy index e6b2c91a..f00fadae 100644 --- a/app/data/bash-completion/scrcpy +++ b/app/data/bash-completion/scrcpy @@ -25,7 +25,6 @@ _scrcpy() { -e --select-tcpip -f --fullscreen --force-adb-forward - --forward-all-clicks -h --help -K --keyboard= @@ -41,6 +40,7 @@ _scrcpy() { -M --max-fps= --mouse= + --mouse-bind= -n --no-control -N --no-playback --no-audio diff --git a/app/data/zsh-completion/_scrcpy b/app/data/zsh-completion/_scrcpy index db04ca10..86fe0b0e 100644 --- a/app/data/zsh-completion/_scrcpy +++ b/app/data/zsh-completion/_scrcpy @@ -32,7 +32,6 @@ arguments=( {-e,--select-tcpip}'[Use TCP/IP device]' {-f,--fullscreen}'[Start in fullscreen]' '--force-adb-forward[Do not attempt to use \"adb reverse\" to connect to the device]' - '--forward-all-clicks[Forward clicks to device]' {-h,--help}'[Print the help]' '-K[Use UHID keyboard (same as --keyboard=uhid)]' '--keyboard=[Set the keyboard input mode]:mode:(disabled sdk uhid aoa)' @@ -47,6 +46,7 @@ arguments=( '-M[Use UHID mouse (same as --mouse=uhid)]' '--max-fps=[Limit the frame rate of screen capture]' '--mouse=[Set the mouse input mode]:mode:(disabled sdk uhid aoa)' + '--mouse-bind=[Configure bindings of secondary clicks]' {-n,--no-control}'[Disable device control \(mirror the device in read only\)]' {-N,--no-playback}'[Disable video and audio playback]' '--no-audio[Disable audio forwarding]' diff --git a/app/scrcpy.1 b/app/scrcpy.1 index 2be9ef59..7a0b3dfb 100644 --- a/app/scrcpy.1 +++ b/app/scrcpy.1 @@ -163,10 +163,6 @@ Start in fullscreen. .B \-\-force\-adb\-forward Do not attempt to use "adb reverse" to connect to the device. -.TP -.B \-\-forward\-all\-clicks -By default, right-click triggers BACK (or POWER on) and middle-click triggers HOME. This option disables these shortcuts and forward the clicks to the device instead. - .TP .B \-h, \-\-help Print this help. @@ -261,6 +257,23 @@ LAlt, LSuper or RSuper toggle the capture mode, to give control of the mouse bac Also see \fB\-\-keyboard\fR. +.TP +.BI "\-\-mouse\-bind " xxxx +Configure bindings of secondary clicks. + +The argument must be exactly 4 characters, one for each secondary click (in order: right click, middle click, 4th click, 5th click). + +Each character must be one of the following: + + - '+': forward the click to the device + - '-': ignore the click + - 'b': trigger shortcut BACK (or turn screen on if off) + - 'h': trigger shortcut HOME + - 's': trigger shortcut APP_SWITCH + - 'n': trigger shortcut "expand notification panel" + +Default is 'bhsn'. + .TP .B \-n, \-\-no\-control diff --git a/app/src/cli.c b/app/src/cli.c index a2eb4254..35230a9a 100644 --- a/app/src/cli.c +++ b/app/src/cli.c @@ -98,6 +98,7 @@ enum { OPT_HID_KEYBOARD_DEPRECATED, OPT_HID_MOUSE_DEPRECATED, OPT_NO_WINDOW, + OPT_MOUSE_BIND, }; struct sc_option { @@ -352,11 +353,9 @@ static const struct sc_option options[] = { "device.", }, { + // deprecated .longopt_id = OPT_FORWARD_ALL_CLICKS, .longopt = "forward-all-clicks", - .text = "By default, right-click triggers BACK (or POWER on) and " - "middle-click triggers HOME. This option disables these " - "shortcuts and forwards the clicks to the device instead.", }, { .shortopt = 'h', @@ -490,6 +489,23 @@ static const struct sc_option options[] = { "control of the mouse back to the computer.\n" "Also see --keyboard.", }, + { + .longopt_id = OPT_MOUSE_BIND, + .longopt = "mouse-bind", + .argdesc = "xxxx", + .text = "Configure bindings of secondary clicks.\n" + "The argument must be exactly 4 characters, one for each " + "secondary click (in order: right click, middle click, 4th " + "click, 5th click).\n" + "Each character must be one of the following:\n" + " '+': forward the click to the device\n" + " '-': ignore the click\n" + " 'b': trigger shortcut BACK (or turn screen on if off)\n" + " 'h': trigger shortcut HOME\n" + " 's': trigger shortcut APP_SWITCH\n" + " 'n': trigger shortcut \"expand notification panel\"\n" + "Default is 'bhsn'.", + }, { .shortopt = 'n', .longopt = "no-control", @@ -2043,6 +2059,58 @@ parse_pause_on_exit(const char *s, enum sc_pause_on_exit *pause_on_exit) { } +static bool +parse_mouse_binding(char c, enum sc_mouse_binding *b) { + switch (c) { + case '+': + *b = SC_MOUSE_BINDING_CLICK; + return true; + case '-': + *b = SC_MOUSE_BINDING_DISABLED; + return true; + case 'b': + *b = SC_MOUSE_BINDING_BACK; + return true; + case 'h': + *b = SC_MOUSE_BINDING_HOME; + return true; + case 's': + *b = SC_MOUSE_BINDING_APP_SWITCH; + return true; + case 'n': + *b = SC_MOUSE_BINDING_EXPAND_NOTIFICATION_PANEL; + return true; + default: + LOGE("Invalid mouse binding: '%c' " + "(expected '+', '-', 'b', 'h', 's' or 'n')", c); + return false; + } +} + +static bool +parse_mouse_bindings(const char *s, struct sc_mouse_bindings *mb) { + if (strlen(s) != 4) { + LOGE("Invalid mouse bindings: '%s' (expected exactly 4 characters from " + "{'+', '-', 'b', 'h', 's', 'n'})", s); + return false; + } + + if (!parse_mouse_binding(s[0], &mb->right_click)) { + return false; + } + if (!parse_mouse_binding(s[1], &mb->middle_click)) { + return false; + } + if (!parse_mouse_binding(s[2], &mb->click4)) { + return false; + } + if (!parse_mouse_binding(s[3], &mb->click5)) { + return false; + } + + return true; +} + static bool parse_args_with_getopt(struct scrcpy_cli_args *args, int argc, char *argv[], const char *optstring, const struct option *longopts) { @@ -2125,6 +2193,11 @@ parse_args_with_getopt(struct scrcpy_cli_args *args, int argc, char *argv[], return false; } break; + case OPT_MOUSE_BIND: + if (!parse_mouse_bindings(optarg, &opts->mouse_bindings)) { + return false; + } + break; case OPT_HID_MOUSE_DEPRECATED: LOGE("--hid-mouse has been removed, use --mouse=aoa or " "--mouse=uhid instead."); @@ -2322,7 +2395,14 @@ parse_args_with_getopt(struct scrcpy_cli_args *args, int argc, char *argv[], } break; case OPT_FORWARD_ALL_CLICKS: - opts->forward_all_clicks = true; + LOGW("--forward-all-clicks is deprecated, " + "use --mouse-bind=++++ instead."); + opts->mouse_bindings = (struct sc_mouse_bindings) { + .right_click = SC_MOUSE_BINDING_CLICK, + .middle_click = SC_MOUSE_BINDING_CLICK, + .click4 = SC_MOUSE_BINDING_CLICK, + .click5 = SC_MOUSE_BINDING_CLICK, + }; break; case OPT_LEGACY_PASTE: opts->legacy_paste = true; diff --git a/app/src/input_events.h b/app/src/input_events.h index 5831ba0f..ed77bcb4 100644 --- a/app/src/input_events.h +++ b/app/src/input_events.h @@ -9,6 +9,7 @@ #include #include "coords.h" +#include "options.h" /* The representation of input events in scrcpy is very close to the SDL API, * for simplicity. @@ -437,15 +438,21 @@ sc_mouse_button_from_sdl(uint8_t button) { static inline uint8_t sc_mouse_buttons_state_from_sdl(uint32_t buttons_state, - bool forward_all_clicks) { + const struct sc_mouse_bindings *mb) { assert(buttons_state < 0x100); // fits in uint8_t uint8_t mask = SC_MOUSE_BUTTON_LEFT; - if (forward_all_clicks) { - mask |= SC_MOUSE_BUTTON_RIGHT - | SC_MOUSE_BUTTON_MIDDLE - | SC_MOUSE_BUTTON_X1 - | SC_MOUSE_BUTTON_X2; + if (!mb || mb->right_click == SC_MOUSE_BINDING_CLICK) { + mask |= SC_MOUSE_BUTTON_RIGHT; + } + if (!mb || mb->middle_click == SC_MOUSE_BINDING_CLICK) { + mask |= SC_MOUSE_BUTTON_MIDDLE; + } + if (!mb || mb->click4 == SC_MOUSE_BINDING_CLICK) { + mask |= SC_MOUSE_BUTTON_X1; + } + if (!mb || mb->click5 == SC_MOUSE_BINDING_CLICK) { + mask |= SC_MOUSE_BUTTON_X2; } return buttons_state & mask; diff --git a/app/src/input_manager.c b/app/src/input_manager.c index f5f7992a..3166fbff 100644 --- a/app/src/input_manager.c +++ b/app/src/input_manager.c @@ -52,6 +52,14 @@ is_shortcut_key(struct sc_input_manager *im, SDL_Keycode keycode) { || (im->sdl_shortcut_mods & KMOD_RGUI && keycode == SDLK_RGUI); } +static inline bool +mouse_bindings_has_secondary_click(const struct sc_mouse_bindings *mb) { + return mb->right_click == SC_MOUSE_BINDING_CLICK + || mb->middle_click == SC_MOUSE_BINDING_CLICK + || mb->click4 == SC_MOUSE_BINDING_CLICK + || mb->click5 == SC_MOUSE_BINDING_CLICK; +} + void sc_input_manager_init(struct sc_input_manager *im, const struct sc_input_manager_params *params) { @@ -67,7 +75,9 @@ sc_input_manager_init(struct sc_input_manager *im, im->kp = params->kp; im->mp = params->mp; - im->forward_all_clicks = params->forward_all_clicks; + im->mouse_bindings = params->mouse_bindings; + im->has_secondary_click = + mouse_bindings_has_secondary_click(&im->mouse_bindings); im->legacy_paste = params->legacy_paste; im->clipboard_autosync = params->clipboard_autosync; @@ -366,8 +376,8 @@ simulate_virtual_finger(struct sc_input_manager *im, msg.inject_touch_event.position.screen_size = im->screen->frame_size; msg.inject_touch_event.position.point = point; msg.inject_touch_event.pointer_id = - im->forward_all_clicks ? POINTER_ID_VIRTUAL_MOUSE - : POINTER_ID_VIRTUAL_FINGER; + im->has_secondary_click ? POINTER_ID_VIRTUAL_MOUSE + : POINTER_ID_VIRTUAL_FINGER; msg.inject_touch_event.pressure = up ? 0.0f : 1.0f; msg.inject_touch_event.action_button = 0; msg.inject_touch_event.buttons = 0; @@ -652,13 +662,12 @@ sc_input_manager_process_mouse_motion(struct sc_input_manager *im, struct sc_mouse_motion_event evt = { .position = sc_input_manager_get_position(im, event->x, event->y), - .pointer_id = im->forward_all_clicks ? POINTER_ID_MOUSE - : POINTER_ID_GENERIC_FINGER, + .pointer_id = im->has_secondary_click ? POINTER_ID_MOUSE + : POINTER_ID_GENERIC_FINGER, .xrel = event->xrel, .yrel = event->yrel, .buttons_state = - sc_mouse_buttons_state_from_sdl(event->state, - im->forward_all_clicks), + sc_mouse_buttons_state_from_sdl(event->state, &im->mouse_bindings), }; assert(im->mp->ops->process_mouse_motion); @@ -709,6 +718,25 @@ sc_input_manager_process_touch(struct sc_input_manager *im, im->mp->ops->process_touch(im->mp, &evt); } +static enum sc_mouse_binding +sc_input_manager_get_binding(const struct sc_mouse_bindings *bindings, + uint8_t sdl_button) { + switch (sdl_button) { + case SDL_BUTTON_LEFT: + return SC_MOUSE_BINDING_CLICK; + case SDL_BUTTON_RIGHT: + return bindings->right_click; + case SDL_BUTTON_MIDDLE: + return bindings->middle_click; + case SDL_BUTTON_X1: + return bindings->click4; + case SDL_BUTTON_X2: + return bindings->click5; + default: + return SC_MOUSE_BINDING_DISABLED; + } +} + static void sc_input_manager_process_mouse_button(struct sc_input_manager *im, const SDL_MouseButtonEvent *event) { @@ -720,28 +748,42 @@ sc_input_manager_process_mouse_button(struct sc_input_manager *im, bool control = im->controller; bool paused = im->screen->paused; bool down = event->type == SDL_MOUSEBUTTONDOWN; - if (control && !paused && !im->forward_all_clicks) { + if (control && !paused) { enum sc_action action = down ? SC_ACTION_DOWN : SC_ACTION_UP; - if (im->kp && event->button == SDL_BUTTON_X1) { - action_app_switch(im, action); - return; - } - if (event->button == SDL_BUTTON_X2 && down) { - if (event->clicks < 2) { - expand_notification_panel(im); - } else { - expand_settings_panel(im); - } - return; - } - if (im->kp && event->button == SDL_BUTTON_RIGHT) { - press_back_or_turn_screen_on(im, action); - return; - } - if (im->kp && event->button == SDL_BUTTON_MIDDLE) { - action_home(im, action); - return; + enum sc_mouse_binding binding = + sc_input_manager_get_binding(&im->mouse_bindings, event->button); + switch (binding) { + case SC_MOUSE_BINDING_DISABLED: + // ignore click + return; + case SC_MOUSE_BINDING_BACK: + if (im->kp) { + press_back_or_turn_screen_on(im, action); + } + return; + case SC_MOUSE_BINDING_HOME: + if (im->kp) { + action_home(im, action); + } + return; + case SC_MOUSE_BINDING_APP_SWITCH: + if (im->kp) { + action_app_switch(im, action); + } + return; + case SC_MOUSE_BINDING_EXPAND_NOTIFICATION_PANEL: + if (down) { + if (event->clicks < 2) { + expand_notification_panel(im); + } else { + expand_settings_panel(im); + } + } + return; + default: + assert(binding == SC_MOUSE_BINDING_CLICK); + break; } } @@ -774,11 +816,10 @@ sc_input_manager_process_mouse_button(struct sc_input_manager *im, .position = sc_input_manager_get_position(im, event->x, event->y), .action = sc_action_from_sdl_mousebutton_type(event->type), .button = sc_mouse_button_from_sdl(event->button), - .pointer_id = im->forward_all_clicks ? POINTER_ID_MOUSE - : POINTER_ID_GENERIC_FINGER, - .buttons_state = - sc_mouse_buttons_state_from_sdl(sdl_buttons_state, - im->forward_all_clicks), + .pointer_id = im->has_secondary_click ? POINTER_ID_MOUSE + : POINTER_ID_GENERIC_FINGER, + .buttons_state = sc_mouse_buttons_state_from_sdl(sdl_buttons_state, + &im->mouse_bindings), }; assert(im->mp->ops->process_mouse_click); @@ -854,8 +895,8 @@ sc_input_manager_process_mouse_wheel(struct sc_input_manager *im, .hscroll = CLAMP(event->x, -1, 1), .vscroll = CLAMP(event->y, -1, 1), #endif - .buttons_state = - sc_mouse_buttons_state_from_sdl(buttons, im->forward_all_clicks), + .buttons_state = sc_mouse_buttons_state_from_sdl(buttons, + &im->mouse_bindings), }; im->mp->ops->process_mouse_scroll(im->mp, &evt); diff --git a/app/src/input_manager.h b/app/src/input_manager.h index 8c45c165..03c42fe6 100644 --- a/app/src/input_manager.h +++ b/app/src/input_manager.h @@ -22,7 +22,8 @@ struct sc_input_manager { struct sc_key_processor *kp; struct sc_mouse_processor *mp; - bool forward_all_clicks; + struct sc_mouse_bindings mouse_bindings; + bool has_secondary_click; bool legacy_paste; bool clipboard_autosync; @@ -49,7 +50,7 @@ struct sc_input_manager_params { struct sc_key_processor *kp; struct sc_mouse_processor *mp; - bool forward_all_clicks; + struct sc_mouse_bindings mouse_bindings; bool legacy_paste; bool clipboard_autosync; uint8_t shortcut_mods; // OR of enum sc_shortcut_mod values diff --git a/app/src/options.c b/app/src/options.c index 4b75ed6a..939108cf 100644 --- a/app/src/options.c +++ b/app/src/options.c @@ -23,6 +23,12 @@ const struct scrcpy_options scrcpy_options_default = { .record_format = SC_RECORD_FORMAT_AUTO, .keyboard_input_mode = SC_KEYBOARD_INPUT_MODE_AUTO, .mouse_input_mode = SC_MOUSE_INPUT_MODE_AUTO, + .mouse_bindings = { + .right_click = SC_MOUSE_BINDING_BACK, + .middle_click = SC_MOUSE_BINDING_HOME, + .click4 = SC_MOUSE_BINDING_APP_SWITCH, + .click5 = SC_MOUSE_BINDING_EXPAND_NOTIFICATION_PANEL, + }, .camera_facing = SC_CAMERA_FACING_ANY, .port_range = { .first = DEFAULT_LOCAL_PORT_RANGE_FIRST, @@ -68,7 +74,6 @@ const struct scrcpy_options scrcpy_options_default = { .force_adb_forward = false, .disable_screensaver = false, .forward_key_repeat = true, - .forward_all_clicks = false, .legacy_paste = false, .power_off_on_close = false, .clipboard_autosync = true, diff --git a/app/src/options.h b/app/src/options.h index 85817341..17615c75 100644 --- a/app/src/options.h +++ b/app/src/options.h @@ -155,6 +155,22 @@ enum sc_mouse_input_mode { SC_MOUSE_INPUT_MODE_AOA, }; +enum sc_mouse_binding { + SC_MOUSE_BINDING_DISABLED, + SC_MOUSE_BINDING_CLICK, + SC_MOUSE_BINDING_BACK, + SC_MOUSE_BINDING_HOME, + SC_MOUSE_BINDING_APP_SWITCH, + SC_MOUSE_BINDING_EXPAND_NOTIFICATION_PANEL, +}; + +struct sc_mouse_bindings { + enum sc_mouse_binding right_click; + enum sc_mouse_binding middle_click; + enum sc_mouse_binding click4; + enum sc_mouse_binding click5; +}; + enum sc_key_inject_mode { // Inject special keys, letters and space as key events. // Inject numbers and punctuation as text events. @@ -208,6 +224,7 @@ struct scrcpy_options { enum sc_record_format record_format; enum sc_keyboard_input_mode keyboard_input_mode; enum sc_mouse_input_mode mouse_input_mode; + struct sc_mouse_bindings mouse_bindings; enum sc_camera_facing camera_facing; struct sc_port_range port_range; uint32_t tunnel_host; @@ -250,7 +267,6 @@ struct scrcpy_options { bool force_adb_forward; bool disable_screensaver; bool forward_key_repeat; - bool forward_all_clicks; bool legacy_paste; bool power_off_on_close; bool clipboard_autosync; diff --git a/app/src/scrcpy.c b/app/src/scrcpy.c index 5f13ee53..85b89935 100644 --- a/app/src/scrcpy.c +++ b/app/src/scrcpy.c @@ -712,7 +712,7 @@ scrcpy(struct scrcpy_options *options) { .fp = fp, .kp = kp, .mp = mp, - .forward_all_clicks = options->forward_all_clicks, + .mouse_bindings = options->mouse_bindings, .legacy_paste = options->legacy_paste, .clipboard_autosync = options->clipboard_autosync, .shortcut_mods = options->shortcut_mods, diff --git a/app/src/screen.c b/app/src/screen.c index 56f13f99..55a06ab3 100644 --- a/app/src/screen.c +++ b/app/src/screen.c @@ -481,7 +481,7 @@ sc_screen_init(struct sc_screen *screen, .screen = screen, .kp = params->kp, .mp = params->mp, - .forward_all_clicks = params->forward_all_clicks, + .mouse_bindings = params->mouse_bindings, .legacy_paste = params->legacy_paste, .clipboard_autosync = params->clipboard_autosync, .shortcut_mods = params->shortcut_mods, diff --git a/app/src/screen.h b/app/src/screen.h index 437e7633..079d4fbb 100644 --- a/app/src/screen.h +++ b/app/src/screen.h @@ -79,7 +79,7 @@ struct sc_screen_params { struct sc_key_processor *kp; struct sc_mouse_processor *mp; - bool forward_all_clicks; + struct sc_mouse_bindings mouse_bindings; bool legacy_paste; bool clipboard_autosync; uint8_t shortcut_mods; // OR of enum sc_shortcut_mod values diff --git a/app/src/usb/screen_otg.c b/app/src/usb/screen_otg.c index e1d5cb01..33500e0c 100644 --- a/app/src/usb/screen_otg.c +++ b/app/src/usb/screen_otg.c @@ -169,7 +169,7 @@ sc_screen_otg_process_mouse_motion(struct sc_screen_otg *screen, // .position not used for HID events .xrel = event->xrel, .yrel = event->yrel, - .buttons_state = sc_mouse_buttons_state_from_sdl(event->state, true), + .buttons_state = sc_mouse_buttons_state_from_sdl(event->state, NULL), }; assert(mp->ops->process_mouse_motion); @@ -189,7 +189,7 @@ sc_screen_otg_process_mouse_button(struct sc_screen_otg *screen, .action = sc_action_from_sdl_mousebutton_type(event->type), .button = sc_mouse_button_from_sdl(event->button), .buttons_state = - sc_mouse_buttons_state_from_sdl(sdl_buttons_state, true), + sc_mouse_buttons_state_from_sdl(sdl_buttons_state, NULL), }; assert(mp->ops->process_mouse_click); @@ -209,7 +209,7 @@ sc_screen_otg_process_mouse_wheel(struct sc_screen_otg *screen, .hscroll = event->x, .vscroll = event->y, .buttons_state = - sc_mouse_buttons_state_from_sdl(sdl_buttons_state, true), + sc_mouse_buttons_state_from_sdl(sdl_buttons_state, NULL), }; assert(mp->ops->process_mouse_scroll); diff --git a/doc/control.md b/doc/control.md index 87897894..34eb7a6a 100644 --- a/doc/control.md +++ b/doc/control.md @@ -106,15 +106,6 @@ only inverts _x_. This only works for the default mouse mode (`--mouse=sdk`). -## Right-click and middle-click - -By default, right-click triggers BACK (or POWER on) and middle-click triggers -HOME. To disable these shortcuts and forward the clicks to the device instead: - -```bash -scrcpy --forward-all-clicks -``` - ## File drop ### Install APK diff --git a/doc/mouse.md b/doc/mouse.md index d0342954..146956f5 100644 --- a/doc/mouse.md +++ b/doc/mouse.md @@ -68,3 +68,41 @@ debugging disabled (see [OTG](otg.md)). Note: On Windows, it may only work in [OTG mode](otg.md), not while mirroring (it is not possible to open a USB device if it is already open by another process like the _adb daemon_). + + +## Mouse bindings + +By default, right-click triggers BACK (or POWER on) and middle-click triggers +HOME. In addition, the 4th click triggers APP_SWITCH and the 5th click expands +the notification panel. + +The shortcuts can be configured using `--mouse-bind=xxxx`. The argument must be +exactly 4 characters, one for each secondary click: + +``` +--mouse-bind=xxxx + ^^^^ + |||| + ||| `- 5th click + || `-- 4th click + | `--- middle click + `---- right click +``` + +Each character must be one of the following: + + - `+`: forward the click to the device + - `-`: ignore the click + - `b`: trigger shortcut BACK (or turn screen on if off) + - `h`: trigger shortcut HOME + - `s`: trigger shortcut APP_SWITCH + - `n`: trigger shortcut "expand notification panel" + +For example: + +```bash +scrcpy --mouse-bind=bhsn # the default mode +scrcpy --mouse-bind=++++ # forward all clicks +scrcpy --mouse-bind=++bh # forward right and middle clicks, + # use 4th and 5th for BACK and HOME +``` From f5e6b8092afd82bab402e7c2c3d00b1719f9bb57 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Mon, 24 Jun 2024 23:11:42 +0200 Subject: [PATCH 34/44] Forward all clicks by default for UHID/AOA By default, only the left click is forwarded to the device, and secondary clicks trigger shortcuts (the behavior can be configured by --mouse-bind=xxxx). But when the mouse mode is relative (AOA and UHID modes), forward all clicks by default. This makes more sense since the cursor is handled on the device side, the user expects all mouse buttons to be forwarded. Refs PR #5022 --- app/scrcpy.1 | 2 +- app/src/cli.c | 26 +++++++++++++++++++++++++- app/src/input_manager.c | 1 + app/src/options.c | 8 ++++---- app/src/options.h | 1 + doc/mouse.md | 16 +++++++++------- 6 files changed, 41 insertions(+), 13 deletions(-) diff --git a/app/scrcpy.1 b/app/scrcpy.1 index 7a0b3dfb..b2021f26 100644 --- a/app/scrcpy.1 +++ b/app/scrcpy.1 @@ -272,7 +272,7 @@ Each character must be one of the following: - 's': trigger shortcut APP_SWITCH - 'n': trigger shortcut "expand notification panel" -Default is 'bhsn'. +Default is 'bhsn' for SDK mouse, and '++++' for AOA and UHID. .TP diff --git a/app/src/cli.c b/app/src/cli.c index 35230a9a..d3ba3463 100644 --- a/app/src/cli.c +++ b/app/src/cli.c @@ -504,7 +504,7 @@ static const struct sc_option options[] = { " 'h': trigger shortcut HOME\n" " 's': trigger shortcut APP_SWITCH\n" " 'n': trigger shortcut \"expand notification panel\"\n" - "Default is 'bhsn'.", + "Default is 'bhsn' for SDK mouse, and '++++' for AOA and UHID.", }, { .shortopt = 'n', @@ -2690,6 +2690,30 @@ parse_args_with_getopt(struct scrcpy_cli_args *args, int argc, char *argv[], } } + // If mouse bindings are not explictly set, configure default bindings + if (opts->mouse_bindings.right_click == SC_MOUSE_BINDING_AUTO) { + assert(opts->mouse_bindings.middle_click == SC_MOUSE_BINDING_AUTO); + assert(opts->mouse_bindings.click4 == SC_MOUSE_BINDING_AUTO); + assert(opts->mouse_bindings.click5 == SC_MOUSE_BINDING_AUTO); + + // By default, forward all clicks only for UHID and AOA + if (opts->mouse_input_mode == SC_MOUSE_INPUT_MODE_SDK) { + opts->mouse_bindings = (struct sc_mouse_bindings) { + .right_click = SC_MOUSE_BINDING_BACK, + .middle_click = SC_MOUSE_BINDING_HOME, + .click4 = SC_MOUSE_BINDING_APP_SWITCH, + .click5 = SC_MOUSE_BINDING_EXPAND_NOTIFICATION_PANEL, + }; + } else { + opts->mouse_bindings = (struct sc_mouse_bindings) { + .right_click = SC_MOUSE_BINDING_CLICK, + .middle_click = SC_MOUSE_BINDING_CLICK, + .click4 = SC_MOUSE_BINDING_CLICK, + .click5 = SC_MOUSE_BINDING_CLICK, + }; + } + } + if (otg) { if (!opts->control) { LOGE("--no-control is not allowed in OTG mode"); diff --git a/app/src/input_manager.c b/app/src/input_manager.c index 3166fbff..43b10d2d 100644 --- a/app/src/input_manager.c +++ b/app/src/input_manager.c @@ -753,6 +753,7 @@ sc_input_manager_process_mouse_button(struct sc_input_manager *im, enum sc_mouse_binding binding = sc_input_manager_get_binding(&im->mouse_bindings, event->button); + assert(binding != SC_MOUSE_BINDING_AUTO); switch (binding) { case SC_MOUSE_BINDING_DISABLED: // ignore click diff --git a/app/src/options.c b/app/src/options.c index 939108cf..352d9895 100644 --- a/app/src/options.c +++ b/app/src/options.c @@ -24,10 +24,10 @@ const struct scrcpy_options scrcpy_options_default = { .keyboard_input_mode = SC_KEYBOARD_INPUT_MODE_AUTO, .mouse_input_mode = SC_MOUSE_INPUT_MODE_AUTO, .mouse_bindings = { - .right_click = SC_MOUSE_BINDING_BACK, - .middle_click = SC_MOUSE_BINDING_HOME, - .click4 = SC_MOUSE_BINDING_APP_SWITCH, - .click5 = SC_MOUSE_BINDING_EXPAND_NOTIFICATION_PANEL, + .right_click = SC_MOUSE_BINDING_AUTO, + .middle_click = SC_MOUSE_BINDING_AUTO, + .click4 = SC_MOUSE_BINDING_AUTO, + .click5 = SC_MOUSE_BINDING_AUTO, }, .camera_facing = SC_CAMERA_FACING_ANY, .port_range = { diff --git a/app/src/options.h b/app/src/options.h index 17615c75..d5b090b7 100644 --- a/app/src/options.h +++ b/app/src/options.h @@ -156,6 +156,7 @@ enum sc_mouse_input_mode { }; enum sc_mouse_binding { + SC_MOUSE_BINDING_AUTO, SC_MOUSE_BINDING_DISABLED, SC_MOUSE_BINDING_CLICK, SC_MOUSE_BINDING_BACK, diff --git a/doc/mouse.md b/doc/mouse.md index 146956f5..42d70766 100644 --- a/doc/mouse.md +++ b/doc/mouse.md @@ -72,12 +72,14 @@ process like the _adb daemon_). ## Mouse bindings -By default, right-click triggers BACK (or POWER on) and middle-click triggers -HOME. In addition, the 4th click triggers APP_SWITCH and the 5th click expands -the notification panel. +By default, with SDK mouse, right-click triggers BACK (or POWER on) and +middle-click triggers HOME. In addition, the 4th click triggers APP_SWITCH and +the 5th click expands the notification panel. -The shortcuts can be configured using `--mouse-bind=xxxx`. The argument must be -exactly 4 characters, one for each secondary click: +In AOA and UHID mouse modes, all clicks are forwarded by default. + +The shortcuts can be configured using `--mouse-bind=xxxx` for any mouse mode. +The argument must be exactly 4 characters, one for each secondary click: ``` --mouse-bind=xxxx @@ -101,8 +103,8 @@ Each character must be one of the following: For example: ```bash -scrcpy --mouse-bind=bhsn # the default mode -scrcpy --mouse-bind=++++ # forward all clicks +scrcpy --mouse-bind=bhsn # the default mode with SDK mouse +scrcpy --mouse-bind=++++ # forward all clicks (default for AOA/UHID) scrcpy --mouse-bind=++bh # forward right and middle clicks, # use 4th and 5th for BACK and HOME ``` From 76332282783f72bc611dcb1b871f4baacd59de1d Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Fri, 21 Jun 2024 16:54:00 +0200 Subject: [PATCH 35/44] Forward mouse hover events Also add an option --no-mouse-hover to get the old behavior. Fixes #2743 Fixes #3070 PR #5039 --- app/data/bash-completion/scrcpy | 1 + app/data/zsh-completion/_scrcpy | 1 + app/scrcpy.1 | 4 ++++ app/src/cli.c | 16 ++++++++++++++++ app/src/mouse_sdk.c | 13 ++++++++----- app/src/mouse_sdk.h | 4 +++- app/src/options.c | 1 + app/src/options.h | 1 + app/src/scrcpy.c | 3 ++- doc/mouse.md | 8 ++++++++ 10 files changed, 45 insertions(+), 7 deletions(-) diff --git a/app/data/bash-completion/scrcpy b/app/data/bash-completion/scrcpy index f00fadae..b35ea5e4 100644 --- a/app/data/bash-completion/scrcpy +++ b/app/data/bash-completion/scrcpy @@ -50,6 +50,7 @@ _scrcpy() { --no-downsize-on-error --no-key-repeat --no-mipmaps + --no-mouse-hover --no-power-on --no-video --no-video-playback diff --git a/app/data/zsh-completion/_scrcpy b/app/data/zsh-completion/_scrcpy index 86fe0b0e..5afca977 100644 --- a/app/data/zsh-completion/_scrcpy +++ b/app/data/zsh-completion/_scrcpy @@ -56,6 +56,7 @@ arguments=( '--no-downsize-on-error[Disable lowering definition on MediaCodec error]' '--no-key-repeat[Do not forward repeated key events when a key is held down]' '--no-mipmaps[Disable the generation of mipmaps]' + '--no-mouse-hover[Do not forward mouse hover events]' '--no-power-on[Do not power on the device on start]' '--no-video[Disable video forwarding]' '--no-video-playback[Disable video playback]' diff --git a/app/scrcpy.1 b/app/scrcpy.1 index b2021f26..cf8dfa7f 100644 --- a/app/scrcpy.1 +++ b/app/scrcpy.1 @@ -317,6 +317,10 @@ Do not forward repeated key events when a key is held down. .B \-\-no\-mipmaps If the renderer is OpenGL 3.0+ or OpenGL ES 2.0+, then mipmaps are automatically generated to improve downscaling quality. This option disables the generation of mipmaps. +.TP +.B \-\-no\-mouse\-hover +Do not forward mouse hover (mouse motion without any clicks) events. + .TP .B \-\-no\-power\-on Do not power on the device on start. diff --git a/app/src/cli.c b/app/src/cli.c index d3ba3463..08a4aa3f 100644 --- a/app/src/cli.c +++ b/app/src/cli.c @@ -99,6 +99,7 @@ enum { OPT_HID_MOUSE_DEPRECATED, OPT_NO_WINDOW, OPT_MOUSE_BIND, + OPT_NO_MOUSE_HOVER, }; struct sc_option { @@ -568,6 +569,12 @@ static const struct sc_option options[] = { "mipmaps are automatically generated to improve downscaling " "quality. This option disables the generation of mipmaps.", }, + { + .longopt_id = OPT_NO_MOUSE_HOVER, + .longopt = "no-mouse-hover", + .text = "Do not forward mouse hover (mouse motion without any clicks) " + "events.", + }, { .longopt_id = OPT_NO_POWER_ON, .longopt = "no-power-on", @@ -2198,6 +2205,9 @@ parse_args_with_getopt(struct scrcpy_cli_args *args, int argc, char *argv[], return false; } break; + case OPT_NO_MOUSE_HOVER: + opts->mouse_hover = false; + break; case OPT_HID_MOUSE_DEPRECATED: LOGE("--hid-mouse has been removed, use --mouse=aoa or " "--mouse=uhid instead."); @@ -2758,6 +2768,12 @@ parse_args_with_getopt(struct scrcpy_cli_args *args, int argc, char *argv[], } } + if (opts->mouse_input_mode != SC_MOUSE_INPUT_MODE_SDK + && !opts->mouse_hover) { + LOGE("--no-mouse-over is specific to --mouse=sdk"); + return false; + } + if ((opts->tunnel_host || opts->tunnel_port) && !opts->force_adb_forward) { LOGI("Tunnel host/port is set, " "--force-adb-forward automatically enabled."); diff --git a/app/src/mouse_sdk.c b/app/src/mouse_sdk.c index 620fb52c..a7998972 100644 --- a/app/src/mouse_sdk.c +++ b/app/src/mouse_sdk.c @@ -58,17 +58,18 @@ convert_touch_action(enum sc_touch_action action) { static void sc_mouse_processor_process_mouse_motion(struct sc_mouse_processor *mp, const struct sc_mouse_motion_event *event) { - if (!event->buttons_state) { + struct sc_mouse_sdk *m = DOWNCAST(mp); + + if (!m->mouse_hover && !event->buttons_state) { // Do not send motion events when no click is pressed return; } - struct sc_mouse_sdk *m = DOWNCAST(mp); - struct sc_control_msg msg = { .type = SC_CONTROL_MSG_TYPE_INJECT_TOUCH_EVENT, .inject_touch_event = { - .action = AMOTION_EVENT_ACTION_MOVE, + .action = event->buttons_state ? AMOTION_EVENT_ACTION_MOVE + : AMOTION_EVENT_ACTION_HOVER_MOVE, .pointer_id = event->pointer_id, .position = event->position, .pressure = 1.f, @@ -145,8 +146,10 @@ sc_mouse_processor_process_touch(struct sc_mouse_processor *mp, } void -sc_mouse_sdk_init(struct sc_mouse_sdk *m, struct sc_controller *controller) { +sc_mouse_sdk_init(struct sc_mouse_sdk *m, struct sc_controller *controller, + bool mouse_hover) { m->controller = controller; + m->mouse_hover = mouse_hover; static const struct sc_mouse_processor_ops ops = { .process_mouse_motion = sc_mouse_processor_process_mouse_motion, diff --git a/app/src/mouse_sdk.h b/app/src/mouse_sdk.h index 444a6ad5..142b89bb 100644 --- a/app/src/mouse_sdk.h +++ b/app/src/mouse_sdk.h @@ -13,9 +13,11 @@ struct sc_mouse_sdk { struct sc_mouse_processor mouse_processor; // mouse processor trait struct sc_controller *controller; + bool mouse_hover; }; void -sc_mouse_sdk_init(struct sc_mouse_sdk *m, struct sc_controller *controller); +sc_mouse_sdk_init(struct sc_mouse_sdk *m, struct sc_controller *controller, + bool mouse_hover); #endif diff --git a/app/src/options.c b/app/src/options.c index 352d9895..5556d1f9 100644 --- a/app/src/options.c +++ b/app/src/options.c @@ -92,6 +92,7 @@ const struct scrcpy_options scrcpy_options_default = { .camera_high_speed = false, .list = 0, .window = true, + .mouse_hover = true, }; enum sc_orientation diff --git a/app/src/options.h b/app/src/options.h index d5b090b7..f840a989 100644 --- a/app/src/options.h +++ b/app/src/options.h @@ -290,6 +290,7 @@ struct scrcpy_options { #define SC_OPTION_LIST_CAMERA_SIZES 0x8 uint8_t list; bool window; + bool mouse_hover; }; extern const struct scrcpy_options scrcpy_options_default; diff --git a/app/src/scrcpy.c b/app/src/scrcpy.c index 85b89935..5e78dbf3 100644 --- a/app/src/scrcpy.c +++ b/app/src/scrcpy.c @@ -681,7 +681,8 @@ scrcpy(struct scrcpy_options *options) { } if (options->mouse_input_mode == SC_MOUSE_INPUT_MODE_SDK) { - sc_mouse_sdk_init(&s->mouse_sdk, &s->controller); + sc_mouse_sdk_init(&s->mouse_sdk, &s->controller, + options->mouse_hover); mp = &s->mouse_sdk.mouse_processor; } else if (options->mouse_input_mode == SC_MOUSE_INPUT_MODE_UHID) { bool ok = sc_mouse_uhid_init(&s->mouse_uhid, &s->controller); diff --git a/doc/mouse.md b/doc/mouse.md index 42d70766..1c62ddd0 100644 --- a/doc/mouse.md +++ b/doc/mouse.md @@ -18,6 +18,14 @@ Note that on some devices, an additional option must be enabled in developer options for this mouse mode to work. See [prerequisites](/README.md#prerequisites). +### Mouse hover + +By default, mouse hover (mouse motion without any clicks) events are forwarded +to the device. This can be disabled with: + +``` +scrcpy --no-mouse-hover +``` ## Physical mouse simulation From 1e3deabd6c9e9937094bba27c8a7c452453534be Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Sat, 29 Jun 2024 13:05:46 +0200 Subject: [PATCH 36/44] Do not call avcodec_close() The documentation of avcodec_close() says: > Do not use this function. Use avcodec_free_context() to destroy a > codec context (either open or closed). It was deprecated in FFmpeg 7 by commit 1cc24d749569a42510399a29b034f7a77bdec34e: > Its use has been discouraged since 2016, but now is no longer used in > avformat, so there is no reason to keep it public. --- app/src/demuxer.c | 1 - app/src/icon.c | 12 +++++------- app/src/v4l2_sink.c | 5 +---- 3 files changed, 6 insertions(+), 12 deletions(-) diff --git a/app/src/demuxer.c b/app/src/demuxer.c index c27ea292..7223b553 100644 --- a/app/src/demuxer.c +++ b/app/src/demuxer.c @@ -278,7 +278,6 @@ run_demuxer(void *data) { finally_close_sinks: sc_packet_source_sinks_close(&demuxer->packet_source); finally_free_context: - // This also calls avcodec_close() internally avcodec_free_context(&codec_ctx); end: demuxer->cbs->on_ended(demuxer, status, demuxer->cbs_userdata); diff --git a/app/src/icon.c b/app/src/icon.c index 0dddefa3..a76a85c9 100644 --- a/app/src/icon.c +++ b/app/src/icon.c @@ -117,21 +117,21 @@ decode_image(const char *path) { AVFrame *frame = av_frame_alloc(); if (!frame) { LOG_OOM(); - goto close_codec; + goto free_codec_ctx; } AVPacket *packet = av_packet_alloc(); if (!packet) { LOG_OOM(); av_frame_free(&frame); - goto close_codec; + goto free_codec_ctx; } if (av_read_frame(ctx, packet) < 0) { LOGE("Could not read frame"); av_packet_free(&packet); av_frame_free(&frame); - goto close_codec; + goto free_codec_ctx; } int ret; @@ -139,22 +139,20 @@ decode_image(const char *path) { LOGE("Could not send icon packet: %d", ret); av_packet_free(&packet); av_frame_free(&frame); - goto close_codec; + goto free_codec_ctx; } if ((ret = avcodec_receive_frame(codec_ctx, frame)) != 0) { LOGE("Could not receive icon frame: %d", ret); av_packet_free(&packet); av_frame_free(&frame); - goto close_codec; + goto free_codec_ctx; } av_packet_free(&packet); result = frame; -close_codec: - avcodec_close(codec_ctx); free_codec_ctx: avcodec_free_context(&codec_ctx); close_input: diff --git a/app/src/v4l2_sink.c b/app/src/v4l2_sink.c index 3b3eb8d0..087e9af4 100644 --- a/app/src/v4l2_sink.c +++ b/app/src/v4l2_sink.c @@ -240,7 +240,7 @@ sc_v4l2_sink_open(struct sc_v4l2_sink *vs, const AVCodecContext *ctx) { vs->frame = av_frame_alloc(); if (!vs->frame) { LOG_OOM(); - goto error_avcodec_close; + goto error_avcodec_free_context; } vs->packet = av_packet_alloc(); @@ -268,8 +268,6 @@ error_av_packet_free: av_packet_free(&vs->packet); error_av_frame_free: av_frame_free(&vs->frame); -error_avcodec_close: - avcodec_close(vs->encoder_ctx); error_avcodec_free_context: avcodec_free_context(&vs->encoder_ctx); error_avio_close: @@ -297,7 +295,6 @@ sc_v4l2_sink_close(struct sc_v4l2_sink *vs) { av_packet_free(&vs->packet); av_frame_free(&vs->frame); - avcodec_close(vs->encoder_ctx); avcodec_free_context(&vs->encoder_ctx); avio_close(vs->format_ctx->pb); avformat_free_context(vs->format_ctx); From 48c2c030938a87426ce8e5e7e1f26b8f059d1932 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Sat, 29 Jun 2024 12:48:32 +0200 Subject: [PATCH 37/44] Upgrade FFmpeg (7.0.1) for Windows --- app/deps/ffmpeg.sh | 5 ++-- app/deps/patches/ffmpeg-6.1-fix-build.patch | 27 --------------------- 2 files changed, 2 insertions(+), 30 deletions(-) delete mode 100644 app/deps/patches/ffmpeg-6.1-fix-build.patch diff --git a/app/deps/ffmpeg.sh b/app/deps/ffmpeg.sh index 19fb2991..ef92d4a5 100755 --- a/app/deps/ffmpeg.sh +++ b/app/deps/ffmpeg.sh @@ -4,10 +4,10 @@ DEPS_DIR=$(dirname ${BASH_SOURCE[0]}) cd "$DEPS_DIR" . common -VERSION=6.1.1 +VERSION=7.0.1 FILENAME=ffmpeg-$VERSION.tar.xz PROJECT_DIR=ffmpeg-$VERSION -SHA256SUM=8684f4b00f94b85461884c3719382f1261f0d9eb3d59640a1f4ac0873616f968 +SHA256SUM=bce9eeb0f17ef8982390b1f37711a61b4290dc8c2a0c1a37b5857e85bfb0e4ff cd "$SOURCES_DIR" @@ -17,7 +17,6 @@ then else get_file "https://ffmpeg.org/releases/$FILENAME" "$FILENAME" "$SHA256SUM" tar xf "$FILENAME" # First level directory is "$PROJECT_DIR" - patch -d "$PROJECT_DIR" -p1 < "$PATCHES_DIR"/ffmpeg-6.1-fix-build.patch fi mkdir -p "$BUILD_DIR/$PROJECT_DIR" diff --git a/app/deps/patches/ffmpeg-6.1-fix-build.patch b/app/deps/patches/ffmpeg-6.1-fix-build.patch deleted file mode 100644 index ed4df48d..00000000 --- a/app/deps/patches/ffmpeg-6.1-fix-build.patch +++ /dev/null @@ -1,27 +0,0 @@ -From 03c80197afb324da38c9b70254231e3fdcfa68fc Mon Sep 17 00:00:00 2001 -From: Romain Vimont -Date: Sun, 12 Nov 2023 17:58:50 +0100 -Subject: [PATCH] Fix FFmpeg 6.1 build - -Build failed on tag n6.1 With --enable-decoder=av1 but without ---enable-muxer=av1. ---- - libavcodec/Makefile | 2 +- - 1 file changed, 1 insertion(+), 1 deletion(-) - -diff --git a/libavcodec/Makefile b/libavcodec/Makefile -index 580a8d6b54..aff19b670c 100644 ---- a/libavcodec/Makefile -+++ b/libavcodec/Makefile -@@ -249,7 +249,7 @@ OBJS-$(CONFIG_ATRAC3PAL_DECODER) += atrac3plusdec.o atrac3plus.o \ - OBJS-$(CONFIG_ATRAC9_DECODER) += atrac9dec.o - OBJS-$(CONFIG_AURA_DECODER) += cyuv.o - OBJS-$(CONFIG_AURA2_DECODER) += aura.o --OBJS-$(CONFIG_AV1_DECODER) += av1dec.o -+OBJS-$(CONFIG_AV1_DECODER) += av1dec.o av1_parse.o - OBJS-$(CONFIG_AV1_CUVID_DECODER) += cuviddec.o - OBJS-$(CONFIG_AV1_MEDIACODEC_DECODER) += mediacodecdec.o - OBJS-$(CONFIG_AV1_MEDIACODEC_ENCODER) += mediacodecenc.o --- -2.42.0 - From f13f00021fddebb3249748dca82ccb4ac7547505 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Sat, 29 Jun 2024 12:49:40 +0200 Subject: [PATCH 38/44] Upgrade SDL (2.30.4) for Windows --- app/deps/sdl.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/deps/sdl.sh b/app/deps/sdl.sh index 36c7ab1c..589f93e5 100755 --- a/app/deps/sdl.sh +++ b/app/deps/sdl.sh @@ -4,10 +4,10 @@ DEPS_DIR=$(dirname ${BASH_SOURCE[0]}) cd "$DEPS_DIR" . common -VERSION=2.28.5 +VERSION=2.30.4 FILENAME=SDL-$VERSION.tar.gz PROJECT_DIR=SDL-release-$VERSION -SHA256SUM=9f0556e4a24ef5b267010038ad9e9948b62f236d5bcc4b22179f95ef62d84023 +SHA256SUM=dcc2c8c9c3e9e1a7c8d61d9522f1cba4e9b740feb560dcb15234030984610ee2 cd "$SOURCES_DIR" From 343f715323580398d446d6d1935a31cdbb668031 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Sat, 29 Jun 2024 12:53:39 +0200 Subject: [PATCH 39/44] Upgrade platform-tools (35.0.0) for Windows --- app/deps/adb.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/deps/adb.sh b/app/deps/adb.sh index e2408216..58a54659 100755 --- a/app/deps/adb.sh +++ b/app/deps/adb.sh @@ -4,10 +4,10 @@ DEPS_DIR=$(dirname ${BASH_SOURCE[0]}) cd "$DEPS_DIR" . common -VERSION=34.0.5 +VERSION=35.0.0 FILENAME=platform-tools_r$VERSION-windows.zip PROJECT_DIR=platform-tools-$VERSION -SHA256SUM=3f8320152704377de150418a3c4c9d07d16d80a6c0d0d8f7289c22c499e33571 +SHA256SUM=7ab78a8f8b305ae4d0de647d99c43599744de61a0838d3a47bda0cdffefee87e cd "$SOURCES_DIR" From 89df38f64160556aaf3db5b56e93d165c5eb76fd Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Sat, 29 Jun 2024 16:52:45 +0200 Subject: [PATCH 40/44] Bump version to 2.5 --- app/scrcpy-windows.rc | 2 +- meson.build | 2 +- server/build.gradle | 4 ++-- server/build_without_gradle.sh | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/app/scrcpy-windows.rc b/app/scrcpy-windows.rc index 059e91d4..717d9cb2 100644 --- a/app/scrcpy-windows.rc +++ b/app/scrcpy-windows.rc @@ -13,7 +13,7 @@ BEGIN VALUE "LegalCopyright", "Romain Vimont, Genymobile" VALUE "OriginalFilename", "scrcpy.exe" VALUE "ProductName", "scrcpy" - VALUE "ProductVersion", "2.4" + VALUE "ProductVersion", "2.5" END END BLOCK "VarFileInfo" diff --git a/meson.build b/meson.build index 22d0f4ef..1d11e574 100644 --- a/meson.build +++ b/meson.build @@ -1,5 +1,5 @@ project('scrcpy', 'c', - version: '2.4', + version: '2.5', meson_version: '>= 0.48', default_options: [ 'c_std=c11', diff --git a/server/build.gradle b/server/build.gradle index 6a1b09df..d17ffcb2 100644 --- a/server/build.gradle +++ b/server/build.gradle @@ -7,8 +7,8 @@ android { applicationId "com.genymobile.scrcpy" minSdkVersion 21 targetSdkVersion 34 - versionCode 20400 - versionName "2.4" + versionCode 20500 + versionName "2.5" testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" } buildTypes { diff --git a/server/build_without_gradle.sh b/server/build_without_gradle.sh index 7f7d7921..74bbd8ae 100755 --- a/server/build_without_gradle.sh +++ b/server/build_without_gradle.sh @@ -12,7 +12,7 @@ set -e SCRCPY_DEBUG=false -SCRCPY_VERSION_NAME=2.4 +SCRCPY_VERSION_NAME=2.5 PLATFORM=${ANDROID_PLATFORM:-34} BUILD_TOOLS=${ANDROID_BUILD_TOOLS:-34.0.0} From a8871bfad77ed1d0b968f3919df685a301849f8f Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Sat, 29 Jun 2024 17:51:36 +0200 Subject: [PATCH 41/44] Update links to 2.5 --- README.md | 2 +- doc/build.md | 6 +++--- doc/windows.md | 12 ++++++------ install_release.sh | 4 ++-- 4 files changed, 12 insertions(+), 12 deletions(-) diff --git a/README.md b/README.md index a672b327..3185652b 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ source for the project. Do not download releases from random websites, even if their name contains `scrcpy`.** -# scrcpy (v2.4) +# scrcpy (v2.5) scrcpy diff --git a/doc/build.md b/doc/build.md index 01319a10..a35910f8 100644 --- a/doc/build.md +++ b/doc/build.md @@ -233,10 +233,10 @@ install` must be run as root)._ #### Option 2: Use prebuilt server - - [`scrcpy-server-v2.4`][direct-scrcpy-server] - SHA-256: `93c272b7438605c055e127f7444064ed78fa9ca49f81156777fd201e79ce7ba3` + - [`scrcpy-server-v2.5`][direct-scrcpy-server] + SHA-256: `1488b1105d6aff534873a26bf610cd2aea06ee867dd7a4d9c6bb2c091396eb15` -[direct-scrcpy-server]: https://github.com/Genymobile/scrcpy/releases/download/v2.4/scrcpy-server-v2.4 +[direct-scrcpy-server]: https://github.com/Genymobile/scrcpy/releases/download/v2.5/scrcpy-server-v2.5 Download the prebuilt server somewhere, and specify its path during the Meson configuration: diff --git a/doc/windows.md b/doc/windows.md index e3053188..139c3419 100644 --- a/doc/windows.md +++ b/doc/windows.md @@ -4,14 +4,14 @@ Download the [latest release]: - - [`scrcpy-win64-v2.4.zip`][direct-win64] (64-bit) - SHA-256: `9dc56f21bfa455352ec0c58b40feaf2fb02d67372910a4235e298ece286ff3a9` - - [`scrcpy-win32-v2.4.zip`][direct-win32] (32-bit) - SHA-256: `cf92acc45eef37c6ee2db819f92e420ced3bc50f1348dd57f7d6ca1fc80f6116` + - [`scrcpy-win64-v2.5.zip`][direct-win64] (64-bit) + SHA-256: `345cf04a66a9144281dce72ca4e82adfd2c3092463196e586051df4c69e1507b` + - [`scrcpy-win32-v2.5.zip`][direct-win32] (32-bit) + SHA-256: `d56312a92471565fa4f3a6b94e8eb07717c4c90f2c0f05b03ba444e1001806ec` [latest release]: https://github.com/Genymobile/scrcpy/releases/latest -[direct-win64]: https://github.com/Genymobile/scrcpy/releases/download/v2.4/scrcpy-win64-v2.4.zip -[direct-win32]: https://github.com/Genymobile/scrcpy/releases/download/v2.4/scrcpy-win32-v2.4.zip +[direct-win64]: https://github.com/Genymobile/scrcpy/releases/download/v2.5/scrcpy-win64-v2.5.zip +[direct-win32]: https://github.com/Genymobile/scrcpy/releases/download/v2.5/scrcpy-win32-v2.5.zip and extract it. diff --git a/install_release.sh b/install_release.sh index 0be5675c..2bd6d7e6 100755 --- a/install_release.sh +++ b/install_release.sh @@ -2,8 +2,8 @@ set -e BUILDDIR=build-auto -PREBUILT_SERVER_URL=https://github.com/Genymobile/scrcpy/releases/download/v2.4/scrcpy-server-v2.4 -PREBUILT_SERVER_SHA256=93c272b7438605c055e127f7444064ed78fa9ca49f81156777fd201e79ce7ba3 +PREBUILT_SERVER_URL=https://github.com/Genymobile/scrcpy/releases/download/v2.5/scrcpy-server-v2.5 +PREBUILT_SERVER_SHA256=1488b1105d6aff534873a26bf610cd2aea06ee867dd7a4d9c6bb2c091396eb15 echo "[scrcpy] Downloading prebuilt server..." wget "$PREBUILT_SERVER_URL" -O scrcpy-server From 1d3b6dac6962d67d91b6e5ec93a39d99c5f44641 Mon Sep 17 00:00:00 2001 From: Fr_Dae Date: Wed, 3 Jul 2024 11:49:01 +0200 Subject: [PATCH 42/44] Improve bug report template Use titles and capital letters. PR #5051 Signed-off-by: Romain Vimont --- .github/ISSUE_TEMPLATE/bug_report.md | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md index 1c04da7f..c06ac786 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -10,14 +10,16 @@ assignees: '' - [ ] I have read the [FAQ](https://github.com/Genymobile/scrcpy/blob/master/FAQ.md). - [ ] I have searched in existing [issues](https://github.com/Genymobile/scrcpy/issues). -**Environment** - - OS: [e.g. Debian, Windows, macOS...] - - scrcpy version: [e.g. 1.12.1] - - installation method: [e.g. manual build, apt, snap, brew, Windows release...] - - device model: - - Android version: [e.g. 10] +## Environment + + - **OS:** [e.g. Debian, Windows, macOS...] + - **Scrcpy version:** [e.g. 1.12.1] + - **Installation method:** [e.g. manual build, apt, snap, brew, Windows release...] + - **Device model:** + - **Android version:** [e.g. 10] + +## Describe the bug -**Describe the bug** A clear and concise description of what the bug is. On errors, please provide the output of the console (and `adb logcat` if relevant). From 126da0cb18576cc40b9506c676af3a2d569c760c Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Fri, 5 Jul 2024 23:57:33 +0200 Subject: [PATCH 43/44] Rework bug report template checks Remove explicit checkboxes, and add a link to prerequisites. --- .github/ISSUE_TEMPLATE/bug_report.md | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md index c06ac786..977f2d8d 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -7,8 +7,14 @@ assignees: '' --- - - [ ] I have read the [FAQ](https://github.com/Genymobile/scrcpy/blob/master/FAQ.md). - - [ ] I have searched in existing [issues](https://github.com/Genymobile/scrcpy/issues). +_Please read the [prerequisites] to run scrcpy._ + +[prerequisites]: https://github.com/Genymobile/scrcpy#prerequisites + +_Also read the [FAQ] and check if your [issue][issues] already exists._ + +[FAQ]: https://github.com/Genymobile/scrcpy/blob/master/FAQ.md +[issues]: https://github.com/Genymobile/scrcpy/issues ## Environment From cc8e6133b0c9975ef2ed16086960e822ab750404 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Fri, 5 Jul 2024 23:58:00 +0200 Subject: [PATCH 44/44] Upgrade default versions in bug report template --- .github/ISSUE_TEMPLATE/bug_report.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md index 977f2d8d..576d4666 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -19,10 +19,10 @@ _Also read the [FAQ] and check if your [issue][issues] already exists._ ## Environment - **OS:** [e.g. Debian, Windows, macOS...] - - **Scrcpy version:** [e.g. 1.12.1] + - **Scrcpy version:** [e.g. 2.5] - **Installation method:** [e.g. manual build, apt, snap, brew, Windows release...] - **Device model:** - - **Android version:** [e.g. 10] + - **Android version:** [e.g. 14] ## Describe the bug