Skip to content

Commit

Permalink
Add OTG mode
Browse files Browse the repository at this point in the history
Add an option --otg to run scrcpy with only physical keyboard and mouse
simulation (HID over AOA), without mirroring and without requiring adb.

To avoid adding complexity into the scrcpy initialization and screen
implementation, OTG mode is implemented totally separately, with a
separate window.

PR #2974 <#2974>
  • Loading branch information
rom1v committed Jan 27, 2022
1 parent 36aaf70 commit 91418c7
Show file tree
Hide file tree
Showing 11 changed files with 615 additions and 2 deletions.
2 changes: 2 additions & 0 deletions app/meson.build
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,8 @@ if usb_support
'src/usb/aoa_hid.c',
'src/usb/hid_keyboard.c',
'src/usb/hid_mouse.c',
'src/usb/scrcpy_otg.c',
'src/usb/screen_otg.c',
'src/usb/usb.c',
]
endif
Expand Down
12 changes: 12 additions & 0 deletions app/scrcpy.1
Original file line number Diff line number Diff line change
Expand Up @@ -162,6 +162,18 @@ Do not forward repeated key events when a key is held down.
.B \-\-no\-mipmaps
If the renderer is OpenGL 3.0+ or OpenGL ES 2.0+, then mipmaps are automatically generated to improve downscaling quality. This option disables the generation of mipmaps.

.TP
.B \-\-otg
Run in OTG mode: simulate physical keyboard and mouse, as if the computer keyboard and mouse were plugged directly to the device via an OTG cable.

In this mode, adb (USB debugging) is not necessary, and mirroring is disabled.

LAlt, LSuper or RSuper toggle the mouse 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.

See \fB\-\-hid\-keyboard\fR and \fB\-\-hid\-mouse\fR.

.TP
.BI "\-p, \-\-port " port[:port]
Set the TCP port (range) used by the client to listen.
Expand Down
61 changes: 61 additions & 0 deletions app/src/cli.c
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@
#define OPT_TCPIP 1033
#define OPT_RAW_KEY_EVENTS 1034
#define OPT_NO_DOWNSIZE_ON_ERROR 1035
#define OPT_OTG 1036

