Compare commits
12 Commits
rawkeyeven
...
logicalsca
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e3a07c3309 | ||
|
|
2e40285244 | ||
|
|
eca99d5af7 | ||
|
|
6a1fb070f7 | ||
|
|
27bed948d4 | ||
|
|
66def38b73 | ||
|
|
a60aef5aaf | ||
|
|
28015c3ee4 | ||
|
|
af9808cf02 | ||
|
|
34550311be | ||
|
|
55d33ddd5f | ||
|
|
6d2d803003 |
@@ -312,6 +312,12 @@ To show physical touches while scrcpy is running:
|
||||
scrcpy -t
|
||||
```
|
||||
|
||||
The app may be started directly in fullscreen:
|
||||
|
||||
```
|
||||
scrcpy -f
|
||||
```
|
||||
|
||||
To run without installing:
|
||||
|
||||
```bash
|
||||
@@ -338,15 +344,12 @@ To run without installing:
|
||||
| turn screen on | _Right-click²_ |
|
||||
| paste computer clipboard to device | `Ctrl`+`v` |
|
||||
| enable/disable FPS counter (on stdout) | `Ctrl`+`i` |
|
||||
| toggle "raw key events" mode ([#87]) | `Ctrl`+`k` |
|
||||
| install APK from computer | drag & drop APK file |
|
||||
| push file to `/sdcard/` | drag & drop non-APK file |
|
||||
|
||||
_¹Double-click on black borders to remove them._
|
||||
_²Right-click turns the screen on if it was off, presses BACK otherwise._
|
||||
|
||||
[#87]: https://github.com/Genymobile/scrcpy/issues/87
|
||||
|
||||
|
||||
## Why _scrcpy_?
|
||||
|
||||
|
||||
@@ -18,9 +18,25 @@ static inline const char *get_adb_command() {
|
||||
return adb_command;
|
||||
}
|
||||
|
||||
static void show_adb_err_msg(enum process_result err) {
|
||||
switch (err) {
|
||||
case PROCESS_ERROR_GENERIC:
|
||||
LOGE("Failed to execute adb");
|
||||
break;
|
||||
case PROCESS_ERROR_MISSING_BINARY:
|
||||
LOGE("'adb' command not found (make it accessible from your PATH "
|
||||
"or define its full path in the ADB environment variable)");
|
||||
break;
|
||||
case PROCESS_SUCCESS:
|
||||
/* do nothing */
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
process_t adb_execute(const char *serial, const char *const adb_cmd[], int len) {
|
||||
const char *cmd[len + 4];
|
||||
int i;
|
||||
process_t process;
|
||||
cmd[0] = get_adb_command();
|
||||
if (serial) {
|
||||
cmd[1] = "-s";
|
||||
@@ -32,7 +48,12 @@ process_t adb_execute(const char *serial, const char *const adb_cmd[], int len)
|
||||
|
||||
memcpy(&cmd[i], adb_cmd, len * sizeof(const char *));
|
||||
cmd[len + i] = NULL;
|
||||
return cmd_execute(cmd[0], cmd);
|
||||
enum process_result r = cmd_execute(cmd[0], cmd, &process);
|
||||
if (r != PROCESS_SUCCESS) {
|
||||
show_adb_err_msg(r);
|
||||
return PROCESS_NONE;
|
||||
}
|
||||
return process;
|
||||
}
|
||||
|
||||
process_t adb_forward(const char *serial, uint16_t local_port, const char *device_socket_name) {
|
||||
|
||||
@@ -32,7 +32,13 @@
|
||||
#endif
|
||||
# define NO_EXIT_CODE -1
|
||||
|
||||
process_t cmd_execute(const char *path, const char *const argv[]);
|
||||
enum process_result {
|
||||
PROCESS_SUCCESS,
|
||||
PROCESS_ERROR_GENERIC,
|
||||
PROCESS_ERROR_MISSING_BINARY,
|
||||
};
|
||||
|
||||
enum process_result cmd_execute(const char *path, const char *const argv[], process_t *process);
|
||||
SDL_bool cmd_terminate(process_t pid);
|
||||
SDL_bool cmd_simple_wait(process_t pid, exit_code_t *exit_code);
|
||||
|
||||
|
||||
@@ -70,83 +70,6 @@ static enum android_metastate convert_meta_state(SDL_Keymod mod) {
|
||||
return autocomplete_metastate(metastate);
|
||||
}
|
||||
|
||||
// only used if raw_key_events is enabled
|
||||
static SDL_bool convert_text_keycode(SDL_Keycode from, enum android_keycode *to) {
|
||||
switch (from) {
|
||||
MAP(SDLK_SPACE, AKEYCODE_SPACE);
|
||||
MAP(SDLK_HASH, AKEYCODE_POUND);
|
||||
MAP(SDLK_QUOTE, AKEYCODE_APOSTROPHE);
|
||||
MAP(SDLK_ASTERISK, AKEYCODE_STAR);
|
||||
MAP(SDLK_COMMA, AKEYCODE_COMMA);
|
||||
MAP(SDLK_MINUS, AKEYCODE_MINUS);
|
||||
MAP(SDLK_PERIOD, AKEYCODE_PERIOD);
|
||||
MAP(SDLK_SLASH, AKEYCODE_SLASH);
|
||||
MAP(SDLK_0, AKEYCODE_0);
|
||||
MAP(SDLK_1, AKEYCODE_1);
|
||||
MAP(SDLK_2, AKEYCODE_2);
|
||||
MAP(SDLK_3, AKEYCODE_3);
|
||||
MAP(SDLK_4, AKEYCODE_4);
|
||||
MAP(SDLK_5, AKEYCODE_5);
|
||||
MAP(SDLK_6, AKEYCODE_6);
|
||||
MAP(SDLK_7, AKEYCODE_7);
|
||||
MAP(SDLK_8, AKEYCODE_8);
|
||||
MAP(SDLK_9, AKEYCODE_9);
|
||||
MAP(SDLK_SEMICOLON, AKEYCODE_SEMICOLON);
|
||||
MAP(SDLK_EQUALS, AKEYCODE_EQUALS);
|
||||
MAP(SDLK_AT, AKEYCODE_AT);
|
||||
MAP(SDLK_LEFTBRACKET, AKEYCODE_LEFT_BRACKET);
|
||||
MAP(SDLK_BACKSLASH, AKEYCODE_BACKSLASH);
|
||||
MAP(SDLK_RIGHTBRACKET, AKEYCODE_RIGHT_BRACKET);
|
||||
MAP(SDLK_BACKQUOTE, AKEYCODE_GRAVE);
|
||||
MAP(SDLK_a, AKEYCODE_A);
|
||||
MAP(SDLK_b, AKEYCODE_B);
|
||||
MAP(SDLK_c, AKEYCODE_C);
|
||||
MAP(SDLK_d, AKEYCODE_D);
|
||||
MAP(SDLK_e, AKEYCODE_E);
|
||||
MAP(SDLK_f, AKEYCODE_F);
|
||||
MAP(SDLK_g, AKEYCODE_G);
|
||||
MAP(SDLK_h, AKEYCODE_H);
|
||||
MAP(SDLK_i, AKEYCODE_I);
|
||||
MAP(SDLK_j, AKEYCODE_J);
|
||||
MAP(SDLK_k, AKEYCODE_K);
|
||||
MAP(SDLK_l, AKEYCODE_L);
|
||||
MAP(SDLK_m, AKEYCODE_M);
|
||||
MAP(SDLK_n, AKEYCODE_N);
|
||||
MAP(SDLK_o, AKEYCODE_O);
|
||||
MAP(SDLK_p, AKEYCODE_P);
|
||||
MAP(SDLK_q, AKEYCODE_Q);
|
||||
MAP(SDLK_r, AKEYCODE_R);
|
||||
MAP(SDLK_s, AKEYCODE_S);
|
||||
MAP(SDLK_t, AKEYCODE_T);
|
||||
MAP(SDLK_u, AKEYCODE_U);
|
||||
MAP(SDLK_v, AKEYCODE_V);
|
||||
MAP(SDLK_w, AKEYCODE_W);
|
||||
MAP(SDLK_x, AKEYCODE_X);
|
||||
MAP(SDLK_y, AKEYCODE_Y);
|
||||
MAP(SDLK_z, AKEYCODE_Z);
|
||||
MAP(SDLK_KP_ENTER, AKEYCODE_NUMPAD_ENTER);
|
||||
MAP(SDLK_KP_1, AKEYCODE_NUMPAD_1);
|
||||
MAP(SDLK_KP_2, AKEYCODE_NUMPAD_2);
|
||||
MAP(SDLK_KP_3, AKEYCODE_NUMPAD_3);
|
||||
MAP(SDLK_KP_4, AKEYCODE_NUMPAD_4);
|
||||
MAP(SDLK_KP_5, AKEYCODE_NUMPAD_5);
|
||||
MAP(SDLK_KP_6, AKEYCODE_NUMPAD_6);
|
||||
MAP(SDLK_KP_7, AKEYCODE_NUMPAD_7);
|
||||
MAP(SDLK_KP_8, AKEYCODE_NUMPAD_8);
|
||||
MAP(SDLK_KP_9, AKEYCODE_NUMPAD_9);
|
||||
MAP(SDLK_KP_0, AKEYCODE_NUMPAD_0);
|
||||
MAP(SDLK_KP_DIVIDE, AKEYCODE_NUMPAD_DIVIDE);
|
||||
MAP(SDLK_KP_MULTIPLY, AKEYCODE_NUMPAD_MULTIPLY);
|
||||
MAP(SDLK_KP_MINUS, AKEYCODE_NUMPAD_SUBTRACT);
|
||||
MAP(SDLK_KP_PLUS, AKEYCODE_NUMPAD_ADD);
|
||||
MAP(SDLK_KP_PERIOD, AKEYCODE_NUMPAD_DOT);
|
||||
MAP(SDLK_KP_EQUALS, AKEYCODE_NUMPAD_EQUALS);
|
||||
MAP(SDLK_KP_LEFTPAREN, AKEYCODE_NUMPAD_LEFT_PAREN);
|
||||
MAP(SDLK_KP_RIGHTPAREN, AKEYCODE_NUMPAD_RIGHT_PAREN);
|
||||
FAIL;
|
||||
}
|
||||
}
|
||||
|
||||
static SDL_bool convert_keycode(SDL_Keycode from, enum android_keycode *to) {
|
||||
switch (from) {
|
||||
MAP(SDLK_RETURN, AKEYCODE_ENTER);
|
||||
@@ -196,8 +119,7 @@ static enum android_motionevent_buttons convert_mouse_buttons(Uint32 state) {
|
||||
}
|
||||
|
||||
SDL_bool input_key_from_sdl_to_android(const SDL_KeyboardEvent *from,
|
||||
struct control_event *to,
|
||||
SDL_bool raw_key_events) {
|
||||
struct control_event *to) {
|
||||
to->type = CONTROL_EVENT_TYPE_KEYCODE;
|
||||
|
||||
if (!convert_keycode_action(from->type, &to->keycode_event.action)) {
|
||||
@@ -205,10 +127,7 @@ SDL_bool input_key_from_sdl_to_android(const SDL_KeyboardEvent *from,
|
||||
}
|
||||
|
||||
if (!convert_keycode(from->keysym.sym, &to->keycode_event.keycode)) {
|
||||
if (!raw_key_events ||
|
||||
!convert_text_keycode(from->keysym.sym, &to->keycode_event.keycode)) {
|
||||
return SDL_FALSE;
|
||||
}
|
||||
return SDL_FALSE;
|
||||
}
|
||||
|
||||
to->keycode_event.metastate = convert_meta_state(from->keysym.mod);
|
||||
|
||||
@@ -16,9 +16,7 @@ struct complete_mouse_wheel_event {
|
||||
};
|
||||
|
||||
SDL_bool input_key_from_sdl_to_android(const SDL_KeyboardEvent *from,
|
||||
struct control_event *to,
|
||||
SDL_bool raw_key_events);
|
||||
|
||||
struct control_event *to);
|
||||
SDL_bool mouse_button_from_sdl_to_android(const SDL_MouseButtonEvent *from,
|
||||
struct size screen_size,
|
||||
struct control_event *to);
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
#ifndef FILE_HANDLER_H
|
||||
#define FILE_HADNELR_H
|
||||
#define FILE_HANDLER_H
|
||||
|
||||
#include <SDL2/SDL_mutex.h>
|
||||
#include <SDL2/SDL_stdinc.h>
|
||||
|
||||
@@ -129,10 +129,6 @@ static void clipboard_paste(struct controller *controller) {
|
||||
|
||||
void input_manager_process_text_input(struct input_manager *input_manager,
|
||||
const SDL_TextInputEvent *event) {
|
||||
if (input_manager->raw_key_events) {
|
||||
// we will forward the raw key events instead
|
||||
return;
|
||||
}
|
||||
struct control_event control_event;
|
||||
control_event.type = CONTROL_EVENT_TYPE_TEXT;
|
||||
control_event.text_event.text = SDL_strdup(event->text);
|
||||
@@ -220,21 +216,13 @@ void input_manager_process_key(struct input_manager *input_manager,
|
||||
switch_fps_counter_state(input_manager->frames);
|
||||
}
|
||||
return;
|
||||
case SDLK_k:
|
||||
if (!repeat && event->type == SDL_KEYDOWN) {
|
||||
input_manager->raw_key_events ^= SDL_TRUE; // toggle
|
||||
LOGI("Raw key events mode %s",
|
||||
input_manager->raw_key_events ? "enabled" : "disabled");
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
struct control_event control_event;
|
||||
SDL_bool raw_key_events = input_manager->raw_key_events;
|
||||
if (input_key_from_sdl_to_android(event, &control_event, raw_key_events)) {
|
||||
if (input_key_from_sdl_to_android(event, &control_event)) {
|
||||
if (!controller_push_event(input_manager->controller, &control_event)) {
|
||||
LOGW("Cannot send control event");
|
||||
}
|
||||
|
||||
@@ -11,7 +11,6 @@ struct input_manager {
|
||||
struct controller *controller;
|
||||
struct frames *frames;
|
||||
struct screen *screen;
|
||||
SDL_bool raw_key_events;
|
||||
};
|
||||
|
||||
void input_manager_process_text_input(struct input_manager *input_manager,
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
struct args {
|
||||
const char *serial;
|
||||
const char *crop;
|
||||
SDL_bool fullscreen;
|
||||
SDL_bool help;
|
||||
SDL_bool version;
|
||||
SDL_bool show_touches;
|
||||
@@ -36,6 +37,9 @@ static void usage(const char *arg0) {
|
||||
" (typically, portrait for a phone, landscape for a tablet).\n"
|
||||
" Any --max-size value is computed on the cropped size.\n"
|
||||
"\n"
|
||||
" -f, --fullscreen\n"
|
||||
" Start in fullscreen.\n"
|
||||
"\n"
|
||||
" -h, --help\n"
|
||||
" Print this help.\n"
|
||||
"\n"
|
||||
@@ -200,6 +204,7 @@ static SDL_bool parse_args(struct args *args, int argc, char *argv[]) {
|
||||
static const struct option long_options[] = {
|
||||
{"bit-rate", required_argument, NULL, 'b'},
|
||||
{"crop", required_argument, NULL, 'c'},
|
||||
{"fullscreen", no_argument, NULL, 'f'},
|
||||
{"help", no_argument, NULL, 'h'},
|
||||
{"max-size", required_argument, NULL, 'm'},
|
||||
{"port", required_argument, NULL, 'p'},
|
||||
@@ -209,7 +214,7 @@ static SDL_bool parse_args(struct args *args, int argc, char *argv[]) {
|
||||
{NULL, 0, NULL, 0 },
|
||||
};
|
||||
int c;
|
||||
while ((c = getopt_long(argc, argv, "b:c:hm:p:s:tv", long_options, NULL)) != -1) {
|
||||
while ((c = getopt_long(argc, argv, "b:c:fhm:p:s:tv", long_options, NULL)) != -1) {
|
||||
switch (c) {
|
||||
case 'b':
|
||||
if (!parse_bit_rate(optarg, &args->bit_rate)) {
|
||||
@@ -219,6 +224,9 @@ static SDL_bool parse_args(struct args *args, int argc, char *argv[]) {
|
||||
case 'c':
|
||||
args->crop = optarg;
|
||||
break;
|
||||
case 'f':
|
||||
args->fullscreen = SDL_TRUE;
|
||||
break;
|
||||
case 'h':
|
||||
args->help = SDL_TRUE;
|
||||
break;
|
||||
@@ -305,6 +313,7 @@ int main(int argc, char *argv[]) {
|
||||
.max_size = args.max_size,
|
||||
.bit_rate = args.bit_rate,
|
||||
.show_touches = args.show_touches,
|
||||
.fullscreen = args.fullscreen,
|
||||
};
|
||||
int res = scrcpy(&options) ? 0 : 1;
|
||||
|
||||
|
||||
@@ -35,7 +35,6 @@ static struct input_manager input_manager = {
|
||||
.controller = &controller,
|
||||
.frames = &frames,
|
||||
.screen = &screen,
|
||||
.raw_key_events = SDL_FALSE,
|
||||
};
|
||||
|
||||
#if defined(__APPLE__) || defined(__WINDOWS__)
|
||||
@@ -89,6 +88,7 @@ static SDL_bool event_loop(void) {
|
||||
switch (event.window.event) {
|
||||
case SDL_WINDOWEVENT_EXPOSED:
|
||||
case SDL_WINDOWEVENT_SIZE_CHANGED:
|
||||
screen_update_scale(&screen);
|
||||
screen_render(&screen);
|
||||
break;
|
||||
}
|
||||
@@ -224,6 +224,10 @@ SDL_bool scrcpy(const struct scrcpy_options *options) {
|
||||
show_touches_waited = SDL_TRUE;
|
||||
}
|
||||
|
||||
if (options->fullscreen) {
|
||||
screen_switch_fullscreen(&screen);
|
||||
}
|
||||
|
||||
ret = event_loop();
|
||||
LOGD("quit...");
|
||||
|
||||
|
||||
@@ -10,6 +10,7 @@ struct scrcpy_options {
|
||||
Uint16 max_size;
|
||||
Uint32 bit_rate;
|
||||
SDL_bool show_touches;
|
||||
SDL_bool fullscreen;
|
||||
};
|
||||
|
||||
SDL_bool scrcpy(const struct scrcpy_options *options);
|
||||
|
||||
@@ -132,6 +132,17 @@ static inline struct size get_initial_optimal_size(struct size frame_size) {
|
||||
return get_optimal_size(frame_size, frame_size);
|
||||
}
|
||||
|
||||
// apply hidpi scaling and call SDL_RenderSetLogicalSize
|
||||
static inline SDL_bool render_set_scaled_logical_size(struct screen *screen, struct size size) {
|
||||
int w, h, dw, dh;
|
||||
SDL_GL_GetDrawableSize(screen->window, &w, &h);
|
||||
SDL_GetWindowSize(screen->window, &dw, &dh);
|
||||
// use 32 bits unsigned not to lose precision (width and height fit in 16 bits)
|
||||
int scaled_x = (Uint32) size.width * (Uint32) w / (Uint32) dw;
|
||||
int scaled_y = (Uint32) size.height * (Uint32) h / (Uint32) dh;
|
||||
return SDL_RenderSetLogicalSize(screen->renderer, scaled_x, scaled_y);
|
||||
}
|
||||
|
||||
void screen_init(struct screen *screen) {
|
||||
*screen = (struct screen) SCREEN_INITIALIZER;
|
||||
}
|
||||
@@ -163,7 +174,7 @@ SDL_bool screen_init_rendering(struct screen *screen, const char *device_name, s
|
||||
return SDL_FALSE;
|
||||
}
|
||||
|
||||
if (SDL_RenderSetLogicalSize(screen->renderer, frame_size.width, frame_size.height)) {
|
||||
if (render_set_scaled_logical_size(screen, frame_size)) {
|
||||
LOGE("Could not set renderer logical size: %s", SDL_GetError());
|
||||
screen_destroy(screen);
|
||||
return SDL_FALSE;
|
||||
@@ -208,7 +219,7 @@ void screen_destroy(struct screen *screen) {
|
||||
// recreate the texture and resize the window if the frame size has changed
|
||||
static SDL_bool prepare_for_frame(struct screen *screen, struct size new_frame_size) {
|
||||
if (screen->frame_size.width != new_frame_size.width || screen->frame_size.height != new_frame_size.height) {
|
||||
if (SDL_RenderSetLogicalSize(screen->renderer, new_frame_size.width, new_frame_size.height)) {
|
||||
if (render_set_scaled_logical_size(screen, new_frame_size)) {
|
||||
LOGE("Could not set renderer logical size: %s", SDL_GetError());
|
||||
return SDL_FALSE;
|
||||
}
|
||||
@@ -302,3 +313,7 @@ void screen_resize_to_pixel_perfect(struct screen *screen) {
|
||||
LOGD("Resized to pixel-perfect");
|
||||
}
|
||||
}
|
||||
|
||||
void screen_update_scale(struct screen *screen) {
|
||||
render_set_scaled_logical_size(screen, screen->frame_size);
|
||||
}
|
||||
|
||||
@@ -66,4 +66,8 @@ void screen_resize_to_fit(struct screen *screen);
|
||||
// resize window to 1:1 (pixel-perfect)
|
||||
void screen_resize_to_pixel_perfect(struct screen *screen);
|
||||
|
||||
// recompute the scale in case the window moved from/to another screen with a
|
||||
// different HiDPI
|
||||
void screen_update_scale(struct screen *screen);
|
||||
|
||||
#endif
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
#include "command.h"
|
||||
|
||||
#include <errno.h>
|
||||
#include <fcntl.h>
|
||||
#include <signal.h>
|
||||
#include <stdlib.h>
|
||||
#include <sys/types.h>
|
||||
@@ -7,18 +9,65 @@
|
||||
#include <unistd.h>
|
||||
#include "log.h"
|
||||
|
||||
pid_t cmd_execute(const char *path, const char *const argv[]) {
|
||||
pid_t pid = fork();
|
||||
if (pid == -1) {
|
||||
perror("fork");
|
||||
return -1;
|
||||
enum process_result cmd_execute(const char *path, const char *const argv[], pid_t *pid) {
|
||||
int fd[2];
|
||||
|
||||
if (pipe(fd) == -1) {
|
||||
perror("pipe");
|
||||
return PROCESS_ERROR_GENERIC;
|
||||
}
|
||||
if (pid == 0) {
|
||||
execvp(path, (char *const *)argv);
|
||||
perror("exec");
|
||||
|
||||
enum process_result ret = PROCESS_SUCCESS;
|
||||
|
||||
*pid = fork();
|
||||
if (*pid == -1) {
|
||||
perror("fork");
|
||||
ret = PROCESS_ERROR_GENERIC;
|
||||
goto end;
|
||||
}
|
||||
|
||||
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(path, (char *const *)argv);
|
||||
if (errno == ENOENT) {
|
||||
ret = PROCESS_ERROR_MISSING_BINARY;
|
||||
} else {
|
||||
ret = PROCESS_ERROR_GENERIC;
|
||||
}
|
||||
perror("exec");
|
||||
} else {
|
||||
perror("fcntl");
|
||||
ret = PROCESS_ERROR_GENERIC;
|
||||
}
|
||||
// send ret to the parent
|
||||
if (write(fd[1], &ret, sizeof(ret)) == -1) {
|
||||
perror("write");
|
||||
}
|
||||
// close write side before exiting
|
||||
close(fd[1]);
|
||||
_exit(1);
|
||||
}
|
||||
return pid;
|
||||
|
||||
end:
|
||||
if (fd[0] != -1) {
|
||||
close(fd[0]);
|
||||
}
|
||||
if (fd[1] != -1) {
|
||||
close(fd[1]);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
SDL_bool cmd_terminate(pid_t pid) {
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
#include "log.h"
|
||||
#include "str_util.h"
|
||||
|
||||
HANDLE cmd_execute(const char *path, const char *const argv[]) {
|
||||
enum process_result cmd_execute(const char *path, const char *const argv[], HANDLE *handle) {
|
||||
STARTUPINFO si;
|
||||
PROCESS_INFORMATION pi;
|
||||
memset(&si, 0, sizeof(si));
|
||||
@@ -18,7 +18,8 @@ HANDLE cmd_execute(const char *path, const char *const argv[]) {
|
||||
size_t ret = xstrjoin(cmd, argv, ' ', sizeof(cmd));
|
||||
if (ret >= sizeof(cmd)) {
|
||||
LOGE("Command too long (%" PRIsizet " chars)", sizeof(cmd) - 1);
|
||||
return NULL;
|
||||
*handle = NULL;
|
||||
return PROCESS_ERROR_GENERIC;
|
||||
}
|
||||
|
||||
#ifdef WINDOWS_NOCONSOLE
|
||||
@@ -27,10 +28,15 @@ HANDLE cmd_execute(const char *path, const char *const argv[]) {
|
||||
int flags = 0;
|
||||
#endif
|
||||
if (!CreateProcess(NULL, cmd, NULL, NULL, FALSE, flags, NULL, NULL, &si, &pi)) {
|
||||
return NULL;
|
||||
*handle = NULL;
|
||||
if (GetLastError() == ERROR_FILE_NOT_FOUND) {
|
||||
return PROCESS_ERROR_MISSING_BINARY;
|
||||
}
|
||||
return PROCESS_ERROR_GENERIC;
|
||||
}
|
||||
|
||||
return pi.hProcess;
|
||||
*handle = pi.hProcess;
|
||||
return PROCESS_SUCCESS;
|
||||
}
|
||||
|
||||
SDL_bool cmd_terminate(HANDLE handle) {
|
||||
|
||||
@@ -5,9 +5,9 @@ import android.net.LocalSocket;
|
||||
import android.net.LocalSocketAddress;
|
||||
|
||||
import java.io.Closeable;
|
||||
import java.io.FileDescriptor;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
|
||||
public final class DesktopConnection implements Closeable {
|
||||
@@ -18,14 +18,14 @@ public final class DesktopConnection implements Closeable {
|
||||
|
||||
private final LocalSocket socket;
|
||||
private final InputStream inputStream;
|
||||
private final OutputStream outputStream;
|
||||
private final FileDescriptor fd;
|
||||
|
||||
private final ControlEventReader reader = new ControlEventReader();
|
||||
|
||||
private DesktopConnection(LocalSocket socket) throws IOException {
|
||||
this.socket = socket;
|
||||
inputStream = socket.getInputStream();
|
||||
outputStream = socket.getOutputStream();
|
||||
fd = socket.getFileDescriptor();
|
||||
}
|
||||
|
||||
private static LocalSocket connect(String abstractName) throws IOException {
|
||||
@@ -78,11 +78,11 @@ public final class DesktopConnection implements Closeable {
|
||||
buffer[DEVICE_NAME_FIELD_LENGTH + 1] = (byte) width;
|
||||
buffer[DEVICE_NAME_FIELD_LENGTH + 2] = (byte) (height >> 8);
|
||||
buffer[DEVICE_NAME_FIELD_LENGTH + 3] = (byte) height;
|
||||
outputStream.write(buffer, 0, buffer.length);
|
||||
IO.writeFully(fd, buffer, 0, buffer.length);
|
||||
}
|
||||
|
||||
public OutputStream getOutputStream() {
|
||||
return outputStream;
|
||||
public FileDescriptor getFd() {
|
||||
return fd;
|
||||
}
|
||||
|
||||
public ControlEvent receiveControlEvent() throws IOException {
|
||||
|
||||
31
server/src/main/java/com/genymobile/scrcpy/IO.java
Normal file
31
server/src/main/java/com/genymobile/scrcpy/IO.java
Normal file
@@ -0,0 +1,31 @@
|
||||
package com.genymobile.scrcpy;
|
||||
|
||||
import android.system.ErrnoException;
|
||||
import android.system.Os;
|
||||
import android.system.OsConstants;
|
||||
|
||||
import java.io.FileDescriptor;
|
||||
import java.io.IOException;
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
public class IO {
|
||||
private IO() {
|
||||
// not instantiable
|
||||
}
|
||||
|
||||
public static void writeFully(FileDescriptor fd, ByteBuffer from) throws IOException {
|
||||
while (from.hasRemaining()) {
|
||||
try {
|
||||
Os.write(fd, from);
|
||||
} catch (ErrnoException e) {
|
||||
if (e.errno != OsConstants.EINTR) {
|
||||
throw new IOException(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static void writeFully(FileDescriptor fd, byte[] buffer, int offset, int len) throws IOException {
|
||||
writeFully(fd, ByteBuffer.wrap(buffer, offset, len));
|
||||
}
|
||||
}
|
||||
@@ -9,8 +9,8 @@ import android.media.MediaFormat;
|
||||
import android.os.IBinder;
|
||||
import android.view.Surface;
|
||||
|
||||
import java.io.FileDescriptor;
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
|
||||
@@ -48,7 +48,7 @@ public class ScreenEncoder implements Device.RotationListener {
|
||||
return rotationChanged.getAndSet(false);
|
||||
}
|
||||
|
||||
public void streamScreen(Device device, OutputStream outputStream) throws IOException {
|
||||
public void streamScreen(Device device, FileDescriptor fd) throws IOException {
|
||||
MediaFormat format = createFormat(bitRate, frameRate, iFrameInterval);
|
||||
device.setRotationListener(this);
|
||||
boolean alive;
|
||||
@@ -64,7 +64,7 @@ public class ScreenEncoder implements Device.RotationListener {
|
||||
setDisplaySurface(display, surface, contentRect, videoRect);
|
||||
codec.start();
|
||||
try {
|
||||
alive = encode(codec, outputStream);
|
||||
alive = encode(codec, fd);
|
||||
} finally {
|
||||
codec.stop();
|
||||
destroyDisplay(display);
|
||||
@@ -77,9 +77,7 @@ public class ScreenEncoder implements Device.RotationListener {
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
private boolean encode(MediaCodec codec, FileDescriptor fd) throws IOException {
|
||||
boolean eof = false;
|
||||
MediaCodec.BufferInfo bufferInfo = new MediaCodec.BufferInfo();
|
||||
while (!consumeRotationChange() && !eof) {
|
||||
@@ -91,15 +89,8 @@ public class ScreenEncoder implements Device.RotationListener {
|
||||
break;
|
||||
}
|
||||
if (outputBufferId >= 0) {
|
||||
ByteBuffer outputBuffer = codec.getOutputBuffer(outputBufferId);
|
||||
while (outputBuffer.hasRemaining()) {
|
||||
int remaining = outputBuffer.remaining();
|
||||
int len = Math.min(buf.length, remaining);
|
||||
// the outputBuffer is probably direct (it has no underlying array), and LocalSocket does not expose channels,
|
||||
// so we must copy the data locally to write them manually to the output stream
|
||||
outputBuffer.get(buf, 0, len);
|
||||
outputStream.write(buf, 0, len);
|
||||
}
|
||||
ByteBuffer codecBuffer = codec.getOutputBuffer(outputBufferId);
|
||||
IO.writeFully(fd, codecBuffer);
|
||||
}
|
||||
} finally {
|
||||
if (outputBufferId >= 0) {
|
||||
|
||||
@@ -21,7 +21,7 @@ public final class Server {
|
||||
|
||||
try {
|
||||
// synchronous
|
||||
screenEncoder.streamScreen(device, connection.getOutputStream());
|
||||
screenEncoder.streamScreen(device, connection.getFd());
|
||||
} catch (IOException e) {
|
||||
// this is expected on close
|
||||
Ln.d("Screen streaming stopped");
|
||||
|
||||
Reference in New Issue
Block a user