Compare commits

...

35 Commits

Author SHA1 Message Date
781bb7397b Copy server params
This is a preliminary step necessary to move the server to a separate
thread.
2021-10-27 23:40:52 +02:00
b876cf9ddc Reorder server and server_params
This will allow to define a server_params field in server.
2021-10-27 23:20:47 +02:00
34eb10ea0b Define v4l2 option field only if HAVE_V4L2 2021-10-27 18:43:47 +02:00
27fa23846d Define default options as const struct
This is more readable than a macro, and we could ifdef some fields.
2021-10-27 18:43:47 +02:00
e4d5c1ce36 Move scrcpy option structs to options.h
This will allow to define symbols in options.c without all the
dependencies of scrcpy.c.
2021-10-27 18:43:47 +02:00
ac23bec144 Expose socket interruption
On Linux, socket functions are unblocked by shutdown(), but on Windows
they are unblocked by closesocket().

Expose net_interrupt() and net_close() to abstract these differences:
 - net_interrupt() calls shutdown() on Linux and closesocket() on
   Windows (if not already called);
 - net_close() calls close() on Linux and closesocket() on Windows (if
   not already called).

This simplifies the server code, and prevents a data race on close
(reported by TSAN) on Linux (but does not fix it on Windows):

    WARNING: ThreadSanitizer: data race (pid=836124)
      Write of size 8 at 0x7ba0000000d0 by main thread:
        #0 close ../../../../src/libsanitizer/tsan/tsan_interceptors_posix.cpp:1690 (libtsan.so.0+0x359d8)
        #1 net_close ../app/src/util/net.c:211 (scrcpy+0x1c76b)
        #2 close_socket ../app/src/server.c:330 (scrcpy+0x19442)
        #3 server_stop ../app/src/server.c:522 (scrcpy+0x19e33)
        #4 scrcpy ../app/src/scrcpy.c:532 (scrcpy+0x156fc)
        #5 main ../app/src/main.c:92 (scrcpy+0x622a)

      Previous read of size 8 at 0x7ba0000000d0 by thread T6:
        #0 recv ../../../../src/libsanitizer/sanitizer_common/sanitizer_common_interceptors.inc:6603 (libtsan.so.0+0x4f4a6)
        #1 net_recv ../app/src/util/net.c:167 (scrcpy+0x1c5a7)
        #2 run_receiver ../app/src/receiver.c:76 (scrcpy+0x12819)
        #3 <null> <null> (libSDL2-2.0.so.0+0x84f40)
2021-10-27 18:41:58 +02:00
e5ea13770b Add socket wrapper
This paves the way to store an additional "closed" flag on Windows
to interrupt and close properly.
2021-10-26 22:49:57 +02:00
3eac212af1 Use net_send() from net_send_all()
This will make net_send_all() continue to work even if net_send()
behavior is changed.
2021-10-26 22:49:45 +02:00
3adff37c2d Use sc_ prefix for sockets
Rename:
 - socket_t to sc_socket
 - INVALID_SOCKET to SC_INVALID_SOCKET
2021-10-26 22:49:45 +02:00
eb6afe7669 Move net_init() and net_cleanup() upwards
These two functions are global, define them at the top of the
implementation file. This is consistent with the header file.
2021-10-26 22:49:45 +02:00
5222f213f4 Update FAQ to mention HID keyboard 2021-10-26 21:30:04 +02:00
e4163321f0 Delay HID events on Ctrl+v
When Ctrl+v is pressed, a control is sent to the device to set the
device clipboard before injecting Ctrl+v.

With the InputManager method, it is guaranteed that the device
synchronization is executed before handling Ctrl+v, since the commands
are executed on the device in sequence.

However, HID are injected from the computer, so there is no such
guarantee. As a consequence, on Android, Ctrl+v triggers a paste with
the old clipboard content.

To workaround the issue, wait a bit (2 milliseconds) from the AOA
thread before injecting the event, to leave enough time for the
clipboard to be set before injecting Ctrl+v.
2021-10-26 21:30:04 +02:00
c96874b257 Synchronize HID keyboard state on first event
When an AOA HID keyboard is registered, CAPSLOCK and NUMLOCK are both
disabled, regardless of the state of the computer keyboard.

To synchronize the state, on first key event, inject CAPSLOCK and/or
NUMLOCK if necessary.
2021-10-26 21:30:04 +02:00
511356710d Retrieve device serial for AOA
The serial is necessary to find the correct Android device for AOA.

If it is not explicitly provided by the user via -s, then execute "adb
getserialno" to retrieve it.
2021-10-26 21:30:04 +02:00
d55015e4cf Expose function to get the device serial
Expose adb_get_serialno() to retrieve the device serial via the command
"adb getserialno".
2021-10-26 21:30:04 +02:00
0681480809 Add read_pipe_all()
Add a convenience function to read from a pipe until all requested data
has been read.
2021-10-26 21:30:04 +02:00
96b18dabaa Expose adb execution with redirection
Expose the redirection feature to the adb API.
2021-10-26 21:30:04 +02:00
eaf4afaad9 Add command execution with redirection
Expose command execution with pipes to stdin, stdout and stderr.

This will allow to read the result of adb commands.
2021-10-26 21:30:04 +02:00
207082977a Add support for USB HID keyboard over AOAv2
This provides a better input experience, by simulating a physical
keyboard. It converts SDL keyboard events to proper HID events, and send
them over AOAv2.

This is a rewriting and bugfix of the origin code from @amosbird:
<https://github.com/Genymobile/scrcpy/issues/279#issuecomment-453819354>

The feature is enabled the command line option -K or --hid-keyboard,
and is only available on Linux, over USB.

Refs <https://source.android.com/devices/accessories/aoa2#hid-support>
Refs <https://www.usb.org/sites/default/files/hid1_11.pdf>

PR #2632 <https://github.com/Genymobile/scrcpy/pull/2632>

Signed-off-by: Romain Vimont <rom@rom1v.com>
2021-10-26 21:30:04 +02:00
f7d1efdf1d Extract mouse processor trait
Mainly for consistency with the keyboard processor trait.

This could allow to provide alternative mouse processors.
2021-10-26 21:30:04 +02:00
bcf5a9750f Extract keyboard processor trait
This will allow to provide alternative key processors.
2021-10-26 21:30:04 +02:00
056d36ce4a Fix trait header guards 2021-10-26 21:30:04 +02:00
bea3197c3f Remove unused markdown link in README 2021-10-25 18:22:48 +02:00
d6568f64af Mention SCRCPY_ICON_PATH envvar in README 2021-10-25 18:22:17 +02:00
580f5d8bb9 Add scrcpy icon to README 2021-10-25 18:08:37 +02:00
1e340caf76 Remove legacy scrcpy icon
Remove the old icon in XPM format and the code to load it.
2021-10-25 18:08:37 +02:00
3d5b31e0cb Add icon source in SVG format
Scrcpy only uses the PNG format (because SDL only supports bitmap
icons), but keep the SVG source in the repo.
2021-10-25 18:08:37 +02:00
6004f0b6b0 Use a new scrcpy icon
Use the new icon designed by @varlesh:
<https://github.com/Genymobile/scrcpy/pull/1987#issuecomment-949684080>

Load it from a PNG file (SDL only supports bitmap icons).
2021-10-25 18:08:37 +02:00
12ed2f2402 Add support for palette icon formats
To support more icon formats.
2021-10-25 18:08:37 +02:00
0e4564da03 Add icon loader
Add helper to load icons from image files via FFmpeg.
2021-10-25 18:08:37 +02:00
156d958e77 Move common instruction out of ifdef
Both ifdef-branches return server_path.
2021-10-25 18:08:31 +02:00
7229e3cce0 Extract util function to build a local file path
Finding a local file in the scrcpy directory may be useful for files
other than scrcpy-server in the future.
2021-10-25 16:29:43 +02:00
a7e41b0f85 Fix code style 2021-10-21 18:36:34 +02:00
46d3e35c30 Fix "Could not find v4l2 muxer"
The AVOutputFormat name is a comma-separated list. In theory, possible
names for V4L2 are:

    - "video4linux2,v4l2"
    - "v4l2,video4linux2"
    - "v4l2"
    - "video4linux2"

To find the muxer in all cases, we must request exactly one muxer name
at a time.

PR #2718 <https://github.com/Genymobile/scrcpy/pull/2718>

Co-authored-by: Romain Vimont <rom@rom1v.com>
Signed-off-by: Romain Vimont <rom@rom1v.com>
2021-10-21 16:26:48 +02:00
07d75eb336 Simplify net_send_all()
There is no need to declare the variable before the loop.
2021-10-17 16:21:37 +02:00
57 changed files with 2858 additions and 1011 deletions

View File

@ -14,7 +14,8 @@ First, you need to install the required packages:
# for Debian/Ubuntu
sudo apt install ffmpeg libsdl2-2.0-0 adb wget \
gcc git pkg-config meson ninja-build libsdl2-dev \
libavcodec-dev libavdevice-dev libavformat-dev libavutil-dev
libavcodec-dev libavdevice-dev libavformat-dev libavutil-dev \
libusb-1.0-0 libusb-dev
```
Then clone the repo and execute the installation script
@ -88,11 +89,12 @@ Install the required packages from your package manager.
```bash
# runtime dependencies
sudo apt install ffmpeg libsdl2-2.0-0 adb
sudo apt install ffmpeg libsdl2-2.0-0 adb libusb-1.0-0
# client build dependencies
sudo apt install gcc git pkg-config meson ninja-build libsdl2-dev \
libavcodec-dev libavdevice-dev libavformat-dev libavutil-dev
libavcodec-dev libavdevice-dev libavformat-dev libavutil-dev \
libusb-dev
# server build dependencies
sudo apt install openjdk-11-jdk
@ -114,7 +116,7 @@ pip3 install meson
sudo dnf install https://download1.rpmfusion.org/free/fedora/rpmfusion-free-release-$(rpm -E %fedora).noarch.rpm
# client build dependencies
sudo dnf install SDL2-devel ffms2-devel meson gcc make
sudo dnf install SDL2-devel ffms2-devel libusb-devel meson gcc make
# server build dependencies
sudo dnf install java-devel

10
FAQ.md
View File