struct sc_option {
char shortopt;
Expand Down Expand Up @@ -276,6 +277,20 @@ static const struct sc_option options[] = {
"mipmaps are automatically generated to improve downscaling "
"quality. This option disables the generation of mipmaps.",
},
{
.longopt_id = OPT_OTG,
.longopt = "otg",
.text = "Run in OTG mode: simulate physical keyboard and mouse, "
"as if the computer keyboard and mouse were plugged directly "
"to the device via an OTG cable.\n"
"In this mode, adb (USB debugging) is not necessary, and "
"mirroring is disabled.\n"
"LAlt, LSuper or RSuper toggle the mouse capture mode, to give "
"control of the mouse back to the computer.\n"
"It may only work over USB, and is currently only supported "
"on Linux.\n"
"See --hid-keyboard and --hid-mouse.",
},
{
.shortopt = 'p',
.longopt = "port",
Expand Down Expand Up @@ -1500,6 +1515,15 @@ parse_args_with_getopt(struct scrcpy_cli_args *args, int argc, char *argv[],
case OPT_NO_DOWNSIZE_ON_ERROR:
opts->downsize_on_error = false;
break;
case OPT_OTG:
#ifdef HAVE_USB
opts->otg = true;
break;
#else
LOGE("OTG mode (--otg) is not supported on this platform. It "
"is only available on Linux.");
return false;
#endif
case OPT_V4L2_SINK:
#ifdef HAVE_V4L2
opts->v4l2_device = optarg;
Expand Down Expand Up @@ -1610,6 +1634,43 @@ parse_args_with_getopt(struct scrcpy_cli_args *args, int argc, char *argv[],
}
}

#ifdef HAVE_USB
if (opts->otg) {
// OTG mode is compatible with only very few options.
// Only report obvious errors.
if (opts->record_filename) {
LOGE("OTG mode: could not record");
return false;
}
if (opts->turn_screen_off) {
LOGE("OTG mode: could not turn screen off");
return false;
}
if (opts->stay_awake) {
LOGE("OTG mode: could not stay awake");
return false;
}
if (opts->show_touches) {
LOGE("OTG mode: could not request to show touches");
return false;
}
if (opts->power_off_on_close) {
LOGE("OTG mode: could not request power off on close");
return false;
}
if (opts->display_id) {
LOGE("OTG mode: could not select display");
return false;
}
#ifdef HAVE_V4L2
if (opts->v4l2_device) {
LOGE("OTG mode: could not sink to V4L2 device");
return false;
}
#endif
}
#endif

return true;
}

Expand Down
1 change: 1 addition & 0 deletions app/src/events.h
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,4 @@
#define EVENT_STREAM_STOPPED (SDL_USEREVENT + 1)
#define EVENT_SERVER_CONNECTION_FAILED (SDL_USEREVENT + 2)
#define EVENT_SERVER_CONNECTED (SDL_USEREVENT + 3)
#define EVENT_USB_DEVICE_DISCONNECTED (SDL_USEREVENT + 4)
10 changes: 8 additions & 2 deletions app/src/main.c
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
#include "cli.h"
#include "options.h"
#include "scrcpy.h"
#include "usb/scrcpy_otg.h"
#include "util/log.h"

static void
Expand Down Expand Up @@ -88,9 +89,14 @@ main(int argc, char *argv[]) {
return 1;
}

int res = scrcpy(&args.opts) ? 0 : 1;
#ifdef HAVE_USB
bool ok = args.opts.otg ? scrcpy_otg(&args.opts)
: scrcpy(&args.opts);
#else
bool ok = scrcpy(&args.opts);
#endif

avformat_network_deinit(); // ignore failure

return res;
return ok ? 0 : 1;
}
3 changes: 3 additions & 0 deletions app/src/options.c
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,9 @@ const struct scrcpy_options scrcpy_options_default = {
.display_id = 0,
.display_buffer = 0,
.v4l2_buffer = 0,
#ifdef HAVE_USB
.otg = false,
#endif
.show_touches = false,
.fullscreen = false,
.always_on_top = false,
Expand Down
3 changes: 3 additions & 0 deletions app/src/options.h
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,9 @@ struct scrcpy_options {
uint32_t display_id;
sc_tick display_buffer;
sc_tick v4l2_buffer;
#ifdef HAVE_USB
bool otg;
#endif
bool show_touches;
bool fullscreen;
bool always_on_top;
Expand Down
213 changes: 213 additions & 0 deletions app/src/usb/scrcpy_otg.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,213 @@
#include "scrcpy_otg.h"

#include <SDL2/SDL.h>

#include "events.h"
#include "screen_otg.h"
#include "util/log.h"

struct scrcpy_otg {
struct sc_usb usb;
struct sc_aoa aoa;
struct sc_hid_keyboard keyboard;
struct sc_hid_mouse mouse;

struct sc_screen_otg screen_otg;
};

static void
sc_usb_on_disconnected(struct sc_usb *usb, void *userdata) {
(void) usb;
(void) userdata;

SDL_Event event;
event.type = EVENT_USB_DEVICE_DISCONNECTED;
int ret = SDL_PushEvent(&event);
if (ret < 0) {
LOGE("Could not post USB disconnection event: %s", SDL_GetError());
}
}

static bool
event_loop(struct scrcpy_otg *s) {
SDL_Event event;
while (SDL_WaitEvent(&event)) {
switch (event.type) {
case EVENT_USB_DEVICE_DISCONNECTED:
LOGW("Device disconnected");
return false;
case SDL_QUIT:
LOGD("User requested to quit");
return true;
default:
sc_screen_otg_handle_event(&s->screen_otg, &event);
break;
}
}
return false;
}

bool
scrcpy_otg(struct scrcpy_options *options) {
static struct scrcpy_otg scrcpy_otg;
struct scrcpy_otg *s = &scrcpy_otg;

const char *serial = options->serial;

// Minimal SDL initialization
if (SDL_Init(SDL_INIT_EVENTS)) {
LOGC("Could not initialize SDL: %s", SDL_GetError());
return false;
}

atexit(SDL_Quit);

bool ret = false;

struct sc_hid_keyboard *keyboard = NULL;
struct sc_hid_mouse *mouse = NULL;
bool usb_device_initialized = false;
bool usb_connected = false;
bool aoa_started = false;
bool aoa_initialized = false;

static const struct sc_usb_callbacks cbs = {
.on_disconnected = sc_usb_on_disconnected,
};
bool ok = sc_usb_init(&s->usb);
if (!ok) {
return false;
}

struct sc_usb_device usb_devices[16];
ssize_t count = sc_usb_find_devices(&s->usb, serial, usb_devices,
ARRAY_LEN(usb_devices));
if (count < 0) {
LOGE("Could not list USB devices");
goto end;
}

if (count == 0) {
if (serial) {
LOGE("Could not find USB device %s", serial);
} else {
LOGE("Could not find any USB device");
}
goto end;
}

if (count > 1) {
if (serial) {
LOGE("Multiple (%d) USB devices with serial %s:", (int) count,
serial);
} else {
LOGE("Multiple (%d) USB devices:", (int) count);
}
for (size_t i = 0; i < (size_t) count; ++i) {
struct sc_usb_device *d = &usb_devices[i];
LOGE(" %-18s (%04" PRIx16 ":%04" PRIx16 ") %s %s",
d->serial, d->vid, d->pid, d->manufacturer, d->product);
}
if (!serial) {
LOGE("Specify the device via -s or --serial");
}
sc_usb_device_destroy_all(usb_devices, count);
goto end;
}
usb_device_initialized = true;

struct sc_usb_device *usb_device = &usb_devices[0];

LOGI("USB device: %s (%04" PRIx16 ":%04" PRIx16 ") %s %s",
usb_device->serial, usb_device->vid, usb_device->pid,
usb_device->manufacturer, usb_device->product);

ok = sc_usb_connect(&s->usb, usb_device->device, &cbs, NULL);
if (!ok) {
goto end;
}
usb_connected = true;

ok = sc_aoa_init(&s->aoa, &s->usb, NULL);
if (!ok) {
goto end;
}
aoa_initialized = true;

ok = sc_hid_keyboard_init(&s->keyboard, &s->aoa);
if (!ok) {
goto end;
}
keyboard = &s->keyboard;

ok = sc_hid_mouse_init(&s->mouse, &s->aoa);
if (!ok) {
goto end;
}
mouse = &s->mouse;

ok = sc_aoa_start(&s->aoa);
if (!ok) {
goto end;
}
aoa_started = true;

const char *window_title = options->window_title;
if (!window_title) {
window_title = usb_device->product ? usb_device->product : "scrcpy";
}

struct sc_screen_otg_params params = {
.keyboard = keyboard,
.mouse = mouse,
.window_title = window_title,
.always_on_top = options->always_on_top,
.window_x = options->window_x,
.window_y = options->window_y,
.window_borderless = options->window_borderless,
};

ok = sc_screen_otg_init(&s->screen_otg, &params);
if (!ok) {
goto end;
}

// usb_device not needed anymore
sc_usb_device_destroy(usb_device);
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) {
sc_hid_mouse_destroy(&s->mouse);
}
if (keyboard) {
sc_hid_keyboard_destroy(&s->keyboard);
}

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) {
sc_usb_device_destroy(usb_device);
}

sc_usb_destroy(&s->usb);

return ret;
}
12 changes: 12 additions & 0 deletions app/src/usb/scrcpy_otg.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
#ifndef SCRCPY_OTG_H
#define SCRCPY_OTG_H

#include "common.h"

#include <stdbool.h>
#include "options.h"

bool
scrcpy_otg(struct scrcpy_options *options);

#endif
Loading

0 comments on commit 91418c7

Please sign in to comment.