Compare commits

...

22 Commits

Author SHA1 Message Date
Romain Vimont
d9bc5082ab Disable USB features for win32
Currently, there is an issue with the libusb prebuilt dll.

Refs libusb/#1049 <https://github.com/libusb/libusb/issues/1049>
PR #3011 <https://github.com/Genymobile/scrcpy/pull/3011>
2022-02-20 17:39:52 +01:00
Romain Vimont
73a5311ac6 Forbid HID input without OTG on Windows
On Windows, if the adb daemon is running, opening the USB device will
necessarily fail, so HID input is not possible.

Refs #2773 <https://github.com/Genymobile/scrcpy/issues/2773>
PR #3011 <https://github.com/Genymobile/scrcpy/pull/3011>
2022-02-20 17:39:42 +01:00
Romain Vimont
25296ae167 Kill adb daemon in OTG mode on Windows
On Windows, it is not possible to open a USB device from several
process, so HID events may only work if no adb daemon is running.

PR #3011 <https://github.com/Genymobile/scrcpy/pull/3011>
2022-02-20 17:39:39 +01:00
Romain Vimont
3bb24b3926 Make intr optional for adb commands
All adb commands are executed with an "interruptor", so that they can be
interrupted on Ctrl+C.

Make this interruptor optional, so that we could call "adb kill-server"
in OTG mode. This command always returns almost immediately anyway.

Ideally, we should make all blocking calls interruptible (including
libusb calls, by using the asynchronous API), but it's a lot of work,
and in practice it works well enough.

PR #3011 <https://github.com/Genymobile/scrcpy/pull/3011>
2022-02-20 17:39:36 +01:00
Romain Vimont
6ee75c0cff Remove obsolete text in error message
The HID/OTG features are now available on all platforms.

PR #3011 <https://github.com/Genymobile/scrcpy/pull/3011>
2022-02-20 17:39:30 +01:00
Romain Vimont
6b65cd405a Build for Windows with libusb support
Fixes #2773 <https://github.com/Genymobile/scrcpy/issues/2773>
PR #3011 <https://github.com/Genymobile/scrcpy/pull/3011>
2022-02-20 17:39:12 +01:00
Romain Vimont
ff3cb31cb4 Fix libusb callback for Windows
Add LIBUSB_CALL so that the callback has the correct signature on
Windows (including __attribute__((stdcall))).

PR #3011 <https://github.com/Genymobile/scrcpy/pull/3011>
2022-02-20 17:39:09 +01:00
Romain Vimont
06243e7c3c Avoid PRIx16 printf format on Windows
Convert uint16_t to unsigned to avoid using PRIx16, which may not exist
on Windows.

PR #3011 <https://github.com/Genymobile/scrcpy/pull/3011>
2022-02-20 17:39:04 +01:00
Romain Vimont
b9b2879789 Remove USB hotplug callback error log
If it fails, the error is already logged by sc_usb_register_callback().

PR #3011 <https://github.com/Genymobile/scrcpy/pull/3011>
2022-02-20 17:38:51 +01:00
Romain Vimont
be1936bb85 Report USB device disconnection when detected
USB device disconnection is detected via a hotplug callback when it is
supported.

In addition, report disconnection on libusb calls returning
LIBUSB_ERROR_NO_DEVICE or LIBUSB_ERROR_NOT_FOUND. This allows to detect
disconnection after a libusb call when hotplug is not available.

PR #3011 <https://github.com/Genymobile/scrcpy/pull/3011>
2022-02-20 17:38:14 +01:00
Romain Vimont
3ee3f8dc02 Work around mouse capture SDL bug on macOS
On macOS, SDL relative mouse mode does not work correctly when the
cursor is outside the window.

As a workaround, move the cursor inside the window before setting the
relative mouse mode.

Refs SDL/#5340 <https://github.com/libsdl-org/SDL/issues/5340>
PR #3031 <https://github.com/Genymobile/scrcpy/pull/3031>
2022-02-20 17:28:02 +01:00
Romain Vimont
9db42341e4 Pass screen instance to mouse capture functions
Using the screen instance or not in these functions is an implementation
detail. Further changes will require the screen instance.

Refs 7848a387c8
PR #3031 <https://github.com/Genymobile/scrcpy/pull/3031>
2022-02-20 17:27:53 +01:00
Romain Vimont
82a99f69ec Remove "linux-only" mentions for HID/OTG features
HID/OTG features are not limited to Linux anymore.

PR #3031 <https://github.com/Genymobile/scrcpy/pull/3031>
2022-02-20 17:27:43 +01:00
Romain Vimont
33202491e1 Build on macOS with libusb support
Fixes #2774 <https://github.com/Genymobile/scrcpy/issues/2774>
PR #3031 <https://github.com/Genymobile/scrcpy/pull/3031>
2022-02-20 17:27:22 +01:00
Romain Vimont
b4fd882ece Fix typo 2022-02-20 17:21:25 +01:00
Romain Vimont
c4ab65eb79 Remove useless '\n' in log 2022-02-18 21:18:36 +01:00
Romain Vimont
6edf50d447 Remove fprintf() in tests
It was committed by mistake.
2022-02-18 19:34:54 +01:00
Romain Vimont
4b018be789 Add --print-fps to enable FPS counter on start
The FPS counter could be enabled/disabled via MOD+i.

Add a command line option to enable it on start. This is consistent with
other features like --turn-screen-off or --fullscreen.

Fixes #468 <https://github.com/Genymobile/scrcpy/issues/468>
PR #3030 <https://github.com/Genymobile/scrcpy/pull/3030>
2022-02-18 18:16:50 +01:00
Romain Vimont
fa93c8a91b Move FPS counter start/stop logs
This will allow to print the same logs for every start/stop call.

PR #3030 <https://github.com/Genymobile/scrcpy/pull/3030>
2022-02-18 18:16:12 +01:00
Romain Vimont
03705b828b Use sc_prefix for fps counter 2022-02-17 19:55:24 +01:00
Romain Vimont
85edba20e7 Enforce deadline reached on timeout
The value of sc_tick_now() has microsecond precision, but
sc_cond_timedwait() has only millisecond precision.

To guarantee that sc_tick_now() >= deadline when sc_cond_timedwait()
returns due to timeout, round up to the next millisecond.

This avoids to call a non-blocking sc_cond_timedwait() in a loop for no
reason until a target deadline during up to 1 millisecond.

Refs 682a691173
2022-02-16 18:29:30 +01:00
Romain Vimont
2a872c3865 Fix fps_counter tick type
The type uint32_t is not sufficient to store the result of
sc_tick_now().

As a consequence, the FPS counter entered a live loop and caused a lock
starvation (deadlock in practice).

Refs ec871dd3f5
Refs 682a691173
2022-02-16 18:12:57 +01:00
27 changed files with 307 additions and 110 deletions

View File

@@ -161,7 +161,8 @@ install the required packages:
```bash ```bash
# runtime dependencies # runtime dependencies
pacman -S mingw-w64-x86_64-SDL2 \ pacman -S mingw-w64-x86_64-SDL2 \
mingw-w64-x86_64-ffmpeg mingw-w64-x86_64-ffmpeg \
mingw-w64-x86_64-libusb
# client build dependencies # client build dependencies
pacman -S mingw-w64-x86_64-make \ pacman -S mingw-w64-x86_64-make \
@@ -175,7 +176,8 @@ For a 32 bits version, replace `x86_64` by `i686`:
```bash ```bash
# runtime dependencies # runtime dependencies
pacman -S mingw-w64-i686-SDL2 \ pacman -S mingw-w64-i686-SDL2 \
mingw-w64-i686-ffmpeg mingw-w64-i686-ffmpeg \
mingw-w64-i686-libusb
# client build dependencies # client build dependencies
pacman -S mingw-w64-i686-make \ pacman -S mingw-w64-i686-make \
@@ -199,7 +201,7 @@ Install the packages with [Homebrew]:
```bash ```bash
# runtime dependencies # runtime dependencies
brew install sdl2 ffmpeg brew install sdl2 ffmpeg libusb
# client build dependencies # client build dependencies
brew install pkg-config meson brew install pkg-config meson

View File

