Compare commits

...

15 Commits

Author SHA1 Message Date
24efeace4b Expose named input modes (wip)
Replace:
 - -K/--hid-keyboard by --keyboard-input-mode
 - -M/--hid-mouse by --mouse-input-mode

Each supports 3 possible values (for now):
 - disable
 - inject
 - aoa

Signed-off-by: Romain Vimont <rom@rom1v.com>
2024-01-15 22:10:43 +01:00
2ad93d1fc0 Fix scrcpy_otg() return value on error
The function now returns an enum scrcpy_exit_code, not a bool.
2024-01-15 22:01:19 +01:00
d067a11478 Do not power on if no video
Power on the device on start only if video capture is enabled.

Note that it only impacts display mirroring, since control is completely
disabled if video source is camera.

Refs 110b3a16f6
2024-01-07 21:12:39 +01:00
cd4056d0f3 Fix include formatting 2024-01-02 10:22:28 +01:00
6a58891e13 Use current time as initial timestamp on error
If the initial timestamp could not be retrieved, use the current time as
returned by System.nanoTime(). In practice, it is the same time base as
AudioRecord timestamps.

Fixes #4536 <https://github.com/Genymobile/scrcpy/issues/4536>
2023-12-16 20:17:33 +01:00
ec41896c85 Fix integer overflow for audio packet duration
The result is assigned to a long (64-bit signed integer), but the
intermediate multiplication was stored in an int (32-bit signed
integer).

This value is only used as a fallback when no timestamp could be
retrieved, that's why it did not cause too much harm so far.

Fixes #4536 <https://github.com/Genymobile/scrcpy/issues/4536>
2023-12-16 20:16:31 +01:00
4cd61b5a90 Fix checkstyle violation
Reported by checkstyle:

> [ant:checkstyle] [INFO]
> scrcpy/server/src/main/java/com/genymobile/scrcpy/wrappers/ClipboardManager.java:48:
> Line is longer than 150 characters (found 167). [LineLength]
2023-12-16 20:12:58 +01:00
d2ed4510a7 Simulate tilt multitouch event by pressing Shift
PR #4529 <https://github.com/Genymobile/scrcpy/pull/4529>

Signed-off-by: Romain Vimont <rom@rom1v.com>
2023-12-15 22:12:07 +01:00
604dfd7c6b Fix incorrect compgen usage
PR #4532 <https://github.com/Genymobile/scrcpy/pull/4532>

Signed-off-by: Romain Vimont <rom@rom1v.com>
2023-12-14 17:08:19 +01:00
af69689ec1 Fix bash completion syntax
PR #4532 <https://github.com/Genymobile/scrcpy/pull/4532>

Signed-off-by: Romain Vimont <rom@rom1v.com>
2023-12-14 17:07:20 +01:00
cbce42336d Fix manpage syntax
The '-' character must be escaped.

Fixes #4528 <https://github.com/Genymobile/scrcpy/issues/4528>
2023-12-13 13:42:57 +01:00
c9a4d2b38f Use up-to-date values on display fold change
When a display is folded or unfolded, the maxSize may have been updated
since the option was passed, and deviceSize must be updated.

Refs #4469 <https://github.com/Genymobile/scrcpy/pull/4469>
2023-12-04 08:50:12 +01:00
1beec99f82 Explicitly exit cleanup process
This avoids an internal crash reported in `adb logcat`.

Refs #4456 <https://github.com/Genymobile/scrcpy/pull/4456#issuecomment-1837427802>
2023-12-04 08:45:35 +01:00
5ce8672ebc Add clipboard workaround for IQOO device
Fixes #4492 <https://github.com/Genymobile/scrcpy/issues/4492>
2023-12-04 08:43:31 +01:00
3001f8a2d5 Adapt AudioRecord workaround to Android 14
Android 14 added a new int parameter "halInputFlags" to an internal
method:
<f6135d75db>

Fixes #4492 <https://github.com/Genymobile/scrcpy/issues/4492>
2023-12-03 18:01:11 +01:00
18 changed files with 291 additions and 81 deletions

View File

@ -27,8 +27,8 @@ _scrcpy() {
--force-adb-forward --force-adb-forward
--forward-all-clicks --forward-all-clicks
-h --help -h --help
--keyboard-input-mode=
--kill-adb-on-close --kill-adb-on-close
-K --hid-keyboard
--legacy-paste --legacy-paste
--list-camera-sizes --list-camera-sizes
--list-cameras --list-cameras
@ -115,13 +115,16 @@ _scrcpy() {
COMPREPLY=($(compgen -W 'front back external' -- "$cur")) COMPREPLY=($(compgen -W 'front back external' -- "$cur"))
return return
;; ;;
--orientation --keyboard-input-mode)
--display-orientation) COMPREPLY=($(compgen -W 'inject aoa' -- "$cur"))
COMPREPLY=($(compgen -> '0 90 180 270 flip0 flip90 flip180 flip270' -- "$cur")) return
;;
--orientation|--display-orientation)
COMPREPLY=($(compgen -W '0 90 180 270 flip0 flip90 flip180 flip270' -- "$cur"))
return return
;; ;;
--record-orientation) --record-orientation)
COMPREPLY=($(compgen -> '0 90 180 270' -- "$cur")) COMPREPLY=($(compgen -W '0 90 180 270' -- "$cur"))
return return
;; ;;
--lock-video-orientation) --lock-video-orientation)

View File

