Compare commits

..

1 Commits

Author SHA1 Message Date
Romain Vimont
1de8506a0d Support Android API 19
Since "adb forward" fallback has been implemented, it is easy to support
API 19.

Replace the incompatible calls related to MediaCodec to use
minSdkVersion 19 instead of 21.
2018-03-28 22:13:58 +02:00
25 changed files with 82 additions and 869 deletions

9
FAQ.md
View File

@@ -79,12 +79,3 @@ meson x --buildtype release -Dhidpi_support=false
```
However, the video will be displayed at lower resolution.
### KWin compositor crashes
On Plasma Desktop, compositor is disabled while _scrcpy_ is running.
As a workaround, [disable "Block compositing"][kwin].
[kwin]: https://github.com/Genymobile/scrcpy/issues/114#issuecomment-378778613

View File

@@ -9,9 +9,6 @@
#
# "make release-portable" builds a zip containing the client and the server.
#
# On Windows with MSYS2/mingw64, execute:
# GRADLE="$PWD/gradlew" mingw32-make release-portable
#
# This is a simple Makefile because Meson is not flexible enough to execute some
# arbitrary commands.

View File

@@ -9,7 +9,7 @@ and _MacOS_.
## Requirements
The Android part requires at least API 21 (Android 5.0).
The Android part requires at least API 19 (Android 4.4).
You need [adb]. It is available in the [Android SDK platform
tools][platform-tools], or packaged in your distribution (`android-adb-tools`).
@@ -23,15 +23,14 @@ and extract the following files to a directory accessible from your `PATH`:
Make sure you [enabled adb debugging][enable-adb] on your device(s).
The client requires [FFmpeg], [LibSDL2] and [LibUSB].
The client requires [FFmpeg] and [LibSDL2].
[adb]: https://developer.android.com/studio/command-line/adb.html
[enable-adb]: https://developer.android.com/studio/command-line/adb.html#Enabling
[platform-tools]: https://developer.android.com/studio/releases/platform-tools.html
[platform-tools-windows]: https://dl.google.com/android/repository/platform-tools-latest-windows.zip
[ffmpeg]: https://en.wikipedia.org/wiki/FFmpeg
[libsdl2]: https://en.wikipedia.org/wiki/Simple_DirectMedia_Layer
[libusb]: https://en.wikipedia.org/wiki/Libusb
[LibSDL2]: https://en.wikipedia.org/wiki/Simple_DirectMedia_Layer
## Build and install
@@ -45,12 +44,12 @@ Install the required packages from your package manager.
```bash
# runtime dependencies
sudo apt install ffmpeg libsdl2-2.0.0 libusb-1.0-0
sudo apt install ffmpeg libsdl2-2.0.0
# client build dependencies
sudo apt install make gcc pkg-config meson \
libavcodec-dev libavformat-dev libavutil-dev \
libsdl2-dev libusb-1.0-0-dev
libsdl2-dev
# server build dependencies
sudo apt install openjdk-8-jdk
@@ -105,22 +104,7 @@ pacman -S mingw-w64-x86_64-SDL2 \
pacman -S mingw-w64-x86_64-make \
mingw-w64-x86_64-gcc \
mingw-w64-x86_64-pkg-config \
mingw-w64-x86_64-meson \
mingw-w64-x86_64-libusb
```
For a 32 bits version, replace `x86_64` by `i686`:
```bash
# runtime dependencies
pacman -S mingw-w64-i686-SDL2 \
mingw-w64-i686-ffmpeg
# client build dependencies
pacman -S mingw-w64-i686-make \
mingw-w64-i686-gcc \
mingw-w64-i686-pkg-config \
mingw-w64-i686-meson
mingw-w64-x86_64-meson
```
Java (>= 7) is not available in MSYS2, so if you plan to build the server,
@@ -132,23 +116,16 @@ export PATH="$JAVA_HOME/bin:$PATH"
#### Mac OS
The application is available in [Homebrew]. Just install it:
Use [Homebrew] to install the packages:
[Homebrew]: https://brew.sh/
```bash
brew install scrcpy
```
Instead, you may want to build it manually. Install the packages:
```bash
# runtime dependencies
brew install sdl2 ffmpeg
# client build dependencies
brew install pkg-config meson libusb
brew install pkg-config meson
```
Additionally, if you want to build the server, install Java 8 from Caskroom, and
@@ -276,12 +253,6 @@ To show physical touches while scrcpy is running:
scrcpy -t
```
To enable audio forwarding:
```bash
scrcpy -a
```
To run without installing:
```bash
@@ -302,8 +273,8 @@ To run without installing:
| click on `BACK` | `Ctrl`+`b` \| _Right-click²_ |
| click on `APP_SWITCH` | `Ctrl`+`s` |
| click on `MENU` | `Ctrl`+`m` |
| click on `VOLUME_UP` | `Ctrl`+`` _(up)_ |
| click on `VOLUME_DOWN` | `Ctrl`+`` _(down)_ |
| click on `VOLUME_UP` | `Ctrl`+`+` |
| click on `VOLUME_DOWN` | `Ctrl`+`-` |
| click on `POWER` | `Ctrl`+`p` |
| turn screen on | _Right-click²_ |
| paste computer clipboard to device | `Ctrl`+`v` |

View File

@@ -18,13 +18,6 @@ src = [
'src/tinyxpm.c',
]
if get_option('audio_support')
src += [
'src/aoa.c',
'src/audio.c'
]
endif
dependencies = [
dependency('libavformat'),
dependency('libavcodec'),
@@ -32,10 +25,6 @@ dependencies = [
dependency('sdl2'),
]
if get_option('audio_support')
dependencies += dependency('libusb-1.0')
endif
cc = meson.get_compiler('c')
if host_machine.system() == 'windows'
@@ -94,9 +83,6 @@ conf.set('SKIP_FRAMES', get_option('skip_frames'))
# enable High DPI support
conf.set('HIDPI_SUPPORT', get_option('hidpi_support'))
# enable audio support (enable audio forwarding with --forward-audio)
conf.set('AUDIO_SUPPORT', get_option('audio_support'))
configure_file(configuration: conf, output: 'config.h')
src_dir = include_directories('src')

View File

@@ -1,206 +0,0 @@
#include "aoa.h"
#include "command.h" // must be first to include "winsock2.h" before "windows.h"
#include <libusb-1.0/libusb.h>
#include "log.h"
// <https://source.android.com/devices/accessories/aoa2>
#define AOA_GET_PROTOCOL 51
#define AOA_START_ACCESSORY 53
#define AOA_SET_AUDIO_MODE 58
#define AUDIO_MODE_NO_AUDIO 0
#define AUDIO_MODE_S16LSB_STEREO_44100HZ 1
#define DEFAULT_TIMEOUT 1000
typedef struct control_params {
uint8_t request_type;
uint8_t request;
uint16_t value;
uint16_t index;
unsigned char *data;
uint16_t length;
unsigned int timeout;
} control_params;
static void log_libusb_error(enum libusb_error errcode) {
LOGE("%s", libusb_strerror(errcode));
}
static SDL_bool control_transfer(libusb_device_handle *handle, control_params *params) {
int r = libusb_control_transfer(handle,
params->request_type,
params->request,
params->value,
params->index,
params->data,
params->length,
params->timeout);
if (r < 0) {
log_libusb_error(r);
return SDL_FALSE;
}
return SDL_TRUE;
}
static SDL_bool get_serial(libusb_device *device, struct libusb_device_descriptor *desc, unsigned char *data, int length) {
libusb_device_handle *handle;
int r;
if ((r = libusb_open(device, &handle))) {
// silently ignore
LOGD("USB: cannot open device %04x:%04x (%s)", desc->idVendor, desc->idProduct, libusb_strerror(r));
return SDL_FALSE;
}
if (!desc->iSerialNumber) {
LOGD("USB: device %04x:%04x has no serial number available", desc->idVendor, desc->idProduct);
libusb_close(handle);
return SDL_FALSE;
}
if ((r = libusb_get_string_descriptor_ascii(handle, desc->iSerialNumber, data, length)) <= 0) {
// silently ignore
LOGD("USB: cannot read serial of device %04x:%04x (%s)", desc->idVendor, desc->idProduct, libusb_strerror(r));
libusb_close(handle);
return SDL_FALSE;
}
data[length - 1] = '\0'; // just in case
libusb_close(handle);
return SDL_TRUE;
}
static libusb_device *find_device(const char *serial) {
libusb_device **list;
libusb_device *found = NULL;
ssize_t cnt = libusb_get_device_list(NULL, &list);
ssize_t i = 0;
if (cnt < 0) {
log_libusb_error(cnt);
return NULL;
}
for (i = 0; i < cnt; ++i) {
libusb_device *device = list[i];
struct libusb_device_descriptor desc;
libusb_get_device_descriptor(device, &desc);
char usb_serial[128];
if (get_serial(device, &desc, (unsigned char *) usb_serial, sizeof(usb_serial))) {
if (!strncmp(serial, usb_serial, sizeof(usb_serial))) {
libusb_ref_device(device);
found = device;
LOGD("USB device with serial %s found: %04x:%04x", serial, desc.idVendor, desc.idProduct);
break;
}
}
}
libusb_free_device_list(list, 1);
return found;
}
static SDL_bool aoa_get_protocol(libusb_device_handle *handle, uint16_t *version) {
unsigned char data[2];
control_params params = {
.request_type = LIBUSB_ENDPOINT_IN | LIBUSB_REQUEST_TYPE_VENDOR,
.request = AOA_GET_PROTOCOL,
.value = 0,
.index = 0,
.data = data,
.length = sizeof(data),
.timeout = DEFAULT_TIMEOUT
};
if (control_transfer(handle, &params)) {
// little endian
*version = (data[1] << 8) | data[0];
return SDL_TRUE;
}
return SDL_FALSE;
}
static SDL_bool set_audio_mode(libusb_device_handle *handle, uint16_t mode) {
control_params params = {
.request_type = LIBUSB_ENDPOINT_OUT | LIBUSB_REQUEST_TYPE_VENDOR,
.request = AOA_SET_AUDIO_MODE,
// <https://source.android.com/devices/accessories/aoa2.html#audio-support>
.value = mode,
.index = 0, // unused
.data = NULL,
.length = 0,
.timeout = DEFAULT_TIMEOUT
};
return control_transfer(handle, &params);
}
static SDL_bool start_accessory(libusb_device_handle *handle) {
control_params params = {
.request_type = LIBUSB_ENDPOINT_OUT | LIBUSB_REQUEST_TYPE_VENDOR,
.request = AOA_START_ACCESSORY,
.value = 0, // unused
.index = 0, // unused
.data = NULL,
.length = 0,
.timeout = DEFAULT_TIMEOUT
};
return control_transfer(handle, &params);
}
SDL_bool aoa_init(void) {
return !libusb_init(NULL);
}
void aoa_exit(void) {
libusb_exit(NULL);
}
SDL_bool aoa_forward_audio(const char *serial, SDL_bool forward) {
LOGD("%s audio accessory...", forward ? "Enabling" : "Disabling");
libusb_device *device = find_device(serial);
if (!device) {
LOGE("Cannot find USB device having serial %s", serial);
return SDL_FALSE;
}
SDL_bool ret = SDL_FALSE;
libusb_device_handle *handle;
int r = libusb_open(device, &handle);
if (r) {
log_libusb_error(r);
goto finally_unref_device;
}
uint16_t version;
if (!aoa_get_protocol(handle, &version)) {
LOGE("Cannot get AOA protocol version");
goto finally_close_handle;
}
LOGD("Device AOA version: %" PRIu16 "\n", version);
if (version < 2) {
LOGE("Device does not support AOA 2: %" PRIu16, version);
goto finally_close_handle;
}
uint16_t mode = forward ? AUDIO_MODE_S16LSB_STEREO_44100HZ : AUDIO_MODE_NO_AUDIO;
if (!set_audio_mode(handle, mode)) {
LOGE("Cannot set audio mode: %" PRIu16, mode);
goto finally_close_handle;
}
if (!start_accessory(handle)) {
LOGE("Cannot start accessory");
return SDL_FALSE;
}
ret = SDL_TRUE;
finally_close_handle:
libusb_close(handle);
finally_unref_device:
libusb_unref_device(device);
return ret;
}

View File

@@ -1,15 +0,0 @@
#ifndef AOA_H
#define AOA_H
#include <SDL2/SDL_stdinc.h>
#define AUDIO_MODE_NO_AUDIO 0
#define AUDIO_MODE_S16LSB_STEREO_44100HZ 1
SDL_bool aoa_init(void);
void aoa_exit(void);
// serial must not be NULL
SDL_bool aoa_forward_audio(const char *serial, SDL_bool forward);
#endif

View File

@@ -1,205 +0,0 @@
#include "audio.h"
#include <SDL2/SDL.h>
#include "aoa.h"
#include "command.h"
#include "log.h"
SDL_bool sdl_audio_init(void) {
if (SDL_InitSubSystem(SDL_INIT_AUDIO)) {
LOGC("Could not initialize SDL audio: %s", SDL_GetError());
return SDL_FALSE;
}
return SDL_TRUE;
}
static void init_audio_spec(SDL_AudioSpec *spec) {
SDL_zero(*spec);
spec->freq = 44100;
spec->format = AUDIO_S16LSB;
spec->channels = 2;
spec->samples = 1024;
}
SDL_bool audio_player_init(struct audio_player *player, const char *serial) {
player->serial = SDL_strdup(serial);
return !!player->serial;
}
void audio_player_destroy(struct audio_player *player) {
SDL_free((void *) player->serial);
}
static void audio_input_callback(void *userdata, Uint8 *stream, int len) {
struct audio_player *player = userdata;
if (SDL_QueueAudio(player->output_device, stream, len)) {
LOGE("Cannot queue audio: %s", SDL_GetError());
}
}
static int get_matching_audio_device(const char *serial, int count) {
for (int i = 0; i < count; ++i) {
LOGD("Audio input #%d: %s", i, SDL_GetAudioDeviceName(i, 1));
}
char model[128];
int r = adb_read_model(serial, model, sizeof(model));
if (r <= 0) {
LOGE("Cannot read Android device model");
return -1;
}
LOGD("Device model is: %s", model);
// iterate backwards since the matching device is probably the last one
for (int i = count - 1; i >= 0; i--) {
// model is a NUL-terminated string
const char *name = SDL_GetAudioDeviceName(i, 1);
if (strstr(name, model)) {
// the device name contains the device model, we found it!
return i;
}
}
return -1;
}
static SDL_AudioDeviceID open_accessory_audio_input(struct audio_player *player) {
int count = SDL_GetNumAudioDevices(1);
if (!count) {
LOGE("No audio input source found");
return 0;
}
int selected = get_matching_audio_device(player->serial, count);
if (selected == -1) {
LOGE("Cannot find the Android accessory audio input source");
return 0;
}
const char *selected_name = SDL_GetAudioDeviceName(selected, 1);
LOGI("Selecting audio input source: %s", selected_name);
SDL_AudioSpec spec;
init_audio_spec(&spec);
spec.callback = audio_input_callback;
spec.userdata = player;
int id = SDL_OpenAudioDevice(selected_name, 1, &spec, NULL, 0);
if (!id) {
LOGE("Cannot open audio input: %s", SDL_GetError());
}
return id;
}
static SDL_AudioDeviceID open_default_audio_output() {
SDL_AudioSpec spec;
init_audio_spec(&spec);
int id = SDL_OpenAudioDevice(NULL, 0, &spec, NULL, 0);
if (!id) {
LOGE("Cannot open audio output: %s", SDL_GetError());
}
return id;
}
SDL_bool audio_player_open(struct audio_player *player) {
player->output_device = open_default_audio_output();
if (!player->output_device) {
return SDL_FALSE;
}
player->input_device = open_accessory_audio_input(player);
if (!player->input_device) {
SDL_CloseAudioDevice(player->output_device);
return SDL_FALSE;
}
return SDL_TRUE;
}
static void audio_player_set_paused(struct audio_player *player, SDL_bool paused) {
SDL_PauseAudioDevice(player->input_device, paused);
SDL_PauseAudioDevice(player->output_device, paused);
}
void audio_player_play(struct audio_player *player) {
audio_player_set_paused(player, SDL_FALSE);
}
void audio_player_pause(struct audio_player *player) {
audio_player_set_paused(player, SDL_TRUE);
}
void audio_player_close(struct audio_player *player) {
SDL_CloseAudioDevice(player->input_device);
SDL_CloseAudioDevice(player->output_device);
}
SDL_bool audio_forwarding_start(struct audio_player *player, const char *serial) {
if (!aoa_init()) {
LOGE("Cannot initialize AOA");
return SDL_FALSE;
}
char serialno[128];
if (!serial) {
LOGD("No serial provided, request it to the device");
int r = adb_read_serialno(NULL, serialno, sizeof(serialno));
if (r <= 0) {
LOGE("Cannot read serial from the device");
goto error_aoa_exit;
}
LOGD("Device serial is %s", serialno);
serial = serialno;
}
if (!audio_player_init(player, serial)) {
LOGE("Cannot initialize audio player");
goto error_aoa_exit;
}
// adb connection will be reset!
if (!aoa_forward_audio(player->serial, SDL_TRUE)) {
LOGE("AOA audio forwarding failed");
goto error_destroy_player;
}
LOGI("Audio accessory enabled");
if (!sdl_audio_init()) {
goto error_disable_audio_forwarding;
}
LOGI("Waiting 2s for USB reconfiguration...");
SDL_Delay(2000);
if (!audio_player_open(player)) {
goto error_disable_audio_forwarding;
}
audio_player_play(player);
return SDL_TRUE;
error_disable_audio_forwarding:
if (!aoa_forward_audio(serial, SDL_FALSE)) {
LOGW("Cannot disable audio forwarding");
}
error_destroy_player:
audio_player_destroy(player);
error_aoa_exit:
aoa_exit();
return SDL_FALSE;
}
void audio_forwarding_stop(struct audio_player *player) {
audio_player_close(player);
if (aoa_forward_audio(player->serial, SDL_FALSE)) {
LOGI("Audio forwarding disabled");
} else {
LOGW("Cannot disable audio forwarding");
}
aoa_exit();
audio_player_destroy(player);
}

View File

@@ -1,29 +0,0 @@
#ifndef AUDIO_H
#define AUDIO_H
#include <SDL2/SDL_audio.h>
#include <SDL2/SDL_stdinc.h>
struct audio_player {
const char *serial;
SDL_AudioDeviceID input_device;
SDL_AudioDeviceID output_device;
};
SDL_bool sdl_audio_init(void);
// serial must not be NULL
SDL_bool audio_player_init(struct audio_player *player, const char *serial);
void audio_player_destroy(struct audio_player *player);
SDL_bool audio_player_open(struct audio_player *player);
void audio_player_close(struct audio_player *player);
void audio_player_play(struct audio_player *player);
void audio_player_pause(struct audio_player *player);
// for convenience, these functions handle everything
SDL_bool audio_forwarding_start(struct audio_player *player, const char *serial);
void audio_forwarding_stop(struct audio_player *player);
#endif

View File

@@ -18,7 +18,8 @@ static inline const char *get_adb_command() {
return adb_command;
}
static void fill_cmd(const char *cmd[], const char *serial, const char *const adb_cmd[], int len) {
process_t adb_execute(const char *serial, const char *const adb_cmd[], int len) {
const char *cmd[len + 4];
int i;
cmd[0] = get_adb_command();
if (serial) {
@@ -31,21 +32,9 @@ static void fill_cmd(const char *cmd[], const char *serial, const char *const ad
memcpy(&cmd[i], adb_cmd, len * sizeof(const char *));
cmd[len + i] = NULL;
}
process_t adb_execute(const char *serial, const char *const adb_cmd[], int len) {
const char *cmd[len + 4];
fill_cmd(cmd, serial, adb_cmd, len);
return cmd_execute(cmd[0], cmd);
}
process_t adb_execute_redirect(const char *serial, const char *const adb_cmd[], int len,
pipe_t *pipe_stdin, pipe_t *pipe_stdout, pipe_t *pipe_stderr) {
const char *cmd[len + 4];
fill_cmd(cmd, serial, adb_cmd, len);
return cmd_execute_redirect(cmd[0], cmd, pipe_stdin, pipe_stdout, pipe_stderr);
}
process_t adb_forward(const char *serial, uint16_t local_port, const char *device_socket_name) {
char local[4 + 5 + 1]; // tcp:PORT
char remote[108 + 14 + 1]; // localabstract:NAME
@@ -88,40 +77,6 @@ process_t adb_remove_path(const char *serial, const char *path) {
return adb_execute(serial, adb_cmd, ARRAY_LEN(adb_cmd));
}
static int adb_execute_get_output(const char *serial, const char *const adb_cmd[], int adb_cmd_len,
char *data, size_t data_len, const char *name) {
pipe_t pipe_stdout;
process_t proc = adb_execute_redirect(serial, adb_cmd, adb_cmd_len, NULL, &pipe_stdout, NULL);
if (!process_check_success(proc, name)) {
return -1;
}
int r = read_pipe(pipe_stdout, data, data_len);
close_pipe(pipe_stdout);
return r;
}
static int truncate_first_line(char *data, int len) {
data[len - 1] = '\0';
char *eol = strpbrk(data, "\r\n");
if (eol) {
*eol = '\0';
len = eol - data;
}
return len;
}
int adb_read_serialno(const char *serial, char *data, size_t len) {
const char *const adb_cmd[] = {"get-serialno"};
int r = adb_execute_get_output(serial, adb_cmd, ARRAY_LEN(adb_cmd), data, len, "get-serialno");
return r <= 0 ? r : truncate_first_line(data, r);
}
int adb_read_model(const char *serial, char *data, size_t len) {
const char *const adb_cmd[] = {"shell", "getprop", "ro.product.model"};
int r = adb_execute_get_output(serial, adb_cmd, ARRAY_LEN(adb_cmd), data, len, "getprop model");
return r <= 0 ? r : truncate_first_line(data, r);
}
SDL_bool process_check_success(process_t proc, const char *name) {
if (proc == PROCESS_NONE) {
LOGE("Could not execute \"%s\"", name);

View File

@@ -24,26 +24,19 @@
# define PROCESS_NONE NULL
typedef HANDLE process_t;
typedef DWORD exit_code_t;
typedef HANDLE pipe_t;
#else
# include <sys/types.h>
# define PROCESS_NONE -1
typedef pid_t process_t;
typedef int exit_code_t;
typedef int pipe_t;
#endif
# define NO_EXIT_CODE -1
process_t cmd_execute(const char *path, const char *const argv[]);
process_t cmd_execute_redirect(const char *path, const char *const argv[],
pipe_t *pipe_stdin, pipe_t *pipe_stdout, pipe_t *pipe_stderr);
SDL_bool cmd_terminate(process_t pid);
SDL_bool cmd_simple_wait(process_t pid, exit_code_t *exit_code);
process_t adb_execute(const char *serial, const char *const adb_cmd[], int len);
process_t adb_execute_redirect(const char *serial, const char *const adb_cmd[], int 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);
process_t adb_forward_remove(const char *serial, uint16_t local_port);
process_t adb_reverse(const char *serial, const char *device_socket_name, uint16_t local_port);
@@ -51,15 +44,8 @@ process_t adb_reverse_remove(const char *serial, const char *device_socket_name)
process_t adb_push(const char *serial, const char *local, const char *remote);
process_t adb_remove_path(const char *serial, const char *path);
// return number of bytes read (-1 on error)
int adb_read_serialno(const char *serial, char *data, size_t len);
int adb_read_model(const char *serial, char *data, size_t len);
// convenience function to wait for a successful process execution
// automatically log process errors with the provided process name
SDL_bool process_check_success(process_t process, const char *name);
int read_pipe(pipe_t pipe, char *data, size_t len);
void close_pipe(pipe_t pipe);
#endif

View File

@@ -73,7 +73,6 @@ static enum android_metastate convert_meta_state(SDL_Keymod mod) {
static SDL_bool convert_keycode(SDL_Keycode from, enum android_keycode *to) {
switch (from) {
MAP(SDLK_RETURN, AKEYCODE_ENTER);
MAP(SDLK_KP_ENTER, AKEYCODE_NUMPAD_ENTER);
MAP(SDLK_ESCAPE, AKEYCODE_ESCAPE);
MAP(SDLK_BACKSPACE, AKEYCODE_DEL);
MAP(SDLK_TAB, AKEYCODE_TAB);

View File

@@ -29,6 +29,11 @@ static struct point get_mouse_point(struct screen *screen) {
};
}
static SDL_bool is_ctrl_down(void) {
const Uint8 *state = SDL_GetKeyboardState(NULL);
return state[SDL_SCANCODE_LCTRL] || state[SDL_SCANCODE_RCTRL];
}
static void send_keycode(struct controller *controller, enum android_keycode keycode, const char *name) {
// send DOWN event
struct control_event control_event;
@@ -123,6 +128,18 @@ static void clipboard_paste(struct controller *controller) {
void input_manager_process_text_input(struct input_manager *input_manager,
const SDL_TextInputEvent *event) {
if (is_ctrl_down()) {
switch (event->text[0]) {
case '+':
action_volume_up(input_manager->controller);
break;
case '-':
action_volume_down(input_manager->controller);
break;
}
return;
}
struct control_event control_event;
control_event.type = CONTROL_EVENT_TYPE_TEXT;
control_event.text_event.text = SDL_strdup(event->text);
@@ -172,12 +189,6 @@ void input_manager_process_key(struct input_manager *input_manager,
case SDLK_p:
action_power(input_manager->controller);
return;
case SDLK_DOWN:
action_volume_down(input_manager->controller);
return;
case SDLK_UP:
action_volume_up(input_manager->controller);
return;
case SDLK_v:
clipboard_paste(input_manager->controller);
return;

View File

@@ -13,9 +13,6 @@ struct args {
SDL_bool help;
SDL_bool version;
SDL_bool show_touches;
#ifdef AUDIO_SUPPORT
SDL_bool forward_audio;
#endif
Uint16 port;
Uint16 max_size;
Uint32 bit_rate;
@@ -26,12 +23,6 @@ static void usage(const char *arg0) {
"Usage: %s [options]\n"
"\n"
"Options:\n"
#ifdef AUDIO_SUPPORT
"\n"
" -a, --forward-audio\n"
" Forward audio from the device to the computer over USB\n"
" (experimental).\n"
#endif
"\n"
" -b, --bit-rate value\n"
" Encode the video at the given bit-rate, expressed in bits/s.\n"
@@ -90,10 +81,10 @@ static void usage(const char *arg0) {
" Ctrl+m\n"
" click on MENU\n"
"\n"
" Ctrl+Up\n"
" Ctrl+'+'\n"
" click on VOLUME_UP\n"
"\n"
" Ctrl+Down\n"
" Ctrl+'-'\n"
" click on VOLUME_DOWN\n"
"\n"
" Ctrl+p\n"
@@ -197,31 +188,18 @@ static SDL_bool parse_port(char *optarg, Uint16 *port) {
static SDL_bool parse_args(struct args *args, int argc, char *argv[]) {
static const struct option long_options[] = {
#ifdef AUDIO_SUPPORT
{"forward-audio", no_argument, NULL, 'a'},
#endif
{"bit-rate", required_argument, NULL, 'b'},
{"help", no_argument, NULL, 'h'},
{"max-size", required_argument, NULL, 'm'},
{"port", required_argument, NULL, 'p'},
{"serial", required_argument, NULL, 's'},
{"show-touches", no_argument, NULL, 't'},
{"version", no_argument, NULL, 'v'},
{NULL, 0, NULL, 0 },
{"bit-rate", required_argument, NULL, 'b'},
{"help", no_argument, NULL, 'h'},
{"max-size", required_argument, NULL, 'm'},
{"port", required_argument, NULL, 'p'},
{"serial", required_argument, NULL, 's'},
{"show-touches", no_argument, NULL, 't'},
{"version", no_argument, NULL, 'v'},
{NULL, 0, NULL, 0 },
};
int c;
#ifdef AUDIO_SUPPORT
# define AUDIO_SHORT_PARAM "a"
#else
# define AUDIO_SHORT_PARAM
#endif
while ((c = getopt_long(argc, argv, AUDIO_SHORT_PARAM "b:hm:p:s:tv", long_options, NULL)) != -1) {
while ((c = getopt_long(argc, argv, "b:hm:p:s:tv", long_options, NULL)) != -1) {
switch (c) {
#ifdef AUDIO_SUPPORT
case 'a':
args->forward_audio = SDL_TRUE;
break;
#endif
case 'b':
if (!parse_bit_rate(optarg, &args->bit_rate)) {
return SDL_FALSE;
@@ -278,9 +256,6 @@ int main(int argc, char *argv[]) {
.port = DEFAULT_LOCAL_PORT,
.max_size = DEFAULT_MAX_SIZE,
.bit_rate = DEFAULT_BIT_RATE,
#ifdef AUDIO_SUPPORT
.forward_audio = SDL_FALSE,
#endif
};
if (!parse_args(&args, argc, argv)) {
return 1;
@@ -312,15 +287,10 @@ int main(int argc, char *argv[]) {
.max_size = args.max_size,
.bit_rate = args.bit_rate,
.show_touches = args.show_touches,
#ifdef AUDIO_SUPPORT
.forward_audio = args.forward_audio,
#endif
};
int res = scrcpy(&options) ? 0 : 1;
avformat_network_deinit(); // ignore failure
SDL_Quit();
return res;
}

View File

@@ -82,11 +82,11 @@ 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) {
ssize_t net_send(socket_t socket, 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) {
ssize_t net_send_all(socket_t socket, void *buf, size_t len) {
ssize_t w;
while (len > 0) {
w = send(socket, buf, len, 0);
@@ -94,7 +94,7 @@ ssize_t net_send_all(socket_t socket, const void *buf, size_t len) {
return -1;
}
len -= w;
buf = (char *) buf + w;
buf += w;
}
return w;
}

View File

@@ -26,8 +26,8 @@ socket_t net_accept(socket_t 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);
ssize_t net_recv_all(socket_t socket, void *buf, size_t len);
ssize_t net_send(socket_t socket, const void *buf, size_t len);
ssize_t net_send_all(socket_t socket, const void *buf, size_t len);
ssize_t net_send(socket_t socket, void *buf, size_t len);
ssize_t net_send_all(socket_t socket, void *buf, size_t len);
// how is SHUT_RD (read), SHUT_WR (write) or SHUT_RDWR (both)
SDL_bool net_shutdown(socket_t socket, int how);
SDL_bool net_close(socket_t socket);

View File

@@ -7,8 +7,6 @@
#include <sys/time.h>
#include <SDL2/SDL.h>
#include "aoa.h"
#include "audio.h"
#include "command.h"
#include "common.h"
#include "controller.h"
@@ -31,10 +29,6 @@ static struct frames frames;
static struct decoder decoder;
static struct controller controller;
#ifdef AUDIO_SUPPORT
static struct audio_player audio_player;
#endif
static struct input_manager input_manager = {
.controller = &controller,
.frames = &frames,
@@ -126,8 +120,9 @@ static void wait_show_touches(process_t process) {
}
SDL_bool scrcpy(const struct scrcpy_options *options) {
if (!SDL_SetHint(SDL_HINT_NO_SIGNAL_HANDLERS, "1")) {
LOGW("Cannot request to keep default signal handlers");
if (!server_start(&server, options->serial, options->port,
options->max_size, options->bit_rate)) {
return SDL_FALSE;
}
process_t proc_show_touches;
@@ -138,23 +133,13 @@ SDL_bool scrcpy(const struct scrcpy_options *options) {
show_touches_waited = SDL_FALSE;
}
#ifdef AUDIO_SUPPORT
if (options->forward_audio) {
if (!audio_forwarding_start(&audio_player, options->serial)) {
return SDL_FALSE;
}
}
#endif
SDL_bool ret = SDL_TRUE;
if (!server_start(&server, options->serial, options->port,
options->max_size, options->bit_rate)) {
ret = SDL_FALSE;
goto finally_disable_audio_forwarding;
if (!SDL_SetHint(SDL_HINT_NO_SIGNAL_HANDLERS, "1")) {
LOGW("Cannot request to keep default signal handlers");
}
if (!sdl_video_init()) {
if (!sdl_init_and_configure()) {
ret = SDL_FALSE;
goto finally_destroy_server;
}
@@ -243,12 +228,6 @@ finally_destroy_server:
}
server_destroy(&server);
finally_disable_audio_forwarding:
#ifdef AUDIO_SUPPORT
if (options->forward_audio) {
audio_forwarding_stop(&audio_player);
}
#endif
return ret;
}

View File

@@ -2,7 +2,6 @@
#define SCRCPY_H
#include <SDL2/SDL_stdinc.h>
#include "config.h"
struct scrcpy_options {
const char *serial;
@@ -10,9 +9,6 @@ struct scrcpy_options {
Uint16 max_size;
Uint32 bit_rate;
SDL_bool show_touches;
#ifdef AUDIO_SUPPORT
SDL_bool forward_audio;
#endif
};
SDL_bool scrcpy(const struct scrcpy_options *options);

View File

@@ -10,12 +10,14 @@
#define DISPLAY_MARGINS 96
SDL_bool sdl_video_init(void) {
if (SDL_InitSubSystem(SDL_INIT_VIDEO)) {
LOGC("Could not initialize SDL video: %s", SDL_GetError());
SDL_bool sdl_init_and_configure(void) {
if (SDL_Init(SDL_INIT_VIDEO)) {
LOGC("Could not initialize SDL: %s", SDL_GetError());
return SDL_FALSE;
}
atexit(SDL_Quit);
// Use the best available scale quality
if (!SDL_SetHint(SDL_HINT_RENDER_SCALE_QUALITY, "2")) {
LOGW("Could not enable bilinear filtering");

View File

@@ -35,7 +35,7 @@ struct screen {
}
// init SDL and set appropriate hints
SDL_bool sdl_video_init(void);
SDL_bool sdl_init_and_configure(void);
// initialize default values
void screen_init(struct screen *screen);

View File

@@ -19,93 +19,6 @@ pid_t cmd_execute(const char *path, const char *const argv[]) {
return pid;
}
pid_t cmd_execute_redirect(const char *path, const char *const argv[],
int *pipe_stdin, int *pipe_stdout, int *pipe_stderr) {
int in[2];
int out[2];
int err[2];
if (pipe_stdin) {
if (pipe(in) == -1) {
perror("pipe");
return -1;
}
}
if (pipe_stdout) {
if (pipe(out) == -1) {
perror("pipe");
// clean up
if (pipe_stdin) {
close(in[0]);
close(in[1]);
}
return -1;
}
}
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]);
}
return -1;
}
}
pid_t pid = fork();
if (pid == -1) {
perror("fork");
return -1;
}
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]);
}
execvp(path, (char *const *)argv);
perror("exec");
_exit(1);
}
if (pipe_stdin) {
close(in[0]);
*pipe_stdin = in[1];
}
if (pipe_stdout) {
*pipe_stdout = out[0];
close(out[1]);
}
if (pipe_stderr) {
*pipe_stderr = err[0];
close(err[1]);
}
return pid;
}
SDL_bool cmd_terminate(pid_t pid) {
return kill(pid, SIGTERM) != -1;
}
@@ -124,13 +37,3 @@ SDL_bool cmd_simple_wait(pid_t pid, int *exit_code) {
}
return !code;
}
int 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

@@ -3,27 +3,20 @@
#include "log.h"
#include "strutil.h"
static int build_cmd(char *cmd, size_t len, const char *const argv[]) {
// Windows command-line parsing is WTF:
// <http://daviddeley.com/autohotkey/parameters/parameters.htm#WINPASS>
// only make it work for this very specific program
// (don't handle escaping nor quotes)
size_t ret = xstrjoin(cmd, argv, ' ', len);
if (ret >= len) {
LOGE("Command too long (%" PRIsizet " chars)", len - 1);
return -1;
}
return 0;
}
HANDLE cmd_execute(const char *path, const char *const argv[]) {
STARTUPINFO si;
PROCESS_INFORMATION pi;
memset(&si, 0, sizeof(si));
si.cb = sizeof(si);
// Windows command-line parsing is WTF:
// <http://daviddeley.com/autohotkey/parameters/parameters.htm#WINPASS>
// only make it work for this very specific program
// (don't handle escaping nor quotes)
char cmd[256];
if (build_cmd(cmd, sizeof(cmd), argv)) {
size_t ret = xstrjoin(cmd, argv, ' ', sizeof(cmd));
if (ret >= sizeof(cmd)) {
LOGE("Command too long (%" PRIsizet " chars)", sizeof(cmd) - 1);
return NULL;
}
@@ -34,75 +27,6 @@ HANDLE cmd_execute(const char *path, const char *const argv[]) {
return pi.hProcess;
}
HANDLE cmd_execute_redirect(const char *path, const char *const argv[],
HANDLE *pipe_stdin, HANDLE *pipe_stdout, HANDLE *pipe_stderr) {
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 NULL;
}
}
if (pipe_stdout) {
if (!CreatePipe(pipe_stdout, &stdout_write_handle, &sa, 0)) {
perror("pipe");
// clean up
if (pipe_stdin) {
CloseHandle(&stdin_read_handle);
CloseHandle(pipe_stdin);
}
return NULL;
}
}
if (pipe_stderr) {
if (!CreatePipe(pipe_stderr, &stderr_write_handle, &sa, 0)) {
perror("pipe");
// clean up
if (pipe_stdin) {
CloseHandle(&stdin_read_handle);
CloseHandle(pipe_stdin);
}
if (pipe_stdout) {
CloseHandle(pipe_stdout);
CloseHandle(&stdout_write_handle);
}
}
}
STARTUPINFO si;
PROCESS_INFORMATION pi;
memset(&si, 0, sizeof(si));
si.cb = sizeof(si);
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[256];
if (build_cmd(cmd, sizeof(cmd), argv)) {
return NULL;
}
if (!CreateProcess(NULL, cmd, NULL, NULL, TRUE, CREATE_NO_WINDOW, NULL, NULL, &si, &pi)) {
return NULL;
}
return pi.hProcess;
}
SDL_bool cmd_terminate(HANDLE handle) {
return TerminateProcess(handle, 1) && CloseHandle(handle);
}
@@ -118,17 +42,3 @@ SDL_bool cmd_simple_wait(HANDLE handle, DWORD *exit_code) {
}
return !code;
}
int 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

@@ -4,4 +4,3 @@ option('prebuilt_server', type: 'string', description: 'Path of the prebuilt ser
option('override_server_path', type: 'string', description: 'Hardcoded path to find the server at runtime')
option('skip_frames', type: 'boolean', value: true, description: 'Always display the most recent frame')
option('hidpi_support', type: 'boolean', value: true, description: 'Enable High DPI support')
option('audio_support', type: 'boolean', value: true, description: 'Enable audio support')

View File

@@ -4,7 +4,7 @@ android {
compileSdkVersion 27
defaultConfig {
applicationId "com.genymobile.scrcpy"
minSdkVersion 21
minSdkVersion 19
targetSdkVersion 27
versionCode 2
versionName "1.1"

View File

@@ -6,6 +6,7 @@ import android.graphics.Rect;
import android.media.MediaCodec;
import android.media.MediaCodecInfo;
import android.media.MediaFormat;
import android.os.Build;
import android.os.IBinder;
import android.view.Surface;
@@ -77,11 +78,17 @@ public class ScreenEncoder implements Device.RotationListener {
}
}
@SuppressWarnings("deprecation") // Android API 19 requires to call deprecated methods
private boolean encode(MediaCodec codec, OutputStream outputStream) throws IOException {
@SuppressWarnings("checkstyle:MagicNumber")
byte[] buf = new byte[bitRate / 8]; // may contain up to 1 second of video
boolean eof = false;
MediaCodec.BufferInfo bufferInfo = new MediaCodec.BufferInfo();
ByteBuffer[] outputBuffers = null;
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {
outputBuffers = codec.getOutputBuffers();
}
while (!consumeRotationChange() && !eof) {
int outputBufferId = codec.dequeueOutputBuffer(bufferInfo, -1);
eof = (bufferInfo.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0;
@@ -91,7 +98,12 @@ public class ScreenEncoder implements Device.RotationListener {
break;
}
if (outputBufferId >= 0) {
ByteBuffer outputBuffer = codec.getOutputBuffer(outputBufferId);
ByteBuffer outputBuffer;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
outputBuffer = codec.getOutputBuffer(outputBufferId);
} else {
outputBuffer = outputBuffers[outputBufferId];
}
while (outputBuffer.hasRemaining()) {
int remaining = outputBuffer.remaining();
int len = Math.min(buf.length, remaining);
@@ -100,6 +112,8 @@ public class ScreenEncoder implements Device.RotationListener {
outputBuffer.get(buf, 0, len);
outputStream.write(buf, 0, len);
}
} else if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP && outputBufferId == MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED) {
outputBuffers = codec.getOutputBuffers();
}
} finally {
if (outputBufferId >= 0) {

View File

@@ -3,9 +3,6 @@ package com.genymobile.scrcpy;
import android.view.KeyEvent;
import android.view.MotionEvent;
import org.junit.Assert;
import org.junit.Test;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.DataOutputStream;
@@ -13,6 +10,8 @@ import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import org.junit.Assert;
import org.junit.Test;
public class ControlEventReaderTest {