@@ -215,6 +215,15 @@ scrcpy --max-fps 15
This is officially supported since Android 10, but may work on earlier versions. This is officially supported since Android 10, but may work on earlier versions.
The actual capture framerate may be printed to the console:
```
scrcpy --print-fps
```
It may also be enabled or disabled at any time with <kbd>MOD</kbd>+<kbd>i</kbd>.
#### Crop #### Crop
The device screen may be cropped to mirror only part of the screen. The device screen may be cropped to mirror only part of the screen.

View File

@@ -74,7 +74,7 @@ if v4l2_support
src += [ 'src/v4l2_sink.c' ] src += [ 'src/v4l2_sink.c' ]
endif endif
usb_support = get_option('usb') and host_machine.system() == 'linux' usb_support = get_option('usb')
if usb_support if usb_support
src += [ src += [
'src/usb/aoa_hid.c', 'src/usb/aoa_hid.c',
@@ -141,9 +141,22 @@ else
include_directories: include_directories(ffmpeg_include_dir) include_directories: include_directories(ffmpeg_include_dir)
) )
prebuilt_libusb = meson.get_cross_property('prebuilt_libusb')
prebuilt_libusb_root = meson.get_cross_property('prebuilt_libusb_root')
libusb_bin_dir = meson.current_source_dir() + '/prebuilt-deps/data/' + prebuilt_libusb + '/dll'
libusb_include_dir = 'prebuilt-deps/data/' + prebuilt_libusb_root + '/include'
libusb = declare_dependency(
dependencies: [
cc.find_library('libusb-1.0', dirs: libusb_bin_dir),
],
include_directories: include_directories(libusb_include_dir)
)
dependencies = [ dependencies = [
ffmpeg, ffmpeg,
sdl2, sdl2,
libusb,
cc.find_library('mingw32') cc.find_library('mingw32')
] ]

View File

@@ -0,0 +1,28 @@
#!/usr/bin/env bash
set -e
DIR=$(dirname ${BASH_SOURCE[0]})
cd "$DIR"
. common
mkdir -p "$PREBUILT_DATA_DIR"
cd "$PREBUILT_DATA_DIR"
DEP_DIR=libusb-1.0.25
FILENAME=libusb-1.0.25.7z
SHA256SUM=3d1c98416f454026034b2b5d67f8a294053898cb70a8b489874e75b136c6674d
if [[ -d "$DEP_DIR" ]]
then
echo "$DEP_DIR" found
exit 0
fi
get_file "https://github.com/libusb/libusb/releases/download/v1.0.25/$FILENAME" "$FILENAME" "$SHA256SUM"
mkdir "$DEP_DIR"
cd "$DEP_DIR"
7z x "../$FILENAME" \
MinGW32/dll/libusb-1.0.dll \
MinGW64/dll/libusb-1.0.dll \
include /

View File

