2022-01-22 11:09:41 +01:00
|
|
|
#include "scrcpy_otg.h"
|
|
|
|
|
2024-12-20 20:58:41 +01:00
|
|
|
#include <assert.h>
|
|
|
|
#include <stdbool.h>
|
|
|
|
#include <stdlib.h>
|
2025-07-11 09:42:03 +02:00
|
|
|
#include <SDL3/SDL.h>
|
2022-01-22 11:09:41 +01:00
|
|
|
|
2024-12-20 20:58:41 +01:00
|
|
|
#ifdef _WIN32
|
|
|
|
# include "adb/adb.h"
|
|
|
|
#endif
|
2022-01-22 11:09:41 +01:00
|
|
|
#include "events.h"
|
2024-12-20 20:58:41 +01:00
|
|
|
#include "usb/screen_otg.h"
|
|
|
|
#include "usb/aoa_hid.h"
|
|
|
|
#include "usb/gamepad_aoa.h"
|
|
|
|
#include "usb/keyboard_aoa.h"
|
|
|
|
#include "usb/mouse_aoa.h"
|
2022-01-22 11:09:41 +01:00
|
|
|
#include "util/log.h"
|
|
|
|
|
|
|
|
struct scrcpy_otg {
|
|
|
|
struct sc_usb usb;
|
|
|
|
struct sc_aoa aoa;
|
2024-01-25 22:57:02 +01:00
|
|
|
struct sc_keyboard_aoa keyboard;
|
2024-01-25 23:04:09 +01:00
|
|
|
struct sc_mouse_aoa mouse;
|
2024-09-06 23:08:08 +02:00
|
|
|
struct sc_gamepad_aoa gamepad;
|
2022-01-22 11:09:41 +01:00
|
|
|
|
|
|
|
struct sc_screen_otg screen_otg;
|
|
|
|
};
|
|
|
|
|
|
|
|
static void
|
|
|
|
sc_usb_on_disconnected(struct sc_usb *usb, void *userdata) {
|
|
|
|
(void) usb;
|
|
|
|
(void) userdata;
|
|
|
|
|
2024-09-06 23:08:08 +02:00
|
|
|
sc_push_event(SC_EVENT_USB_DEVICE_DISCONNECTED);
|
2022-01-22 11:09:41 +01:00
|
|
|
}
|
|
|
|
|
2022-03-05 15:47:58 +01:00
|
|
|
static enum scrcpy_exit_code
|
2022-01-22 11:09:41 +01:00
|
|
|
event_loop(struct scrcpy_otg *s) {
|
|
|
|
SDL_Event event;
|
|
|
|
while (SDL_WaitEvent(&event)) {
|
|
|
|
switch (event.type) {
|
2023-02-10 18:46:12 +01:00
|
|
|
case SC_EVENT_USB_DEVICE_DISCONNECTED:
|
2022-01-22 11:09:41 +01:00
|
|
|
LOGW("Device disconnected");
|
2022-03-05 15:47:58 +01:00
|
|
|
return SCRCPY_EXIT_DISCONNECTED;
|
2024-09-06 23:08:08 +02:00
|
|
|
case SC_EVENT_AOA_OPEN_ERROR:
|
|
|
|
LOGE("AOA open error");
|
|
|
|
return SCRCPY_EXIT_FAILURE;
|
2025-07-11 09:42:03 +02:00
|
|
|
case SDL_EVENT_QUIT:
|
2022-01-22 11:09:41 +01:00
|
|
|
LOGD("User requested to quit");
|
2022-03-05 15:47:58 +01:00
|
|
|
return SCRCPY_EXIT_SUCCESS;
|
2022-01-22 11:09:41 +01:00
|
|
|
default:
|
|
|
|
sc_screen_otg_handle_event(&s->screen_otg, &event);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
2022-03-05 15:47:58 +01:00
|
|
|
return SCRCPY_EXIT_FAILURE;
|
2022-01-22 11:09:41 +01:00
|
|
|
}
|
|
|
|
|
2022-03-05 15:47:58 +01:00
|
|
|
enum scrcpy_exit_code
|
2022-01-22 11:09:41 +01:00
|
|
|
scrcpy_otg(struct scrcpy_options *options) {
|
|
|
|
static struct scrcpy_otg scrcpy_otg;
|
|
|
|
struct scrcpy_otg *s = &scrcpy_otg;
|
|
|
|
|
|
|
|
const char *serial = options->serial;
|
|
|
|
|
2024-09-10 09:16:05 +02:00
|
|
|
if (!SDL_SetHint(SDL_HINT_JOYSTICK_ALLOW_BACKGROUND_EVENTS, "1")) {
|
|
|
|
LOGW("Could not allow joystick background events");
|
|
|
|
}
|
|
|
|
|
2022-01-22 11:09:41 +01:00
|
|
|
// Minimal SDL initialization
|
2025-07-11 09:42:03 +02:00
|
|
|
if (!SDL_Init(SDL_INIT_EVENTS)) {
|
2022-02-05 14:06:03 +01:00
|
|
|
LOGE("Could not initialize SDL: %s", SDL_GetError());
|
2024-01-15 22:01:19 +01:00
|
|
|
return SCRCPY_EXIT_FAILURE;
|
2022-01-22 11:09:41 +01:00
|
|
|
}
|
|
|
|
|
2024-09-06 23:08:08 +02:00
|
|
|
if (options->gamepad_input_mode != SC_GAMEPAD_INPUT_MODE_DISABLED) {
|
2025-07-11 09:42:03 +02:00
|
|
|
if (!SDL_Init(SDL_INIT_GAMEPAD)) {
|
2024-09-06 23:08:08 +02:00
|
|
|
LOGE("Could not initialize SDL controller: %s", SDL_GetError());
|
|
|
|
// Not fatal, keyboard/mouse should still work
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-01-22 11:09:41 +01:00
|
|
|
atexit(SDL_Quit);
|
|
|
|
|
2022-01-27 23:32:37 +01:00
|
|
|
if (!SDL_SetHint(SDL_HINT_MOUSE_FOCUS_CLICKTHROUGH, "1")) {
|
|
|
|
LOGW("Could not enable mouse focus clickthrough");
|
|
|
|
}
|
|
|
|
|
2022-03-05 15:47:58 +01:00
|
|
|
enum scrcpy_exit_code ret = SCRCPY_EXIT_FAILURE;
|
2022-01-22 11:09:41 +01:00
|
|
|
|
2024-01-25 22:57:02 +01:00
|
|
|
struct sc_keyboard_aoa *keyboard = NULL;
|
2024-01-25 23:04:09 +01:00
|
|
|
struct sc_mouse_aoa *mouse = NULL;
|
2024-09-06 23:08:08 +02:00
|
|
|
struct sc_gamepad_aoa *gamepad = NULL;
|
2022-01-22 11:09:41 +01:00
|
|
|
bool usb_device_initialized = false;
|
|
|
|
bool usb_connected = false;
|
|
|
|
bool aoa_started = false;
|
|
|
|
bool aoa_initialized = false;
|
|
|
|
|
2022-02-10 08:42:15 +01:00
|
|
|
#ifdef _WIN32
|
|
|
|
// On Windows, only one process could open a USB device
|
|
|
|
// <https://github.com/Genymobile/scrcpy/issues/2773>
|
2023-06-05 19:45:20 +02:00
|
|
|
LOGI("Killing adb server (if any)...");
|
2024-12-01 15:47:40 +01:00
|
|
|
if (sc_adb_init()) {
|
|
|
|
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);
|
|
|
|
sc_adb_destroy();
|
|
|
|
} else {
|
|
|
|
LOGW("Could not call adb executable, adb server not killed");
|
|
|
|
}
|
2022-02-10 08:42:15 +01:00
|
|
|
#endif
|
|
|
|
|
2022-01-22 11:09:41 +01:00
|
|
|
static const struct sc_usb_callbacks cbs = {
|
|
|
|
.on_disconnected = sc_usb_on_disconnected,
|
|
|
|
};
|
|
|
|
bool ok = sc_usb_init(&s->usb);
|
|
|
|
if (!ok) {
|
2022-03-05 15:47:58 +01:00
|
|
|
return SCRCPY_EXIT_FAILURE;
|
2022-01-22 11:09:41 +01:00
|
|
|
}
|
|
|
|
|
2022-02-05 19:12:59 +01:00
|
|
|
struct sc_usb_device usb_device;
|
|
|
|
ok = sc_usb_select_device(&s->usb, serial, &usb_device);
|
|
|
|
if (!ok) {
|
2022-01-22 11:09:41 +01:00
|
|
|
goto end;
|
|
|
|
}
|
|
|
|
|
2022-02-10 08:46:14 +01:00
|
|
|
usb_device_initialized = true;
|
|
|
|
|
2022-02-05 19:12:59 +01:00
|
|
|
ok = sc_usb_connect(&s->usb, usb_device.device, &cbs, NULL);
|
2022-01-22 11:09:41 +01:00
|
|
|
if (!ok) {
|
|
|
|
goto end;
|
|
|
|
}
|
|
|
|
usb_connected = true;
|
|
|
|
|
|
|
|
ok = sc_aoa_init(&s->aoa, &s->usb, NULL);
|
|
|
|
if (!ok) {
|
|
|
|
goto end;
|
|
|
|
}
|
|
|
|
aoa_initialized = true;
|
|
|
|
|
2023-11-28 17:17:35 +08:00
|
|
|
assert(options->keyboard_input_mode == SC_KEYBOARD_INPUT_MODE_AOA
|
|
|
|
|| options->keyboard_input_mode == SC_KEYBOARD_INPUT_MODE_DISABLED);
|
|
|
|
assert(options->mouse_input_mode == SC_MOUSE_INPUT_MODE_AOA
|
|
|
|
|| options->mouse_input_mode == SC_MOUSE_INPUT_MODE_DISABLED);
|
2024-09-06 23:08:08 +02:00
|
|
|
assert(options->gamepad_input_mode == SC_GAMEPAD_INPUT_MODE_AOA
|
|
|
|
|| options->gamepad_input_mode == SC_GAMEPAD_INPUT_MODE_DISABLED);
|
2023-11-28 17:17:35 +08:00
|
|
|
|
2022-01-26 22:50:10 +01:00
|
|
|
bool enable_keyboard =
|
2023-11-28 17:17:35 +08:00
|
|
|
options->keyboard_input_mode == SC_KEYBOARD_INPUT_MODE_AOA;
|
2022-01-26 22:50:10 +01:00
|
|
|
bool enable_mouse =
|
2023-11-28 17:17:35 +08:00
|
|
|
options->mouse_input_mode == SC_MOUSE_INPUT_MODE_AOA;
|
2024-09-06 23:08:08 +02:00
|
|
|
bool enable_gamepad =
|
|
|
|
options->gamepad_input_mode == SC_GAMEPAD_INPUT_MODE_AOA;
|
2022-01-22 11:09:41 +01:00
|
|
|
|
2022-01-26 22:50:10 +01:00
|
|
|
if (enable_keyboard) {
|
2024-01-25 22:57:02 +01:00
|
|
|
ok = sc_keyboard_aoa_init(&s->keyboard, &s->aoa);
|
2022-01-26 22:50:10 +01:00
|
|
|
if (!ok) {
|
|
|
|
goto end;
|
|
|
|
}
|
|
|
|
keyboard = &s->keyboard;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (enable_mouse) {
|
2024-01-25 23:04:09 +01:00
|
|
|
ok = sc_mouse_aoa_init(&s->mouse, &s->aoa);
|
2022-01-26 22:50:10 +01:00
|
|
|
if (!ok) {
|
|
|
|
goto end;
|
|
|
|
}
|
|
|
|
mouse = &s->mouse;
|
2022-01-22 11:09:41 +01:00
|
|
|
}
|
|
|
|
|
2024-09-06 23:08:08 +02:00
|
|
|
if (enable_gamepad) {
|
|
|
|
sc_gamepad_aoa_init(&s->gamepad, &s->aoa);
|
|
|
|
gamepad = &s->gamepad;
|
|
|
|
}
|
|
|
|
|
2022-01-22 11:09:41 +01:00
|
|
|
ok = sc_aoa_start(&s->aoa);
|
|
|
|
if (!ok) {
|
|
|
|
goto end;
|
|
|
|
}
|
|
|
|
aoa_started = true;
|
|
|
|
|
|
|
|
const char *window_title = options->window_title;
|
|
|
|
if (!window_title) {
|
2022-02-05 19:12:59 +01:00
|
|
|
window_title = usb_device.product ? usb_device.product : "scrcpy";
|
2022-01-22 11:09:41 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
struct sc_screen_otg_params params = {
|
|
|
|
.keyboard = keyboard,
|
|
|
|
.mouse = mouse,
|
2024-09-06 23:08:08 +02:00
|
|
|
.gamepad = gamepad,
|
2022-01-22 11:09:41 +01:00
|
|
|
.window_title = window_title,
|
|
|
|
.always_on_top = options->always_on_top,
|
|
|
|
.window_x = options->window_x,
|
|
|
|
.window_y = options->window_y,
|
2022-04-25 18:44:42 +02:00
|
|
|
.window_width = options->window_width,
|
|
|
|
.window_height = options->window_height,
|
2022-01-22 11:09:41 +01:00
|
|
|
.window_borderless = options->window_borderless,
|
2024-09-27 18:32:39 +02:00
|
|
|
.shortcut_mods = options->shortcut_mods,
|
2022-01-22 11:09:41 +01:00
|
|
|
};
|
|
|
|
|
|
|
|
ok = sc_screen_otg_init(&s->screen_otg, ¶ms);
|
|
|
|
if (!ok) {
|
|
|
|
goto end;
|
|
|
|
}
|
|
|
|
|
|
|
|
// usb_device not needed anymore
|
2022-02-05 19:12:59 +01:00
|
|
|
sc_usb_device_destroy(&usb_device);
|
2022-01-22 11:09:41 +01:00
|
|
|
usb_device_initialized = false;
|
|
|
|
|
|
|
|
ret = event_loop(s);
|
|
|
|
LOGD("quit...");
|
|
|
|
|
|
|
|
end:
|
|
|
|
if (aoa_started) {
|
|
|
|
sc_aoa_stop(&s->aoa);
|
|
|
|
}
|
|
|
|
sc_usb_stop(&s->usb);
|
|
|
|
|
|
|
|
if (mouse) {
|
2024-01-25 23:04:09 +01:00
|
|
|
sc_mouse_aoa_destroy(&s->mouse);
|
2022-01-22 11:09:41 +01:00
|
|
|
}
|
|
|
|
if (keyboard) {
|
2024-01-25 22:57:02 +01:00
|
|
|
sc_keyboard_aoa_destroy(&s->keyboard);
|
2022-01-22 11:09:41 +01:00
|
|
|
}
|
2024-09-06 23:08:08 +02:00
|
|
|
if (gamepad) {
|
|
|
|
sc_gamepad_aoa_destroy(&s->gamepad);
|
|
|
|
}
|
2022-01-22 11:09:41 +01:00
|
|
|
|
|
|
|
if (aoa_initialized) {
|
|
|
|
sc_aoa_join(&s->aoa);
|
|
|
|
sc_aoa_destroy(&s->aoa);
|
|
|
|
}
|
|
|
|
|
|
|
|
sc_usb_join(&s->usb);
|
|
|
|
|
|
|
|
if (usb_connected) {
|
|
|
|
sc_usb_disconnect(&s->usb);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (usb_device_initialized) {
|
2022-02-05 19:12:59 +01:00
|
|
|
sc_usb_device_destroy(&usb_device);
|
2022-01-22 11:09:41 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
sc_usb_destroy(&s->usb);
|
|
|
|
|
|
|
|
return ret;
|
|
|
|
}
|