@ -34,8 +34,8 @@ arguments=(
'--force-adb-forward[Do not attempt to use \"adb reverse\" to connect to the device]' '--force-adb-forward[Do not attempt to use \"adb reverse\" to connect to the device]'
'--forward-all-clicks[Forward clicks to device]' '--forward-all-clicks[Forward clicks to device]'
{-h,--help}'[Print the help]' {-h,--help}'[Print the help]'
'--keyboard-input-mode=[Set the keyboard input mode]:mode:(inject aoa)'
'--kill-adb-on-close[Kill adb when scrcpy terminates]' '--kill-adb-on-close[Kill adb when scrcpy terminates]'
{-K,--hid-keyboard}'[Simulate a physical keyboard by using HID over AOAv2]'
'--legacy-paste[Inject computer clipboard text as a sequence of key events on Ctrl+v]' '--legacy-paste[Inject computer clipboard text as a sequence of key events on Ctrl+v]'
'--list-camera-sizes[List the valid camera capture sizes]' '--list-camera-sizes[List the valid camera capture sizes]'
'--list-cameras[List cameras available on the device]' '--list-cameras[List cameras available on the device]'

View File

@ -124,7 +124,7 @@ Use USB device (if there is exactly one, like adb -d).
Also see \fB\-e\fR (\fB\-\-select\-tcpip\fR). Also see \fB\-e\fR (\fB\-\-select\-tcpip\fR).
.TP .TP
.BI "\-\-disable-screensaver" .BI "\-\-disable\-screensaver"
Disable screensaver while scrcpy is running. Disable screensaver while scrcpy is running.
.TP .TP
@ -642,7 +642,11 @@ Enable/disable FPS counter (print frames/second in logs)
.TP .TP
.B Ctrl+click-and-move .B Ctrl+click-and-move
Pinch-to-zoom from the center of the screen Pinch-to-zoom and rotate from the center of the screen
.TP
.B Shift+click-and-move
Tilt (slide vertically with two fingers)
.TP .TP
.B Drag & drop APK file .B Drag & drop APK file

View File

@ -4,16 +4,16 @@
#include "common.h" #include "common.h"
#include <stdbool.h> #include <stdbool.h>
#include "trait/frame_sink.h"
#include <util/audiobuf.h>
#include <util/average.h>
#include <util/thread.h>
#include <util/tick.h>
#include <libavformat/avformat.h> #include <libavformat/avformat.h>
#include <libswresample/swresample.h> #include <libswresample/swresample.h>
#include <SDL2/SDL.h> #include <SDL2/SDL.h>
#include "trait/frame_sink.h"
#include "util/audiobuf.h"
#include "util/average.h"
#include "util/thread.h"
#include "util/tick.h"
struct sc_audio_player { struct sc_audio_player {
struct sc_frame_sink frame_sink; struct sc_frame_sink frame_sink;

View File

@ -93,6 +93,8 @@ enum {
OPT_DISPLAY_ORIENTATION, OPT_DISPLAY_ORIENTATION,
OPT_RECORD_ORIENTATION, OPT_RECORD_ORIENTATION,
OPT_ORIENTATION, OPT_ORIENTATION,
OPT_KEYBOARD_INPUT_MODE,
OPT_MOUSE_INPUT_MODE,
}; };
struct sc_option { struct sc_option {
@ -359,14 +361,19 @@ static const struct sc_option options[] = {
.text = "Print this help.", .text = "Print this help.",
}, },
{ {
.longopt_id = OPT_KILL_ADB_ON_CLOSE, .longopt_id = OPT_KEYBOARD_INPUT_MODE,
.longopt = "kill-adb-on-close", .longopt = "keyboard-input-mode",
.text = "Kill adb when scrcpy terminates.", .argdesc = "value",
}, .text = "Select how to send keyboard inputs to the device.\n"
{ "Possible values are \"disable\", \"inject\" and \"aoa\".\n"
.shortopt = 'K', "\n"
.longopt = "hid-keyboard", "\"disable\" does not send keyboard inputs to the device.\n"
.text = "Simulate a physical keyboard by using HID over AOAv2.\n" "\n"
"\"inject\" uses the Android system API to deliver keyboard\n"
"events to applications.\n"
"\n"
"\"aoa\" simulates a physical keyboard using the AOAv2\n"
"protocol. It may only work over USB.\n"
"It provides a better experience for IME users, and allows to " "It provides a better experience for IME users, and allows to "
"generate non-ASCII characters, contrary to the default " "generate non-ASCII characters, contrary to the default "
"injection method.\n" "injection method.\n"
@ -378,7 +385,19 @@ static const struct sc_option options[] = {
"android.settings.HARD_KEYBOARD_SETTINGS`.\n" "android.settings.HARD_KEYBOARD_SETTINGS`.\n"
"However, the option is only available when the HID keyboard " "However, the option is only available when the HID keyboard "
"is enabled (or a physical keyboard is connected).\n" "is enabled (or a physical keyboard is connected).\n"
"Also see --hid-mouse.", "Also see --mouse-input-mode and --otg."
"\n"
"Default is \"inject\" (or \"aoa\" if --otg is set).",
},
{
.longopt_id = OPT_KILL_ADB_ON_CLOSE,
.longopt = "kill-adb-on-close",
.text = "Kill adb when scrcpy terminates.",
},
{
// deprecated
.shortopt = 'K',
.longopt = "hid-keyboard",
}, },
{ {
.longopt_id = OPT_LEGACY_PASTE, .longopt_id = OPT_LEGACY_PASTE,
@ -440,7 +459,7 @@ static const struct sc_option options[] = {
"LAlt, LSuper or RSuper toggle the capture mode, to give " "LAlt, LSuper or RSuper toggle the capture mode, to give "
"control of the mouse back to the computer.\n" "control of the mouse back to the computer.\n"
"It may only work over USB.\n" "It may only work over USB.\n"
"Also see --hid-keyboard.", "Also see --keyboard-input-mode and --otg.",
}, },
{ {
.longopt_id = OPT_MAX_FPS, .longopt_id = OPT_MAX_FPS,
@ -449,6 +468,26 @@ static const struct sc_option options[] = {
.text = "Limit the frame rate of screen capture (officially supported " .text = "Limit the frame rate of screen capture (officially supported "
"since Android 10, but may work on earlier versions).", "since Android 10, but may work on earlier versions).",
}, },
{
.longopt_id = OPT_MOUSE_INPUT_MODE,
.longopt = "mouse-input-mode",
.argdesc = "value",
.text = "Select how to send mouse inputs to the device.\n"
"Possible values are \"disable\", \"inject\" and \"aoa\".\n"
"\n"
"\"disable\" does not send mouse inputs to the device.\n"
"\n"
"\"inject\" uses the Android system API to deliver mouse\n"
"events to applications.\n"
"\n"
"\"aoa\" simulates a physical mouse using the AOAv2\n"
"protocol. It may only work over USB.\n"
"In this mode, the computer mouse is captured to control the "
"device directly (relative mouse mode).\n"
"LAlt, LSuper or RSuper toggle the capture mode, to give "
"control of the mouse back to the computer.\n"
"Also see --keyboard-input-mode and --otg.",
},
{ {
.shortopt = 'n', .shortopt = 'n',
.longopt = "no-control", .longopt = "no-control",
@ -543,10 +582,11 @@ static const struct sc_option options[] = {
"mirroring is disabled.\n" "mirroring is disabled.\n"
"LAlt, LSuper or RSuper toggle the mouse capture mode, to give " "LAlt, LSuper or RSuper toggle the mouse capture mode, to give "
"control of the mouse back to the computer.\n" "control of the mouse back to the computer.\n"
"If any of --hid-keyboard or --hid-mouse is set, only enable " "Keyboard and mouse may be disabled separately using\n"
"keyboard or mouse respectively, otherwise enable both.\n" "--keyboard-input-mode=disable and\n"
"--mouse-input-mode=disable.\n"
"It may only work over USB.\n" "It may only work over USB.\n"
"See --hid-keyboard and --hid-mouse.", "See --keyboard-input-mode and --mouse-input-mode.",
}, },
{ {
.shortopt = 'p', .shortopt = 'p',
@ -947,7 +987,11 @@ static const struct sc_shortcut shortcuts[] = {
}, },
{ {
.shortcuts = { "Ctrl+click-and-move" }, .shortcuts = { "Ctrl+click-and-move" },
.text = "Pinch-to-zoom from the center of the screen", .text = "Pinch-to-zoom and rotate from the center of the screen",
},
{
.shortcuts = { "Shift+click-and-move" },
.text = "Tilt (slide vertically with two fingers)",
}, },
{ {
.shortcuts = { "Drag & drop APK file" }, .shortcuts = { "Drag & drop APK file" },
@ -1898,6 +1942,61 @@ parse_camera_fps(const char *s, uint16_t *camera_fps) {
return true; return true;
} }
static bool
parse_keyboard_input_mode(const char *optarg,
enum sc_keyboard_input_mode *mode) {
if (!strcmp(optarg, "disable")) {
*mode = SC_KEYBOARD_INPUT_MODE_DISABLED;
return true;
}
if (!strcmp(optarg, "inject")) {
*mode = SC_KEYBOARD_INPUT_MODE_INJECT;
return true;
}
if (!strcmp(optarg, "aoa")) {
#ifdef HAVE_USB
*mode = SC_KEYBOARD_INPUT_MODE_AOA;
return true;
#else
LOGE("--keyboard-input-mode=aoa is disabled.");
return false;
#endif
}
LOGE("Unsupported keyboard input mode: %s (expected disable, inject, aoa)",
optarg);
return false;
}
static bool
parse_mouse_input_mode(const char *optarg, enum sc_mouse_input_mode *mode) {
if (!strcmp(optarg, "disable")) {
*mode = SC_MOUSE_INPUT_MODE_DISABLED;
return true;
}
if (!strcmp(optarg, "inject")) {
*mode = SC_MOUSE_INPUT_MODE_INJECT;
return true;
}
if (!strcmp(optarg, "aoa")) {
#ifdef HAVE_USB
*mode = SC_MOUSE_INPUT_MODE_AOA;
return true;
#else
LOGE("--mouse-input-mode=aoa is disabled.");
return false;
#endif
}
LOGE("Unsupported mouse input mode: %s (expected disable, inject, aoa)",
optarg);
return false;
}
static bool static bool
parse_time_limit(const char *s, sc_tick *tick) { parse_time_limit(const char *s, sc_tick *tick) {
long value; long value;
@ -1987,12 +2086,20 @@ parse_args_with_getopt(struct scrcpy_cli_args *args, int argc, char *argv[],
break; break;
case 'K': case 'K':
#ifdef HAVE_USB #ifdef HAVE_USB
opts->keyboard_input_mode = SC_KEYBOARD_INPUT_MODE_HID; LOGW("-K/--hid-keyboard is deprecated, use "
"--keyboard-input-mode=aoa instead.");
opts->keyboard_input_mode = SC_KEYBOARD_INPUT_MODE_AOA;
break; break;
#else #else
LOGE("HID over AOA (-K/--hid-keyboard) is disabled."); LOGE("HID over AOA (-K/--hid-keyboard) is disabled.");
return false; return false;
#endif #endif
case OPT_KEYBOARD_INPUT_MODE:
if (!parse_keyboard_input_mode(optarg,
&opts->keyboard_input_mode)) {
return false;
}
break;
case OPT_MAX_FPS: case OPT_MAX_FPS:
if (!parse_max_fps(optarg, &opts->max_fps)) { if (!parse_max_fps(optarg, &opts->max_fps)) {
return false; return false;
@ -2005,12 +2112,19 @@ parse_args_with_getopt(struct scrcpy_cli_args *args, int argc, char *argv[],
break; break;
case 'M': case 'M':
#ifdef HAVE_USB #ifdef HAVE_USB
opts->mouse_input_mode = SC_MOUSE_INPUT_MODE_HID; LOGW("-M/--hid-mouse is deprecated, use --mouse-input-mode=aoa "
"instead.");
opts->mouse_input_mode = SC_MOUSE_INPUT_MODE_AOA;
break; break;
#else #else
LOGE("HID over AOA (-M/--hid-mouse) is disabled."); LOGE("HID over AOA (-M/--hid-mouse) is disabled.");
return false; return false;
#endif #endif
case OPT_MOUSE_INPUT_MODE:
if (!parse_mouse_input_mode(optarg, &opts->mouse_input_mode)) {
return false;
}
break;
case OPT_LOCK_VIDEO_ORIENTATION: case OPT_LOCK_VIDEO_ORIENTATION:
if (!parse_lock_video_orientation(optarg, if (!parse_lock_video_orientation(optarg,
&opts->lock_video_orientation)) { &opts->lock_video_orientation)) {
@ -2394,6 +2508,8 @@ parse_args_with_getopt(struct scrcpy_cli_args *args, int argc, char *argv[],
if (!opts->video) { if (!opts->video) {
opts->video_playback = false; opts->video_playback = false;
// Do not power on the device on start if video capture is disabled
opts->power_on = false;
} }
if (!opts->audio) { if (!opts->audio) {
@ -2455,6 +2571,15 @@ parse_args_with_getopt(struct scrcpy_cli_args *args, int argc, char *argv[],
} }
#endif #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_INJECT;
}
if (opts->mouse_input_mode == SC_MOUSE_INPUT_MODE_AUTO) {
opts->mouse_input_mode = otg ? SC_MOUSE_INPUT_MODE_AOA
: SC_MOUSE_INPUT_MODE_INJECT;
}
if ((opts->tunnel_host || opts->tunnel_port) && !opts->force_adb_forward) { if ((opts->tunnel_host || opts->tunnel_port) && !opts->force_adb_forward) {
LOGI("Tunnel host/port is set, " LOGI("Tunnel host/port is set, "
"--force-adb-forward automatically enabled."); "--force-adb-forward automatically enabled.");
@ -2615,12 +2740,12 @@ parse_args_with_getopt(struct scrcpy_cli_args *args, int argc, char *argv[],
} }
# ifdef _WIN32 # ifdef _WIN32
if (!otg && (opts->keyboard_input_mode == SC_KEYBOARD_INPUT_MODE_HID if (!otg && (opts->keyboard_input_mode == SC_KEYBOARD_INPUT_MODE_AOA
|| opts->mouse_input_mode == SC_MOUSE_INPUT_MODE_HID)) { || opts->mouse_input_mode == SC_MOUSE_INPUT_MODE_AOA)) {
LOGE("On Windows, it is not possible to open a USB device already open " LOGE("On Windows, it is not possible to open a USB device already open "
"by another process (like adb)."); "by another process (like adb).");
LOGE("Therefore, -K/--hid-keyboard and -M/--hid-mouse may only work in " LOGE("Therefore, --keyboard-input-mode=aoa and --mouse-input-mode=aoa "
"OTG mode (--otg)."); "may only work in OTG mode (--otg).");
return false; return false;
} }
# endif # endif

View File

@ -76,6 +76,8 @@ sc_input_manager_init(struct sc_input_manager *im,
im->sdl_shortcut_mods.count = shortcut_mods->count; im->sdl_shortcut_mods.count = shortcut_mods->count;
im->vfinger_down = false; im->vfinger_down = false;
im->vfinger_invert_x = false;
im->vfinger_invert_y = false;
im->last_keycode = SDLK_UNKNOWN; im->last_keycode = SDLK_UNKNOWN;
im->last_mod = 0; im->last_mod = 0;
@ -347,9 +349,14 @@ simulate_virtual_finger(struct sc_input_manager *im,
} }
static struct sc_point static struct sc_point
inverse_point(struct sc_point point, struct sc_size size) { inverse_point(struct sc_point point, struct sc_size size,
point.x = size.width - point.x; bool invert_x, bool invert_y) {
point.y = size.height - point.y; if (invert_x) {
point.x = size.width - point.x;
}
if (invert_y) {
point.y = size.height - point.y;
}
return point; return point;
} }
@ -605,7 +612,9 @@ sc_input_manager_process_mouse_motion(struct sc_input_manager *im,
struct sc_point mouse = struct sc_point mouse =
sc_screen_convert_window_to_frame_coords(im->screen, event->x, sc_screen_convert_window_to_frame_coords(im->screen, event->x,
event->y); event->y);
struct sc_point vfinger = inverse_point(mouse, im->screen->frame_size); struct sc_point vfinger = inverse_point(mouse, im->screen->frame_size,
im->vfinger_invert_x,
im->vfinger_invert_y);
simulate_virtual_finger(im, AMOTION_EVENT_ACTION_MOVE, vfinger); simulate_virtual_finger(im, AMOTION_EVENT_ACTION_MOVE, vfinger);
} }
} }
@ -726,7 +735,7 @@ sc_input_manager_process_mouse_button(struct sc_input_manager *im,
return; return;
} }
// Pinch-to-zoom simulation. // Pinch-to-zoom, rotate and tilt simulation.
// //
// If Ctrl is hold when the left-click button is pressed, then // If Ctrl is hold when the left-click button is pressed, then
// pinch-to-zoom mode is enabled: on every mouse event until the left-click // pinch-to-zoom mode is enabled: on every mouse event until the left-click
@ -735,14 +744,29 @@ sc_input_manager_process_mouse_button(struct sc_input_manager *im,
// //
// In other words, the center of the rotation/scaling is the center of the // In other words, the center of the rotation/scaling is the center of the
// screen. // screen.
#define CTRL_PRESSED (SDL_GetModState() & (KMOD_LCTRL | KMOD_RCTRL)) //
// To simulate a tilt gesture (a vertical slide with two fingers), Shift
// can be used instead of Ctrl. The "virtual finger" has a position
// inverted with respect to the vertical axis of symmetry in the middle of
// the screen.
const SDL_Keymod keymod = SDL_GetModState();
const bool ctrl_pressed = keymod & KMOD_CTRL;
const bool shift_pressed = keymod & KMOD_SHIFT;
if (event->button == SDL_BUTTON_LEFT && if (event->button == SDL_BUTTON_LEFT &&
((down && !im->vfinger_down && CTRL_PRESSED) || ((down && !im->vfinger_down &&
((ctrl_pressed && !shift_pressed) ||
(!ctrl_pressed && shift_pressed))) ||
(!down && im->vfinger_down))) { (!down && im->vfinger_down))) {
struct sc_point mouse = struct sc_point mouse =
sc_screen_convert_window_to_frame_coords(im->screen, event->x, sc_screen_convert_window_to_frame_coords(im->screen, event->x,
event->y); event->y);
struct sc_point vfinger = inverse_point(mouse, im->screen->frame_size); if (down) {
im->vfinger_invert_x = ctrl_pressed || shift_pressed;
im->vfinger_invert_y = ctrl_pressed;
}
struct sc_point vfinger = inverse_point(mouse, im->screen->frame_size,
im->vfinger_invert_x,
im->vfinger_invert_y);
enum android_motionevent_action action = down enum android_motionevent_action action = down
? AMOTION_EVENT_ACTION_DOWN ? AMOTION_EVENT_ACTION_DOWN
: AMOTION_EVENT_ACTION_UP; : AMOTION_EVENT_ACTION_UP;

View File

@ -32,6 +32,8 @@ struct sc_input_manager {
} sdl_shortcut_mods; } sdl_shortcut_mods;
bool vfinger_down; bool vfinger_down;
bool vfinger_invert_x;
bool vfinger_invert_y;
// Tracks the number of identical consecutive shortcut key down events. // Tracks the number of identical consecutive shortcut key down events.
// Not to be confused with event->repeat, which counts the number of // Not to be confused with event->repeat, which counts the number of

View File

@ -21,8 +21,8 @@ const struct scrcpy_options scrcpy_options_default = {
.video_source = SC_VIDEO_SOURCE_DISPLAY, .video_source = SC_VIDEO_SOURCE_DISPLAY,
.audio_source = SC_AUDIO_SOURCE_AUTO, .audio_source = SC_AUDIO_SOURCE_AUTO,
.record_format = SC_RECORD_FORMAT_AUTO, .record_format = SC_RECORD_FORMAT_AUTO,
.keyboard_input_mode = SC_KEYBOARD_INPUT_MODE_INJECT, .keyboard_input_mode = SC_KEYBOARD_INPUT_MODE_AUTO,
.mouse_input_mode = SC_MOUSE_INPUT_MODE_INJECT, .mouse_input_mode = SC_MOUSE_INPUT_MODE_AUTO,
.camera_facing = SC_CAMERA_FACING_ANY, .camera_facing = SC_CAMERA_FACING_ANY,
.port_range = { .port_range = {
.first = DEFAULT_LOCAL_PORT_RANGE_FIRST, .first = DEFAULT_LOCAL_PORT_RANGE_FIRST,

View File

@ -140,13 +140,17 @@ enum sc_lock_video_orientation {
}; };
enum sc_keyboard_input_mode { enum sc_keyboard_input_mode {
SC_KEYBOARD_INPUT_MODE_AUTO,
SC_KEYBOARD_INPUT_MODE_DISABLED,
SC_KEYBOARD_INPUT_MODE_INJECT, SC_KEYBOARD_INPUT_MODE_INJECT,
SC_KEYBOARD_INPUT_MODE_HID, SC_KEYBOARD_INPUT_MODE_AOA,
}; };
enum sc_mouse_input_mode { enum sc_mouse_input_mode {
SC_MOUSE_INPUT_MODE_AUTO,
SC_MOUSE_INPUT_MODE_DISABLED,
SC_MOUSE_INPUT_MODE_INJECT, SC_MOUSE_INPUT_MODE_INJECT,
SC_MOUSE_INPUT_MODE_HID, SC_MOUSE_INPUT_MODE_AOA,
}; };
enum sc_key_inject_mode { enum sc_key_inject_mode {

View File

@ -543,11 +543,11 @@ scrcpy(struct scrcpy_options *options) {
if (options->control) { if (options->control) {
#ifdef HAVE_USB #ifdef HAVE_USB
bool use_hid_keyboard = bool use_aoa_keyboard =
options->keyboard_input_mode == SC_KEYBOARD_INPUT_MODE_HID; options->keyboard_input_mode == SC_KEYBOARD_INPUT_MODE_AOA;
bool use_hid_mouse = bool use_aoa_mouse =
options->mouse_input_mode == SC_MOUSE_INPUT_MODE_HID; options->mouse_input_mode == SC_MOUSE_INPUT_MODE_AOA;
if (use_hid_keyboard || use_hid_mouse) { if (use_aoa_keyboard || use_aoa_mouse) {
bool ok = sc_acksync_init(&s->acksync); bool ok = sc_acksync_init(&s->acksync);
if (!ok) { if (!ok) {
goto end; goto end;
@ -590,7 +590,7 @@ scrcpy(struct scrcpy_options *options) {
goto aoa_hid_end; goto aoa_hid_end;
} }
if (use_hid_keyboard) { if (use_aoa_keyboard) {
if (sc_hid_keyboard_init(&s->keyboard_hid, &s->aoa)) { if (sc_hid_keyboard_init(&s->keyboard_hid, &s->aoa)) {
hid_keyboard_initialized = true; hid_keyboard_initialized = true;
kp = &s->keyboard_hid.key_processor; kp = &s->keyboard_hid.key_processor;
@ -599,7 +599,7 @@ scrcpy(struct scrcpy_options *options) {
} }
} }
if (use_hid_mouse) { if (use_aoa_mouse) {
if (sc_hid_mouse_init(&s->mouse_hid, &s->aoa)) { if (sc_hid_mouse_init(&s->mouse_hid, &s->aoa)) {
hid_mouse_initialized = true; hid_mouse_initialized = true;
mp = &s->mouse_hid.mouse_processor; mp = &s->mouse_hid.mouse_processor;
@ -636,19 +636,19 @@ aoa_hid_end:
if (use_hid_keyboard && !hid_keyboard_initialized) { if (use_hid_keyboard && !hid_keyboard_initialized) {
LOGE("Fallback to default keyboard injection method " LOGE("Fallback to default keyboard injection method "
"(-K/--hid-keyboard ignored)"); "(--keyboard-input-mode=aoa ignored)");
options->keyboard_input_mode = SC_KEYBOARD_INPUT_MODE_INJECT; options->keyboard_input_mode = SC_KEYBOARD_INPUT_MODE_INJECT;
} }
if (use_hid_mouse && !hid_mouse_initialized) { if (use_hid_mouse && !hid_mouse_initialized) {
LOGE("Fallback to default mouse injection method " LOGE("Fallback to default mouse injection method "
"(-M/--hid-mouse ignored)"); "(--mouse-input-mode=aoa ignored)");
options->mouse_input_mode = SC_MOUSE_INPUT_MODE_INJECT; options->mouse_input_mode = SC_MOUSE_INPUT_MODE_INJECT;
} }
} }
#else #else
assert(options->keyboard_input_mode != SC_KEYBOARD_INPUT_MODE_HID); assert(options->keyboard_input_mode != SC_KEYBOARD_INPUT_MODE_AOA);
assert(options->mouse_input_mode != SC_MOUSE_INPUT_MODE_HID); assert(options->mouse_input_mode != SC_MOUSE_INPUT_MODE_AOA);
#endif #endif
// keyboard_input_mode may have been reset if HID mode failed // keyboard_input_mode may have been reset if HID mode failed

View File

@ -53,6 +53,25 @@ scrcpy_otg(struct scrcpy_options *options) {
static struct scrcpy_otg scrcpy_otg; static struct scrcpy_otg scrcpy_otg;
struct scrcpy_otg *s = &scrcpy_otg; struct scrcpy_otg *s = &scrcpy_otg;
bool enable_keyboard;
assert(options->keyboard_input_mode != SC_KEYBOARD_INPUT_MODE_AUTO);
switch (options->keyboard_input_mode) {
case SC_KEYBOARD_INPUT_MODE_AOA:
enable_keyboard = true;
break;
case SC_KEYBOARD_INPUT_MODE_DISABLED:
enable_keyboard = false;
break;
default:
LOGE("In --otg mode, --keyboard-input-mode must be either aoa or "
"disable");
goto end;
}
if (options->keyboard_input_mode != SC_KEYBOARD_INPUT_MODE_AOA
&& options->keyboard_input_mode != SC_KEYBOARD_INPUT_MODE_DISABLED) {
return SCRCPY_EXIT_FAILURE;
}
const char *serial = options->serial; const char *serial = options->serial;
if (!SDL_SetHint(SDL_HINT_RENDER_SCALE_QUALITY, "1")) { if (!SDL_SetHint(SDL_HINT_RENDER_SCALE_QUALITY, "1")) {
@ -62,7 +81,7 @@ scrcpy_otg(struct scrcpy_options *options) {
// Minimal SDL initialization // Minimal SDL initialization
if (SDL_Init(SDL_INIT_EVENTS)) { if (SDL_Init(SDL_INIT_EVENTS)) {
LOGE("Could not initialize SDL: %s", SDL_GetError()); LOGE("Could not initialize SDL: %s", SDL_GetError());
return false; return SCRCPY_EXIT_FAILURE;
} }
atexit(SDL_Quit); atexit(SDL_Quit);
@ -118,9 +137,9 @@ scrcpy_otg(struct scrcpy_options *options) {
aoa_initialized = true; aoa_initialized = true;
bool enable_keyboard = bool enable_keyboard =
options->keyboard_input_mode == SC_KEYBOARD_INPUT_MODE_HID; options->keyboard_input_mode == SC_KEYBOARD_INPUT_MODE_AOA;
bool enable_mouse = bool enable_mouse =
options->mouse_input_mode == SC_MOUSE_INPUT_MODE_HID; options->mouse_input_mode == SC_MOUSE_INPUT_MODE_AOA;
// If neither --hid-keyboard or --hid-mouse is passed, enable both // If neither --hid-keyboard or --hid-mouse is passed, enable both
if (!enable_keyboard && !enable_mouse) { if (!enable_keyboard && !enable_mouse) {

View File

@ -85,7 +85,7 @@ way as <kbd>MOD</kbd>+<kbd>Shift</kbd>+<kbd>v</kbd>).
To disable automatic clipboard synchronization, use To disable automatic clipboard synchronization, use
`--no-clipboard-autosync`. `--no-clipboard-autosync`.
## Pinch-to-zoom ## Pinch-to-zoom, rotate and tilt simulation
To simulate "pinch-to-zoom": <kbd>Ctrl</kbd>+_click-and-move_. To simulate "pinch-to-zoom": <kbd>Ctrl</kbd>+_click-and-move_.
@ -93,8 +93,12 @@ More precisely, hold down <kbd>Ctrl</kbd> while pressing the left-click button.
Until the left-click button is released, all mouse movements scale and rotate Until the left-click button is released, all mouse movements scale and rotate
the content (if supported by the app) relative to the center of the screen. the content (if supported by the app) relative to the center of the screen.
To simulate a tilt gesture: <kbd>Shift</kbd>+_click-and-move-up-or-down_.
Technically, _scrcpy_ generates additional touch events from a "virtual finger" Technically, _scrcpy_ generates additional touch events from a "virtual finger"
at a location inverted through the center of the screen. at a location inverted through the center of the screen. When pressing
<kbd>Ctrl</kbd> the x and y coordinates are inverted. Using <kbd>Shift</kbd>
only inverts x.
## Key repeat ## Key repeat

View File

@ -49,7 +49,8 @@ _<kbd>[Super]</kbd> is typically the <kbd>Windows</kbd> or <kbd>Cmd</kbd> key._
| Synchronize clipboards and paste⁵ | <kbd>MOD</kbd>+<kbd>v</kbd> | Synchronize clipboards and paste⁵ | <kbd>MOD</kbd>+<kbd>v</kbd>
| Inject computer clipboard text | <kbd>MOD</kbd>+<kbd>Shift</kbd>+<kbd>v</kbd> | Inject computer clipboard text | <kbd>MOD</kbd>+<kbd>Shift</kbd>+<kbd>v</kbd>
| Enable/disable FPS counter (on stdout) | <kbd>MOD</kbd>+<kbd>i</kbd> | Enable/disable FPS counter (on stdout) | <kbd>MOD</kbd>+<kbd>i</kbd>
| Pinch-to-zoom | <kbd>Ctrl</kbd>+_click-and-move_ | Pinch-to-zoom/rotate | <kbd>Ctrl</kbd>+_click-and-move_
| Tilt (slide vertically with 2 fingers) | <kbd>Shift</kbd>+_click-and-move_
| Drag & drop APK file | Install APK from computer | Drag & drop APK file | Install APK from computer
| Drag & drop non-APK file | [Push file to device](control.md#push-file-to-device) | Drag & drop non-APK file | [Push file to device](control.md#push-file-to-device)

View File

@ -153,13 +153,14 @@ public final class AudioCapture {
previousRecorderTimestamp = timestamp.nanoTime; previousRecorderTimestamp = timestamp.nanoTime;
} else { } else {
if (nextPts == 0) { if (nextPts == 0) {
Ln.w("Could not get any audio timestamp"); Ln.w("Could not get initial audio timestamp");
nextPts = System.nanoTime() / 1000;
} }
// compute from previous timestamp and packet size // compute from previous timestamp and packet size
pts = nextPts; pts = nextPts;
} }
long durationUs = r * 1000000 / (CHANNELS * BYTES_PER_SAMPLE * SAMPLE_RATE); long durationUs = r * 1000000L / (CHANNELS * BYTES_PER_SAMPLE * SAMPLE_RATE);
nextPts = pts + durationUs; nextPts = pts + durationUs;
if (previousPts != 0 && pts < previousPts + ONE_SAMPLE_US) { if (previousPts != 0 && pts < previousPts + ONE_SAMPLE_US) {

View File

@ -187,5 +187,7 @@ public final class CleanUp {
Device.setScreenPowerMode(Device.POWER_MODE_NORMAL); Device.setScreenPowerMode(Device.POWER_MODE_NORMAL);
} }
} }
System.exit(0);
} }
} }

View File

@ -45,11 +45,11 @@ public final class Device {
void onClipboardTextChanged(String text); void onClipboardTextChanged(String text);
} }
private final Size deviceSize;
private final Rect crop; private final Rect crop;
private int maxSize; private int maxSize;
private final int lockVideoOrientation; private final int lockVideoOrientation;
private Size deviceSize;
private ScreenInfo screenInfo; private ScreenInfo screenInfo;
private RotationListener rotationListener; private RotationListener rotationListener;
private FoldListener foldListener; private FoldListener foldListener;
@ -116,8 +116,8 @@ public final class Device {
return; return;
} }
screenInfo = ScreenInfo.computeScreenInfo(displayInfo.getRotation(), displayInfo.getSize(), options.getCrop(), deviceSize = displayInfo.getSize();
options.getMaxSize(), options.getLockVideoOrientation()); screenInfo = ScreenInfo.computeScreenInfo(displayInfo.getRotation(), deviceSize, crop, maxSize, lockVideoOrientation);
// notify // notify
if (foldListener != null) { if (foldListener != null) {
foldListener.onFoldChanged(displayId, folded); foldListener.onFoldChanged(displayId, folded);

View File

@ -285,16 +285,28 @@ public final class Workarounds {
Method getParcelMethod = attributionSourceState.getClass().getDeclaredMethod("getParcel"); Method getParcelMethod = attributionSourceState.getClass().getDeclaredMethod("getParcel");
Parcel attributionSourceParcel = (Parcel) getParcelMethod.invoke(attributionSourceState); Parcel attributionSourceParcel = (Parcel) getParcelMethod.invoke(attributionSourceState);
// private native int native_setup(Object audiorecordThis, if (Build.VERSION.SDK_INT < Build.VERSION_CODES.UPSIDE_DOWN_CAKE) {
// Object /*AudioAttributes*/ attributes, // private native int native_setup(Object audiorecordThis,
// int[] sampleRate, int channelMask, int channelIndexMask, int audioFormat, // Object /*AudioAttributes*/ attributes,
// int buffSizeInBytes, int[] sessionId, @NonNull Parcel attributionSource, // int[] sampleRate, int channelMask, int channelIndexMask, int audioFormat,
// long nativeRecordInJavaObj, int maxSharedAudioHistoryMs); // int buffSizeInBytes, int[] sessionId, @NonNull Parcel attributionSource,
Method nativeSetupMethod = AudioRecord.class.getDeclaredMethod("native_setup", Object.class, Object.class, int[].class, int.class, // long nativeRecordInJavaObj, int maxSharedAudioHistoryMs);
int.class, int.class, int.class, int[].class, Parcel.class, long.class, int.class); Method nativeSetupMethod = AudioRecord.class.getDeclaredMethod("native_setup", Object.class, Object.class, int[].class,
nativeSetupMethod.setAccessible(true); int.class, int.class, int.class, int.class, int[].class, Parcel.class, long.class, int.class);
initResult = (int) nativeSetupMethod.invoke(audioRecord, new WeakReference<AudioRecord>(audioRecord), attributes, sampleRateArray, nativeSetupMethod.setAccessible(true);
channelMask, channelIndexMask, audioRecord.getAudioFormat(), bufferSizeInBytes, session, attributionSourceParcel, 0L, 0); initResult = (int) nativeSetupMethod.invoke(audioRecord, new WeakReference<AudioRecord>(audioRecord), attributes,
sampleRateArray, channelMask, channelIndexMask, audioRecord.getAudioFormat(), bufferSizeInBytes, session,
attributionSourceParcel, 0L, 0);
} else {
// Android 14 added a new int parameter "halInputFlags"
// <https://github.com/aosp-mirror/platform_frameworks_base/commit/f6135d75db79b1d48fad3a3b3080d37be20a2313>
Method nativeSetupMethod = AudioRecord.class.getDeclaredMethod("native_setup", Object.class, Object.class, int[].class,
int.class, int.class, int.class, int.class, int[].class, Parcel.class, long.class, int.class, int.class);
nativeSetupMethod.setAccessible(true);
initResult = (int) nativeSetupMethod.invoke(audioRecord, new WeakReference<AudioRecord>(audioRecord), attributes,
sampleRateArray, channelMask, channelIndexMask, audioRecord.getAudioFormat(), bufferSizeInBytes, session,
attributionSourceParcel, 0L, 0, 0);
}
} }
} }

View File

@ -41,8 +41,14 @@ public final class ClipboardManager {
getPrimaryClipMethod = manager.getClass().getMethod("getPrimaryClip", String.class, String.class, int.class, int.class); getPrimaryClipMethod = manager.getClass().getMethod("getPrimaryClip", String.class, String.class, int.class, int.class);
getMethodVersion = 2; getMethodVersion = 2;
} catch (NoSuchMethodException e3) { } catch (NoSuchMethodException e3) {
getPrimaryClipMethod = manager.getClass().getMethod("getPrimaryClip", String.class, int.class, String.class); try {
getMethodVersion = 3; getPrimaryClipMethod = manager.getClass().getMethod("getPrimaryClip", String.class, int.class, String.class);
getMethodVersion = 3;
} catch (NoSuchMethodException e4) {
getPrimaryClipMethod = manager.getClass()
.getMethod("getPrimaryClip", String.class, String.class, int.class, int.class, boolean.class);
getMethodVersion = 4;
}
} }
} }
} }
@ -87,8 +93,11 @@ public final class ClipboardManager {
return (ClipData) method.invoke(manager, FakeContext.PACKAGE_NAME, null, FakeContext.ROOT_UID); return (ClipData) method.invoke(manager, FakeContext.PACKAGE_NAME, null, FakeContext.ROOT_UID);
case 2: case 2:
return (ClipData) method.invoke(manager, FakeContext.PACKAGE_NAME, null, FakeContext.ROOT_UID, 0); return (ClipData) method.invoke(manager, FakeContext.PACKAGE_NAME, null, FakeContext.ROOT_UID, 0);
default: case 3:
return (ClipData) method.invoke(manager, FakeContext.PACKAGE_NAME, FakeContext.ROOT_UID, null); return (ClipData) method.invoke(manager, FakeContext.PACKAGE_NAME, FakeContext.ROOT_UID, null);
default:
// The last boolean parameter is "userOperate"
return (ClipData) method.invoke(manager, FakeContext.PACKAGE_NAME, null, FakeContext.ROOT_UID, 0, true);
} }
} }