@ -118,13 +118,17 @@ In developer options, enable:
### Special characters do not work
Injecting text input is [limited to ASCII characters][text-input]. A trick
allows to also inject some [accented characters][accented-characters], but
that's all. See [#37].
The default text injection method is [limited to ASCII characters][text-input].
A trick allows to also inject some [accented characters][accented-characters],
but that's all. See [#37].
Since scrcpy v1.20 on Linux, it is possible to simulate a [physical
keyboard][hid] (HID).
[text-input]: https://github.com/Genymobile/scrcpy/issues?q=is%3Aopen+is%3Aissue+label%3Aunicode
[accented-characters]: https://blog.rom1v.com/2018/03/introducing-scrcpy/#handle-accented-characters
[#37]: https://github.com/Genymobile/scrcpy/issues/37
[hid]: README.md#physical-keyboard-simulation-hid
## Client issues

View File

@ -1,5 +1,7 @@
# scrcpy (v1.19)
![icon](data/icon.png)
[Read in another language](#translations)
This application provides display and control of Android devices connected on
@ -671,6 +673,39 @@ content (if supported by the app) relative to the center of the screen.
Concretely, scrcpy generates additional touch events from a "virtual finger" at
a location inverted through the center of the screen.
#### Physical keyboard simulation (HID)
By default, scrcpy uses Android key or text injection: it works everywhere, but
is limited to ASCII.
On Linux, scrcpy can simulate a USB physical keyboard on Android to provide a
better input experience (using [USB HID over AOAv2][hid-aoav2]): the virtual
keyboard is disabled and it works for all characters and IME.
[hid-aoav2]: https://source.android.com/devices/accessories/aoa2#hid-support
However, it only works if the device is connected by USB, and is currently only
supported on Linux.
To enable this mode:
```bash
scrcpy --hid-keyboard
scrcpy -K # short version
```
If it fails for some reason (for example because the device is not connected via
USB), it automatically fallbacks to the default mode (with a log in the
console). This allows to use the same command line options when connected over
USB and TCP/IP.
In this mode, raw key events (scancodes) are sent to the device, independently
of the host key mapping. Therefore, if your keyboard layout does not match, it
must be configured on the Android device, in Settings → System → Languages and
input → [Physical keyboard].
[Physical keyboard]: https://github.com/Genymobile/scrcpy/pull/2632#issuecomment-923756915
#### Text injection preference
@ -690,6 +725,9 @@ scrcpy --prefer-text
(but this will break keyboard behavior in games)
This option has no effect on HID keyboard (all key events are sent as
scancodes in this mode).
[textevents]: https://blog.rom1v.com/2018/03/introducing-scrcpy/#handle-text-input
[prefertext]: https://github.com/Genymobile/scrcpy/issues/650#issuecomment-512945343
@ -705,6 +743,9 @@ To avoid forwarding repeated key events:
scrcpy --no-key-repeat
```
This option has no effect on HID keyboard (key repeat is handled by Android
directly in this mode).
#### Right-click and middle-click
@ -826,7 +867,7 @@ ADB=/path/to/adb scrcpy
To override the path of the `scrcpy-server` file, configure its path in
`SCRCPY_SERVER_PATH`.
[useful]: https://github.com/Genymobile/scrcpy/issues/278#issuecomment-429330345
To override the icon, configure its path in `SCRCPY_ICON_PATH`.
## Why _scrcpy_?

View File

@ -8,19 +8,21 @@ src = [
'src/controller.c',
'src/decoder.c',
'src/device_msg.c',
'src/event_converter.c',
'src/icon.c',
'src/file_handler.c',
'src/fps_counter.c',
'src/frame_buffer.c',
'src/input_manager.c',
'src/keyboard_inject.c',
'src/mouse_inject.c',
'src/opengl.c',
'src/options.c',
'src/receiver.c',
'src/recorder.c',
'src/scrcpy.c',
'src/screen.c',
'src/server.c',
'src/stream.c',
'src/tiny_xpm.c',
'src/video_buffer.c',
'src/util/log.c',
'src/util/net.c',
@ -41,6 +43,14 @@ if v4l2_support
src += [ 'src/v4l2_sink.c' ]
endif
aoa_hid_support = host_machine.system() == 'linux'
if aoa_hid_support
src += [
'src/aoa_hid.c',
'src/hid_keyboard.c',
]
endif
check_functions = [
'strdup'
]
@ -61,8 +71,11 @@ if not get_option('crossbuild_windows')
dependencies += dependency('libavdevice')
endif
else
if aoa_hid_support
dependencies += dependency('libusb-1.0')
endif
else
# cross-compile mingw32 build (from Linux to Windows)
prebuilt_sdl2 = meson.get_cross_property('prebuilt_sdl2')
sdl2_bin_dir = meson.current_source_dir() + '/../prebuilt-deps/' + prebuilt_sdl2 + '/bin'
@ -139,6 +152,9 @@ conf.set('SERVER_DEBUGGER_METHOD_NEW', get_option('server_debugger_method') == '
# enable V4L2 support (linux only)
conf.set('HAVE_V4L2', v4l2_support)
# enable HID over AOA support (linux only)
conf.set('HAVE_AOA_HID', aoa_hid_support)
configure_file(configuration: conf, output: 'config.h')
src_dir = include_directories('src')
@ -150,6 +166,9 @@ executable('scrcpy', src,
c_args: [])
install_man('scrcpy.1')
install_data('../data/icon.png',
rename: 'scrcpy.png',
install_dir: 'share/icons/hicolor/256x256/apps')
### TESTS
@ -166,6 +185,7 @@ if get_option('buildtype') == 'debug'
['test_cli', [
'tests/test_cli.c',
'src/cli.c',
'src/options.c',
'src/util/str_util.c',
]],
['test_clock', [

View File

@ -82,6 +82,14 @@ Start in fullscreen.
.B \-h, \-\-help
Print this help.
.TP
.B \-K, \-\-hid\-keyboard
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.
It may only work over USB, and is currently only supported on Linux.
.TP
.B \-\-legacy\-paste
Inject computer clipboard text as a sequence of key events on Ctrl+v (like MOD+Shift+v).

View File

@ -109,7 +109,9 @@ show_adb_err_msg(enum process_result err, const char *const argv[]) {
}
process_t
adb_execute(const char *serial, const char *const adb_cmd[], size_t len) {
adb_execute_redirect(const char *serial, const char *const adb_cmd[],
size_t len, pipe_t *pipe_stdin, pipe_t *pipe_stdout,
pipe_t *pipe_stderr) {
int i;
process_t process;
@ -129,7 +131,9 @@ adb_execute(const char *serial, const char *const adb_cmd[], size_t len) {
memcpy(&argv[i], adb_cmd, len * sizeof(const char *));
argv[len + i] = NULL;
enum process_result r = process_execute(argv, &process);
enum process_result r =
process_execute_redirect(argv, &process, pipe_stdin, pipe_stdout,
pipe_stderr);
if (r != PROCESS_SUCCESS) {
show_adb_err_msg(r, argv);
process = PROCESS_NONE;
@ -139,6 +143,11 @@ adb_execute(const char *serial, const char *const adb_cmd[], size_t len) {
return process;
}
process_t
adb_execute(const char *serial, const char *const adb_cmd[], size_t len) {
return adb_execute_redirect(serial, adb_cmd, len, NULL, NULL, NULL);
}
process_t
adb_forward(const char *serial, uint16_t local_port,
const char *device_socket_name) {
@ -224,3 +233,47 @@ adb_install(const char *serial, const char *local) {
return proc;
}
static ssize_t
adb_execute_for_output(const char *serial, const char *const adb_cmd[],
size_t adb_cmd_len, char *buf, size_t buf_len,
const char *name) {
pipe_t pipe_stdout;
process_t proc = adb_execute_redirect(serial, adb_cmd, adb_cmd_len, NULL,
&pipe_stdout, NULL);
ssize_t r = read_pipe_all(pipe_stdout, buf, buf_len);
close_pipe(pipe_stdout);
if (!process_check_success(proc, name, true)) {
return -1;
}
return r;
}
static size_t
truncate_first_line(char *data, size_t len) {
data[len - 1] = '\0';
char *eol = strpbrk(data, "\r\n");
if (eol) {
*eol = '\0';
len = eol - data;
}
return len;
}
char *
adb_get_serialno(void) {
char buf[128];
const char *const adb_cmd[] = {"get-serialno"};
ssize_t r = adb_execute_for_output(NULL, adb_cmd, ARRAY_LEN(adb_cmd),
buf, sizeof(buf), "get-serialno");
if (r <= 0) {
return NULL;
}
truncate_first_line(buf, r);
return strdup(buf);
}

View File

@ -11,6 +11,11 @@
process_t
adb_execute(const char *serial, const char *const adb_cmd[], size_t len);
process_t
adb_execute_redirect(const char *serial, const char *const adb_cmd[],
size_t len, pipe_t *pipe_stdin, pipe_t *pipe_stdout,
pipe_t *pipe_stderr);
process_t
adb_forward(const char *serial, uint16_t local_port,
const char *device_socket_name);
@ -31,4 +36,8 @@ adb_push(const char *serial, const char *local, const char *remote);
process_t
adb_install(const char *serial, const char *local);
// Return the result of "adb get-serialno".
char *
adb_get_serialno(void);
#endif

385
app/src/aoa_hid.c Normal file
View File

@ -0,0 +1,385 @@
#include "util/log.h"
#include <assert.h>
#include <stdio.h>
#include "aoa_hid.h"
// See <https://source.android.com/devices/accessories/aoa2#hid-support>.
#define ACCESSORY_REGISTER_HID 54
#define ACCESSORY_SET_HID_REPORT_DESC 56
#define ACCESSORY_SEND_HID_EVENT 57
#define ACCESSORY_UNREGISTER_HID 55
#define DEFAULT_TIMEOUT 1000
static void
sc_hid_event_log(const struct sc_hid_event *event) {
// HID Event: [00] FF FF FF FF...
assert(event->size);
unsigned buffer_size = event->size * 3 + 1;
char *buffer = malloc(buffer_size);
if (!buffer) {
return;
}
for (unsigned i = 0; i < event->size; ++i) {
snprintf(buffer + i * 3, 4, " %02x", event->buffer[i]);
}
LOGV("HID Event: [%d]%s", event->accessory_id, buffer);
free(buffer);
}
void
sc_hid_event_init(struct sc_hid_event *hid_event, uint16_t accessory_id,
unsigned char *buffer, uint16_t buffer_size) {
hid_event->accessory_id = accessory_id;
hid_event->buffer = buffer;
hid_event->size = buffer_size;
hid_event->delay = 0;
}
void
sc_hid_event_destroy(struct sc_hid_event *hid_event) {
free(hid_event->buffer);
}
static inline void
log_libusb_error(enum libusb_error errcode) {
LOGW("libusb error: %s", libusb_strerror(errcode));
}
static bool
accept_device(libusb_device *device, const char *serial) {
// do not log any USB error in this function, it is expected that many USB
// devices available on the computer have permission restrictions
struct libusb_device_descriptor desc;
libusb_get_device_descriptor(device, &desc);
if (!desc.iSerialNumber) {
return false;
}
libusb_device_handle *handle;
int result = libusb_open(device, &handle);
if (result < 0) {
return false;
}
char buffer[128];
result = libusb_get_string_descriptor_ascii(handle, desc.iSerialNumber,
(unsigned char *) buffer,
sizeof(buffer));
libusb_close(handle);
if (result < 0) {
return false;
}
buffer[sizeof(buffer) - 1] = '\0'; // just in case
// accept the device if its serial matches
return !strcmp(buffer, serial);
}
static libusb_device *
sc_aoa_find_usb_device(const char *serial) {
if (!serial) {
return NULL;
}
libusb_device **list;
libusb_device *result = NULL;
ssize_t count = libusb_get_device_list(NULL, &list);
if (count < 0) {
log_libusb_error((enum libusb_error) count);
return NULL;
}
for (size_t i = 0; i < (size_t) count; ++i) {
libusb_device *device = list[i];
if (accept_device(device, serial)) {
result = libusb_ref_device(device);
break;
}
}
libusb_free_device_list(list, 1);
return result;
}
static int
sc_aoa_open_usb_handle(libusb_device *device, libusb_device_handle **handle) {
int result = libusb_open(device, handle);
if (result < 0) {
log_libusb_error((enum libusb_error) result);
return result;
}
return 0;
}
bool
sc_aoa_init(struct sc_aoa *aoa, const char *serial) {
cbuf_init(&aoa->queue);
if (!sc_mutex_init(&aoa->mutex)) {
return false;
}
if (!sc_cond_init(&aoa->event_cond)) {
sc_mutex_destroy(&aoa->mutex);
return false;
}
if (libusb_init(&aoa->usb_context) != LIBUSB_SUCCESS) {
sc_cond_destroy(&aoa->event_cond);
sc_mutex_destroy(&aoa->mutex);
return false;
}
aoa->usb_device = sc_aoa_find_usb_device(serial);
if (!aoa->usb_device) {
LOGW("USB device of serial %s not found", serial);
libusb_exit(aoa->usb_context);
sc_mutex_destroy(&aoa->mutex);
sc_cond_destroy(&aoa->event_cond);
return false;
}
if (sc_aoa_open_usb_handle(aoa->usb_device, &aoa->usb_handle) < 0) {
LOGW("Open USB handle failed");
libusb_unref_device(aoa->usb_device);
libusb_exit(aoa->usb_context);
sc_cond_destroy(&aoa->event_cond);
sc_mutex_destroy(&aoa->mutex);
return false;
}
aoa->stopped = false;
return true;
}
void
sc_aoa_destroy(struct sc_aoa *aoa) {
// Destroy remaining events
struct sc_hid_event event;
while (cbuf_take(&aoa->queue, &event)) {
sc_hid_event_destroy(&event);
}
libusb_close(aoa->usb_handle);
libusb_unref_device(aoa->usb_device);
libusb_exit(aoa->usb_context);
sc_cond_destroy(&aoa->event_cond);
sc_mutex_destroy(&aoa->mutex);
}
static bool
sc_aoa_register_hid(struct sc_aoa *aoa, uint16_t accessory_id,
uint16_t report_desc_size) {
uint8_t request_type = LIBUSB_ENDPOINT_OUT | LIBUSB_REQUEST_TYPE_VENDOR;
uint8_t request = ACCESSORY_REGISTER_HID;
// <https://source.android.com/devices/accessories/aoa2.html#hid-support>
// value (arg0): accessory assigned ID for the HID device
// index (arg1): total length of the HID report descriptor
uint16_t value = accessory_id;
uint16_t index = report_desc_size;
unsigned char *buffer = NULL;
uint16_t length = 0;
int result = libusb_control_transfer(aoa->usb_handle, request_type, request,
value, index, buffer, length,
DEFAULT_TIMEOUT);
if (result < 0) {
log_libusb_error((enum libusb_error) result);
return false;
}
return true;
}
static bool
sc_aoa_set_hid_report_desc(struct sc_aoa *aoa, uint16_t accessory_id,
const unsigned char *report_desc,
uint16_t report_desc_size) {
uint8_t request_type = LIBUSB_ENDPOINT_OUT | LIBUSB_REQUEST_TYPE_VENDOR;
uint8_t request = ACCESSORY_SET_HID_REPORT_DESC;
/**
* If the HID descriptor is longer than the endpoint zero max packet size,
* the descriptor will be sent in multiple ACCESSORY_SET_HID_REPORT_DESC
* commands. The data for the descriptor must be sent sequentially
* if multiple packets are needed.
* <https://source.android.com/devices/accessories/aoa2.html#hid-support>
*
* libusb handles packet abstraction internally, so we don't need to care
* about bMaxPacketSize0 here.
*
* See <https://libusb.sourceforge.io/api-1.0/libusb_packetoverflow.html>
*/
// value (arg0): accessory assigned ID for the HID device
// index (arg1): offset of data (buffer) in descriptor
uint16_t value = accessory_id;
uint16_t index = 0;
// libusb_control_transfer expects a pointer to non-const
unsigned char *buffer = (unsigned char *) report_desc;
uint16_t length = report_desc_size;
int result = libusb_control_transfer(aoa->usb_handle, request_type, request,
value, index, buffer, length,
DEFAULT_TIMEOUT);
if (result < 0) {
log_libusb_error((enum libusb_error) result);
return false;
}
return true;
}
bool
sc_aoa_setup_hid(struct sc_aoa *aoa, uint16_t accessory_id,
const unsigned char *report_desc, uint16_t report_desc_size) {
bool ok = sc_aoa_register_hid(aoa, accessory_id, report_desc_size);
if (!ok) {
return false;
}
ok = sc_aoa_set_hid_report_desc(aoa, accessory_id, report_desc,
report_desc_size);
if (!ok) {
if (!sc_aoa_unregister_hid(aoa, accessory_id)) {
LOGW("Could not unregister HID");
}
return false;
}
return true;
}
static bool
sc_aoa_send_hid_event(struct sc_aoa *aoa, const struct sc_hid_event *event) {
uint8_t request_type = LIBUSB_ENDPOINT_OUT | LIBUSB_REQUEST_TYPE_VENDOR;
uint8_t request = ACCESSORY_SEND_HID_EVENT;
// <https://source.android.com/devices/accessories/aoa2.html#hid-support>
// value (arg0): accessory assigned ID for the HID device
// index (arg1): 0 (unused)
uint16_t value = event->accessory_id;
uint16_t index = 0;
unsigned char *buffer = event->buffer;
uint16_t length = event->size;
int result = libusb_control_transfer(aoa->usb_handle, request_type, request,
value, index, buffer, length,
DEFAULT_TIMEOUT);
if (result < 0) {
log_libusb_error((enum libusb_error) result);
return false;
}
return true;
}
bool
sc_aoa_unregister_hid(struct sc_aoa *aoa, const uint16_t accessory_id) {
uint8_t request_type = LIBUSB_ENDPOINT_OUT | LIBUSB_REQUEST_TYPE_VENDOR;
uint8_t request = ACCESSORY_UNREGISTER_HID;
// <https://source.android.com/devices/accessories/aoa2.html#hid-support>
// value (arg0): accessory assigned ID for the HID device
// index (arg1): 0
uint16_t value = accessory_id;
uint16_t index = 0;
unsigned char *buffer = NULL;
uint16_t length = 0;
int result = libusb_control_transfer(aoa->usb_handle, request_type, request,
value, index, buffer, length,
DEFAULT_TIMEOUT);
if (result < 0) {
log_libusb_error((enum libusb_error) result);
return false;
}
return true;
}
bool
sc_aoa_push_hid_event(struct sc_aoa *aoa, const struct sc_hid_event *event) {
if (sc_get_log_level() <= SC_LOG_LEVEL_VERBOSE) {
sc_hid_event_log(event);
}
sc_mutex_lock(&aoa->mutex);
bool was_empty = cbuf_is_empty(&aoa->queue);
bool res = cbuf_push(&aoa->queue, *event);
if (was_empty) {
sc_cond_signal(&aoa->event_cond);
}
sc_mutex_unlock(&aoa->mutex);
return res;
}
static int
run_aoa_thread(void *data) {
struct sc_aoa *aoa = data;
for (;;) {
sc_mutex_lock(&aoa->mutex);
while (!aoa->stopped && cbuf_is_empty(&aoa->queue)) {
sc_cond_wait(&aoa->event_cond, &aoa->mutex);
}
if (aoa->stopped) {
// Stop immediately, do not process further events
sc_mutex_unlock(&aoa->mutex);
break;
}
struct sc_hid_event event;
bool non_empty = cbuf_take(&aoa->queue, &event);
assert(non_empty);
(void) non_empty;
assert(event.delay >= 0);
if (event.delay) {
// Wait during the specified delay before injecting the HID event
sc_tick deadline = sc_tick_now() + event.delay;
bool timed_out = false;
while (!aoa->stopped && !timed_out) {
timed_out = !sc_cond_timedwait(&aoa->event_cond, &aoa->mutex,
deadline);
}
if (aoa->stopped) {
sc_mutex_unlock(&aoa->mutex);
break;
}
}
sc_mutex_unlock(&aoa->mutex);
bool ok = sc_aoa_send_hid_event(aoa, &event);
sc_hid_event_destroy(&event);
if (!ok) {
LOGW("Could not send HID event to USB device");
}
}
return 0;
}
bool
sc_aoa_start(struct sc_aoa *aoa) {
LOGD("Starting AOA thread");
bool ok = sc_thread_create(&aoa->thread, run_aoa_thread, "aoa_thread", aoa);
if (!ok) {
LOGC("Could not start AOA thread");
return false;
}
return true;
}
void
sc_aoa_stop(struct sc_aoa *aoa) {
sc_mutex_lock(&aoa->mutex);
aoa->stopped = true;
sc_cond_signal(&aoa->event_cond);
sc_mutex_unlock(&aoa->mutex);
}
void
sc_aoa_join(struct sc_aoa *aoa) {
sc_thread_join(&aoa->thread, NULL);
}

66
app/src/aoa_hid.h Normal file
View File

@ -0,0 +1,66 @@
#ifndef SC_AOA_HID_H
#define SC_AOA_HID_H
#include <stdint.h>
#include <stdbool.h>
#include <libusb-1.0/libusb.h>
#include "util/cbuf.h"
#include "util/thread.h"
#include "util/tick.h"
struct sc_hid_event {
uint16_t accessory_id;
unsigned char *buffer;
uint16_t size;
sc_tick delay;
};
// Takes ownership of buffer
void
sc_hid_event_init(struct sc_hid_event *hid_event, uint16_t accessory_id,
unsigned char *buffer, uint16_t buffer_size);
void
sc_hid_event_destroy(struct sc_hid_event *hid_event);
struct sc_hid_event_queue CBUF(struct sc_hid_event, 64);
struct sc_aoa {
libusb_context *usb_context;
libusb_device *usb_device;
libusb_device_handle *usb_handle;
sc_thread thread;
sc_mutex mutex;
sc_cond event_cond;
bool stopped;
struct sc_hid_event_queue queue;
};
bool
sc_aoa_init(struct sc_aoa *aoa, const char *serial);
void
sc_aoa_destroy(struct sc_aoa *aoa);
bool
sc_aoa_start(struct sc_aoa *aoa);
void
sc_aoa_stop(struct sc_aoa *aoa);
void
sc_aoa_join(struct sc_aoa *aoa);
bool
sc_aoa_setup_hid(struct sc_aoa *aoa, uint16_t accessory_id,
const unsigned char *report_desc, uint16_t report_desc_size);
bool
sc_aoa_unregister_hid(struct sc_aoa *aoa, uint16_t accessory_id);
bool
sc_aoa_push_hid_event(struct sc_aoa *aoa, const struct sc_hid_event *event);
#endif

View File

@ -6,7 +6,7 @@
#include <stdio.h>
#include <unistd.h>
#include "scrcpy.h"
#include "options.h"
#include "util/log.h"
#include "util/str_util.h"
@ -76,6 +76,14 @@ scrcpy_print_usage(const char *arg0) {
" -f, --fullscreen\n"
" Start in fullscreen.\n"
"\n"
" -K, --hid-keyboard\n"
" Simulate a physical keyboard by using HID over AOAv2.\n"
" It provides a better experience for IME users, and allows to\n"
" generate non-ASCII characters, contrary to the default\n"
" injection method.\n"
" It may only work over USB, and is currently only supported\n"
" on Linux.\n"
"\n"
" -h, --help\n"
" Print this help.\n"
"\n"
@ -738,6 +746,7 @@ scrcpy_parse_args(struct scrcpy_cli_args *args, int argc, char *argv[]) {
OPT_FORWARD_ALL_CLICKS},
{"fullscreen", no_argument, NULL, 'f'},
{"help", no_argument, NULL, 'h'},
{"hid-keyboard", no_argument, NULL, 'K'},
{"legacy-paste", no_argument, NULL, OPT_LEGACY_PASTE},
{"lock-video-orientation", optional_argument, NULL,
OPT_LOCK_VIDEO_ORIENTATION},
@ -784,7 +793,7 @@ scrcpy_parse_args(struct scrcpy_cli_args *args, int argc, char *argv[]) {
optind = 0; // reset to start from the first argument in tests
int c;
while ((c = getopt_long(argc, argv, "b:c:fF:hm:nNp:r:s:StTvV:w",
while ((c = getopt_long(argc, argv, "b:c:fF:hKm:nNp:r:s:StTvV:w",
long_options, NULL)) != -1) {
switch (c) {
case 'b':
@ -817,6 +826,9 @@ scrcpy_parse_args(struct scrcpy_cli_args *args, int argc, char *argv[]) {
case 'h':
args->help = true;
break;
case 'K':
opts->keyboard_input_mode = SC_KEYBOARD_INPUT_MODE_HID;
break;
case OPT_MAX_FPS:
if (!parse_max_fps(optarg, &opts->max_fps)) {
return false;

View File

@ -5,7 +5,7 @@
#include <stdbool.h>
#include "scrcpy.h"
#include "options.h"
struct scrcpy_cli_args {
struct scrcpy_options opts;

View File

@ -5,7 +5,7 @@
#include "util/log.h"
bool
controller_init(struct controller *controller, socket_t control_socket) {
controller_init(struct controller *controller, sc_socket control_socket) {
cbuf_init(&controller->queue);
bool ok = receiver_init(&controller->receiver, control_socket);
@ -47,7 +47,7 @@ controller_destroy(struct controller *controller) {
bool
controller_push_msg(struct controller *controller,
const struct control_msg *msg) {
const struct control_msg *msg) {
if (sc_get_log_level() <= SC_LOG_LEVEL_VERBOSE) {
control_msg_log(msg);
}
@ -63,14 +63,14 @@ controller_push_msg(struct controller *controller,
}
static bool
process_msg(struct controller *controller,
const struct control_msg *msg) {
process_msg(struct controller *controller, const struct control_msg *msg) {
static unsigned char serialized_msg[CONTROL_MSG_MAX_SIZE];
size_t length = control_msg_serialize(msg, serialized_msg);
if (!length) {
return false;
}
ssize_t w = net_send_all(controller->control_socket, serialized_msg, length);
ssize_t w =
net_send_all(controller->control_socket, serialized_msg, length);
return (size_t) w == length;
}

View File

@ -14,7 +14,7 @@
struct control_msg_queue CBUF(struct control_msg, 64);
struct controller {
socket_t control_socket;
sc_socket control_socket;
sc_thread thread;
sc_mutex mutex;
sc_cond msg_cond;
@ -24,7 +24,7 @@ struct controller {
};
bool
controller_init(struct controller *controller, socket_t control_socket);
controller_init(struct controller *controller, sc_socket control_socket);
void
controller_destroy(struct controller *controller);

View File

@ -1,30 +0,0 @@
#ifndef CONVERT_H
#define CONVERT_H
#include "common.h"
#include <stdbool.h>
#include <SDL2/SDL_events.h>
#include "control_msg.h"
bool
convert_keycode_action(SDL_EventType from, enum android_keyevent_action *to);
enum android_metastate
convert_meta_state(SDL_Keymod mod);
bool
convert_keycode(SDL_Keycode from, enum android_keycode *to, uint16_t mod,
bool prefer_text);
enum android_motionevent_buttons
convert_mouse_buttons(uint32_t state);
bool
convert_mouse_action(SDL_EventType from, enum android_motionevent_action *to);
bool
convert_touch_action(SDL_EventType from, enum android_motionevent_action *to);
#endif

363
app/src/hid_keyboard.c Normal file
View File

@ -0,0 +1,363 @@
#include "hid_keyboard.h"
#include <assert.h>
#include <SDL2/SDL_events.h>
#include "util/log.h"
/** Downcast key processor to hid_keyboard */
#define DOWNCAST(KP) container_of(KP, struct sc_hid_keyboard, key_processor)
#define HID_KEYBOARD_ACCESSORY_ID 1
#define HID_MODIFIER_NONE 0x00
#define HID_MODIFIER_LEFT_CONTROL (1 << 0)
#define HID_MODIFIER_LEFT_SHIFT (1 << 1)
#define HID_MODIFIER_LEFT_ALT (1 << 2)
#define HID_MODIFIER_LEFT_GUI (1 << 3)
#define HID_MODIFIER_RIGHT_CONTROL (1 << 4)
#define HID_MODIFIER_RIGHT_SHIFT (1 << 5)
#define HID_MODIFIER_RIGHT_ALT (1 << 6)
#define HID_MODIFIER_RIGHT_GUI (1 << 7)
#define HID_KEYBOARD_INDEX_MODIFIER 0
#define HID_KEYBOARD_INDEX_KEYS 2
// USB HID protocol says 6 keys in an event is the requirement for BIOS
// keyboard support, though OS could support more keys via modifying the report
// desc. 6 should be enough for scrcpy.
#define HID_KEYBOARD_MAX_KEYS 6
#define HID_KEYBOARD_EVENT_SIZE (2 + HID_KEYBOARD_MAX_KEYS)
#define HID_RESERVED 0x00
#define HID_ERROR_ROLL_OVER 0x01
/**
* For HID over AOAv2, only report descriptor is needed.
*
* The specification is available here:
* <https://www.usb.org/sites/default/files/hid1_11.pdf>
*
* In particular, read:
* - 6.2.2 Report Descriptor
* - Appendix B.1 Protocol 1 (Keyboard)
* - Appendix C: Keyboard Implementation
*
* Normally a basic HID keyboard uses 8 bytes:
* Modifier Reserved Key Key Key Key Key Key
*
* You can dump your device's report descriptor with:
*
* sudo usbhid-dump -m vid:pid -e descriptor
*
* (change vid:pid' to your device's vendor ID and product ID).
*/
static const unsigned char keyboard_report_desc[] = {
// Usage Page (Generic Desktop)
0x05, 0x01,
// Usage (Keyboard)
0x09, 0x06,
// Collection (Application)
0xA1, 0x01,
// Usage Page (Key Codes)
0x05, 0x07,
// Usage Minimum (224)
0x19, 0xE0,
// Usage Maximum (231)
0x29, 0xE7,
// Logical Minimum (0)
0x15, 0x00,
// Logical Maximum (1)
0x25, 0x01,
// Report Size (1)
0x75, 0x01,
// Report Count (8)
0x95, 0x08,
// Input (Data, Variable, Absolute): Modifier byte
0x81, 0x02,
// Report Size (8)
0x75, 0x08,
// Report Count (1)
0x95, 0x01,
// Input (Constant): Reserved byte
0x81, 0x01,
// Usage Page (LEDs)
0x05, 0x08,
// Usage Minimum (1)
0x19, 0x01,
// Usage Maximum (5)
0x29, 0x05,
// Report Size (1)
0x75, 0x01,
// Report Count (5)
0x95, 0x05,
// Output (Data, Variable, Absolute): LED report
0x91, 0x02,
// Report Size (3)
0x75, 0x03,
// Report Count (1)
0x95, 0x01,
// Output (Constant): LED report padding
0x91, 0x01,
// Usage Page (Key Codes)
0x05, 0x07,
// Usage Minimum (0)
0x19, 0x00,
// Usage Maximum (101)
0x29, SC_HID_KEYBOARD_KEYS - 1,
// Logical Minimum (0)
0x15, 0x00,
// Logical Maximum(101)
0x25, SC_HID_KEYBOARD_KEYS - 1,
// Report Size (8)
0x75, 0x08,
// Report Count (6)
0x95, HID_KEYBOARD_MAX_KEYS,
// Input (Data, Array): Keys
0x81, 0x00,
// End Collection
0xC0
};
static unsigned char
sdl_keymod_to_hid_modifiers(SDL_Keymod mod) {
unsigned char modifiers = HID_MODIFIER_NONE;
if (mod & KMOD_LCTRL) {
modifiers |= HID_MODIFIER_LEFT_CONTROL;
}
if (mod & KMOD_LSHIFT) {
modifiers |= HID_MODIFIER_LEFT_SHIFT;
}
if (mod & KMOD_LALT) {
modifiers |= HID_MODIFIER_LEFT_ALT;
}
if (mod & KMOD_LGUI) {
modifiers |= HID_MODIFIER_LEFT_GUI;
}
if (mod & KMOD_RCTRL) {
modifiers |= HID_MODIFIER_RIGHT_CONTROL;
}
if (mod & KMOD_RSHIFT) {
modifiers |= HID_MODIFIER_RIGHT_SHIFT;
}
if (mod & KMOD_RALT) {
modifiers |= HID_MODIFIER_RIGHT_ALT;
}
if (mod & KMOD_RGUI) {
modifiers |= HID_MODIFIER_RIGHT_GUI;
}
return modifiers;
}
static bool
sc_hid_keyboard_event_init(struct sc_hid_event *hid_event) {
unsigned char *buffer = malloc(HID_KEYBOARD_EVENT_SIZE);
if (!buffer) {
return false;
}
buffer[HID_KEYBOARD_INDEX_MODIFIER] = HID_MODIFIER_NONE;
buffer[1] = HID_RESERVED;
memset(&buffer[HID_KEYBOARD_INDEX_KEYS], 0, HID_KEYBOARD_MAX_KEYS);
sc_hid_event_init(hid_event, HID_KEYBOARD_ACCESSORY_ID, buffer,
HID_KEYBOARD_EVENT_SIZE);
return true;
}
static inline bool
scancode_is_modifier(SDL_Scancode scancode) {
return scancode >= SDL_SCANCODE_LCTRL && scancode <= SDL_SCANCODE_RGUI;
}
static bool
convert_hid_keyboard_event(struct sc_hid_keyboard *kb,
struct sc_hid_event *hid_event,
const SDL_KeyboardEvent *event) {
SDL_Scancode scancode = event->keysym.scancode;
assert(scancode >= 0);
// SDL also generates events when only modifiers are pressed, we cannot
// ignore them totally, for example press 'a' first then press 'Control',
// if we ignore 'Control' event, only 'a' is sent.
if (scancode >= SC_HID_KEYBOARD_KEYS && !scancode_is_modifier(scancode)) {
// Scancode to ignore
return false;
}
if (!sc_hid_keyboard_event_init(hid_event)) {
LOGW("Could not initialize HID keyboard event");
return false;
}
unsigned char modifiers = sdl_keymod_to_hid_modifiers(event->keysym.mod);
if (scancode < SC_HID_KEYBOARD_KEYS) {
// Pressed is true and released is false
kb->keys[scancode] = (event->type == SDL_KEYDOWN);
LOGV("keys[%02x] = %s", scancode,
kb->keys[scancode] ? "true" : "false");
}
hid_event->buffer[HID_KEYBOARD_INDEX_MODIFIER] = modifiers;
unsigned char *keys_buffer = &hid_event->buffer[HID_KEYBOARD_INDEX_KEYS];
// Re-calculate pressed keys every time
int keys_pressed_count = 0;
for (int i = 0; i < SC_HID_KEYBOARD_KEYS; ++i) {
if (kb->keys[i]) {
// USB HID protocol says that if keys exceeds report count, a
// phantom state should be reported
if (keys_pressed_count >= HID_KEYBOARD_MAX_KEYS) {
// Pantom state:
// - Modifiers
// - Reserved
// - ErrorRollOver * HID_MAX_KEYS
memset(keys_buffer, HID_ERROR_ROLL_OVER, HID_KEYBOARD_MAX_KEYS);
goto end;
}
keys_buffer[keys_pressed_count] = i;
++keys_pressed_count;
}
}
end:
LOGV("hid keyboard: key %-4s scancode=%02x (%u) mod=%02x",
event->type == SDL_KEYDOWN ? "down" : "up", event->keysym.scancode,
event->keysym.scancode, modifiers);
return true;
}
static bool
push_mod_lock_state(struct sc_hid_keyboard *kb, uint16_t sdl_mod) {
bool capslock = sdl_mod & KMOD_CAPS;
bool numlock = sdl_mod & KMOD_NUM;
if (!capslock && !numlock) {
// Nothing to do
return true;
}
struct sc_hid_event hid_event;
if (!sc_hid_keyboard_event_init(&hid_event)) {
LOGW("Could not initialize HID keyboard event");
return false;
}
#define SC_SCANCODE_CAPSLOCK SDL_SCANCODE_CAPSLOCK
#define SC_SCANCODE_NUMLOCK SDL_SCANCODE_NUMLOCKCLEAR
unsigned i = 0;
if (capslock) {
hid_event.buffer[HID_KEYBOARD_INDEX_KEYS + i] = SC_SCANCODE_CAPSLOCK;
++i;
}
if (numlock) {
hid_event.buffer[HID_KEYBOARD_INDEX_KEYS + i] = SC_SCANCODE_NUMLOCK;
++i;
}
if (!sc_aoa_push_hid_event(kb->aoa, &hid_event)) {
sc_hid_event_destroy(&hid_event);
LOGW("Could request HID event");
return false;
}
LOGD("HID keyboard state synchronized");
return true;
}
static void
sc_key_processor_process_key(struct sc_key_processor *kp,
const SDL_KeyboardEvent *event) {
if (event->repeat) {
// In USB HID protocol, key repeat is handled by the host (Android), so
// just ignore key repeat here.
return;
}
struct sc_hid_keyboard *kb = DOWNCAST(kp);
struct sc_hid_event hid_event;
// Not all keys are supported, just ignore unsupported keys
if (convert_hid_keyboard_event(kb, &hid_event, event)) {
if (!kb->mod_lock_synchronized) {
// Inject CAPSLOCK and/or NUMLOCK if necessary to synchronize
// keyboard state
if (push_mod_lock_state(kb, event->keysym.mod)) {
kb->mod_lock_synchronized = true;
}
}
SDL_Keycode keycode = event->keysym.sym;
bool down = event->type == SDL_KEYDOWN;
bool ctrl = event->keysym.mod & KMOD_CTRL;
bool shift = event->keysym.mod & KMOD_SHIFT;
if (ctrl && !shift && keycode == SDLK_v && down) {
// Ctrl+v is pressed, so clipboard synchronization has been
// requested. Wait a bit so that the clipboard is set before
// injecting Ctrl+v via HID, otherwise it would paste the old
// clipboard content.
hid_event.delay = SC_TICK_FROM_MS(2);
}
if (!sc_aoa_push_hid_event(kb->aoa, &hid_event)) {
sc_hid_event_destroy(&hid_event);
LOGW("Could request HID event");
}
}
}
static void
sc_key_processor_process_text(struct sc_key_processor *kp,
const SDL_TextInputEvent *event) {
(void) kp;
(void) event;
// Never forward text input via HID (all the keys are injected separately)
}
bool
sc_hid_keyboard_init(struct sc_hid_keyboard *kb, struct sc_aoa *aoa) {
kb->aoa = aoa;
bool ok = sc_aoa_setup_hid(aoa, HID_KEYBOARD_ACCESSORY_ID,
keyboard_report_desc,
ARRAY_LEN(keyboard_report_desc));
if (!ok) {
LOGW("Register HID keyboard failed");
return false;
}
// Reset all states
memset(kb->keys, false, SC_HID_KEYBOARD_KEYS);
kb->mod_lock_synchronized = false;
static const struct sc_key_processor_ops ops = {
.process_key = sc_key_processor_process_key,
.process_text = sc_key_processor_process_text,
};
kb->key_processor.ops = &ops;
return true;
}
void
sc_hid_keyboard_destroy(struct sc_hid_keyboard *kb) {
// Unregister HID keyboard so the soft keyboard shows again on Android
bool ok = sc_aoa_unregister_hid(kb->aoa, HID_KEYBOARD_ACCESSORY_ID);
if (!ok) {
LOGW("Could not unregister HID keyboard");
}
}

44
app/src/hid_keyboard.h Normal file
View File

@ -0,0 +1,44 @@
#ifndef SC_HID_KEYBOARD_H
#define SC_HID_KEYBOARD_H
#include "common.h"
#include <stdbool.h>
#include "aoa_hid.h"
#include "trait/key_processor.h"
// See "SDL2/SDL_scancode.h".
// Maybe SDL_Keycode is used by most people, but SDL_Scancode is taken from USB
// HID protocol.
// 0x65 is Application, typically AT-101 Keyboard ends here.
#define SC_HID_KEYBOARD_KEYS 0x66
/**
* HID keyboard events are sequence-based, every time keyboard state changes
* it sends an array of currently pressed keys, the host is responsible for
* compare events and determine which key becomes pressed and which key becomes
* released. In order to convert SDL_KeyboardEvent to HID events, we first use
* an array of keys to save each keys' state. And when a SDL_KeyboardEvent was
* emitted, we updated our state, and then we use a loop to generate HID
* events. The sequence of array elements is unimportant and when too much keys
* pressed at the same time (more than report count), we should generate
* phantom state. Don't forget that modifiers should be updated too, even for
* phantom state.
*/
struct sc_hid_keyboard {
struct sc_key_processor key_processor; // key processor trait
struct sc_aoa *aoa;
bool keys[SC_HID_KEYBOARD_KEYS];
bool mod_lock_synchronized;
};
bool
sc_hid_keyboard_init(struct sc_hid_keyboard *kb, struct sc_aoa *aoa);
void
sc_hid_keyboard_destroy(struct sc_hid_keyboard *kb);
#endif

282
app/src/icon.c Normal file
View File

@ -0,0 +1,282 @@
#include "icon.h"
#include <assert.h>
#include <stdbool.h>
#include <libavformat/avformat.h>
#include <libavutil/pixdesc.h>
#include <libavutil/pixfmt.h>
#include "config.h"
#include "compat.h"
#include "util/log.h"
#include "util/process.h"
#include "util/str_util.h"
#define SCRCPY_PORTABLE_ICON_FILENAME "icon.png"
#define SCRCPY_DEFAULT_ICON_PATH \
PREFIX "/share/icons/hicolor/256x256/apps/scrcpy.png"
static char *
get_icon_path(void) {
#ifdef __WINDOWS__
const wchar_t *icon_path_env = _wgetenv(L"SCRCPY_ICON_PATH");
#else
const char *icon_path_env = getenv("SCRCPY_ICON_PATH");
#endif
if (icon_path_env) {
// if the envvar is set, use it
#ifdef __WINDOWS__
char *icon_path = utf8_from_wide_char(icon_path_env);
#else
char *icon_path = strdup(icon_path_env);
#endif
if (!icon_path) {
LOGE("Could not allocate memory");
return NULL;
}
LOGD("Using SCRCPY_ICON_PATH: %s", icon_path);
return icon_path;
}
#ifndef PORTABLE
LOGD("Using icon: " SCRCPY_DEFAULT_ICON_PATH);
char *icon_path = strdup(SCRCPY_DEFAULT_ICON_PATH);
if (!icon_path) {
LOGE("Could not allocate memory");
return NULL;
}
#else
char *icon_path = get_local_file_path(SCRCPY_PORTABLE_ICON_FILENAME);
if (!icon_path) {
LOGE("Could not get icon path");
return NULL;
}
LOGD("Using icon (portable): %s", icon_path);
#endif
return icon_path;
}
static AVFrame *
decode_image(const char *path) {
AVFrame *result = NULL;
AVFormatContext *ctx = avformat_alloc_context();
if (!ctx) {
LOGE("Could not allocate image decoder context");
return NULL;
}
if (avformat_open_input(&ctx, path, NULL, NULL) < 0) {
LOGE("Could not open image codec: %s", path);
goto free_ctx;
}
if (avformat_find_stream_info(ctx, NULL) < 0) {
LOGE("Could not find image stream info");
goto close_input;
}
int stream = av_find_best_stream(ctx, AVMEDIA_TYPE_VIDEO, -1, -1, NULL, 0);
if (stream < 0 ) {
LOGE("Could not find best image stream");
goto close_input;
}
AVCodecParameters *params = ctx->streams[stream]->codecpar;
AVCodec *codec = avcodec_find_decoder(params->codec_id);
if (!codec) {
LOGE("Could not find image decoder");
goto close_input;
}
AVCodecContext *codec_ctx = avcodec_alloc_context3(codec);
if (!codec_ctx) {
LOGE("Could not allocate codec context");
goto close_input;
}
if (avcodec_parameters_to_context(codec_ctx, params) < 0) {
LOGE("Could not fill codec context");
goto free_codec_ctx;
}
if (avcodec_open2(codec_ctx, codec, NULL) < 0) {
LOGE("Could not open image codec");
goto free_codec_ctx;
}
AVFrame *frame = av_frame_alloc();
if (!frame) {
LOGE("Could not allocate frame");
goto close_codec;
}
AVPacket *packet = av_packet_alloc();
if (!packet) {
LOGE("Could not allocate packet");
av_frame_free(&frame);
goto close_codec;
}
if (av_read_frame(ctx, packet) < 0) {
LOGE("Could not read frame");
av_packet_free(&packet);
av_frame_free(&frame);
goto close_codec;
}
int ret;
if ((ret = avcodec_send_packet(codec_ctx, packet)) < 0) {
LOGE("Could not send icon packet: %d", ret);
av_packet_free(&packet);
av_frame_free(&frame);
goto close_codec;
}
if ((ret = avcodec_receive_frame(codec_ctx, frame)) != 0) {
LOGE("Could not receive icon frame: %d", ret);
av_packet_free(&packet);
av_frame_free(&frame);
goto close_codec;
}
av_packet_free(&packet);
result = frame;
close_codec:
avcodec_close(codec_ctx);
free_codec_ctx:
avcodec_free_context(&codec_ctx);
close_input:
avformat_close_input(&ctx);
free_ctx:
avformat_free_context(ctx);
return result;
}
static SDL_PixelFormatEnum
to_sdl_pixel_format(enum AVPixelFormat fmt) {
switch (fmt) {
case AV_PIX_FMT_RGB24: return SDL_PIXELFORMAT_RGB24;
case AV_PIX_FMT_BGR24: return SDL_PIXELFORMAT_BGR24;
case AV_PIX_FMT_ARGB: return SDL_PIXELFORMAT_ARGB32;
case AV_PIX_FMT_RGBA: return SDL_PIXELFORMAT_RGBA32;
case AV_PIX_FMT_ABGR: return SDL_PIXELFORMAT_ABGR32;
case AV_PIX_FMT_BGRA: return SDL_PIXELFORMAT_BGRA32;
case AV_PIX_FMT_RGB565BE: return SDL_PIXELFORMAT_RGB565;
case AV_PIX_FMT_RGB555BE: return SDL_PIXELFORMAT_RGB555;
case AV_PIX_FMT_BGR565BE: return SDL_PIXELFORMAT_BGR565;
case AV_PIX_FMT_BGR555BE: return SDL_PIXELFORMAT_BGR555;
case AV_PIX_FMT_RGB444BE: return SDL_PIXELFORMAT_RGB444;
case AV_PIX_FMT_BGR444BE: return SDL_PIXELFORMAT_BGR444;
case AV_PIX_FMT_PAL8: return SDL_PIXELFORMAT_INDEX8;
default: return SDL_PIXELFORMAT_UNKNOWN;
}
}
static SDL_Surface *
load_from_path(const char *path) {
AVFrame *frame = decode_image(path);
if (!frame) {
return NULL;
}
const AVPixFmtDescriptor *desc = av_pix_fmt_desc_get(frame->format);
if (!desc) {
LOGE("Could not get icon format descriptor");
goto error;
}
bool is_packed = !(desc->flags & AV_PIX_FMT_FLAG_PLANAR);
if (!is_packed) {
LOGE("Could not load non-packed icon");
goto error;
}
SDL_PixelFormatEnum format = to_sdl_pixel_format(frame->format);
if (format == SDL_PIXELFORMAT_UNKNOWN) {
LOGE("Unsupported icon pixel format: %s (%d)", desc->name,
frame->format);
goto error;
}
int bits_per_pixel = av_get_bits_per_pixel(desc);
SDL_Surface *surface =
SDL_CreateRGBSurfaceWithFormatFrom(frame->data[0],
frame->width, frame->height,
bits_per_pixel,
frame->linesize[0],
format);
if (!surface) {
LOGE("Could not create icon surface");
goto error;
}
if (frame->format == AV_PIX_FMT_PAL8) {
// Initialize the SDL palette
uint8_t *data = frame->data[1];
SDL_Color colors[256];
for (int i = 0; i < 256; ++i) {
SDL_Color *color = &colors[i];
// The palette is transported in AVFrame.data[1], is 1024 bytes
// long (256 4-byte entries) and is formatted the same as in
// AV_PIX_FMT_RGB32 described above (i.e., it is also
// endian-specific).
// <https://ffmpeg.org/doxygen/4.1/pixfmt_8h.html#a9a8e335cf3be472042bc9f0cf80cd4c5>
#if SDL_BYTEORDER == SDL_BIG_ENDIAN
color->a = data[i * 4];
color->r = data[i * 4 + 1];
color->g = data[i * 4 + 2];
color->b = data[i * 4 + 3];
#else
color->a = data[i * 4 + 3];
color->r = data[i * 4 + 2];
color->g = data[i * 4 + 1];
color->b = data[i * 4];
#endif
}
SDL_Palette *palette = surface->format->palette;
assert(palette);
int ret = SDL_SetPaletteColors(palette, colors, 0, 256);
if (ret) {
LOGE("Could not set palette colors");
SDL_FreeSurface(surface);
goto error;
}
}
surface->userdata = frame; // frame owns the data
return surface;
error:
av_frame_free(&frame);
return NULL;
}
SDL_Surface *
scrcpy_icon_load() {
char *icon_path = get_icon_path();
if (!icon_path) {
return NULL;
}
SDL_Surface *icon = load_from_path(icon_path);
free(icon_path);
return icon;
}
void
scrcpy_icon_destroy(SDL_Surface *icon) {
AVFrame *frame = icon->userdata;
assert(frame);
av_frame_free(&frame);
SDL_FreeSurface(icon);
}

16
app/src/icon.h Normal file
View File

@ -0,0 +1,16 @@
#ifndef ICON_H
#define ICON_H
#include "common.h"
#include <stdbool.h>
#include <SDL2/SDL.h>
#include <libavformat/avformat.h>
SDL_Surface *
scrcpy_icon_load(void);
void
scrcpy_icon_destroy(SDL_Surface *icon);
#endif

View File

@ -1,53 +0,0 @@
/* XPM */
static char * icon_xpm[] = {
"48 48 2 1",
" c None",
". c #96C13E",
" .. .. ",
" ... ... ",
" ... ...... ... ",
" ................ ",
" .............. ",
" ................ ",
" .................. ",
" .................... ",
" ..... ........ ..... ",
" ..... ........ ..... ",
" ...................... ",
" ........................ ",
" ........................ ",
" ........................ ",
" ",
" ",
" .... ........................ .... ",
" ...... ........................ ...... ",
" ...... ........................ ...... ",
" ...... ........................ ...... ",
" ...... ........................ ...... ",
" ...... ........................ ...... ",
" ...... ........................ ...... ",
" ...... ........................ ...... ",
" ...... ........................ ...... ",
" ...... ........................ ...... ",
" ...... ........................ ...... ",
" ...... ........................ ...... ",
" ...... ........................ ...... ",
" ...... ........................ ...... ",
" ...... ........................ ...... ",
" ...... ........................ ...... ",
" ...... ........................ ...... ",
" ...... ........................ ...... ",
" ...... ........................ ...... ",
" .... ........................ .... ",
" ........................ ",
" ...................... ",
" ...... ...... ",
" ...... ...... ",
" ...... ...... ",
" ...... ...... ",
" ...... ...... ",
" ...... ...... ",
" ...... ...... ",
" ...... ...... ",
" ...... ...... ",
" .... .... "};

View File

@ -3,7 +3,6 @@
#include <assert.h>
#include <SDL2/SDL_keycode.h>
#include "event_converter.h"
#include "util/log.h"
static const int ACTION_DOWN = 1;
@ -53,15 +52,18 @@ is_shortcut_mod(struct input_manager *im, uint16_t sdl_mod) {
void
input_manager_init(struct input_manager *im, struct controller *controller,
struct screen *screen,
struct screen *screen, struct sc_key_processor *kp,
struct sc_mouse_processor *mp,
const struct scrcpy_options *options) {
assert(!options->control || (kp && kp->ops));
assert(!options->control || (mp && mp->ops));
im->controller = controller;
im->screen = screen;
im->repeat = 0;
im->kp = kp;
im->mp = mp;
im->control = options->control;
im->forward_key_repeat = options->forward_key_repeat;
im->prefer_text = options->prefer_text;
im->forward_all_clicks = options->forward_all_clicks;
im->legacy_paste = options->legacy_paste;
@ -323,26 +325,8 @@ input_manager_process_text_input(struct input_manager *im,
// A shortcut must never generate text events
return;
}
if (!im->prefer_text) {
char c = event->text[0];
if (isalpha(c) || c == ' ') {
assert(event->text[1] == '\0');
// letters and space are handled as raw key event
return;
}
}
struct control_msg msg;
msg.type = CONTROL_MSG_TYPE_INJECT_TEXT;
msg.inject_text.text = strdup(event->text);
if (!msg.inject_text.text) {
LOGW("Could not strdup input text");
return;
}
if (!controller_push_msg(im->controller, &msg)) {
free(msg.inject_text.text);
LOGW("Could not request 'inject text'");
}
im->kp->ops->process_text(im->kp, event);
}
static bool
@ -375,27 +359,6 @@ inverse_point(struct point point, struct size size) {
return point;
}
static bool
convert_input_key(const SDL_KeyboardEvent *from, struct control_msg *to,
bool prefer_text, uint32_t repeat) {
to->type = CONTROL_MSG_TYPE_INJECT_KEYCODE;
if (!convert_keycode_action(from->type, &to->inject_keycode.action)) {
return false;
}
uint16_t mod = from->keysym.mod;
if (!convert_keycode(from->keysym.sym, &to->inject_keycode.keycode, mod,
prefer_text)) {
return false;
}
to->inject_keycode.repeat = repeat;
to->inject_keycode.metastate = convert_meta_state(mod);
return true;
}
static void
input_manager_process_key(struct input_manager *im,
const SDL_KeyboardEvent *event) {
@ -549,15 +512,6 @@ input_manager_process_key(struct input_manager *im,
return;
}
if (event->repeat) {
if (!im->forward_key_repeat) {
return;
}
++im->repeat;
} else {
im->repeat = 0;
}
if (ctrl && !shift && keycode == SDLK_v && down && !repeat) {
if (im->legacy_paste) {
// inject the text as input events
@ -569,27 +523,7 @@ input_manager_process_key(struct input_manager *im,
set_device_clipboard(controller, false);
}
struct control_msg msg;
if (convert_input_key(event, &msg, im->prefer_text, im->repeat)) {
if (!controller_push_msg(controller, &msg)) {
LOGW("Could not request 'inject keycode'");
}
}
}
static bool
convert_mouse_motion(const SDL_MouseMotionEvent *from, struct screen *screen,
struct control_msg *to) {
to->type = CONTROL_MSG_TYPE_INJECT_TOUCH_EVENT;
to->inject_touch_event.action = AMOTION_EVENT_ACTION_MOVE;
to->inject_touch_event.pointer_id = POINTER_ID_MOUSE;
to->inject_touch_event.position.screen_size = screen->frame_size;
to->inject_touch_event.position.point =
screen_convert_window_to_frame_coords(screen, from->x, from->y);
to->inject_touch_event.pressure = 1.f;
to->inject_touch_event.buttons = convert_mouse_buttons(from->state);
return true;
im->kp->ops->process_key(im->kp, event);
}
static void
@ -607,79 +541,22 @@ input_manager_process_mouse_motion(struct input_manager *im,
// simulated from touch events, so it's a duplicate
return;
}
struct control_msg msg;
if (!convert_mouse_motion(event, im->screen, &msg)) {
return;
}
if (!controller_push_msg(im->controller, &msg)) {
LOGW("Could not request 'inject mouse motion event'");
}
im->mp->ops->process_mouse_motion(im->mp, event);
if (im->vfinger_down) {
struct point mouse = msg.inject_touch_event.position.point;
struct point mouse =
screen_convert_window_to_frame_coords(im->screen, event->x,
event->y);
struct point vfinger = inverse_point(mouse, im->screen->frame_size);
simulate_virtual_finger(im, AMOTION_EVENT_ACTION_MOVE, vfinger);
}
}
static bool
convert_touch(const SDL_TouchFingerEvent *from, struct screen *screen,
struct control_msg *to) {
to->type = CONTROL_MSG_TYPE_INJECT_TOUCH_EVENT;
if (!convert_touch_action(from->type, &to->inject_touch_event.action)) {
return false;
}
to->inject_touch_event.pointer_id = from->fingerId;
to->inject_touch_event.position.screen_size = screen->frame_size;
int dw;
int dh;
SDL_GL_GetDrawableSize(screen->window, &dw, &dh);
// SDL touch event coordinates are normalized in the range [0; 1]
int32_t x = from->x * dw;
int32_t y = from->y * dh;
to->inject_touch_event.position.point =
screen_convert_drawable_to_frame_coords(screen, x, y);
to->inject_touch_event.pressure = from->pressure;
to->inject_touch_event.buttons = 0;
return true;
}
static void
input_manager_process_touch(struct input_manager *im,
const SDL_TouchFingerEvent *event) {
struct control_msg msg;
if (convert_touch(event, im->screen, &msg)) {
if (!controller_push_msg(im->controller, &msg)) {
LOGW("Could not request 'inject touch event'");
}
}
}
static bool
convert_mouse_button(const SDL_MouseButtonEvent *from, struct screen *screen,
struct control_msg *to) {
to->type = CONTROL_MSG_TYPE_INJECT_TOUCH_EVENT;
if (!convert_mouse_action(from->type, &to->inject_touch_event.action)) {
return false;
}
to->inject_touch_event.pointer_id = POINTER_ID_MOUSE;
to->inject_touch_event.position.screen_size = screen->frame_size;
to->inject_touch_event.position.point =
screen_convert_window_to_frame_coords(screen, from->x, from->y);
to->inject_touch_event.pressure =
from->type == SDL_MOUSEBUTTONDOWN ? 1.f : 0.f;
to->inject_touch_event.buttons =
convert_mouse_buttons(SDL_BUTTON(from->button));
return true;
im->mp->ops->process_touch(im->mp, event);
}
static void
@ -739,15 +616,7 @@ input_manager_process_mouse_button(struct input_manager *im,
return;
}
struct control_msg msg;
if (!convert_mouse_button(event, im->screen, &msg)) {
return;
}
if (!controller_push_msg(im->controller, &msg)) {
LOGW("Could not request 'inject mouse button event'");
return;
}
im->mp->ops->process_mouse_button(im->mp, event);
// Pinch-to-zoom simulation.
//
@ -761,7 +630,9 @@ input_manager_process_mouse_button(struct input_manager *im,
#define CTRL_PRESSED (SDL_GetModState() & (KMOD_LCTRL | KMOD_RCTRL))
if ((down && !im->vfinger_down && CTRL_PRESSED)
|| (!down && im->vfinger_down)) {
struct point mouse = msg.inject_touch_event.position.point;
struct point mouse =
screen_convert_window_to_frame_coords(im->screen, event->x,
event->y);
struct point vfinger = inverse_point(mouse, im->screen->frame_size);
enum android_motionevent_action action = down
? AMOTION_EVENT_ACTION_DOWN
@ -773,39 +644,10 @@ input_manager_process_mouse_button(struct input_manager *im,
}
}
static bool
convert_mouse_wheel(const SDL_MouseWheelEvent *from, struct screen *screen,
struct control_msg *to) {
// mouse_x and mouse_y are expressed in pixels relative to the window
int mouse_x;
int mouse_y;
SDL_GetMouseState(&mouse_x, &mouse_y);
struct position position = {
.screen_size = screen->frame_size,
.point = screen_convert_window_to_frame_coords(screen,
mouse_x, mouse_y),
};
to->type = CONTROL_MSG_TYPE_INJECT_SCROLL_EVENT;
to->inject_scroll_event.position = position;
to->inject_scroll_event.hscroll = from->x;
to->inject_scroll_event.vscroll = from->y;
return true;
}
static void
input_manager_process_mouse_wheel(struct input_manager *im,
const SDL_MouseWheelEvent *event) {
struct control_msg msg;
if (convert_mouse_wheel(event, im->screen, &msg)) {
if (!controller_push_msg(im->controller, &msg)) {
LOGW("Could not request 'inject mouse wheel event'");
}
}
im->mp->ops->process_mouse_wheel(im->mp, event);
}
bool

View File

@ -9,20 +9,19 @@
#include "controller.h"
#include "fps_counter.h"
#include "scrcpy.h"
#include "options.h"
#include "screen.h"
#include "trait/key_processor.h"
#include "trait/mouse_processor.h"
struct input_manager {
struct controller *controller;
struct screen *screen;
// SDL reports repeated events as a boolean, but Android expects the actual
// number of repetitions. This variable keeps track of the count.
unsigned repeat;
struct sc_key_processor *kp;
struct sc_mouse_processor *mp;
bool control;
bool forward_key_repeat;
bool prefer_text;
bool forward_all_clicks;
bool legacy_paste;
@ -43,7 +42,9 @@ struct input_manager {
void
input_manager_init(struct input_manager *im, struct controller *controller,
struct screen *screen, const struct scrcpy_options *options);
struct screen *screen, struct sc_key_processor *kp,
struct sc_mouse_processor *mp,
const struct scrcpy_options *options);
bool
input_manager_handle_event(struct input_manager *im, SDL_Event *event);

View File

@ -1,9 +1,20 @@
#include "event_converter.h"
#include "keyboard_inject.h"
#include <assert.h>
#include <SDL2/SDL_events.h>
#include "android/input.h"
#include "control_msg.h"
#include "controller.h"
#include "util/log.h"
/** Downcast key processor to sc_keyboard_inject */
#define DOWNCAST(KP) \
container_of(KP, struct sc_keyboard_inject, key_processor)
#define MAP(FROM, TO) case FROM: *to = TO; return true
#define FAIL default: return false
bool
static bool
convert_keycode_action(SDL_EventType from, enum android_keyevent_action *to) {
switch (from) {
MAP(SDL_KEYDOWN, AKEY_EVENT_ACTION_DOWN);
@ -12,67 +23,7 @@ convert_keycode_action(SDL_EventType from, enum android_keyevent_action *to) {
}
}
static enum android_metastate
autocomplete_metastate(enum android_metastate metastate) {
// fill dependent flags
if (metastate & (AMETA_SHIFT_LEFT_ON | AMETA_SHIFT_RIGHT_ON)) {
metastate |= AMETA_SHIFT_ON;
}
if (metastate & (AMETA_CTRL_LEFT_ON | AMETA_CTRL_RIGHT_ON)) {
metastate |= AMETA_CTRL_ON;
}
if (metastate & (AMETA_ALT_LEFT_ON | AMETA_ALT_RIGHT_ON)) {
metastate |= AMETA_ALT_ON;
}
if (metastate & (AMETA_META_LEFT_ON | AMETA_META_RIGHT_ON)) {
metastate |= AMETA_META_ON;
}
return metastate;
}
enum android_metastate
convert_meta_state(SDL_Keymod mod) {
enum android_metastate metastate = 0;
if (mod & KMOD_LSHIFT) {
metastate |= AMETA_SHIFT_LEFT_ON;
}
if (mod & KMOD_RSHIFT) {
metastate |= AMETA_SHIFT_RIGHT_ON;
}
if (mod & KMOD_LCTRL) {
metastate |= AMETA_CTRL_LEFT_ON;
}
if (mod & KMOD_RCTRL) {
metastate |= AMETA_CTRL_RIGHT_ON;
}
if (mod & KMOD_LALT) {
metastate |= AMETA_ALT_LEFT_ON;
}
if (mod & KMOD_RALT) {
metastate |= AMETA_ALT_RIGHT_ON;
}
if (mod & KMOD_LGUI) { // Windows key
metastate |= AMETA_META_LEFT_ON;
}
if (mod & KMOD_RGUI) { // Windows key
metastate |= AMETA_META_RIGHT_ON;
}
if (mod & KMOD_NUM) {
metastate |= AMETA_NUM_LOCK_ON;
}
if (mod & KMOD_CAPS) {
metastate |= AMETA_CAPS_LOCK_ON;
}
if (mod & KMOD_MODE) { // Alt Gr
// no mapping?
}
// fill the dependent fields
return autocomplete_metastate(metastate);
}
bool
static bool
convert_keycode(SDL_Keycode from, enum android_keycode *to, uint16_t mod,
bool prefer_text) {
switch (from) {
@ -154,42 +105,150 @@ convert_keycode(SDL_Keycode from, enum android_keycode *to, uint16_t mod,
}
}
enum android_motionevent_buttons
convert_mouse_buttons(uint32_t state) {
enum android_motionevent_buttons buttons = 0;
if (state & SDL_BUTTON_LMASK) {
buttons |= AMOTION_EVENT_BUTTON_PRIMARY;
static enum android_metastate
autocomplete_metastate(enum android_metastate metastate) {
// fill dependent flags
if (metastate & (AMETA_SHIFT_LEFT_ON | AMETA_SHIFT_RIGHT_ON)) {
metastate |= AMETA_SHIFT_ON;
}
if (state & SDL_BUTTON_RMASK) {
buttons |= AMOTION_EVENT_BUTTON_SECONDARY;
if (metastate & (AMETA_CTRL_LEFT_ON | AMETA_CTRL_RIGHT_ON)) {
metastate |= AMETA_CTRL_ON;
}
if (state & SDL_BUTTON_MMASK) {
buttons |= AMOTION_EVENT_BUTTON_TERTIARY;
if (metastate & (AMETA_ALT_LEFT_ON | AMETA_ALT_RIGHT_ON)) {
metastate |= AMETA_ALT_ON;
}
if (state & SDL_BUTTON_X1MASK) {
buttons |= AMOTION_EVENT_BUTTON_BACK;
if (metastate & (AMETA_META_LEFT_ON | AMETA_META_RIGHT_ON)) {
metastate |= AMETA_META_ON;
}
if (state & SDL_BUTTON_X2MASK) {
buttons |= AMOTION_EVENT_BUTTON_FORWARD;
}
return buttons;
return metastate;
}
bool
convert_mouse_action(SDL_EventType from, enum android_motionevent_action *to) {
switch (from) {
MAP(SDL_MOUSEBUTTONDOWN, AMOTION_EVENT_ACTION_DOWN);
MAP(SDL_MOUSEBUTTONUP, AMOTION_EVENT_ACTION_UP);
FAIL;
static enum android_metastate
convert_meta_state(SDL_Keymod mod) {
enum android_metastate metastate = 0;
if (mod & KMOD_LSHIFT) {
metastate |= AMETA_SHIFT_LEFT_ON;
}
if (mod & KMOD_RSHIFT) {
metastate |= AMETA_SHIFT_RIGHT_ON;
}
if (mod & KMOD_LCTRL) {
metastate |= AMETA_CTRL_LEFT_ON;
}
if (mod & KMOD_RCTRL) {
metastate |= AMETA_CTRL_RIGHT_ON;
}
if (mod & KMOD_LALT) {
metastate |= AMETA_ALT_LEFT_ON;
}
if (mod & KMOD_RALT) {
metastate |= AMETA_ALT_RIGHT_ON;
}
if (mod & KMOD_LGUI) { // Windows key
metastate |= AMETA_META_LEFT_ON;
}
if (mod & KMOD_RGUI) { // Windows key
metastate |= AMETA_META_RIGHT_ON;
}
if (mod & KMOD_NUM) {
metastate |= AMETA_NUM_LOCK_ON;
}
if (mod & KMOD_CAPS) {
metastate |= AMETA_CAPS_LOCK_ON;
}
if (mod & KMOD_MODE) { // Alt Gr
// no mapping?
}
// fill the dependent fields
return autocomplete_metastate(metastate);
}
static bool
convert_input_key(const SDL_KeyboardEvent *from, struct control_msg *to,
bool prefer_text, uint32_t repeat) {
to->type = CONTROL_MSG_TYPE_INJECT_KEYCODE;
if (!convert_keycode_action(from->type, &to->inject_keycode.action)) {
return false;
}
uint16_t mod = from->keysym.mod;
if (!convert_keycode(from->keysym.sym, &to->inject_keycode.keycode, mod,
prefer_text)) {
return false;
}
to->inject_keycode.repeat = repeat;
to->inject_keycode.metastate = convert_meta_state(mod);
return true;
}
static void
sc_key_processor_process_key(struct sc_key_processor *kp,
const SDL_KeyboardEvent *event) {
struct sc_keyboard_inject *ki = DOWNCAST(kp);
if (event->repeat) {
if (!ki->forward_key_repeat) {
return;
}
++ki->repeat;
} else {
ki->repeat = 0;
}
struct control_msg msg;
if (convert_input_key(event, &msg, ki->prefer_text, ki->repeat)) {
if (!controller_push_msg(ki->controller, &msg)) {
LOGW("Could not request 'inject keycode'");
}
}
}
bool
convert_touch_action(SDL_EventType from, enum android_motionevent_action *to) {
switch (from) {
MAP(SDL_FINGERMOTION, AMOTION_EVENT_ACTION_MOVE);
MAP(SDL_FINGERDOWN, AMOTION_EVENT_ACTION_DOWN);
MAP(SDL_FINGERUP, AMOTION_EVENT_ACTION_UP);
FAIL;
static void
sc_key_processor_process_text(struct sc_key_processor *kp,
const SDL_TextInputEvent *event) {
struct sc_keyboard_inject *ki = DOWNCAST(kp);
if (!ki->prefer_text) {
char c = event->text[0];
if (isalpha(c) || c == ' ') {
assert(event->text[1] == '\0');
// letters and space are handled as raw key event
return;
}
}
struct control_msg msg;
msg.type = CONTROL_MSG_TYPE_INJECT_TEXT;
msg.inject_text.text = strdup(event->text);
if (!msg.inject_text.text) {
LOGW("Could not strdup input text");
return;
}
if (!controller_push_msg(ki->controller, &msg)) {
free(msg.inject_text.text);
LOGW("Could not request 'inject text'");
}
}
void
sc_keyboard_inject_init(struct sc_keyboard_inject *ki,
struct controller *controller,
const struct scrcpy_options *options) {
ki->controller = controller;
ki->prefer_text = options->prefer_text;
ki->forward_key_repeat = options->forward_key_repeat;
ki->repeat = 0;
static const struct sc_key_processor_ops ops = {
.process_key = sc_key_processor_process_key,
.process_text = sc_key_processor_process_text,
};
ki->key_processor.ops = &ops;
}

30
app/src/keyboard_inject.h Normal file
View File

@ -0,0 +1,30 @@
#ifndef SC_KEYBOARD_INJECT_H
#define SC_KEYBOARD_INJECT_H
#include "common.h"
#include <stdbool.h>
#include "controller.h"
#include "options.h"
#include "trait/key_processor.h"
struct sc_keyboard_inject {
struct sc_key_processor key_processor; // key processor trait
struct controller *controller;
// SDL reports repeated events as a boolean, but Android expects the actual
// number of repetitions. This variable keeps track of the count.
unsigned repeat;
bool prefer_text;
bool forward_key_repeat;
};
void
sc_keyboard_inject_init(struct sc_keyboard_inject *ki,
struct controller *controller,
const struct scrcpy_options *options);
#endif

View File

@ -1,5 +1,3 @@
#include "scrcpy.h"
#include "common.h"
#include <assert.h>
@ -13,6 +11,8 @@
#include <SDL2/SDL.h>
#include "cli.h"
#include "options.h"
#include "scrcpy.h"
#include "util/log.h"
static void
@ -48,7 +48,7 @@ main(int argc, char *argv[]) {
#endif
struct scrcpy_cli_args args = {
.opts = SCRCPY_OPTIONS_DEFAULT,
.opts = scrcpy_options_default,
.help = false,
.version = false,
};

211
app/src/mouse_inject.c Normal file
View File

@ -0,0 +1,211 @@
#include "mouse_inject.h"
#include <assert.h>
#include <SDL2/SDL_events.h>
#include "android/input.h"
#include "control_msg.h"
#include "controller.h"
#include "util/log.h"
/** Downcast mouse processor to sc_mouse_inject */
#define DOWNCAST(MP) container_of(MP, struct sc_mouse_inject, mouse_processor)
static enum android_motionevent_buttons
convert_mouse_buttons(uint32_t state) {
enum android_motionevent_buttons buttons = 0;
if (state & SDL_BUTTON_LMASK) {
buttons |= AMOTION_EVENT_BUTTON_PRIMARY;
}
if (state & SDL_BUTTON_RMASK) {
buttons |= AMOTION_EVENT_BUTTON_SECONDARY;
}
if (state & SDL_BUTTON_MMASK) {
buttons |= AMOTION_EVENT_BUTTON_TERTIARY;
}
if (state & SDL_BUTTON_X1MASK) {
buttons |= AMOTION_EVENT_BUTTON_BACK;
}
if (state & SDL_BUTTON_X2MASK) {
buttons |= AMOTION_EVENT_BUTTON_FORWARD;
}
return buttons;
}
#define MAP(FROM, TO) case FROM: *to = TO; return true
#define FAIL default: return false
static bool
convert_mouse_action(SDL_EventType from, enum android_motionevent_action *to) {
switch (from) {
MAP(SDL_MOUSEBUTTONDOWN, AMOTION_EVENT_ACTION_DOWN);
MAP(SDL_MOUSEBUTTONUP, AMOTION_EVENT_ACTION_UP);
FAIL;
}
}
static bool
convert_touch_action(SDL_EventType from, enum android_motionevent_action *to) {
switch (from) {
MAP(SDL_FINGERMOTION, AMOTION_EVENT_ACTION_MOVE);
MAP(SDL_FINGERDOWN, AMOTION_EVENT_ACTION_DOWN);
MAP(SDL_FINGERUP, AMOTION_EVENT_ACTION_UP);
FAIL;
}
}
static bool
convert_mouse_motion(const SDL_MouseMotionEvent *from, struct screen *screen,
struct control_msg *to) {
to->type = CONTROL_MSG_TYPE_INJECT_TOUCH_EVENT;
to->inject_touch_event.action = AMOTION_EVENT_ACTION_MOVE;
to->inject_touch_event.pointer_id = POINTER_ID_MOUSE;
to->inject_touch_event.position.screen_size = screen->frame_size;
to->inject_touch_event.position.point =
screen_convert_window_to_frame_coords(screen, from->x, from->y);
to->inject_touch_event.pressure = 1.f;
to->inject_touch_event.buttons = convert_mouse_buttons(from->state);
return true;
}
static bool
convert_touch(const SDL_TouchFingerEvent *from, struct screen *screen,
struct control_msg *to) {
to->type = CONTROL_MSG_TYPE_INJECT_TOUCH_EVENT;
if (!convert_touch_action(from->type, &to->inject_touch_event.action)) {
return false;
}
to->inject_touch_event.pointer_id = from->fingerId;
to->inject_touch_event.position.screen_size = screen->frame_size;
int dw;
int dh;
SDL_GL_GetDrawableSize(screen->window, &dw, &dh);
// SDL touch event coordinates are normalized in the range [0; 1]
int32_t x = from->x * dw;
int32_t y = from->y * dh;
to->inject_touch_event.position.point =
screen_convert_drawable_to_frame_coords(screen, x, y);
to->inject_touch_event.pressure = from->pressure;
to->inject_touch_event.buttons = 0;
return true;
}
static bool
convert_mouse_button(const SDL_MouseButtonEvent *from, struct screen *screen,
struct control_msg *to) {
to->type = CONTROL_MSG_TYPE_INJECT_TOUCH_EVENT;
if (!convert_mouse_action(from->type, &to->inject_touch_event.action)) {
return false;
}
to->inject_touch_event.pointer_id = POINTER_ID_MOUSE;
to->inject_touch_event.position.screen_size = screen->frame_size;
to->inject_touch_event.position.point =
screen_convert_window_to_frame_coords(screen, from->x, from->y);
to->inject_touch_event.pressure =
from->type == SDL_MOUSEBUTTONDOWN ? 1.f : 0.f;
to->inject_touch_event.buttons =
convert_mouse_buttons(SDL_BUTTON(from->button));
return true;
}
static bool
convert_mouse_wheel(const SDL_MouseWheelEvent *from, struct screen *screen,
struct control_msg *to) {
// mouse_x and mouse_y are expressed in pixels relative to the window
int mouse_x;
int mouse_y;
SDL_GetMouseState(&mouse_x, &mouse_y);
struct position position = {
.screen_size = screen->frame_size,
.point = screen_convert_window_to_frame_coords(screen,
mouse_x, mouse_y),
};
to->type = CONTROL_MSG_TYPE_INJECT_SCROLL_EVENT;
to->inject_scroll_event.position = position;
to->inject_scroll_event.hscroll = from->x;
to->inject_scroll_event.vscroll = from->y;
return true;
}
static void
sc_mouse_processor_process_mouse_motion(struct sc_mouse_processor *mp,
const SDL_MouseMotionEvent *event) {
struct sc_mouse_inject *mi = DOWNCAST(mp);
struct control_msg msg;
if (!convert_mouse_motion(event, mi->screen, &msg)) {
return;
}
if (!controller_push_msg(mi->controller, &msg)) {
LOGW("Could not request 'inject mouse motion event'");
}
}
static void
sc_mouse_processor_process_touch(struct sc_mouse_processor *mp,
const SDL_TouchFingerEvent *event) {
struct sc_mouse_inject *mi = DOWNCAST(mp);
struct control_msg msg;
if (convert_touch(event, mi->screen, &msg)) {
if (!controller_push_msg(mi->controller, &msg)) {
LOGW("Could not request 'inject touch event'");
}
}
}
static void
sc_mouse_processor_process_mouse_button(struct sc_mouse_processor *mp,
const SDL_MouseButtonEvent *event) {
struct sc_mouse_inject *mi = DOWNCAST(mp);
struct control_msg msg;
if (convert_mouse_button(event, mi->screen, &msg)) {
if (!controller_push_msg(mi->controller, &msg)) {
LOGW("Could not request 'inject mouse button event'");
}
}
}
static void
sc_mouse_processor_process_mouse_wheel(struct sc_mouse_processor *mp,
const SDL_MouseWheelEvent *event) {
struct sc_mouse_inject *mi = DOWNCAST(mp);
struct control_msg msg;
if (convert_mouse_wheel(event, mi->screen, &msg)) {
if (!controller_push_msg(mi->controller, &msg)) {
LOGW("Could not request 'inject mouse wheel event'");
}
}
}
void
sc_mouse_inject_init(struct sc_mouse_inject *mi, struct controller *controller,
struct screen *screen) {
mi->controller = controller;
mi->screen = screen;
static const struct sc_mouse_processor_ops ops = {
.process_mouse_motion = sc_mouse_processor_process_mouse_motion,
.process_touch = sc_mouse_processor_process_touch,
.process_mouse_button = sc_mouse_processor_process_mouse_button,
.process_mouse_wheel = sc_mouse_processor_process_mouse_wheel,
};
mi->mouse_processor.ops = &ops;
}

23
app/src/mouse_inject.h Normal file
View File

@ -0,0 +1,23 @@
#ifndef SC_MOUSE_INJECT_H
#define SC_MOUSE_INJECT_H
#include "common.h"
#include <stdbool.h>
#include "controller.h"
#include "screen.h"
#include "trait/mouse_processor.h"
struct sc_mouse_inject {
struct sc_mouse_processor mouse_processor; // mouse processor trait
struct controller *controller;
struct screen *screen;
};
void
sc_mouse_inject_init(struct sc_mouse_inject *mi, struct controller *controller,
struct screen *screen);
#endif

54
app/src/options.c Normal file
View File

@ -0,0 +1,54 @@
#include "options.h"
const struct scrcpy_options scrcpy_options_default = {
.serial = NULL,
.crop = NULL,
.record_filename = NULL,
.window_title = NULL,
.push_target = NULL,
.render_driver = NULL,
.codec_options = NULL,
.encoder_name = NULL,
#ifdef HAVE_V4L2
.v4l2_device = NULL,
#endif
.log_level = SC_LOG_LEVEL_INFO,
.record_format = SC_RECORD_FORMAT_AUTO,
.keyboard_input_mode = SC_KEYBOARD_INPUT_MODE_INJECT,
.port_range = {
.first = DEFAULT_LOCAL_PORT_RANGE_FIRST,
.last = DEFAULT_LOCAL_PORT_RANGE_LAST,
},
.shortcut_mods = {
.data = {SC_MOD_LALT, SC_MOD_LSUPER},
.count = 2,
},
.max_size = 0,
.bit_rate = DEFAULT_BIT_RATE,
.max_fps = 0,
.lock_video_orientation = SC_LOCK_VIDEO_ORIENTATION_UNLOCKED,
.rotation = 0,
.window_x = SC_WINDOW_POSITION_UNDEFINED,
.window_y = SC_WINDOW_POSITION_UNDEFINED,
.window_width = 0,
.window_height = 0,
.display_id = 0,
.display_buffer = 0,
.v4l2_buffer = 0,
.show_touches = false,
.fullscreen = false,
.always_on_top = false,
.control = true,
.display = true,
.turn_screen_off = false,
.prefer_text = false,
.window_borderless = false,
.mipmaps = true,
.stay_awake = false,
.force_adb_forward = false,
.disable_screensaver = false,
.forward_key_repeat = true,
.forward_all_clicks = false,
.legacy_paste = false,
.power_off_on_close = false,
};

113
app/src/options.h Normal file
View File

@ -0,0 +1,113 @@
#ifndef SCRCPY_OPTIONS_H
#define SCRCPY_OPTIONS_H
#include "common.h"
#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
#include "util/tick.h"
enum sc_log_level {
SC_LOG_LEVEL_VERBOSE,
SC_LOG_LEVEL_DEBUG,
SC_LOG_LEVEL_INFO,
SC_LOG_LEVEL_WARN,
SC_LOG_LEVEL_ERROR,
};
enum sc_record_format {
SC_RECORD_FORMAT_AUTO,
SC_RECORD_FORMAT_MP4,
SC_RECORD_FORMAT_MKV,
};
enum sc_lock_video_orientation {
SC_LOCK_VIDEO_ORIENTATION_UNLOCKED = -1,
// lock the current orientation when scrcpy starts
SC_LOCK_VIDEO_ORIENTATION_INITIAL = -2,
SC_LOCK_VIDEO_ORIENTATION_0 = 0,
SC_LOCK_VIDEO_ORIENTATION_1,
SC_LOCK_VIDEO_ORIENTATION_2,
SC_LOCK_VIDEO_ORIENTATION_3,
};
enum sc_keyboard_input_mode {
SC_KEYBOARD_INPUT_MODE_INJECT,
SC_KEYBOARD_INPUT_MODE_HID,
};
#define SC_MAX_SHORTCUT_MODS 8
enum sc_shortcut_mod {
SC_MOD_LCTRL = 1 << 0,
SC_MOD_RCTRL = 1 << 1,
SC_MOD_LALT = 1 << 2,
SC_MOD_RALT = 1 << 3,
SC_MOD_LSUPER = 1 << 4,
SC_MOD_RSUPER = 1 << 5,
};
struct sc_shortcut_mods {
unsigned data[SC_MAX_SHORTCUT_MODS];
unsigned count;
};
struct sc_port_range {
uint16_t first;
uint16_t last;
};
#define SC_WINDOW_POSITION_UNDEFINED (-0x8000)
struct scrcpy_options {
const char *serial;
const char *crop;
const char *record_filename;
const char *window_title;
const char *push_target;
const char *render_driver;
const char *codec_options;
const char *encoder_name;
#ifdef HAVE_V4L2
const char *v4l2_device;
#endif
enum sc_log_level log_level;
enum sc_record_format record_format;
enum sc_keyboard_input_mode keyboard_input_mode;
struct sc_port_range port_range;
struct sc_shortcut_mods shortcut_mods;
uint16_t max_size;
uint32_t bit_rate;
uint16_t max_fps;
enum sc_lock_video_orientation lock_video_orientation;
uint8_t rotation;
int16_t window_x; // SC_WINDOW_POSITION_UNDEFINED for "auto"
int16_t window_y; // SC_WINDOW_POSITION_UNDEFINED for "auto"
uint16_t window_width;
uint16_t window_height;
uint32_t display_id;
sc_tick display_buffer;
sc_tick v4l2_buffer;
bool show_touches;
bool fullscreen;
bool always_on_top;
bool control;
bool display;
bool turn_screen_off;
bool prefer_text;
bool window_borderless;
bool mipmaps;
bool stay_awake;
bool force_adb_forward;
bool disable_screensaver;
bool forward_key_repeat;
bool forward_all_clicks;
bool legacy_paste;
bool power_off_on_close;
};
extern const struct scrcpy_options scrcpy_options_default;
#endif

View File

@ -7,7 +7,7 @@
#include "util/log.h"
bool
receiver_init(struct receiver *receiver, socket_t control_socket) {
receiver_init(struct receiver *receiver, sc_socket control_socket) {
bool ok = sc_mutex_init(&receiver->mutex);
if (!ok) {
return false;

View File

@ -11,13 +11,13 @@
// receive events from the device
// managed by the controller
struct receiver {
socket_t control_socket;
sc_socket control_socket;
sc_thread thread;
sc_mutex mutex;
};
bool
receiver_init(struct receiver *receiver, socket_t control_socket);
receiver_init(struct receiver *receiver, sc_socket control_socket);
void
receiver_destroy(struct receiver *receiver);

View File

@ -7,7 +7,7 @@
#include <libavformat/avformat.h>
#include "coords.h"
#include "scrcpy.h"
#include "options.h"
#include "trait/packet_sink.h"
#include "util/queue.h"
#include "util/thread.h"

View File

@ -18,11 +18,15 @@
#include "events.h"
#include "file_handler.h"
#include "input_manager.h"
#ifdef HAVE_AOA_HID
# include "hid_keyboard.h"
#endif
#include "keyboard_inject.h"
#include "mouse_inject.h"
#include "recorder.h"
#include "screen.h"
#include "server.h"
#include "stream.h"
#include "tiny_xpm.h"
#include "util/log.h"
#include "util/net.h"
#ifdef HAVE_V4L2
@ -40,6 +44,16 @@ struct scrcpy {
#endif
struct controller controller;
struct file_handler file_handler;
#ifdef HAVE_AOA_HID
struct sc_aoa aoa;
#endif
union {
struct sc_keyboard_inject keyboard_inject;
#ifdef HAVE_AOA_HID
struct sc_hid_keyboard keyboard_hid;
#endif
};
struct sc_mouse_inject mouse_inject;
struct input_manager input_manager;
};
@ -240,14 +254,10 @@ stream_on_eos(struct stream *stream, void *userdata) {
}
bool
scrcpy(const struct scrcpy_options *options) {
scrcpy(struct scrcpy_options *options) {
static struct scrcpy scrcpy;
struct scrcpy *s = &scrcpy;
if (!server_init(&s->server)) {
return false;
}
bool ret = false;
bool server_started = false;
@ -257,6 +267,9 @@ scrcpy(const struct scrcpy_options *options) {
bool v4l2_sink_initialized = false;
#endif
bool stream_started = false;
#ifdef HAVE_AOA_HID
bool aoa_hid_initialized = false;
#endif
bool controller_initialized = false;
bool controller_started = false;
bool screen_initialized = false;
@ -280,7 +293,12 @@ scrcpy(const struct scrcpy_options *options) {
.force_adb_forward = options->force_adb_forward,
.power_off_on_close = options->power_off_on_close,
};
if (!server_start(&s->server, &params)) {
if (!server_init(&s->server, &params)) {
return false;
}
if (!server_start(&s->server)) {
goto end;
}
@ -299,7 +317,7 @@ scrcpy(const struct scrcpy_options *options) {
}
if (options->display && options->control) {
if (!file_handler_init(&s->file_handler, s->server.serial,
if (!file_handler_init(&s->file_handler, options->serial,
options->push_target)) {
goto end;
}
@ -412,7 +430,77 @@ scrcpy(const struct scrcpy_options *options) {
}
stream_started = true;
input_manager_init(&s->input_manager, &s->controller, &s->screen, options);
struct sc_key_processor *kp = NULL;
struct sc_mouse_processor *mp = NULL;
if (options->control) {
if (options->keyboard_input_mode == SC_KEYBOARD_INPUT_MODE_HID) {
#ifdef HAVE_AOA_HID
bool aoa_hid_ok = false;
char *serialno = NULL;
const char *serial = options->serial;
if (!serial) {
serialno = adb_get_serialno();
if (!serialno) {
LOGE("Could not get device serial");
goto aoa_hid_end;
}
serial = serialno;
LOGI("Device serial: %s", serial);
}
bool ok = sc_aoa_init(&s->aoa, serial);
free(serialno);
if (!ok) {
goto aoa_hid_end;
}
if (!sc_hid_keyboard_init(&s->keyboard_hid, &s->aoa)) {
sc_aoa_destroy(&s->aoa);
goto aoa_hid_end;
}
if (!sc_aoa_start(&s->aoa)) {
sc_hid_keyboard_destroy(&s->keyboard_hid);
sc_aoa_destroy(&s->aoa);
goto aoa_hid_end;
}
aoa_hid_ok = true;
kp = &s->keyboard_hid.key_processor;
aoa_hid_initialized = true;
aoa_hid_end:
if (!aoa_hid_ok) {
LOGE("Failed to enable HID over AOA, "
"fallback to default keyboard injection method "
"(-K/--hid-keyboard ignored)");
options->keyboard_input_mode = SC_KEYBOARD_INPUT_MODE_INJECT;
}
#else
LOGE("HID over AOA is not supported on this platform, "
"fallback to default keyboard injection method "
"(-K/--hid-keyboard ignored)");
options->keyboard_input_mode = SC_KEYBOARD_INPUT_MODE_INJECT;
#endif
}
// keyboard_input_mode may have been reset if HID mode failed
if (options->keyboard_input_mode == SC_KEYBOARD_INPUT_MODE_INJECT) {
sc_keyboard_inject_init(&s->keyboard_inject, &s->controller,
options);
kp = &s->keyboard_inject.key_processor;
}
sc_mouse_inject_init(&s->mouse_inject, &s->controller, &s->screen);
mp = &s->mouse_inject.mouse_processor;
}
input_manager_init(&s->input_manager, &s->controller, &s->screen, kp, mp,
options);
ret = event_loop(s, options);
LOGD("quit...");
@ -424,6 +512,12 @@ scrcpy(const struct scrcpy_options *options) {
end:
// The stream is not stopped explicitly, because it will stop by itself on
// end-of-stream
#ifdef HAVE_AOA_HID
if (aoa_hid_initialized) {
sc_hid_keyboard_destroy(&s->keyboard_hid);
sc_aoa_stop(&s->aoa);
}
#endif
if (controller_started) {
controller_stop(&s->controller);
}
@ -451,6 +545,13 @@ end:
}
#endif
#ifdef HAVE_AOA_HID
if (aoa_hid_initialized) {
sc_aoa_join(&s->aoa);
sc_aoa_destroy(&s->aoa);
}
#endif
// Destroy the screen only after the stream is guaranteed to be finished,
// because otherwise the screen could receive new frames after destruction
if (screen_initialized) {

View File

@ -4,153 +4,9 @@
#include "common.h"
#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
#include "util/tick.h"
enum sc_log_level {
SC_LOG_LEVEL_VERBOSE,
SC_LOG_LEVEL_DEBUG,
SC_LOG_LEVEL_INFO,
SC_LOG_LEVEL_WARN,
SC_LOG_LEVEL_ERROR,
};
enum sc_record_format {
SC_RECORD_FORMAT_AUTO,
SC_RECORD_FORMAT_MP4,
SC_RECORD_FORMAT_MKV,
};
enum sc_lock_video_orientation {
SC_LOCK_VIDEO_ORIENTATION_UNLOCKED = -1,
// lock the current orientation when scrcpy starts
SC_LOCK_VIDEO_ORIENTATION_INITIAL = -2,
SC_LOCK_VIDEO_ORIENTATION_0 = 0,
SC_LOCK_VIDEO_ORIENTATION_1,
SC_LOCK_VIDEO_ORIENTATION_2,
SC_LOCK_VIDEO_ORIENTATION_3,
};
#define SC_MAX_SHORTCUT_MODS 8
enum sc_shortcut_mod {
SC_MOD_LCTRL = 1 << 0,
SC_MOD_RCTRL = 1 << 1,
SC_MOD_LALT = 1 << 2,
SC_MOD_RALT = 1 << 3,
SC_MOD_LSUPER = 1 << 4,
SC_MOD_RSUPER = 1 << 5,
};
struct sc_shortcut_mods {
unsigned data[SC_MAX_SHORTCUT_MODS];
unsigned count;
};
struct sc_port_range {
uint16_t first;
uint16_t last;
};
#define SC_WINDOW_POSITION_UNDEFINED (-0x8000)
struct scrcpy_options {
const char *serial;
const char *crop;
const char *record_filename;
const char *window_title;
const char *push_target;
const char *render_driver;
const char *codec_options;
const char *encoder_name;
const char *v4l2_device;
enum sc_log_level log_level;
enum sc_record_format record_format;
struct sc_port_range port_range;
struct sc_shortcut_mods shortcut_mods;
uint16_t max_size;
uint32_t bit_rate;
uint16_t max_fps;
enum sc_lock_video_orientation lock_video_orientation;
uint8_t rotation;
int16_t window_x; // SC_WINDOW_POSITION_UNDEFINED for "auto"
int16_t window_y; // SC_WINDOW_POSITION_UNDEFINED for "auto"
uint16_t window_width;
uint16_t window_height;
uint32_t display_id;
sc_tick display_buffer;
sc_tick v4l2_buffer;
bool show_touches;
bool fullscreen;
bool always_on_top;
bool control;
bool display;
bool turn_screen_off;
bool prefer_text;
bool window_borderless;
bool mipmaps;
bool stay_awake;
bool force_adb_forward;
bool disable_screensaver;
bool forward_key_repeat;
bool forward_all_clicks;
bool legacy_paste;
bool power_off_on_close;
};
#define SCRCPY_OPTIONS_DEFAULT { \
.serial = NULL, \
.crop = NULL, \
.record_filename = NULL, \
.window_title = NULL, \
.push_target = NULL, \
.render_driver = NULL, \
.codec_options = NULL, \
.encoder_name = NULL, \
.v4l2_device = NULL, \
.log_level = SC_LOG_LEVEL_INFO, \
.record_format = SC_RECORD_FORMAT_AUTO, \
.port_range = { \
.first = DEFAULT_LOCAL_PORT_RANGE_FIRST, \
.last = DEFAULT_LOCAL_PORT_RANGE_LAST, \
}, \
.shortcut_mods = { \
.data = {SC_MOD_LALT, SC_MOD_LSUPER}, \
.count = 2, \
}, \
.max_size = 0, \
.bit_rate = DEFAULT_BIT_RATE, \
.max_fps = 0, \
.lock_video_orientation = SC_LOCK_VIDEO_ORIENTATION_UNLOCKED, \
.rotation = 0, \
.window_x = SC_WINDOW_POSITION_UNDEFINED, \
.window_y = SC_WINDOW_POSITION_UNDEFINED, \
.window_width = 0, \
.window_height = 0, \
.display_id = 0, \
.display_buffer = 0, \
.v4l2_buffer = 0, \
.show_touches = false, \
.fullscreen = false, \
.always_on_top = false, \
.control = true, \
.display = true, \
.turn_screen_off = false, \
.prefer_text = false, \
.window_borderless = false, \
.mipmaps = true, \
.stay_awake = false, \
.force_adb_forward = false, \
.disable_screensaver = false, \
.forward_key_repeat = true, \
.forward_all_clicks = false, \
.legacy_paste = false, \
.power_off_on_close = false, \
}
#include "options.h"
bool
scrcpy(const struct scrcpy_options *options);
scrcpy(struct scrcpy_options *options);
#endif

View File

@ -5,9 +5,8 @@
#include <SDL2/SDL.h>
#include "events.h"
#include "icon.xpm"
#include "scrcpy.h"
#include "tiny_xpm.h"
#include "icon.h"
#include "options.h"
#include "video_buffer.h"
#include "util/log.h"
@ -405,10 +404,10 @@ screen_init(struct screen *screen, const struct screen_params *params) {
LOGD("Trilinear filtering disabled (not an OpenGL renderer)");
}
SDL_Surface *icon = read_xpm(icon_xpm);
SDL_Surface *icon = scrcpy_icon_load();
if (icon) {
SDL_SetWindowIcon(screen->window, icon);
SDL_FreeSurface(icon);
scrcpy_icon_destroy(icon);
} else {
LOGW("Could not load icon");
}

View File

@ -3,7 +3,6 @@
#include <assert.h>
#include <errno.h>
#include <inttypes.h>
#include <libgen.h>
#include <stdio.h>
#include <SDL2/SDL_timer.h>
#include <SDL2/SDL_platform.h>
@ -48,54 +47,56 @@ get_server_path(void) {
LOGE("Could not allocate memory");
return NULL;
}
// the absolute path is hardcoded
return server_path;
#else
// use scrcpy-server in the same directory as the executable
char *executable_path = get_executable_path();
if (!executable_path) {
LOGE("Could not get executable path, "
"using " SERVER_FILENAME " from current directory");
// not found, use current directory
return strdup(SERVER_FILENAME);
}
// dirname() does not work correctly everywhere, so get the parent
// directory manually.
// See <https://github.com/Genymobile/scrcpy/issues/2619>
char *p = strrchr(executable_path, PATH_SEPARATOR);
if (!p) {
LOGE("Unexpected executable path: \"%s\" (it should contain a '%c')",
executable_path, PATH_SEPARATOR);
free(executable_path);
return strdup(SERVER_FILENAME);
}
*p = '\0'; // modify executable_path in place
char *dir = executable_path;
size_t dirlen = strlen(dir);
// sizeof(SERVER_FILENAME) gives statically the size including the null byte
size_t len = dirlen + 1 + sizeof(SERVER_FILENAME);
char *server_path = malloc(len);
char *server_path = get_local_file_path(SERVER_FILENAME);
if (!server_path) {
LOGE("Could not alloc server path string, "
LOGE("Could not get local file path, "
"using " SERVER_FILENAME " from current directory");
free(executable_path);
return strdup(SERVER_FILENAME);
}
memcpy(server_path, dir, dirlen);
server_path[dirlen] = PATH_SEPARATOR;
memcpy(&server_path[dirlen + 1], SERVER_FILENAME, sizeof(SERVER_FILENAME));
// the final null byte has been copied with SERVER_FILENAME
free(executable_path);
LOGD("Using server (portable): %s", server_path);
return server_path;
#endif
return server_path;
}
static void
server_params_destroy(struct server_params *params) {
// The server stores a copy of the params provided by the user
free((char *) params->serial);
free((char *) params->crop);
free((char *) params->codec_options);
free((char *) params->encoder_name);
}
static bool
server_params_copy(struct server_params *dst, const struct server_params *src) {
*dst = *src;
// The params reference user-allocated memory, so we must copy them to
// handle them from another thread
#define COPY(FIELD) \
dst->FIELD = NULL; \
if (src->FIELD) { \
dst->FIELD = strdup(src->FIELD); \
if (!dst->FIELD) { \
goto error; \
} \
}
COPY(serial);
COPY(crop);
COPY(codec_options);
COPY(encoder_name);
#undef COPY
return true;
error:
server_params_destroy(dst);
return false;
}
static bool
@ -140,13 +141,14 @@ disable_tunnel_forward(const char *serial, uint16_t local_port) {
static bool
disable_tunnel(struct server *server) {
const char *serial = server->params.serial;
if (server->tunnel_forward) {
return disable_tunnel_forward(server->serial, server->local_port);
return disable_tunnel_forward(serial, server->local_port);
}
return disable_tunnel_reverse(server->serial);
return disable_tunnel_reverse(serial);
}
static socket_t
static sc_socket
listen_on_port(uint16_t port) {
#define IPV4_LOCALHOST 0x7F000001
return net_listen(IPV4_LOCALHOST, port, 1);
@ -155,9 +157,10 @@ listen_on_port(uint16_t port) {
static bool
enable_tunnel_reverse_any_port(struct server *server,
struct sc_port_range port_range) {
const char *serial = server->params.serial;
uint16_t port = port_range.first;
for (;;) {
if (!enable_tunnel_reverse(server->serial, port)) {
if (!enable_tunnel_reverse(serial, port)) {
// the command itself failed, it will fail on any port
return false;
}
@ -169,14 +172,14 @@ enable_tunnel_reverse_any_port(struct server *server,
// need to try to connect until the server socket is listening on the
// device.
server->server_socket = listen_on_port(port);
if (server->server_socket != INVALID_SOCKET) {
if (server->server_socket != SC_INVALID_SOCKET) {
// success
server->local_port = port;
return true;
}
// failure, disable tunnel and try another port
if (!disable_tunnel_reverse(server->serial)) {
if (!disable_tunnel_reverse(serial)) {
LOGW("Could not remove reverse tunnel on port %" PRIu16, port);
}
@ -202,9 +205,11 @@ static bool
enable_tunnel_forward_any_port(struct server *server,
struct sc_port_range port_range) {
server->tunnel_forward = true;
const char *serial = server->params.serial;
uint16_t port = port_range.first;
for (;;) {
if (enable_tunnel_forward(server->serial, port)) {
if (enable_tunnel_forward(serial, port)) {
// success
server->local_port = port;
return true;
@ -266,6 +271,8 @@ log_level_to_server_string(enum sc_log_level level) {
static process_t
execute_server(struct server *server, const struct server_params *params) {
const char *serial = server->params.serial;
char max_size_string[6];
char bit_rate_string[11];
char max_fps_string[6];
@ -323,14 +330,14 @@ execute_server(struct server *server, const struct server_params *params) {
// Port: 5005
// Then click on "Debug"
#endif
return adb_execute(server->serial, cmd, ARRAY_LEN(cmd));
return adb_execute(serial, cmd, ARRAY_LEN(cmd));
}
static socket_t
static sc_socket
connect_and_read_byte(uint16_t port) {
socket_t socket = net_connect(IPV4_LOCALHOST, port);
if (socket == INVALID_SOCKET) {
return INVALID_SOCKET;
sc_socket socket = net_connect(IPV4_LOCALHOST, port);
if (socket == SC_INVALID_SOCKET) {
return SC_INVALID_SOCKET;
}
char byte;
@ -339,17 +346,17 @@ connect_and_read_byte(uint16_t port) {
if (net_recv(socket, &byte, 1) != 1) {
// the server is not listening yet behind the adb tunnel
net_close(socket);
return INVALID_SOCKET;
return SC_INVALID_SOCKET;
}
return socket;
}
static socket_t
static sc_socket
connect_to_server(uint16_t port, uint32_t attempts, uint32_t delay) {
do {
LOGD("Remaining connection attempts: %d", (int) attempts);
socket_t socket = connect_and_read_byte(port);
if (socket != INVALID_SOCKET) {
sc_socket socket = connect_and_read_byte(port);
if (socket != SC_INVALID_SOCKET) {
// it worked!
return socket;
}
@ -357,41 +364,37 @@ connect_to_server(uint16_t port, uint32_t attempts, uint32_t delay) {
SDL_Delay(delay);
}
} while (--attempts > 0);
return INVALID_SOCKET;
}
static void
close_socket(socket_t socket) {
assert(socket != INVALID_SOCKET);
net_shutdown(socket, SHUT_RDWR);
if (!net_close(socket)) {
LOGW("Could not close socket");
}
return SC_INVALID_SOCKET;
}
bool
server_init(struct server *server) {
server->serial = NULL;
server->process = PROCESS_NONE;
atomic_flag_clear_explicit(&server->server_socket_closed,
memory_order_relaxed);
bool ok = sc_mutex_init(&server->mutex);
server_init(struct server *server, const struct server_params *params) {
bool ok = server_params_copy(&server->params, params);
if (!ok) {
LOGE("Could not copy server params");
return false;
}
server->process = PROCESS_NONE;
ok = sc_mutex_init(&server->mutex);
if (!ok) {
server_params_destroy(&server->params);
return false;
}
ok = sc_cond_init(&server->process_terminated_cond);
if (!ok) {
sc_mutex_destroy(&server->mutex);
server_params_destroy(&server->params);
return false;
}
server->process_terminated = false;
server->server_socket = INVALID_SOCKET;
server->video_socket = INVALID_SOCKET;
server->control_socket = INVALID_SOCKET;
server->server_socket = SC_INVALID_SOCKET;
server->video_socket = SC_INVALID_SOCKET;
server->control_socket = SC_INVALID_SOCKET;
server->local_port = 0;
@ -413,24 +416,18 @@ run_wait_server(void *data) {
// no need for synchronization, server_socket is initialized before this
// thread was created
if (server->server_socket != INVALID_SOCKET
&& !atomic_flag_test_and_set(&server->server_socket_closed)) {
// On Linux, accept() is unblocked by shutdown(), but on Windows, it is
// unblocked by closesocket(). Therefore, call both (close_socket()).
close_socket(server->server_socket);
if (server->server_socket != SC_INVALID_SOCKET) {
// Unblock any accept()
net_interrupt(server->server_socket);
}
LOGD("Server terminated");
return 0;
}
bool
server_start(struct server *server, const struct server_params *params) {
if (params->serial) {
server->serial = strdup(params->serial);
if (!server->serial) {
return false;
}
}
server_start(struct server *server) {
const struct server_params *params = &server->params;
if (!push_server(params->serial)) {
/* server->serial will be freed on server_destroy() */
@ -467,21 +464,16 @@ server_start(struct server *server, const struct server_params *params) {
return true;
error:
if (!server->tunnel_forward) {
bool was_closed =
atomic_flag_test_and_set(&server->server_socket_closed);
// the thread is not started, the flag could not be already set
assert(!was_closed);
(void) was_closed;
close_socket(server->server_socket);
}
// The server socket (if any) will be closed on server_destroy()
disable_tunnel(server);
return false;
}
static bool
device_read_info(socket_t device_socket, char *device_name, struct size *size) {
device_read_info(sc_socket device_socket, char *device_name,
struct size *size) {
unsigned char buf[DEVICE_NAME_FIELD_LENGTH + 4];
ssize_t r = net_recv_all(device_socket, buf, sizeof(buf));
if (r < DEVICE_NAME_FIELD_LENGTH + 4) {
@ -504,35 +496,35 @@ bool
server_connect_to(struct server *server, char *device_name, struct size *size) {
if (!server->tunnel_forward) {
server->video_socket = net_accept(server->server_socket);
if (server->video_socket == INVALID_SOCKET) {
if (server->video_socket == SC_INVALID_SOCKET) {
return false;
}
server->control_socket = net_accept(server->server_socket);
if (server->control_socket == INVALID_SOCKET) {
if (server->control_socket == SC_INVALID_SOCKET) {
// the video_socket will be cleaned up on destroy
return false;
}
// we don't need the server socket anymore
if (!atomic_flag_test_and_set(&server->server_socket_closed)) {
// close it from here
close_socket(server->server_socket);
// otherwise, it is closed by run_wait_server()
if (!net_close(server->server_socket)) {
LOGW("Could not close server socket on connect");
}
// Do not attempt to close it again on server_destroy()
server->server_socket = SC_INVALID_SOCKET;
} else {
uint32_t attempts = 100;
uint32_t delay = 100; // ms
server->video_socket =
connect_to_server(server->local_port, attempts, delay);
if (server->video_socket == INVALID_SOCKET) {
if (server->video_socket == SC_INVALID_SOCKET) {
return false;
}
// we know that the device is listening, we don't need several attempts
server->control_socket =
net_connect(IPV4_LOCALHOST, server->local_port);
if (server->control_socket == INVALID_SOCKET) {
if (server->control_socket == SC_INVALID_SOCKET) {
return false;
}
}
@ -547,15 +539,20 @@ server_connect_to(struct server *server, char *device_name, struct size *size) {
void
server_stop(struct server *server) {
if (server->server_socket != INVALID_SOCKET
&& !atomic_flag_test_and_set(&server->server_socket_closed)) {
close_socket(server->server_socket);
if (server->server_socket != SC_INVALID_SOCKET) {
if (!net_interrupt(server->server_socket)) {
LOGW("Could not interrupt server socket");
}
}
if (server->video_socket != INVALID_SOCKET) {
close_socket(server->video_socket);
if (server->video_socket != SC_INVALID_SOCKET) {
if (!net_interrupt(server->video_socket)) {
LOGW("Could not interrupt video socket");
}
}
if (server->control_socket != INVALID_SOCKET) {
close_socket(server->control_socket);
if (server->control_socket != SC_INVALID_SOCKET) {
if (!net_interrupt(server->control_socket)) {
LOGW("Could not interrupt control socket");
}
}
assert(server->process != PROCESS_NONE);
@ -592,7 +589,23 @@ server_stop(struct server *server) {
void
server_destroy(struct server *server) {
free(server->serial);
if (server->server_socket != SC_INVALID_SOCKET) {
if (!net_close(server->server_socket)) {
LOGW("Could not close server socket");
}
}
if (server->video_socket != SC_INVALID_SOCKET) {
if (!net_close(server->video_socket)) {
LOGW("Could not close video socket");
}
}
if (server->control_socket != SC_INVALID_SOCKET) {
if (!net_close(server->control_socket)) {
LOGW("Could not close control socket");
}
}
server_params_destroy(&server->params);
sc_cond_destroy(&server->process_terminated_cond);
sc_mutex_destroy(&server->mutex);
}

View File

@ -9,29 +9,11 @@
#include "adb.h"
#include "coords.h"
#include "scrcpy.h"
#include "options.h"
#include "util/log.h"
#include "util/net.h"
#include "util/thread.h"
struct server {
char *serial;
process_t process;
sc_thread wait_server_thread;
atomic_flag server_socket_closed;
sc_mutex mutex;
sc_cond process_terminated_cond;
bool process_terminated;
socket_t server_socket; // only used if !tunnel_forward
socket_t video_socket;
socket_t control_socket;
uint16_t local_port; // selected from port_range
bool tunnel_enabled;
bool tunnel_forward; // use "adb forward" instead of "adb reverse"
};
struct server_params {
const char *serial;
enum sc_log_level log_level;
@ -51,13 +33,32 @@ struct server_params {
bool power_off_on_close;
};
// init default values
struct server {
// The internal allocated strings are copies owned by the server
struct server_params params;
process_t process;
sc_thread wait_server_thread;
sc_mutex mutex;
sc_cond process_terminated_cond;
bool process_terminated;
sc_socket server_socket; // only used if !tunnel_forward
sc_socket video_socket;
sc_socket control_socket;
uint16_t local_port; // selected from port_range
bool tunnel_enabled;
bool tunnel_forward; // use "adb forward" instead of "adb reverse"
};
// init the server with the given params
bool
server_init(struct server *server);
server_init(struct server *server, const struct server_params *params);
// push, enable tunnel et start the server
bool
server_start(struct server *server, const struct server_params *params);
server_start(struct server *server);
#define DEVICE_NAME_FIELD_LENGTH 64
// block until the communication with the server is established

View File

@ -260,7 +260,7 @@ end:
}
void
stream_init(struct stream *stream, socket_t socket,
stream_init(struct stream *stream, sc_socket socket,
const struct stream_callbacks *cbs, void *cbs_userdata) {
stream->socket = socket;
stream->pending = NULL;

View File

@ -14,7 +14,7 @@
#define STREAM_MAX_SINKS 2
struct stream {
socket_t socket;
sc_socket socket;
sc_thread thread;
struct sc_packet_sink *sinks[STREAM_MAX_SINKS];
@ -35,7 +35,7 @@ struct stream_callbacks {
};
void
stream_init(struct stream *stream, socket_t socket,
stream_init(struct stream *stream, sc_socket socket,
const struct stream_callbacks *cbs, void *cbs_userdata);
void

View File

@ -1,5 +1,6 @@
#include "util/process.h"
#include <assert.h>
#include <errno.h>
#include <fcntl.h>
#include <limits.h>
@ -50,65 +51,151 @@ search_executable(const char *file) {
}
enum process_result
process_execute(const char *const argv[], pid_t *pid) {
int fd[2];
process_execute_redirect(const char *const argv[], pid_t *pid, int *pipe_stdin,
int *pipe_stdout, int *pipe_stderr) {
int in[2];
int out[2];
int err[2];
int internal[2]; // communication between parent and children
if (pipe(fd) == -1) {
if (pipe(internal) == -1) {
perror("pipe");
return PROCESS_ERROR_GENERIC;
}
enum process_result ret = PROCESS_SUCCESS;
if (pipe_stdin) {
if (pipe(in) == -1) {
perror("pipe");
close(internal[0]);
close(internal[1]);
return PROCESS_ERROR_GENERIC;
}
}
if (pipe_stdout) {
if (pipe(out) == -1) {
perror("pipe");
// clean up
if (pipe_stdin) {
close(in[0]);
close(in[1]);
}
close(internal[0]);
close(internal[1]);
return PROCESS_ERROR_GENERIC;
}
}
if (pipe_stderr) {
if (pipe(err) == -1) {
perror("pipe");
// clean up
if (pipe_stdout) {
close(out[0]);
close(out[1]);
}
if (pipe_stdin) {
close(in[0]);
close(in[1]);
}
close(internal[0]);
close(internal[1]);
return PROCESS_ERROR_GENERIC;
}
}
*pid = fork();
if (*pid == -1) {
perror("fork");
ret = PROCESS_ERROR_GENERIC;
goto end;
// clean up
if (pipe_stderr) {
close(err[0]);
close(err[1]);
}
if (pipe_stdout) {
close(out[0]);
close(out[1]);
}
if (pipe_stdin) {
close(in[0]);
close(in[1]);
}
close(internal[0]);
close(internal[1]);
return PROCESS_ERROR_GENERIC;
}
if (*pid > 0) {
// parent close write side
close(fd[1]);
fd[1] = -1;
// wait for EOF or receive errno from child
if (read(fd[0], &ret, sizeof(ret)) == -1) {
perror("read");
ret = PROCESS_ERROR_GENERIC;
goto end;
}
} else if (*pid == 0) {
// child close read side
close(fd[0]);
if (fcntl(fd[1], F_SETFD, FD_CLOEXEC) == 0) {
execvp(argv[0], (char *const *)argv);
if (errno == ENOENT) {
ret = PROCESS_ERROR_MISSING_BINARY;
} else {
ret = PROCESS_ERROR_GENERIC;
if (*pid == 0) {
if (pipe_stdin) {
if (in[0] != STDIN_FILENO) {
dup2(in[0], STDIN_FILENO);
close(in[0]);
}
close(in[1]);
}
if (pipe_stdout) {
if (out[1] != STDOUT_FILENO) {
dup2(out[1], STDOUT_FILENO);
close(out[1]);
}
close(out[0]);
}
if (pipe_stderr) {
if (err[1] != STDERR_FILENO) {
dup2(err[1], STDERR_FILENO);
close(err[1]);
}
close(err[0]);
}
close(internal[0]);
enum process_result err;
if (fcntl(internal[1], F_SETFD, FD_CLOEXEC) == 0) {
execvp(argv[0], (char *const *) argv);
perror("exec");
err = errno == ENOENT ? PROCESS_ERROR_MISSING_BINARY
: PROCESS_ERROR_GENERIC;
} else {
perror("fcntl");
ret = PROCESS_ERROR_GENERIC;
err = PROCESS_ERROR_GENERIC;
}
// send ret to the parent
if (write(fd[1], &ret, sizeof(ret)) == -1) {
// send err to the parent
if (write(internal[1], &err, sizeof(err)) == -1) {
perror("write");
}
// close write side before exiting
close(fd[1]);
close(internal[1]);
_exit(1);
}
end:
if (fd[0] != -1) {
close(fd[0]);
// parent
assert(*pid > 0);
close(internal[1]);
enum process_result res = PROCESS_SUCCESS;
// wait for EOF or receive err from child
if (read(internal[0], &res, sizeof(res)) == -1) {
perror("read");
res = PROCESS_ERROR_GENERIC;
}
if (fd[1] != -1) {
close(fd[1]);
close(internal[0]);
if (pipe_stdin) {
close(in[0]);
*pipe_stdin = in[1];
}
return ret;
if (pipe_stdout) {
*pipe_stdout = out[0];
close(out[1]);
}
if (pipe_stderr) {
*pipe_stderr = err[0];
close(err[1]);
}
return res;
}
enum process_result
process_execute(const char *const argv[], pid_t *pid) {
return process_execute_redirect(argv, pid, NULL, NULL, NULL);
}
bool
@ -175,3 +262,15 @@ is_regular_file(const char *path) {
}
return S_ISREG(path_stat.st_mode);
}
ssize_t
read_pipe(int pipe, char *data, size_t len) {
return read(pipe, data, len);
}
void
close_pipe(int pipe) {
if (close(pipe)) {
perror("close pipe");
}
}

View File

@ -23,38 +23,129 @@ build_cmd(char *cmd, size_t len, const char *const argv[]) {
}
enum process_result
process_execute(const char *const argv[], HANDLE *handle) {
process_execute_redirect(const char *const argv[], HANDLE *handle,
HANDLE *pipe_stdin, HANDLE *pipe_stdout,
HANDLE *pipe_stderr) {
enum process_result ret = PROCESS_ERROR_GENERIC;
SECURITY_ATTRIBUTES sa;
sa.nLength = sizeof(SECURITY_ATTRIBUTES);
sa.lpSecurityDescriptor = NULL;
sa.bInheritHandle = TRUE;
HANDLE stdin_read_handle;
HANDLE stdout_write_handle;
HANDLE stderr_write_handle;
if (pipe_stdin) {
if (!CreatePipe(&stdin_read_handle, pipe_stdin, &sa, 0)) {
perror("pipe");
return PROCESS_ERROR_GENERIC;
}
if (!SetHandleInformation(*pipe_stdin, HANDLE_FLAG_INHERIT, 0)) {
LOGE("SetHandleInformation stdin failed");
goto error_close_stdin;
}
}
if (pipe_stdout) {
if (!CreatePipe(pipe_stdout, &stdout_write_handle, &sa, 0)) {
perror("pipe");
goto error_close_stdin;
}
if (!SetHandleInformation(*pipe_stdout, HANDLE_FLAG_INHERIT, 0)) {
LOGE("SetHandleInformation stdout failed");
goto error_close_stdout;
}
}
if (pipe_stderr) {
if (!CreatePipe(pipe_stderr, &stderr_write_handle, &sa, 0)) {
perror("pipe");
goto error_close_stdout;
}
if (!SetHandleInformation(*pipe_stderr, HANDLE_FLAG_INHERIT, 0)) {
LOGE("SetHandleInformation stderr failed");
goto error_close_stderr;
}
}
STARTUPINFOW si;
PROCESS_INFORMATION pi;
memset(&si, 0, sizeof(si));
si.cb = sizeof(si);
if (pipe_stdin || pipe_stdout || pipe_stderr) {
si.dwFlags = STARTF_USESTDHANDLES;
if (pipe_stdin) {
si.hStdInput = stdin_read_handle;
}
if (pipe_stdout) {
si.hStdOutput = stdout_write_handle;
}
if (pipe_stderr) {
si.hStdError = stderr_write_handle;
}
}
char *cmd = malloc(CMD_MAX_LEN);
if (!cmd || !build_cmd(cmd, CMD_MAX_LEN, argv)) {
*handle = NULL;
return PROCESS_ERROR_GENERIC;
goto error_close_stderr;
}
wchar_t *wide = utf8_to_wide_char(cmd);
free(cmd);
if (!wide) {
LOGC("Could not allocate wide char string");
return PROCESS_ERROR_GENERIC;
goto error_close_stderr;
}
if (!CreateProcessW(NULL, wide, NULL, NULL, FALSE, 0, NULL, NULL, &si,
if (!CreateProcessW(NULL, wide, NULL, NULL, TRUE, 0, NULL, NULL, &si,
&pi)) {
free(wide);
*handle = NULL;
if (GetLastError() == ERROR_FILE_NOT_FOUND) {
return PROCESS_ERROR_MISSING_BINARY;
ret = PROCESS_ERROR_MISSING_BINARY;
}
return PROCESS_ERROR_GENERIC;
goto error_close_stderr;
}
// These handles are used by the child process, close them for this process
if (pipe_stdin) {
CloseHandle(stdin_read_handle);
}
if (pipe_stdout) {
CloseHandle(stdout_write_handle);
}
if (pipe_stderr) {
CloseHandle(stderr_write_handle);
}
free(wide);
*handle = pi.hProcess;
return PROCESS_SUCCESS;
error_close_stderr:
if (pipe_stderr) {
CloseHandle(*pipe_stderr);
CloseHandle(stderr_write_handle);
}
error_close_stdout:
if (pipe_stdout) {
CloseHandle(*pipe_stdout);
CloseHandle(stdout_write_handle);
}
error_close_stdin:
if (pipe_stdin) {
CloseHandle(*pipe_stdin);
CloseHandle(stdin_read_handle);
}
return ret;
}
enum process_result
process_execute(const char *const argv[], HANDLE *handle) {
return process_execute_redirect(argv, handle, NULL, NULL, NULL);
}
bool
@ -116,3 +207,19 @@ is_regular_file(const char *path) {
}
return S_ISREG(path_stat.st_mode);
}
ssize_t
read_pipe(HANDLE pipe, char *data, size_t len) {
DWORD r;
if (!ReadFile(pipe, data, len, &r, NULL)) {
return -1;
}
return r;
}
void
close_pipe(HANDLE pipe) {
if (!CloseHandle(pipe)) {
LOGW("Cannot close pipe");
}
}

View File

@ -1,119 +0,0 @@
#include "tiny_xpm.h"
#include <assert.h>
#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include "util/log.h"
struct index {
char c;
uint32_t color;
};
static bool
find_color(struct index *index, int len, char c, uint32_t *color) {
// there are typically very few color, so it's ok to iterate over the array
for (int i = 0; i < len; ++i) {
if (index[i].c == c) {
*color = index[i].color;
return true;
}
}
*color = 0;
return false;
}
// We encounter some problems with SDL2_image on MSYS2 (Windows),
// so here is our own XPM parsing not to depend on SDL_image.
//
// We do not hardcode the binary image to keep some flexibility to replace the
// icon easily (just by replacing icon.xpm).
//
// Parameter is not "const char *" because XPM formats are generally stored in a
// (non-const) "char *"
SDL_Surface *
read_xpm(char *xpm[]) {
#ifndef NDEBUG
// patch the XPM to change the icon color in debug mode
xpm[2] = ". c #CC00CC";
#endif
char *endptr;
// *** No error handling, assume the XPM source is valid ***
// (it's in our source repo)
// Assertions are only checked in debug
int width = strtol(xpm[0], &endptr, 10);
int height = strtol(endptr + 1, &endptr, 10);
int colors = strtol(endptr + 1, &endptr, 10);
int chars = strtol(endptr + 1, &endptr, 10);
// sanity checks
assert(0 <= width && width < 256);
assert(0 <= height && height < 256);
assert(0 <= colors && colors < 256);
assert(chars == 1); // this implementation does not support more
(void) chars;
// init index
struct index index[colors];
for (int i = 0; i < colors; ++i) {
const char *line = xpm[1+i];
index[i].c = line[0];
assert(line[1] == '\t');
assert(line[2] == 'c');
assert(line[3] == ' ');
if (line[4] == '#') {
index[i].color = 0xff000000 | strtol(&line[5], &endptr, 0x10);
assert(*endptr == '\0');
} else {
assert(!strcmp("None", &line[4]));
index[i].color = 0;
}
}
// parse image
uint32_t *pixels = SDL_malloc(4 * width * height);
if (!pixels) {
LOGE("Could not allocate icon memory");
return NULL;
}
for (int y = 0; y < height; ++y) {
const char *line = xpm[1 + colors + y];
for (int x = 0; x < width; ++x) {
char c = line[x];
uint32_t color;
bool color_found = find_color(index, colors, c, &color);
assert(color_found);
(void) color_found;
pixels[y * width + x] = color;
}
}
#if SDL_BYTEORDER == SDL_BIG_ENDIAN
uint32_t amask = 0x000000ff;
uint32_t rmask = 0x0000ff00;
uint32_t gmask = 0x00ff0000;
uint32_t bmask = 0xff000000;
#else // little endian, like x86
uint32_t amask = 0xff000000;
uint32_t rmask = 0x00ff0000;
uint32_t gmask = 0x0000ff00;
uint32_t bmask = 0x000000ff;
#endif
SDL_Surface *surface = SDL_CreateRGBSurfaceFrom(pixels,
width, height,
32, 4 * width,
rmask, gmask, bmask, amask);
if (!surface) {
LOGE("Could not create icon surface");
return NULL;
}
// make the surface own the raw pixels
surface->flags &= ~SDL_PREALLOC;
return surface;
}

View File

@ -1,11 +0,0 @@
#ifndef TINYXPM_H
#define TINYXPM_H
#include "common.h"
#include <SDL2/SDL.h>
SDL_Surface *
read_xpm(char *xpm[]);
#endif

View File

@ -1,5 +1,5 @@
#ifndef SC_FRAME_SINK
#define SC_FRAME_SINK
#ifndef SC_FRAME_SINK_H
#define SC_FRAME_SINK_H
#include "common.h"

View File

@ -0,0 +1,29 @@
#ifndef SC_KEY_PROCESSOR_H
#define SC_KEY_PROCESSOR_H
#include "common.h"
#include <assert.h>
#include <stdbool.h>
#include <SDL2/SDL_events.h>
/**
* Key processor trait.
*
* Component able to process and inject keys should implement this trait.
*/
struct sc_key_processor {
const struct sc_key_processor_ops *ops;
};
struct sc_key_processor_ops {
void
(*process_key)(struct sc_key_processor *kp, const SDL_KeyboardEvent *event);
void
(*process_text)(struct sc_key_processor *kp,
const SDL_TextInputEvent *event);
};
#endif

View File

@ -0,0 +1,39 @@
#ifndef SC_MOUSE_PROCESSOR_H
#define SC_MOUSE_PROCESSOR_H
#include "common.h"
#include <assert.h>
#include <stdbool.h>
#include <SDL2/SDL_events.h>
/**
* Mouse processor trait.
*
* Component able to process and inject mouse events should implement this
* trait.
*/
struct sc_mouse_processor {
const struct sc_mouse_processor_ops *ops;
};
struct sc_mouse_processor_ops {
void
(*process_mouse_motion)(struct sc_mouse_processor *mp,
const SDL_MouseMotionEvent *event);
void
(*process_touch)(struct sc_mouse_processor *mp,
const SDL_TouchFingerEvent *event);
void
(*process_mouse_button)(struct sc_mouse_processor *mp,
const SDL_MouseButtonEvent *event);
void
(*process_mouse_wheel)(struct sc_mouse_processor *mp,
const SDL_MouseWheelEvent *event);
};
#endif

View File

@ -1,5 +1,5 @@
#ifndef SC_PACKET_SINK
#define SC_PACKET_SINK
#ifndef SC_PACKET_SINK_H
#define SC_PACKET_SINK_H
#include "common.h"

View File

@ -5,7 +5,7 @@
#include <SDL2/SDL_log.h>
#include "scrcpy.h"
#include "options.h"
#define LOGV(...) SDL_LogVerbose(SDL_LOG_CATEGORY_APPLICATION, __VA_ARGS__)
#define LOGD(...) SDL_LogDebug(SDL_LOG_CATEGORY_APPLICATION, __VA_ARGS__)

View File

@ -1,5 +1,6 @@
#include "net.h"
#include <assert.h>
#include <stdio.h>
#include <SDL2/SDL_platform.h>
@ -7,6 +8,7 @@
#ifdef __WINDOWS__
typedef int socklen_t;
typedef SOCKET sc_raw_socket;
#else
# include <sys/types.h>
# include <sys/socket.h>
@ -17,123 +19,9 @@
typedef struct sockaddr_in SOCKADDR_IN;
typedef struct sockaddr SOCKADDR;
typedef struct in_addr IN_ADDR;
typedef int sc_raw_socket;
#endif
static void
net_perror(const char *s) {
#ifdef _WIN32
int error = WSAGetLastError();
char *wsa_message;
FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM,
NULL, error, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
(char *) &wsa_message, 0, NULL);
// no explicit '\n', wsa_message already contains a trailing '\n'
fprintf(stderr, "%s: [%d] %s", s, error, wsa_message);
LocalFree(wsa_message);
#else
perror(s);
#endif
}
socket_t
net_connect(uint32_t addr, uint16_t port) {
socket_t sock = socket(AF_INET, SOCK_STREAM, 0);
if (sock == INVALID_SOCKET) {
net_perror("socket");
return INVALID_SOCKET;
}
SOCKADDR_IN sin;
sin.sin_family = AF_INET;
sin.sin_addr.s_addr = htonl(addr);
sin.sin_port = htons(port);
if (connect(sock, (SOCKADDR *) &sin, sizeof(sin)) == SOCKET_ERROR) {
net_perror("connect");
net_close(sock);
return INVALID_SOCKET;
}
return sock;
}
socket_t
net_listen(uint32_t addr, uint16_t port, int backlog) {
socket_t sock = socket(AF_INET, SOCK_STREAM, 0);
if (sock == INVALID_SOCKET) {
net_perror("socket");
return INVALID_SOCKET;
}
int reuse = 1;
if (setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, (const void *) &reuse,
sizeof(reuse)) == -1) {
net_perror("setsockopt(SO_REUSEADDR)");
}
SOCKADDR_IN sin;
sin.sin_family = AF_INET;
sin.sin_addr.s_addr = htonl(addr); // htonl() harmless on INADDR_ANY
sin.sin_port = htons(port);
if (bind(sock, (SOCKADDR *) &sin, sizeof(sin)) == SOCKET_ERROR) {
net_perror("bind");
net_close(sock);
return INVALID_SOCKET;
}
if (listen(sock, backlog) == SOCKET_ERROR) {
net_perror("listen");
net_close(sock);
return INVALID_SOCKET;
}
return sock;
}
socket_t
net_accept(socket_t server_socket) {
SOCKADDR_IN csin;
socklen_t sinsize = sizeof(csin);
return accept(server_socket, (SOCKADDR *) &csin, &sinsize);
}
ssize_t
net_recv(socket_t socket, void *buf, size_t len) {
return recv(socket, buf, len, 0);
}
ssize_t
net_recv_all(socket_t socket, void *buf, size_t len) {
return recv(socket, buf, len, MSG_WAITALL);
}
ssize_t
net_send(socket_t socket, const void *buf, size_t len) {
return send(socket, buf, len, 0);
}
ssize_t
net_send_all(socket_t socket, const void *buf, size_t len) {
size_t copied = 0;
ssize_t w = 0;
while (len > 0) {
w = send(socket, buf, len, 0);
if (w == -1) {
return copied ? (ssize_t) copied : -1;
}
len -= w;
buf = (char *) buf + w;
copied += w;
}
return copied;
}
bool
net_shutdown(socket_t socket, int how) {
return !shutdown(socket, how);
}
bool
net_init(void) {
#ifdef __WINDOWS__
@ -154,11 +42,189 @@ net_cleanup(void) {
#endif
}
bool
net_close(socket_t socket) {
static inline sc_socket
wrap(sc_raw_socket sock) {
#ifdef __WINDOWS__
return !closesocket(socket);
if (sock == INVALID_SOCKET) {
return SC_INVALID_SOCKET;
}
struct sc_socket_windows *socket = malloc(sizeof(*socket));
if (!socket) {
closesocket(sock);
return SC_INVALID_SOCKET;
}
socket->socket = sock;
socket->closed = (atomic_flag) ATOMIC_FLAG_INIT;
return socket;
#else
return !close(socket);
return sock;
#endif
}
static inline sc_raw_socket
unwrap(sc_socket socket) {
#ifdef __WINDOWS__
if (socket == SC_INVALID_SOCKET) {
return INVALID_SOCKET;
}
return socket->socket;
#else
return socket;
#endif
}
static void
net_perror(const char *s) {
#ifdef _WIN32
int error = WSAGetLastError();
char *wsa_message;
FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM,
NULL, error, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
(char *) &wsa_message, 0, NULL);
// no explicit '\n', wsa_message already contains a trailing '\n'
fprintf(stderr, "%s: [%d] %s", s, error, wsa_message);
LocalFree(wsa_message);
#else
perror(s);
#endif
}
sc_socket
net_connect(uint32_t addr, uint16_t port) {
sc_raw_socket raw_sock = socket(AF_INET, SOCK_STREAM, 0);
sc_socket sock = wrap(raw_sock);
if (sock == SC_INVALID_SOCKET) {
net_perror("socket");
return SC_INVALID_SOCKET;
}
SOCKADDR_IN sin;
sin.sin_family = AF_INET;
sin.sin_addr.s_addr = htonl(addr);
sin.sin_port = htons(port);
if (connect(raw_sock, (SOCKADDR *) &sin, sizeof(sin)) == SOCKET_ERROR) {
net_perror("connect");
net_close(sock);
return SC_INVALID_SOCKET;
}
return sock;
}
sc_socket
net_listen(uint32_t addr, uint16_t port, int backlog) {
sc_raw_socket raw_sock = socket(AF_INET, SOCK_STREAM, 0);
sc_socket sock = wrap(raw_sock);
if (sock == SC_INVALID_SOCKET) {
net_perror("socket");
return SC_INVALID_SOCKET;
}
int reuse = 1;
if (setsockopt(raw_sock, SOL_SOCKET, SO_REUSEADDR, (const void *) &reuse,
sizeof(reuse)) == -1) {
net_perror("setsockopt(SO_REUSEADDR)");
}
SOCKADDR_IN sin;
sin.sin_family = AF_INET;
sin.sin_addr.s_addr = htonl(addr); // htonl() harmless on INADDR_ANY
sin.sin_port = htons(port);
if (bind(raw_sock, (SOCKADDR *) &sin, sizeof(sin)) == SOCKET_ERROR) {
net_perror("bind");
net_close(sock);
return SC_INVALID_SOCKET;
}
if (listen(raw_sock, backlog) == SOCKET_ERROR) {
net_perror("listen");
net_close(sock);
return SC_INVALID_SOCKET;
}
return sock;
}
sc_socket
net_accept(sc_socket server_socket) {
sc_raw_socket raw_server_socket = unwrap(server_socket);
SOCKADDR_IN csin;
socklen_t sinsize = sizeof(csin);
sc_raw_socket raw_sock =
accept(raw_server_socket, (SOCKADDR *) &csin, &sinsize);
return wrap(raw_sock);
}
ssize_t
net_recv(sc_socket socket, void *buf, size_t len) {
sc_raw_socket raw_sock = unwrap(socket);
return recv(raw_sock, buf, len, 0);
}
ssize_t
net_recv_all(sc_socket socket, void *buf, size_t len) {
sc_raw_socket raw_sock = unwrap(socket);
return recv(raw_sock, buf, len, MSG_WAITALL);
}
ssize_t
net_send(sc_socket socket, const void *buf, size_t len) {
sc_raw_socket raw_sock = unwrap(socket);
return send(raw_sock, buf, len, 0);
}
ssize_t
net_send_all(sc_socket socket, const void *buf, size_t len) {
size_t copied = 0;
while (len > 0) {
ssize_t w = net_send(socket, buf, len);
if (w == -1) {
return copied ? (ssize_t) copied : -1;
}
len -= w;
buf = (char *) buf + w;
copied += w;
}
return copied;
}
bool
net_interrupt(sc_socket socket) {
assert(socket != SC_INVALID_SOCKET);
sc_raw_socket raw_sock = unwrap(socket);
#ifdef __WINDOWS__
if (!atomic_flag_test_and_set(&socket->closed)) {
return !closesocket(raw_sock);
}
return true;
#else
return !shutdown(raw_sock, SHUT_RDWR);
#endif
}
#include <errno.h>
bool
net_close(sc_socket socket) {
sc_raw_socket raw_sock = unwrap(socket);
#ifdef __WINDOWS__
bool ret = true;
if (!atomic_flag_test_and_set(&socket->closed)) {
ret = !closesocket(raw_sock);
}
free(socket);
return ret;
#else
return !close(raw_sock);
#endif
}

View File

@ -8,15 +8,20 @@
#include <SDL2/SDL_platform.h>
#ifdef __WINDOWS__
# include <winsock2.h>
#define SHUT_RD SD_RECEIVE
#define SHUT_WR SD_SEND
#define SHUT_RDWR SD_BOTH
typedef SOCKET socket_t;
#else
# include <stdatomic.h>
# define SC_INVALID_SOCKET NULL
typedef struct sc_socket_windows {
SOCKET socket;
atomic_flag closed;
} *sc_socket;
#else // not __WINDOWS__
# include <sys/socket.h>
# define INVALID_SOCKET -1
typedef int socket_t;
# define SC_INVALID_SOCKET -1
typedef int sc_socket;
#endif
bool
@ -25,33 +30,36 @@ net_init(void);
void
net_cleanup(void);
socket_t
sc_socket
net_connect(uint32_t addr, uint16_t port);
socket_t
sc_socket
net_listen(uint32_t addr, uint16_t port, int backlog);
socket_t
net_accept(socket_t server_socket);
sc_socket
net_accept(sc_socket server_socket);
// the _all versions wait/retry until len bytes have been written/read
ssize_t
net_recv(socket_t socket, void *buf, size_t len);
net_recv(sc_socket socket, void *buf, size_t len);
ssize_t
net_recv_all(socket_t socket, void *buf, size_t len);
net_recv_all(sc_socket socket, void *buf, size_t len);
ssize_t
net_send(socket_t socket, const void *buf, size_t len);
net_send(sc_socket socket, const void *buf, size_t len);
ssize_t
net_send_all(socket_t socket, const void *buf, size_t len);
net_send_all(sc_socket socket, const void *buf, size_t len);
// how is SHUT_RD (read), SHUT_WR (write) or SHUT_RDWR (both)
// Shutdown the socket (or close on Windows) so that any blocking send() or
// recv() are interrupted.
bool
net_shutdown(socket_t socket, int how);
net_interrupt(sc_socket socket);
// Close the socket.
// A socket must always be closed, even if net_interrupt() has been called.
bool
net_close(socket_t socket);
net_close(sc_socket socket);
#endif

View File

@ -1,5 +1,6 @@
#include "process.h"
#include <libgen.h>
#include "log.h"
bool
@ -19,3 +20,59 @@ process_check_success(process_t proc, const char *name, bool close) {
}
return true;
}
char *
get_local_file_path(const char *name) {
char *executable_path = get_executable_path();
if (!executable_path) {
return NULL;
}
// dirname() does not work correctly everywhere, so get the parent
// directory manually.
// See <https://github.com/Genymobile/scrcpy/issues/2619>
char *p = strrchr(executable_path, PATH_SEPARATOR);
if (!p) {
LOGE("Unexpected executable path: \"%s\" (it should contain a '%c')",
executable_path, PATH_SEPARATOR);
free(executable_path);
return NULL;
}
*p = '\0'; // modify executable_path in place
char *dir = executable_path;
size_t dirlen = strlen(dir);
size_t namelen = strlen(name);
size_t len = dirlen + namelen + 2; // +2: '/' and '\0'
char *file_path = malloc(len);
if (!file_path) {
LOGE("Could not alloc path");
free(executable_path);
return NULL;
}
memcpy(file_path, dir, dirlen);
file_path[dirlen] = PATH_SEPARATOR;
// namelen + 1 to copy the final '\0'
memcpy(&file_path[dirlen + 1], name, namelen + 1);
free(executable_path);
return file_path;
}
ssize_t
read_pipe_all(pipe_t pipe, char *data, size_t len) {
size_t copied = 0;
while (len > 0) {
ssize_t r = read_pipe(pipe, data, len);
if (r <= 0) {
return copied ? (ssize_t) copied : r;
}
len -= r;
data += r;
copied += r;
}
return copied;
}

View File

@ -18,6 +18,7 @@
# define NO_EXIT_CODE -1u // max value as unsigned
typedef HANDLE process_t;
typedef DWORD exit_code_t;
typedef HANDLE pipe_t;
#else
@ -29,6 +30,7 @@
# define NO_EXIT_CODE -1
typedef pid_t process_t;
typedef int exit_code_t;
typedef int pipe_t;
#endif
@ -42,6 +44,14 @@ enum process_result {
enum process_result
process_execute(const char *const argv[], process_t *process);
enum process_result
process_execute_redirect(const char *const argv[], process_t *process,
pipe_t *pipe_stdin, pipe_t *pipe_stdout,
pipe_t *pipe_stderr);
bool
process_terminate(process_t pid);
// kill the process
bool
process_terminate(process_t pid);
@ -74,8 +84,22 @@ search_executable(const char *file);
char *
get_executable_path(void);
// Return the absolute path of a file in the same directory as he executable.
// May be NULL on error. To be freed by free().
char *
get_local_file_path(const char *name);
// returns true if the file exists and is not a directory
bool
is_regular_file(const char *path);
ssize_t
read_pipe(pipe_t pipe, char *data, size_t len);
ssize_t
read_pipe_all(pipe_t pipe, char *data, size_t len);
void
close_pipe(pipe_t pipe);
#endif

View File

@ -183,8 +183,11 @@ sc_v4l2_sink_open(struct sc_v4l2_sink *vs) {
goto error_mutex_destroy;
}
// FIXME
const AVOutputFormat *format = find_muxer("video4linux2,v4l2");
const AVOutputFormat *format = find_muxer("v4l2");
if (!format) {
// Alternative name
format = find_muxer("video4linux2");
}
if (!format) {
LOGE("Could not find v4l2 muxer");
goto error_cond_destroy;

View File

@ -4,11 +4,11 @@
#include <string.h>
#include "cli.h"
#include "scrcpy.h"
#include "options.h"
static void test_flag_version(void) {
struct scrcpy_cli_args args = {
.opts = SCRCPY_OPTIONS_DEFAULT,
.opts = scrcpy_options_default,
.help = false,
.version = false,
};
@ -23,7 +23,7 @@ static void test_flag_version(void) {
static void test_flag_help(void) {
struct scrcpy_cli_args args = {
.opts = SCRCPY_OPTIONS_DEFAULT,
.opts = scrcpy_options_default,
.help = false,
.version = false,
};
@ -38,7 +38,7 @@ static void test_flag_help(void) {
static void test_options(void) {
struct scrcpy_cli_args args = {
.opts = SCRCPY_OPTIONS_DEFAULT,
.opts = scrcpy_options_default,
.help = false,
.version = false,
};
@ -100,7 +100,7 @@ static void test_options(void) {
static void test_options2(void) {
struct scrcpy_cli_args args = {
.opts = SCRCPY_OPTIONS_DEFAULT,
.opts = scrcpy_options_default,
.help = false,
.version = false,
};

BIN
data/icon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.4 KiB

16
data/icon.svg Normal file
View File

@ -0,0 +1,16 @@
<svg xmlns="http://www.w3.org/2000/svg" width="48" height="48" version="1.1">
<path style="opacity:0.2" d="m 16.846877,12 c -1.116351,0 -2.227419,0.912229 -2.015075,2 l 3.122973,16 -5.596557,11.109375 C 11.959876,41.871734 11.885244,42.336988 12.177176,43 c 0.278672,0.632897 0.998812,1 1.747448,1 H 24 34.075375 c 0.748637,0 1.468777,-0.367103 1.747448,-1 0.291932,-0.663012 0.217302,-1.128266 -0.181041,-1.890625 L 30.045225,30 33.168198,14 c 0.212344,-1.087771 -0.898724,-2 -2.015075,-2 H 24 Z"/>
<path style="fill:#cccccc" d="m 16.846877,11 c -1.116351,0 -2.227419,0.912229 -2.015075,2 l 3.122973,16 -5.596557,11.109375 C 11.959876,40.871734 11.885244,41.336988 12.177176,42 c 0.278672,0.632897 0.998812,1 1.747448,1 H 24 34.075375 c 0.748637,0 1.468777,-0.367103 1.747448,-1 0.291932,-0.663012 0.217302,-1.128266 -0.181041,-1.890625 L 30.045225,29 33.168198,13 c 0.212344,-1.087771 -0.898724,-2 -2.015075,-2 H 24 Z"/>
<rect style="opacity:0.2" width="40" height="32" x="4" y="6" rx="2" ry="2"/>
<path style="fill:#e4e4e4" d="m 4,33 v 2 c 0,1.108 0.892,2 2,2 h 36 c 1.108,0 2,-0.892 2,-2 v -2 z"/>
<path style="opacity:0.1" d="m 11.494141,15 a 1.5,1.5 0 0 0 -0.832032,0.255859 1.5,1.5 0 0 0 -0.40625,2.082032 l 3.13086,4.654297 C 10.404945,24.606192 8.4012371,28.299159 8.0019531,32.460938 7.9284599,34.000879 9.5546875,34 9.5546875,34 H 38.40625 c 0,0 1.672856,-3.38e-4 1.591797,-1.617188 -0.416529,-4.131451 -2.415618,-7.796833 -5.380859,-10.394531 l 3.126953,-4.65039 a 1.5,1.5 0 0 0 -0.40625,-2.082032 1.5,1.5 0 0 0 -1.125,-0.228515 1.5,1.5 0 0 0 -0.957032,0.634765 l -3.072265,4.566407 C 29.78649,18.814887 26.990024,18 24.001953,18 c -2.989385,0 -5.786177,0.815488 -8.183594,2.230469 l -3.074218,-4.56836 A 1.5,1.5 0 0 0 11.787109,15.027344 1.5,1.5 0 0 0 11.494141,15 Z"/>
<path style="fill:#077063" d="M 6,5 C 4.892,5 4,5.892 4,7 V 33 H 44 V 7 C 44,5.892 43.108,5 42,5 Z"/>
<path style="opacity:0.1;fill:#ffffff" d="M 6,5 C 4.892,5 4,5.892 4,7 V 8 C 4,6.892 4.892,6 6,6 h 36 c 1.108,0 2,0.892 2,2 V 7 C 44,5.892 43.108,5 42,5 Z"/>
<path style="fill:none;stroke:#30dd81;stroke-width:3;stroke-linecap:round" d="M 15.1998,21.000026 11.5,15.5"/>
<path style="fill:none;stroke:#30dd81;stroke-width:3;stroke-linecap:round" d="M 32.799764,21.000026 36.5,15.5"/>
<path style="fill:#30dd81" d="m 24.002386,17.000034 c -8.355868,0 -15.2214979,6.346843 -15.9999669,14.460906 C 7.9289259,33.000882 9.5544999,33 9.5544999,33 H 38.406003 c 0,0 1.672201,-3.35e-4 1.591142,-1.617185 C 39.182807,23.305596 32.331836,17.000034 24.002386,17.000034 Z"/>
<path style="opacity:0.2" d="m 16,25 a 1.9999959,1.9999959 0 0 0 -2,2 1.9999959,1.9999959 0 0 0 2,2 1.9999959,1.9999959 0 0 0 2,-2 1.9999959,1.9999959 0 0 0 -2,-2 z m 16,0 a 1.9999959,1.9999959 0 0 0 -2,2 1.9999959,1.9999959 0 0 0 2,2 1.9999959,1.9999959 0 0 0 2,-2 1.9999959,1.9999959 0 0 0 -2,-2 z"/>
<path style="fill:#ffffff" d="M 15.999996,24.000008 A 1.9999959,1.9999959 0 0 1 17.999992,26.000004 1.9999959,1.9999959 0 0 1 15.999996,28 1.9999959,1.9999959 0 0 1 14,26.000004 1.9999959,1.9999959 0 0 1 15.999996,24.000008 Z"/>
<path style="fill:#ffffff" d="M 31.999996,24.000008 A 1.9999959,1.9999959 0 0 1 33.999991,26.000004 1.9999959,1.9999959 0 0 1 31.999996,28 1.9999959,1.9999959 0 0 1 30,26.000004 1.9999959,1.9999959 0 0 1 31.999996,24.000008 Z"/>
<path style="fill:#ffffff;opacity:0.2" d="M 11.494141 14 A 1.5 1.5 0 0 0 10.662109 14.255859 A 1.5 1.5 0 0 0 10.115234 16.001953 A 1.5 1.5 0 0 1 10.662109 15.255859 A 1.5 1.5 0 0 1 11.494141 15 A 1.5 1.5 0 0 1 11.787109 15.027344 A 1.5 1.5 0 0 1 12.744141 15.662109 L 15.818359 20.230469 C 18.215776 18.815488 21.012568 18 24.001953 18 C 26.990024 18 29.78649 18.814887 32.183594 20.228516 L 35.255859 15.662109 A 1.5 1.5 0 0 1 36.212891 15.027344 A 1.5 1.5 0 0 1 37.337891 15.255859 A 1.5 1.5 0 0 1 37.910156 16.001953 A 1.5 1.5 0 0 0 37.337891 14.255859 A 1.5 1.5 0 0 0 36.212891 14.027344 A 1.5 1.5 0 0 0 35.255859 14.662109 L 32.183594 19.228516 C 29.78649 17.814887 26.990024 17 24.001953 17 C 21.012568 17 18.215776 17.815488 15.818359 19.230469 L 12.744141 14.662109 A 1.5 1.5 0 0 0 11.787109 14.027344 A 1.5 1.5 0 0 0 11.494141 14 z M 35.033203 21.369141 L 34.617188 21.988281 C 37.477056 24.493668 39.433905 27.993356 39.943359 31.945312 C 39.986866 31.783283 40.008864 31.598575 39.998047 31.382812 C 39.601372 27.448291 37.768055 23.938648 35.033203 21.369141 z M 12.970703 21.373047 C 10.220358 23.959215 8.3822757 27.496796 8.0019531 31.460938 C 7.9920657 31.668114 8.0150508 31.844846 8.0585938 32 C 8.5570234 28.027243 10.515755 24.509049 13.386719 21.992188 L 12.970703 21.373047 z"/>
</svg>

After

Width:  |  Height:  |  Size: 4.5 KiB

View File

@ -94,6 +94,7 @@ dist-win32: build-server build-win32
cp "$(WIN32_BUILD_DIR)"/app/scrcpy.exe "$(DIST)/$(WIN32_TARGET_DIR)/"
cp data/scrcpy-console.bat "$(DIST)/$(WIN32_TARGET_DIR)"
cp data/scrcpy-noconsole.vbs "$(DIST)/$(WIN32_TARGET_DIR)"
cp data/icon.png "$(DIST)/$(WIN32_TARGET_DIR)"
cp prebuilt-deps/ffmpeg-4.3.1-win32-shared/bin/avutil-56.dll "$(DIST)/$(WIN32_TARGET_DIR)/"
cp prebuilt-deps/ffmpeg-4.3.1-win32-shared/bin/avcodec-58.dll "$(DIST)/$(WIN32_TARGET_DIR)/"
cp prebuilt-deps/ffmpeg-4.3.1-win32-shared/bin/avformat-58.dll "$(DIST)/$(WIN32_TARGET_DIR)/"
@ -110,6 +111,7 @@ dist-win64: build-server build-win64
cp "$(WIN64_BUILD_DIR)"/app/scrcpy.exe "$(DIST)/$(WIN64_TARGET_DIR)/"
cp data/scrcpy-console.bat "$(DIST)/$(WIN64_TARGET_DIR)"
cp data/scrcpy-noconsole.vbs "$(DIST)/$(WIN64_TARGET_DIR)"
cp data/icon.png "$(DIST)/$(WIN64_TARGET_DIR)"
cp prebuilt-deps/ffmpeg-4.3.1-win64-shared/bin/avutil-56.dll "$(DIST)/$(WIN64_TARGET_DIR)/"
cp prebuilt-deps/ffmpeg-4.3.1-win64-shared/bin/avcodec-58.dll "$(DIST)/$(WIN64_TARGET_DIR)/"
cp prebuilt-deps/ffmpeg-4.3.1-win64-shared/bin/avformat-58.dll "$(DIST)/$(WIN64_TARGET_DIR)/"

4
run
View File

@ -20,4 +20,6 @@ then
exit 1
fi
SCRCPY_SERVER_PATH="$BUILDDIR/server/scrcpy-server" "$BUILDDIR/app/scrcpy" "$@"
SCRCPY_ICON_PATH="data/icon.png" \
SCRCPY_SERVER_PATH="$BUILDDIR/server/scrcpy-server" \
"$BUILDDIR/app/scrcpy" "$@"