@@ -100,7 +100,7 @@ Simulate a physical keyboard by using HID over AOAv2.
This provides a better experience for IME users, and allows to generate non-ASCII characters, contrary to the default injection method. This provides a better experience for IME users, and allows to generate non-ASCII characters, contrary to the default injection method.
It may only work over USB, and is currently only supported on Linux. It may only work over USB.
The keyboard layout must be configured (once and for all) on the device, via Settings -> System -> Languages and input -> Physical keyboard. This settings page can be started directly: The keyboard layout must be configured (once and for all) on the device, via Settings -> System -> Languages and input -> Physical keyboard. This settings page can be started directly:
@@ -142,7 +142,7 @@ In this mode, the computer mouse is captured to control the device directly (rel
LAlt, LSuper or RSuper toggle the capture mode, to give control of the mouse back to the computer. LAlt, LSuper or RSuper toggle the capture mode, to give control of the mouse back to the computer.
It may only work over USB, and is currently only supported on Linux. It may only work over USB.
Also see \fB\-\-hid\-keyboard\fR. Also see \fB\-\-hid\-keyboard\fR.
@@ -190,7 +190,7 @@ LAlt, LSuper or RSuper toggle the mouse capture mode, to give control of the mou
If any of \fB\-\-hid\-keyboard\fR or \fB\-\-hid\-mouse\fR is set, only enable keyboard or mouse respectively, otherwise enable both. If any of \fB\-\-hid\-keyboard\fR or \fB\-\-hid\-mouse\fR is set, only enable keyboard or mouse respectively, otherwise enable both.
It may only work over USB, and is currently only supported on Linux. It may only work over USB.
See \fB\-\-hid\-keyboard\fR and \fB\-\-hid\-mouse\fR. See \fB\-\-hid\-keyboard\fR and \fB\-\-hid\-mouse\fR.
@@ -211,6 +211,10 @@ Inject alpha characters and space as text events instead of key events.
This avoids issues when combining multiple keys to enter special characters, This avoids issues when combining multiple keys to enter special characters,
but breaks the expected behavior of alpha keys in games (typically WASD). but breaks the expected behavior of alpha keys in games (typically WASD).
.TP
.B "\-\-print\-fps
Start FPS counter, to print framerate logs to the console. It can be started or stopped at any time with MOD+i.
.TP .TP
.BI "\-\-push\-target " path .BI "\-\-push\-target " path
Set the target directory for pushing files to the device by drag & drop. It is passed as\-is to "adb push". Set the target directory for pushing files to the device by drag & drop. It is passed as\-is to "adb push".

View File

@@ -150,7 +150,7 @@ process_check_success_internal(sc_pid pid, const char *name, bool close,
static bool static bool
process_check_success_intr(struct sc_intr *intr, sc_pid pid, const char *name, process_check_success_intr(struct sc_intr *intr, sc_pid pid, const char *name,
unsigned flags) { unsigned flags) {
if (!sc_intr_set_process(intr, pid)) { if (intr && !sc_intr_set_process(intr, pid)) {
// Already interrupted // Already interrupted
return false; return false;
} }
@@ -158,7 +158,9 @@ process_check_success_intr(struct sc_intr *intr, sc_pid pid, const char *name,
// Always pass close=false, interrupting would be racy otherwise // Always pass close=false, interrupting would be racy otherwise
bool ret = process_check_success_internal(pid, name, false, flags); bool ret = process_check_success_internal(pid, name, false, flags);
sc_intr_set_process(intr, SC_PROCESS_NONE); if (intr) {
sc_intr_set_process(intr, SC_PROCESS_NONE);
}
// Close separately // Close separately
sc_process_close(pid); sc_process_close(pid);
@@ -202,6 +204,14 @@ sc_adb_start_server(struct sc_intr *intr, unsigned flags) {
return process_check_success_intr(intr, pid, "adb start-server", flags); return process_check_success_intr(intr, pid, "adb start-server", flags);
} }
bool
sc_adb_kill_server(struct sc_intr *intr, unsigned flags) {
const char *const argv[] = SC_ADB_COMMAND("kill-server");
sc_pid pid = sc_adb_execute(argv, flags);
return process_check_success_intr(intr, pid, "adb kill-server", flags);
}
bool bool
sc_adb_forward(struct sc_intr *intr, const char *serial, uint16_t local_port, sc_adb_forward(struct sc_intr *intr, const char *serial, uint16_t local_port,
const char *device_socket_name, unsigned flags) { const char *device_socket_name, unsigned flags) {
@@ -412,7 +422,7 @@ sc_adb_list_devices(struct sc_intr *intr, unsigned flags,
// The implementation assumes that the output of "adb devices -l" fits // The implementation assumes that the output of "adb devices -l" fits
// in the buffer in a single pass // in the buffer in a single pass
LOGW("Result of \"adb devices -l\" does not fit in 4Kb. " LOGW("Result of \"adb devices -l\" does not fit in 4Kb. "
"Please report an issue.\n"); "Please report an issue.");
return -1; return -1;
} }
@@ -676,7 +686,7 @@ sc_adb_get_device_ip(struct sc_intr *intr, const char *serial, unsigned flags) {
// The implementation assumes that the output of "ip route" fits in the // The implementation assumes that the output of "ip route" fits in the
// buffer in a single pass // buffer in a single pass
LOGW("Result of \"ip route\" does not fit in 1Kb. " LOGW("Result of \"ip route\" does not fit in 1Kb. "
"Please report an issue.\n"); "Please report an issue.");
return NULL; return NULL;
} }

View File

@@ -36,6 +36,9 @@ sc_adb_execute(const char *const argv[], unsigned flags);
bool bool
sc_adb_start_server(struct sc_intr *intr, unsigned flags); sc_adb_start_server(struct sc_intr *intr, unsigned flags);
bool
sc_adb_kill_server(struct sc_intr *intr, unsigned flags);
bool bool
sc_adb_forward(struct sc_intr *intr, const char *serial, uint16_t local_port, sc_adb_forward(struct sc_intr *intr, const char *serial, uint16_t local_port,
const char *device_socket_name, unsigned flags); const char *device_socket_name, unsigned flags);

View File

@@ -55,6 +55,7 @@
#define OPT_NO_DOWNSIZE_ON_ERROR 1035 #define OPT_NO_DOWNSIZE_ON_ERROR 1035
#define OPT_OTG 1036 #define OPT_OTG 1036
#define OPT_NO_CLEANUP 1037 #define OPT_NO_CLEANUP 1037
#define OPT_PRINT_FPS 1038
struct sc_option { struct sc_option {
char shortopt; char shortopt;
@@ -117,7 +118,7 @@ static const struct sc_option options[] = {
.text = "Crop the device screen on the server.\n" .text = "Crop the device screen on the server.\n"
"The values are expressed in the device natural orientation " "The values are expressed in the device natural orientation "
"(typically, portrait for a phone, landscape for a tablet). " "(typically, portrait for a phone, landscape for a tablet). "
"Any --max-size value is cmoputed on the cropped size.", "Any --max-size value is computed on the cropped size.",
}, },
{ {
.shortopt = 'd', .shortopt = 'd',
@@ -185,8 +186,7 @@ static const struct sc_option options[] = {
"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"
"It may only work over USB, and is currently only supported " "It may only work over USB.\n"
"on Linux.\n"
"The keyboard layout must be configured (once and for all) on " "The keyboard layout must be configured (once and for all) on "
"the device, via Settings -> System -> Languages and input -> " "the device, via Settings -> System -> Languages and input -> "
"Physical keyboard. This settings page can be started " "Physical keyboard. This settings page can be started "
@@ -238,8 +238,7 @@ static const struct sc_option options[] = {
"device directly (relative mouse mode).\n" "device directly (relative mouse mode).\n"
"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, and is currently only supported " "It may only work over USB.\n"
"on Linux.\n"
"Also see --hid-keyboard.", "Also see --hid-keyboard.",
}, },
{ {
@@ -310,8 +309,7 @@ static const struct sc_option options[] = {
"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 " "If any of --hid-keyboard or --hid-mouse is set, only enable "
"keyboard or mouse respectively, otherwise enable both.\n" "keyboard or mouse respectively, otherwise enable both.\n"
"It may only work over USB, and is currently only supported " "It may only work over USB.\n"
"on Linux.\n"
"See --hid-keyboard and --hid-mouse.", "See --hid-keyboard and --hid-mouse.",
}, },
{ {
@@ -336,6 +334,12 @@ static const struct sc_option options[] = {
"special character, but breaks the expected behavior of alpha " "special character, but breaks the expected behavior of alpha "
"keys in games (typically WASD).", "keys in games (typically WASD).",
}, },
{
.longopt_id = OPT_PRINT_FPS,
.longopt = "print-fps",
.text = "Start FPS counter, to print framerate logs to the console. "
"It can be started or stopped at any time with MOD+i.",
},
{ {
.longopt_id = OPT_PUSH_TARGET, .longopt_id = OPT_PUSH_TARGET,
.longopt = "push-target", .longopt = "push-target",
@@ -1366,8 +1370,7 @@ parse_args_with_getopt(struct scrcpy_cli_args *args, int argc, char *argv[],
opts->keyboard_input_mode = SC_KEYBOARD_INPUT_MODE_HID; opts->keyboard_input_mode = SC_KEYBOARD_INPUT_MODE_HID;
break; break;
#else #else
LOGE("HID over AOA (-K/--hid-keyboard) is disabled (or " LOGE("HID over AOA (-K/--hid-keyboard) is disabled.");
"unsupported on this platform).");
return false; return false;
#endif #endif
case OPT_MAX_FPS: case OPT_MAX_FPS:
@@ -1385,8 +1388,7 @@ parse_args_with_getopt(struct scrcpy_cli_args *args, int argc, char *argv[],
opts->mouse_input_mode = SC_MOUSE_INPUT_MODE_HID; opts->mouse_input_mode = SC_MOUSE_INPUT_MODE_HID;
break; break;
#else #else
LOGE("HID over AOA (-M/--hid-mouse) is disabled (or " LOGE("HID over AOA (-M/--hid-mouse) is disabled.");
"unsupported on this platform).");
return false; return false;
#endif #endif
case OPT_LOCK_VIDEO_ORIENTATION: case OPT_LOCK_VIDEO_ORIENTATION:
@@ -1547,13 +1549,15 @@ parse_args_with_getopt(struct scrcpy_cli_args *args, int argc, char *argv[],
case OPT_NO_CLEANUP: case OPT_NO_CLEANUP:
opts->cleanup = false; opts->cleanup = false;
break; break;
case OPT_PRINT_FPS:
opts->start_fps_counter = true;
break;
case OPT_OTG: case OPT_OTG:
#ifdef HAVE_USB #ifdef HAVE_USB
opts->otg = true; opts->otg = true;
break; break;
#else #else
LOGE("OTG mode (--otg) is disabled (or unsupported on this " LOGE("OTG mode (--otg) is disabled.");
"platform).");
return false; return false;
#endif #endif
case OPT_V4L2_SINK: case OPT_V4L2_SINK:
@@ -1676,6 +1680,18 @@ parse_args_with_getopt(struct scrcpy_cli_args *args, int argc, char *argv[],
} }
#ifdef HAVE_USB #ifdef HAVE_USB
# ifdef _WIN32
if (!opts->otg && (opts->keyboard_input_mode == SC_KEYBOARD_INPUT_MODE_HID
|| opts->mouse_input_mode == SC_MOUSE_INPUT_MODE_HID)) {
LOGE("On Windows, it is not possible to open a USB device already open "
"by another process (like adb).");
LOGE("Therefore, -K/--hid-keyboard and -M/--hid-mouse may only work in "
"OTG mode (--otg).");
return false;
}
# endif
if (opts->otg) { if (opts->otg) {
// OTG mode is compatible with only very few options. // OTG mode is compatible with only very few options.
// Only report obvious errors. // Only report obvious errors.

View File

@@ -4,10 +4,10 @@
#include "util/log.h" #include "util/log.h"
#define FPS_COUNTER_INTERVAL SC_TICK_FROM_SEC(1) #define SC_FPS_COUNTER_INTERVAL SC_TICK_FROM_SEC(1)
bool bool
fps_counter_init(struct fps_counter *counter) { sc_fps_counter_init(struct sc_fps_counter *counter) {
bool ok = sc_mutex_init(&counter->mutex); bool ok = sc_mutex_init(&counter->mutex);
if (!ok) { if (!ok) {
return false; return false;
@@ -27,26 +27,26 @@ fps_counter_init(struct fps_counter *counter) {
} }
void void
fps_counter_destroy(struct fps_counter *counter) { sc_fps_counter_destroy(struct sc_fps_counter *counter) {
sc_cond_destroy(&counter->state_cond); sc_cond_destroy(&counter->state_cond);
sc_mutex_destroy(&counter->mutex); sc_mutex_destroy(&counter->mutex);
} }
static inline bool static inline bool
is_started(struct fps_counter *counter) { is_started(struct sc_fps_counter *counter) {
return atomic_load_explicit(&counter->started, memory_order_acquire); return atomic_load_explicit(&counter->started, memory_order_acquire);
} }
static inline void static inline void
set_started(struct fps_counter *counter, bool started) { set_started(struct sc_fps_counter *counter, bool started) {
atomic_store_explicit(&counter->started, started, memory_order_release); atomic_store_explicit(&counter->started, started, memory_order_release);
} }
// must be called with mutex locked // must be called with mutex locked
static void static void
display_fps(struct fps_counter *counter) { display_fps(struct sc_fps_counter *counter) {
unsigned rendered_per_second = unsigned rendered_per_second =
counter->nr_rendered * SC_TICK_FREQ / FPS_COUNTER_INTERVAL; counter->nr_rendered * SC_TICK_FREQ / SC_FPS_COUNTER_INTERVAL;
if (counter->nr_skipped) { if (counter->nr_skipped) {
LOGI("%u fps (+%u frames skipped)", rendered_per_second, LOGI("%u fps (+%u frames skipped)", rendered_per_second,
counter->nr_skipped); counter->nr_skipped);
@@ -57,7 +57,7 @@ display_fps(struct fps_counter *counter) {
// must be called with mutex locked // must be called with mutex locked
static void static void
check_interval_expired(struct fps_counter *counter, uint32_t now) { check_interval_expired(struct sc_fps_counter *counter, sc_tick now) {
if (now < counter->next_timestamp) { if (now < counter->next_timestamp) {
return; return;
} }
@@ -67,13 +67,13 @@ check_interval_expired(struct fps_counter *counter, uint32_t now) {
counter->nr_skipped = 0; counter->nr_skipped = 0;
// add a multiple of the interval // add a multiple of the interval
uint32_t elapsed_slices = uint32_t elapsed_slices =
(now - counter->next_timestamp) / FPS_COUNTER_INTERVAL + 1; (now - counter->next_timestamp) / SC_FPS_COUNTER_INTERVAL + 1;
counter->next_timestamp += FPS_COUNTER_INTERVAL * elapsed_slices; counter->next_timestamp += SC_FPS_COUNTER_INTERVAL * elapsed_slices;
} }
static int static int
run_fps_counter(void *data) { run_fps_counter(void *data) {
struct fps_counter *counter = data; struct sc_fps_counter *counter = data;
sc_mutex_lock(&counter->mutex); sc_mutex_lock(&counter->mutex);
while (!counter->interrupted) { while (!counter->interrupted) {
@@ -94,9 +94,9 @@ run_fps_counter(void *data) {
} }
bool bool
fps_counter_start(struct fps_counter *counter) { sc_fps_counter_start(struct sc_fps_counter *counter) {
sc_mutex_lock(&counter->mutex); sc_mutex_lock(&counter->mutex);
counter->next_timestamp = sc_tick_now() + FPS_COUNTER_INTERVAL; counter->next_timestamp = sc_tick_now() + SC_FPS_COUNTER_INTERVAL;
counter->nr_rendered = 0; counter->nr_rendered = 0;
counter->nr_skipped = 0; counter->nr_skipped = 0;
sc_mutex_unlock(&counter->mutex); sc_mutex_unlock(&counter->mutex);
@@ -117,22 +117,24 @@ fps_counter_start(struct fps_counter *counter) {
counter->thread_started = true; counter->thread_started = true;
} }
LOGI("FPS counter started");
return true; return true;
} }
void void
fps_counter_stop(struct fps_counter *counter) { sc_fps_counter_stop(struct sc_fps_counter *counter) {
set_started(counter, false); set_started(counter, false);
sc_cond_signal(&counter->state_cond); sc_cond_signal(&counter->state_cond);
LOGI("FPS counter stopped");
} }
bool bool
fps_counter_is_started(struct fps_counter *counter) { sc_fps_counter_is_started(struct sc_fps_counter *counter) {
return is_started(counter); return is_started(counter);
} }
void void
fps_counter_interrupt(struct fps_counter *counter) { sc_fps_counter_interrupt(struct sc_fps_counter *counter) {
if (!counter->thread_started) { if (!counter->thread_started) {
return; return;
} }
@@ -145,7 +147,7 @@ fps_counter_interrupt(struct fps_counter *counter) {
} }
void void
fps_counter_join(struct fps_counter *counter) { sc_fps_counter_join(struct sc_fps_counter *counter) {
if (counter->thread_started) { if (counter->thread_started) {
// interrupted must be set by the thread calling join(), so no need to // interrupted must be set by the thread calling join(), so no need to
// lock for the assertion // lock for the assertion
@@ -156,7 +158,7 @@ fps_counter_join(struct fps_counter *counter) {
} }
void void
fps_counter_add_rendered_frame(struct fps_counter *counter) { sc_fps_counter_add_rendered_frame(struct sc_fps_counter *counter) {
if (!is_started(counter)) { if (!is_started(counter)) {
return; return;
} }
@@ -169,7 +171,7 @@ fps_counter_add_rendered_frame(struct fps_counter *counter) {
} }
void void
fps_counter_add_skipped_frame(struct fps_counter *counter) { sc_fps_counter_add_skipped_frame(struct sc_fps_counter *counter) {
if (!is_started(counter)) { if (!is_started(counter)) {
return; return;
} }

View File

@@ -9,7 +9,7 @@
#include "util/thread.h" #include "util/thread.h"
struct fps_counter { struct sc_fps_counter {
sc_thread thread; sc_thread thread;
sc_mutex mutex; sc_mutex mutex;
sc_cond state_cond; sc_cond state_cond;
@@ -28,32 +28,32 @@ struct fps_counter {
}; };
bool bool
fps_counter_init(struct fps_counter *counter); sc_fps_counter_init(struct sc_fps_counter *counter);
void void
fps_counter_destroy(struct fps_counter *counter); sc_fps_counter_destroy(struct sc_fps_counter *counter);
bool bool
fps_counter_start(struct fps_counter *counter); sc_fps_counter_start(struct sc_fps_counter *counter);
void void
fps_counter_stop(struct fps_counter *counter); sc_fps_counter_stop(struct sc_fps_counter *counter);
bool bool
fps_counter_is_started(struct fps_counter *counter); sc_fps_counter_is_started(struct sc_fps_counter *counter);
// request to stop the thread (on quit) // request to stop the thread (on quit)
// must be called before fps_counter_join() // must be called before sc_fps_counter_join()
void void
fps_counter_interrupt(struct fps_counter *counter); sc_fps_counter_interrupt(struct sc_fps_counter *counter);
void void
fps_counter_join(struct fps_counter *counter); sc_fps_counter_join(struct sc_fps_counter *counter);
void void
fps_counter_add_rendered_frame(struct fps_counter *counter); sc_fps_counter_add_rendered_frame(struct sc_fps_counter *counter);
void void
fps_counter_add_skipped_frame(struct fps_counter *counter); sc_fps_counter_add_skipped_frame(struct sc_fps_counter *counter);
#endif #endif

View File

@@ -242,18 +242,14 @@ set_screen_power_mode(struct sc_controller *controller,
} }
static void static void
switch_fps_counter_state(struct fps_counter *fps_counter) { switch_fps_counter_state(struct sc_fps_counter *fps_counter) {
// the started state can only be written from the current thread, so there // the started state can only be written from the current thread, so there
// is no ToCToU issue // is no ToCToU issue
if (fps_counter_is_started(fps_counter)) { if (sc_fps_counter_is_started(fps_counter)) {
fps_counter_stop(fps_counter); sc_fps_counter_stop(fps_counter);
LOGI("FPS counter stopped");
} else { } else {
if (fps_counter_start(fps_counter)) { sc_fps_counter_start(fps_counter);
LOGI("FPS counter started"); // Any error is already logged
} else {
LOGE("FPS counter starting failed");
}
} }
} }

View File

@@ -63,4 +63,5 @@ const struct scrcpy_options scrcpy_options_default = {
.select_tcpip = false, .select_tcpip = false,
.select_usb = false, .select_usb = false,
.cleanup = true, .cleanup = true,
.start_fps_counter = false,
}; };

View File

@@ -138,6 +138,7 @@ struct scrcpy_options {
bool select_usb; bool select_usb;
bool select_tcpip; bool select_tcpip;
bool cleanup; bool cleanup;
bool start_fps_counter;
}; };
extern const struct scrcpy_options scrcpy_options_default; extern const struct scrcpy_options scrcpy_options_default;

View File

@@ -588,6 +588,7 @@ aoa_hid_end:
.rotation = options->rotation, .rotation = options->rotation,
.mipmaps = options->mipmaps, .mipmaps = options->mipmaps,
.fullscreen = options->fullscreen, .fullscreen = options->fullscreen,
.start_fps_counter = options->start_fps_counter,
.buffering_time = options->display_buffer, .buffering_time = options->display_buffer,
}; };

View File

@@ -163,7 +163,27 @@ sc_screen_is_relative_mode(struct sc_screen *screen) {
} }
static void static void
sc_screen_set_mouse_capture(bool capture) { sc_screen_set_mouse_capture(struct sc_screen *screen, bool capture) {
#ifdef __APPLE__
// Workaround for SDL bug on macOS:
// <https://github.com/libsdl-org/SDL/issues/5340>
if (capture) {
int mouse_x, mouse_y;
SDL_GetGlobalMouseState(&mouse_x, &mouse_y);
int x, y, w, h;
SDL_GetWindowPosition(screen->window, &x, &y);
SDL_GetWindowSize(screen->window, &w, &h);
bool outside_window = mouse_x < x || mouse_x >= x + w
|| mouse_y < y || mouse_y >= y + h;
if (outside_window) {
SDL_WarpMouseInWindow(screen->window, w / 2, h / 2);
}
}
#else
(void) screen;
#endif
if (SDL_SetRelativeMouseMode(capture)) { if (SDL_SetRelativeMouseMode(capture)) {
LOGE("Could not set relative mouse mode to %s: %s", LOGE("Could not set relative mouse mode to %s: %s",
capture ? "true" : "false", SDL_GetError()); capture ? "true" : "false", SDL_GetError());
@@ -171,13 +191,16 @@ sc_screen_set_mouse_capture(bool capture) {
} }
static inline bool static inline bool
sc_screen_get_mouse_capture(void) { sc_screen_get_mouse_capture(struct sc_screen *screen) {
(void) screen;
return SDL_GetRelativeMouseMode(); return SDL_GetRelativeMouseMode();
} }
static inline void static inline void
sc_screen_toggle_mouse_capture(void) { sc_screen_toggle_mouse_capture(struct sc_screen *screen) {
sc_screen_set_mouse_capture(!sc_screen_get_mouse_capture()); (void) screen;
bool new_value = !sc_screen_get_mouse_capture(screen);
sc_screen_set_mouse_capture(screen, new_value);
} }
static void static void
@@ -347,7 +370,7 @@ sc_video_buffer_on_new_frame(struct sc_video_buffer *vb, bool previous_skipped,
bool need_new_event; bool need_new_event;
if (previous_skipped) { if (previous_skipped) {
fps_counter_add_skipped_frame(&screen->fps_counter); sc_fps_counter_add_skipped_frame(&screen->fps_counter);
// The EVENT_NEW_FRAME triggered for the previous frame will consume // The EVENT_NEW_FRAME triggered for the previous frame will consume
// this new frame instead, unless the previous event failed // this new frame instead, unless the previous event failed
need_new_event = screen->event_failed; need_new_event = screen->event_failed;
@@ -386,6 +409,7 @@ sc_screen_init(struct sc_screen *screen,
screen->req.width = params->window_width; screen->req.width = params->window_width;
screen->req.height = params->window_height; screen->req.height = params->window_height;
screen->req.fullscreen = params->fullscreen; screen->req.fullscreen = params->fullscreen;
screen->req.start_fps_counter = params->start_fps_counter;
static const struct sc_video_buffer_callbacks cbs = { static const struct sc_video_buffer_callbacks cbs = {
.on_new_frame = sc_video_buffer_on_new_frame, .on_new_frame = sc_video_buffer_on_new_frame,
@@ -402,7 +426,7 @@ sc_screen_init(struct sc_screen *screen,
goto error_destroy_video_buffer; goto error_destroy_video_buffer;
} }
if (!fps_counter_init(&screen->fps_counter)) { if (!sc_fps_counter_init(&screen->fps_counter)) {
goto error_stop_and_join_video_buffer; goto error_stop_and_join_video_buffer;
} }
@@ -534,7 +558,7 @@ error_destroy_renderer:
error_destroy_window: error_destroy_window:
SDL_DestroyWindow(screen->window); SDL_DestroyWindow(screen->window);
error_destroy_fps_counter: error_destroy_fps_counter:
fps_counter_destroy(&screen->fps_counter); sc_fps_counter_destroy(&screen->fps_counter);
error_stop_and_join_video_buffer: error_stop_and_join_video_buffer:
sc_video_buffer_stop(&screen->vb); sc_video_buffer_stop(&screen->vb);
sc_video_buffer_join(&screen->vb); sc_video_buffer_join(&screen->vb);
@@ -562,6 +586,10 @@ sc_screen_show_initial_window(struct sc_screen *screen) {
sc_screen_switch_fullscreen(screen); sc_screen_switch_fullscreen(screen);
} }
if (screen->req.start_fps_counter) {
sc_fps_counter_start(&screen->fps_counter);
}
SDL_ShowWindow(screen->window); SDL_ShowWindow(screen->window);
} }
@@ -573,13 +601,13 @@ sc_screen_hide_window(struct sc_screen *screen) {
void void
sc_screen_interrupt(struct sc_screen *screen) { sc_screen_interrupt(struct sc_screen *screen) {
sc_video_buffer_stop(&screen->vb); sc_video_buffer_stop(&screen->vb);
fps_counter_interrupt(&screen->fps_counter); sc_fps_counter_interrupt(&screen->fps_counter);
} }
void void
sc_screen_join(struct sc_screen *screen) { sc_screen_join(struct sc_screen *screen) {
sc_video_buffer_join(&screen->vb); sc_video_buffer_join(&screen->vb);
fps_counter_join(&screen->fps_counter); sc_fps_counter_join(&screen->fps_counter);
} }
void void
@@ -591,7 +619,7 @@ sc_screen_destroy(struct sc_screen *screen) {
SDL_DestroyTexture(screen->texture); SDL_DestroyTexture(screen->texture);
SDL_DestroyRenderer(screen->renderer); SDL_DestroyRenderer(screen->renderer);
SDL_DestroyWindow(screen->window); SDL_DestroyWindow(screen->window);
fps_counter_destroy(&screen->fps_counter); sc_fps_counter_destroy(&screen->fps_counter);
sc_video_buffer_destroy(&screen->vb); sc_video_buffer_destroy(&screen->vb);
} }
@@ -701,7 +729,7 @@ sc_screen_update_frame(struct sc_screen *screen) {
sc_video_buffer_consume(&screen->vb, screen->frame); sc_video_buffer_consume(&screen->vb, screen->frame);
AVFrame *frame = screen->frame; AVFrame *frame = screen->frame;
fps_counter_add_rendered_frame(&screen->fps_counter); sc_fps_counter_add_rendered_frame(&screen->fps_counter);
struct sc_size new_frame_size = {frame->width, frame->height}; struct sc_size new_frame_size = {frame->width, frame->height};
if (!prepare_for_frame(screen, new_frame_size)) { if (!prepare_for_frame(screen, new_frame_size)) {
@@ -716,7 +744,7 @@ sc_screen_update_frame(struct sc_screen *screen) {
if (sc_screen_is_relative_mode(screen)) { if (sc_screen_is_relative_mode(screen)) {
// Capture mouse on start // Capture mouse on start
sc_screen_set_mouse_capture(true); sc_screen_set_mouse_capture(screen, true);
} }
} }
@@ -829,7 +857,7 @@ sc_screen_handle_event(struct sc_screen *screen, SDL_Event *event) {
break; break;
case SDL_WINDOWEVENT_FOCUS_LOST: case SDL_WINDOWEVENT_FOCUS_LOST:
if (relative_mode) { if (relative_mode) {
sc_screen_set_mouse_capture(false); sc_screen_set_mouse_capture(screen, false);
} }
break; break;
} }
@@ -859,7 +887,7 @@ sc_screen_handle_event(struct sc_screen *screen, SDL_Event *event) {
if (key == cap) { if (key == cap) {
// A mouse capture key has been pressed then released: // A mouse capture key has been pressed then released:
// toggle the capture mouse mode // toggle the capture mouse mode
sc_screen_toggle_mouse_capture(); sc_screen_toggle_mouse_capture(screen);
} }
// Mouse capture keys are never forwarded to the device // Mouse capture keys are never forwarded to the device
return; return;
@@ -869,7 +897,7 @@ sc_screen_handle_event(struct sc_screen *screen, SDL_Event *event) {
case SDL_MOUSEWHEEL: case SDL_MOUSEWHEEL:
case SDL_MOUSEMOTION: case SDL_MOUSEMOTION:
case SDL_MOUSEBUTTONDOWN: case SDL_MOUSEBUTTONDOWN:
if (relative_mode && !sc_screen_get_mouse_capture()) { if (relative_mode && !sc_screen_get_mouse_capture(screen)) {
// Do not forward to input manager, the mouse will be captured // Do not forward to input manager, the mouse will be captured
// on SDL_MOUSEBUTTONUP // on SDL_MOUSEBUTTONUP
return; return;
@@ -885,8 +913,8 @@ sc_screen_handle_event(struct sc_screen *screen, SDL_Event *event) {
} }
break; break;
case SDL_MOUSEBUTTONUP: case SDL_MOUSEBUTTONUP:
if (relative_mode && !sc_screen_get_mouse_capture()) { if (relative_mode && !sc_screen_get_mouse_capture(screen)) {
sc_screen_set_mouse_capture(true); sc_screen_set_mouse_capture(screen, true);
return; return;
} }
break; break;

View File

@@ -26,7 +26,7 @@ struct sc_screen {
struct sc_input_manager im; struct sc_input_manager im;
struct sc_video_buffer vb; struct sc_video_buffer vb;
struct fps_counter fps_counter; struct sc_fps_counter fps_counter;
// The initial requested window properties // The initial requested window properties
struct { struct {
@@ -35,6 +35,7 @@ struct sc_screen {
uint16_t width; uint16_t width;
uint16_t height; uint16_t height;
bool fullscreen; bool fullscreen;
bool start_fps_counter;
} req; } req;
SDL_Window *window; SDL_Window *window;
@@ -93,6 +94,7 @@ struct sc_screen_params {
bool mipmaps; bool mipmaps;
bool fullscreen; bool fullscreen;
bool start_fps_counter;
sc_tick buffering_time; sc_tick buffering_time;
}; };

View File

@@ -95,6 +95,7 @@ sc_aoa_register_hid(struct sc_aoa *aoa, uint16_t accessory_id,
DEFAULT_TIMEOUT); DEFAULT_TIMEOUT);
if (result < 0) { if (result < 0) {
LOGE("REGISTER_HID: libusb error: %s", libusb_strerror(result)); LOGE("REGISTER_HID: libusb error: %s", libusb_strerror(result));
sc_usb_check_disconnected(aoa->usb, result);
return false; return false;
} }
@@ -131,6 +132,7 @@ sc_aoa_set_hid_report_desc(struct sc_aoa *aoa, uint16_t accessory_id,
DEFAULT_TIMEOUT); DEFAULT_TIMEOUT);
if (result < 0) { if (result < 0) {
LOGE("SET_HID_REPORT_DESC: libusb error: %s", libusb_strerror(result)); LOGE("SET_HID_REPORT_DESC: libusb error: %s", libusb_strerror(result));
sc_usb_check_disconnected(aoa->usb, result);
return false; return false;
} }
@@ -173,6 +175,7 @@ sc_aoa_send_hid_event(struct sc_aoa *aoa, const struct sc_hid_event *event) {
DEFAULT_TIMEOUT); DEFAULT_TIMEOUT);
if (result < 0) { if (result < 0) {
LOGE("SEND_HID_EVENT: libusb error: %s", libusb_strerror(result)); LOGE("SEND_HID_EVENT: libusb error: %s", libusb_strerror(result));
sc_usb_check_disconnected(aoa->usb, result);
return false; return false;
} }
@@ -195,6 +198,7 @@ sc_aoa_unregister_hid(struct sc_aoa *aoa, const uint16_t accessory_id) {
DEFAULT_TIMEOUT); DEFAULT_TIMEOUT);
if (result < 0) { if (result < 0) {
LOGE("UNREGISTER_HID: libusb error: %s", libusb_strerror(result)); LOGE("UNREGISTER_HID: libusb error: %s", libusb_strerror(result));
sc_usb_check_disconnected(aoa->usb, result);
return false; return false;
} }

View File

@@ -2,6 +2,7 @@
#include <SDL2/SDL.h> #include <SDL2/SDL.h>
#include "adb/adb.h"
#include "events.h" #include "events.h"
#include "screen_otg.h" #include "screen_otg.h"
#include "util/log.h" #include "util/log.h"
@@ -75,6 +76,15 @@ scrcpy_otg(struct scrcpy_options *options) {
bool aoa_started = false; bool aoa_started = false;
bool aoa_initialized = false; bool aoa_initialized = false;
#ifdef _WIN32
// On Windows, only one process could open a USB device
// <https://github.com/Genymobile/scrcpy/issues/2773>
LOGI("Killing adb daemon (if any)...");
unsigned flags = SC_ADB_NO_STDOUT | SC_ADB_NO_STDERR | SC_ADB_NO_LOGERR;
// uninterruptible (intr == NULL), but in practice it's very quick
sc_adb_kill_server(NULL, flags);
#endif
static const struct sc_usb_callbacks cbs = { static const struct sc_usb_callbacks cbs = {
.on_disconnected = sc_usb_on_disconnected, .on_disconnected = sc_usb_on_disconnected,
}; };
@@ -91,8 +101,8 @@ scrcpy_otg(struct scrcpy_options *options) {
usb_device_initialized = true; usb_device_initialized = true;
LOGI("USB device: %s (%04" PRIx16 ":%04" PRIx16 ") %s %s", LOGI("USB device: %s (%04x:%04x) %s %s", usb_device.serial,
usb_device.serial, usb_device.vid, usb_device.pid, (unsigned) usb_device.vid, (unsigned) usb_device.pid,
usb_device.manufacturer, usb_device.product); usb_device.manufacturer, usb_device.product);
ok = sc_usb_connect(&s->usb, usb_device.device, &cbs, NULL); ok = sc_usb_connect(&s->usb, usb_device.device, &cbs, NULL);

View File

@@ -5,7 +5,27 @@
#include "util/log.h" #include "util/log.h"
static void static void
sc_screen_otg_set_mouse_capture(bool capture) { sc_screen_otg_set_mouse_capture(struct sc_screen_otg *screen, bool capture) {
#ifdef __APPLE__
// Workaround for SDL bug on macOS:
// <https://github.com/libsdl-org/SDL/issues/5340>
if (capture) {
int mouse_x, mouse_y;
SDL_GetGlobalMouseState(&mouse_x, &mouse_y);
int x, y, w, h;
SDL_GetWindowPosition(screen->window, &x, &y);
SDL_GetWindowSize(screen->window, &w, &h);
bool outside_window = mouse_x < x || mouse_x >= x + w
|| mouse_y < y || mouse_y >= y + h;
if (outside_window) {
SDL_WarpMouseInWindow(screen->window, w / 2, h / 2);
}
}
#else
(void) screen;
#endif
if (SDL_SetRelativeMouseMode(capture)) { if (SDL_SetRelativeMouseMode(capture)) {
LOGE("Could not set relative mouse mode to %s: %s", LOGE("Could not set relative mouse mode to %s: %s",
capture ? "true" : "false", SDL_GetError()); capture ? "true" : "false", SDL_GetError());
@@ -13,13 +33,16 @@ sc_screen_otg_set_mouse_capture(bool capture) {
} }
static inline bool static inline bool
sc_screen_otg_get_mouse_capture(void) { sc_screen_otg_get_mouse_capture(struct sc_screen_otg *screen) {
(void) screen;
return SDL_GetRelativeMouseMode(); return SDL_GetRelativeMouseMode();
} }
static inline void static inline void
sc_screen_otg_toggle_mouse_capture(void) { sc_screen_otg_toggle_mouse_capture(struct sc_screen_otg *screen) {
sc_screen_otg_set_mouse_capture(!sc_screen_otg_get_mouse_capture()); (void) screen;
bool new_value = !sc_screen_otg_get_mouse_capture(screen);
sc_screen_otg_set_mouse_capture(screen, new_value);
} }
static void static void
@@ -86,7 +109,7 @@ sc_screen_otg_init(struct sc_screen_otg *screen,
if (screen->mouse) { if (screen->mouse) {
// Capture mouse on start // Capture mouse on start
sc_screen_otg_set_mouse_capture(true); sc_screen_otg_set_mouse_capture(screen, true);
} }
return true; return true;
@@ -198,7 +221,7 @@ sc_screen_otg_handle_event(struct sc_screen_otg *screen, SDL_Event *event) {
break; break;
case SDL_WINDOWEVENT_FOCUS_LOST: case SDL_WINDOWEVENT_FOCUS_LOST:
if (screen->mouse) { if (screen->mouse) {
sc_screen_otg_set_mouse_capture(false); sc_screen_otg_set_mouse_capture(screen, false);
} }
break; break;
} }
@@ -232,7 +255,7 @@ sc_screen_otg_handle_event(struct sc_screen_otg *screen, SDL_Event *event) {
if (key == cap) { if (key == cap) {
// A mouse capture key has been pressed then released: // A mouse capture key has been pressed then released:
// toggle the capture mouse mode // toggle the capture mouse mode
sc_screen_otg_toggle_mouse_capture(); sc_screen_otg_toggle_mouse_capture(screen);
} }
// Mouse capture keys are never forwarded to the device // Mouse capture keys are never forwarded to the device
return; return;
@@ -244,26 +267,26 @@ sc_screen_otg_handle_event(struct sc_screen_otg *screen, SDL_Event *event) {
} }
break; break;
case SDL_MOUSEMOTION: case SDL_MOUSEMOTION:
if (screen->mouse && sc_screen_otg_get_mouse_capture()) { if (screen->mouse && sc_screen_otg_get_mouse_capture(screen)) {
sc_screen_otg_process_mouse_motion(screen, &event->motion); sc_screen_otg_process_mouse_motion(screen, &event->motion);
} }
break; break;
case SDL_MOUSEBUTTONDOWN: case SDL_MOUSEBUTTONDOWN:
if (screen->mouse && sc_screen_otg_get_mouse_capture()) { if (screen->mouse && sc_screen_otg_get_mouse_capture(screen)) {
sc_screen_otg_process_mouse_button(screen, &event->button); sc_screen_otg_process_mouse_button(screen, &event->button);
} }
break; break;
case SDL_MOUSEBUTTONUP: case SDL_MOUSEBUTTONUP:
if (screen->mouse) { if (screen->mouse) {
if (sc_screen_otg_get_mouse_capture()) { if (sc_screen_otg_get_mouse_capture(screen)) {
sc_screen_otg_process_mouse_button(screen, &event->button); sc_screen_otg_process_mouse_button(screen, &event->button);
} else { } else {
sc_screen_otg_set_mouse_capture(true); sc_screen_otg_set_mouse_capture(screen, true);
} }
} }
break; break;
case SDL_MOUSEWHEEL: case SDL_MOUSEWHEEL:
if (screen->mouse && sc_screen_otg_get_mouse_capture()) { if (screen->mouse && sc_screen_otg_get_mouse_capture(screen)) {
sc_screen_otg_process_mouse_wheel(screen, &event->wheel); sc_screen_otg_process_mouse_wheel(screen, &event->wheel);
} }
break; break;

View File

@@ -40,8 +40,9 @@ sc_usb_read_device(libusb_device *device, struct sc_usb_device *out) {
if (result < 0) { if (result < 0) {
// Log at debug level because it is expected that some non-Android USB // Log at debug level because it is expected that some non-Android USB
// devices present on the computer require special permissions // devices present on the computer require special permissions
LOGD("Open USB device %04" PRIx16 ":%04" PRIx16 ": libusb error: %s", LOGD("Open USB device %04x:%04x: libusb error: %s",
desc.idVendor, desc.idProduct, libusb_strerror(result)); (unsigned) desc.idVendor, (unsigned) desc.idProduct,
libusb_strerror(result));
return false; return false;
} }
@@ -146,8 +147,10 @@ sc_usb_devices_log(enum sc_log_level level, struct sc_usb_device *devices,
for (size_t i = 0; i < count; ++i) { for (size_t i = 0; i < count; ++i) {
struct sc_usb_device *d = &devices[i]; struct sc_usb_device *d = &devices[i];
const char *selection = d->selected ? "-->" : " "; const char *selection = d->selected ? "-->" : " ";
LOG(level, " %s %-18s (%04" PRIx16 ":%04" PRIx16 ") %s %s", // Convert uint16_t to unsigned because PRIx16 may not exist on Windows
selection, d->serial, d->vid, d->pid, d->manufacturer, d->product); LOG(level, " %s %-18s (%04x:%04x) %s %s",
selection, d->serial, (unsigned) d->vid, (unsigned) d->pid,
d->manufacturer, d->product);
} }
} }
@@ -216,7 +219,25 @@ sc_usb_destroy(struct sc_usb *usb) {
libusb_exit(usb->context); libusb_exit(usb->context);
} }
static int static void
sc_usb_report_disconnected(struct sc_usb *usb) {
if (usb->cbs && !atomic_flag_test_and_set(&usb->disconnection_notified)) {
assert(usb->cbs && usb->cbs->on_disconnected);
usb->cbs->on_disconnected(usb, usb->cbs_userdata);
}
}
bool
sc_usb_check_disconnected(struct sc_usb *usb, int result) {
if (result == LIBUSB_ERROR_NO_DEVICE || result == LIBUSB_ERROR_NOT_FOUND) {
sc_usb_report_disconnected(usb);
return false;
}
return true;
}
static LIBUSB_CALL int
sc_usb_libusb_callback(libusb_context *ctx, libusb_device *device, sc_usb_libusb_callback(libusb_context *ctx, libusb_device *device,
libusb_hotplug_event event, void *userdata) { libusb_hotplug_event event, void *userdata) {
(void) ctx; (void) ctx;
@@ -232,8 +253,7 @@ sc_usb_libusb_callback(libusb_context *ctx, libusb_device *device,
return 0; return 0;
} }
assert(usb->cbs && usb->cbs->on_disconnected); sc_usb_report_disconnected(usb);
usb->cbs->on_disconnected(usb, usb->cbs_userdata);
// Do not automatically deregister the callback by returning 1. Instead, // Do not automatically deregister the callback by returning 1. Instead,
// manually deregister to interrupt libusb_handle_events() from the libusb // manually deregister to interrupt libusb_handle_events() from the libusb
@@ -307,6 +327,7 @@ sc_usb_connect(struct sc_usb *usb, libusb_device *device,
if (cbs) { if (cbs) {
atomic_init(&usb->stopped, false); atomic_init(&usb->stopped, false);
usb->disconnection_notified = (atomic_flag) ATOMIC_FLAG_INIT;
if (sc_usb_register_callback(usb)) { if (sc_usb_register_callback(usb)) {
// Create a thread to process libusb events, so that device // Create a thread to process libusb events, so that device
// disconnection could be detected immediately // disconnection could be detected immediately
@@ -317,8 +338,6 @@ sc_usb_connect(struct sc_usb *usb, libusb_device *device,
LOGW("Libusb event thread handler could not be created, USB " LOGW("Libusb event thread handler could not be created, USB "
"device disconnection might not be detected immediately"); "device disconnection might not be detected immediately");
} }
} else {
LOGW("Could not register USB device disconnection callback");
} }
} }

View File

@@ -22,6 +22,7 @@ struct sc_usb {
sc_thread libusb_event_thread; sc_thread libusb_event_thread;
atomic_bool stopped; // only used if cbs != NULL atomic_bool stopped; // only used if cbs != NULL
atomic_flag disconnection_notified;
}; };
struct sc_usb_callbacks { struct sc_usb_callbacks {
@@ -73,6 +74,11 @@ sc_usb_connect(struct sc_usb *usb, libusb_device *device,
void void
sc_usb_disconnect(struct sc_usb *usb); sc_usb_disconnect(struct sc_usb *usb);
// A client should call this function with the return value of a libusb call
// to detect disconnection immediately
bool
sc_usb_check_disconnected(struct sc_usb *usb, int result);
void void
sc_usb_stop(struct sc_usb *usb); sc_usb_stop(struct sc_usb *usb);

View File

@@ -3,27 +3,33 @@
ssize_t ssize_t
sc_pipe_read_intr(struct sc_intr *intr, sc_pid pid, sc_pipe pipe, char *data, sc_pipe_read_intr(struct sc_intr *intr, sc_pid pid, sc_pipe pipe, char *data,
size_t len) { size_t len) {
if (!sc_intr_set_process(intr, pid)) { if (intr && !sc_intr_set_process(intr, pid)) {
// Already interrupted // Already interrupted
return false; return false;
} }
ssize_t ret = sc_pipe_read(pipe, data, len); ssize_t ret = sc_pipe_read(pipe, data, len);
sc_intr_set_process(intr, SC_PROCESS_NONE); if (intr) {
sc_intr_set_process(intr, SC_PROCESS_NONE);
}
return ret; return ret;
} }
ssize_t ssize_t
sc_pipe_read_all_intr(struct sc_intr *intr, sc_pid pid, sc_pipe pipe, sc_pipe_read_all_intr(struct sc_intr *intr, sc_pid pid, sc_pipe pipe,
char *data, size_t len) { char *data, size_t len) {
if (!sc_intr_set_process(intr, pid)) { if (intr && !sc_intr_set_process(intr, pid)) {
// Already interrupted // Already interrupted
return false; return false;
} }
ssize_t ret = sc_pipe_read_all(pipe, data, len); ssize_t ret = sc_pipe_read_all(pipe, data, len);
sc_intr_set_process(intr, SC_PROCESS_NONE); if (intr) {
sc_intr_set_process(intr, SC_PROCESS_NONE);
}
return ret; return ret;
} }

View File

@@ -136,7 +136,9 @@ sc_cond_timedwait(sc_cond *cond, sc_mutex *mutex, sc_tick deadline) {
return false; // timeout return false; // timeout
} }
uint32_t ms = SC_TICK_TO_MS(deadline - now); // Round up to the next millisecond to guarantee that the deadline is
// reached when returning due to timeout
uint32_t ms = SC_TICK_TO_MS(deadline - now + SC_TICK_FROM_MS(1) - 1);
int r = SDL_CondWaitTimeout(cond->cond, mutex->mutex, ms); int r = SDL_CondWaitTimeout(cond->cond, mutex->mutex, ms);
#ifndef NDEBUG #ifndef NDEBUG
if (r < 0) { if (r < 0) {
@@ -148,6 +150,8 @@ sc_cond_timedwait(sc_cond *cond, sc_mutex *mutex, sc_tick deadline) {
memory_order_relaxed); memory_order_relaxed);
#endif #endif
assert(r == 0 || r == SDL_MUTEX_TIMEDOUT); assert(r == 0 || r == SDL_MUTEX_TIMEDOUT);
// The deadline is reached on timeout
assert(r != SDL_MUTEX_TIMEDOUT || sc_tick_now() >= deadline);
return r == 0; return r == 0;
} }

View File

@@ -91,7 +91,6 @@ static void test_adb_devices_daemon_start_mixed() {
struct sc_adb_device *device = &devices[0]; struct sc_adb_device *device = &devices[0];
assert(!strcmp("0123456789abcdef", device->serial)); assert(!strcmp("0123456789abcdef", device->serial));
assert(!strcmp("unauthorized", device->state)); assert(!strcmp("unauthorized", device->state));
fprintf(stderr, "==== [%s]\n", device->model);
assert(!device->model); assert(!device->model);
device = &devices[1]; device = &devices[1];

View File

@@ -21,3 +21,5 @@ ffmpeg_avformat = 'avformat-58'
ffmpeg_avutil = 'avutil-56' ffmpeg_avutil = 'avutil-56'
prebuilt_ffmpeg = 'ffmpeg-win32-4.3.1' prebuilt_ffmpeg = 'ffmpeg-win32-4.3.1'
prebuilt_sdl2 = 'SDL2-2.0.20/i686-w64-mingw32' prebuilt_sdl2 = 'SDL2-2.0.20/i686-w64-mingw32'
prebuilt_libusb_root = 'libusb-1.0.25'
prebuilt_libusb = prebuilt_libusb_root + '/MinGW32'

View File

@@ -21,3 +21,5 @@ ffmpeg_avformat = 'avformat-59'
ffmpeg_avutil = 'avutil-57' ffmpeg_avutil = 'avutil-57'
prebuilt_ffmpeg = 'ffmpeg-win64-5.0' prebuilt_ffmpeg = 'ffmpeg-win64-5.0'
prebuilt_sdl2 = 'SDL2-2.0.20/x86_64-w64-mingw32' prebuilt_sdl2 = 'SDL2-2.0.20/x86_64-w64-mingw32'
prebuilt_libusb_root = 'libusb-1.0.25'
prebuilt_libusb = prebuilt_libusb_root + '/MinGW64'

View File

@@ -66,17 +66,21 @@ prepare-deps-win32:
@app/prebuilt-deps/prepare-adb.sh @app/prebuilt-deps/prepare-adb.sh
@app/prebuilt-deps/prepare-sdl.sh @app/prebuilt-deps/prepare-sdl.sh
@app/prebuilt-deps/prepare-ffmpeg-win32.sh @app/prebuilt-deps/prepare-ffmpeg-win32.sh
@app/prebuilt-deps/prepare-libusb.sh
prepare-deps-win64: prepare-deps-win64:
@app/prebuilt-deps/prepare-adb.sh @app/prebuilt-deps/prepare-adb.sh
@app/prebuilt-deps/prepare-sdl.sh @app/prebuilt-deps/prepare-sdl.sh
@app/prebuilt-deps/prepare-ffmpeg-win64.sh @app/prebuilt-deps/prepare-ffmpeg-win64.sh
@app/prebuilt-deps/prepare-libusb.sh
build-win32: prepare-deps-win32 build-win32: prepare-deps-win32
# -Dusb=false because of libusb-win32 build issue, cf #3011
[ -d "$(WIN32_BUILD_DIR)" ] || ( mkdir "$(WIN32_BUILD_DIR)" && \ [ -d "$(WIN32_BUILD_DIR)" ] || ( mkdir "$(WIN32_BUILD_DIR)" && \
meson "$(WIN32_BUILD_DIR)" \ meson "$(WIN32_BUILD_DIR)" \
--cross-file cross_win32.txt \ --cross-file cross_win32.txt \
--buildtype release --strip -Db_lto=true \ --buildtype release --strip -Db_lto=true \
-Dusb=false \
-Dcompile_server=false \ -Dcompile_server=false \
-Dportable=true ) -Dportable=true )
ninja -C "$(WIN32_BUILD_DIR)" ninja -C "$(WIN32_BUILD_DIR)"
@@ -107,6 +111,7 @@ dist-win32: build-server build-win32
cp app/prebuilt-deps/data/platform-tools-31.0.3/AdbWinApi.dll "$(DIST)/$(WIN32_TARGET_DIR)/" cp app/prebuilt-deps/data/platform-tools-31.0.3/AdbWinApi.dll "$(DIST)/$(WIN32_TARGET_DIR)/"
cp app/prebuilt-deps/data/platform-tools-31.0.3/AdbWinUsbApi.dll "$(DIST)/$(WIN32_TARGET_DIR)/" cp app/prebuilt-deps/data/platform-tools-31.0.3/AdbWinUsbApi.dll "$(DIST)/$(WIN32_TARGET_DIR)/"
cp app/prebuilt-deps/data/SDL2-2.0.20/i686-w64-mingw32/bin/SDL2.dll "$(DIST)/$(WIN32_TARGET_DIR)/" cp app/prebuilt-deps/data/SDL2-2.0.20/i686-w64-mingw32/bin/SDL2.dll "$(DIST)/$(WIN32_TARGET_DIR)/"
#cp app/prebuilt-deps/data/libusb-1.0.25/MinGW32/dll/libusb-1.0.dll "$(DIST)/$(WIN32_TARGET_DIR)/"
dist-win64: build-server build-win64 dist-win64: build-server build-win64
mkdir -p "$(DIST)/$(WIN64_TARGET_DIR)" mkdir -p "$(DIST)/$(WIN64_TARGET_DIR)"
@@ -125,6 +130,7 @@ dist-win64: build-server build-win64
cp app/prebuilt-deps/data/platform-tools-31.0.3/AdbWinApi.dll "$(DIST)/$(WIN64_TARGET_DIR)/" cp app/prebuilt-deps/data/platform-tools-31.0.3/AdbWinApi.dll "$(DIST)/$(WIN64_TARGET_DIR)/"
cp app/prebuilt-deps/data/platform-tools-31.0.3/AdbWinUsbApi.dll "$(DIST)/$(WIN64_TARGET_DIR)/" cp app/prebuilt-deps/data/platform-tools-31.0.3/AdbWinUsbApi.dll "$(DIST)/$(WIN64_TARGET_DIR)/"
cp app/prebuilt-deps/data/SDL2-2.0.20/x86_64-w64-mingw32/bin/SDL2.dll "$(DIST)/$(WIN64_TARGET_DIR)/" cp app/prebuilt-deps/data/SDL2-2.0.20/x86_64-w64-mingw32/bin/SDL2.dll "$(DIST)/$(WIN64_TARGET_DIR)/"
cp app/prebuilt-deps/data/libusb-1.0.25/MinGW64/dll/libusb-1.0.dll "$(DIST)/$(WIN64_TARGET_DIR)/"
zip-win32: dist-win32 zip-win32: dist-win32
cd "$(DIST)/$(WIN32_TARGET_DIR)"; \ cd "$(DIST)/$(WIN32_TARGET_DIR)"; \