Compare commits

...

12 Commits

Author SHA1 Message Date
Romain Vimont
e3a07c3309 update-on-event 2018-09-13 22:24:26 +02:00
Romain Vimont
2e40285244 hidpi-scale-wip 2018-09-13 20:37:45 +02:00
Romain Vimont
eca99d5af7 Fix header guard name 2018-09-13 16:27:19 +02:00
Romain Vimont
6a1fb070f7 Merge branch 'npes87184:dev' into dev (#254)
Return specific error for missing adb binary on Windows
2018-09-09 15:28:56 +02:00
yuchenlin
27bed948d4 Use specific error for missing binary on Windows
Signed-off-by: yuchenlin <npes87184@gmail.com>
Signed-off-by: Romain Vimont <rom@rom1v.com>
2018-09-09 15:28:25 +02:00
Romain Vimont
66def38b73 Avoid additional buffer copy in userspace
Directly send the data from MediaCodec buffers to the LocalSocket,
without an intermediate copy in userspace.
2018-09-09 15:05:06 +02:00
Romain Vimont
a60aef5aaf Merge branch 'philippsandhaus:master' into dev (#252)
Added new command line parameter to start in fullscreen
2018-09-04 18:51:56 +02:00
Romain Vimont
28015c3ee4 Present fullscreen option in README 2018-09-04 18:51:35 +02:00
Philipp Sandhaus
af9808cf02 Add option to start in fullscreen
Signed-off-by: Romain Vimont <rom@rom1v.com>
2018-09-04 18:51:32 +02:00
Romain Vimont
34550311be Merge branch 'npes87184:dev' into dev (#236)
check adb runnable before starting scrcpy
2018-09-04 18:43:17 +02:00
Romain Vimont
55d33ddd5f Do not handle system-specific values in command.c
The common command.c handled process errors from system-specific int
values (errno).

Rather, expose a new enum process_result to handle error cause in a
generic way.
2018-09-04 08:57:07 +02:00
yuchenlin
6d2d803003 Notify adb missing
There are many user who encounters missing adb.
To stop things happens again, we check it and show
sexy response to user.

Signed-off-by: yuchenlin <npes87184@gmail.com>
2018-09-04 08:46:17 +02:00
15 changed files with 185 additions and 41 deletions

View File

@@ -312,6 +312,12 @@ To show physical touches while scrcpy is running:
scrcpy -t scrcpy -t
``` ```
The app may be started directly in fullscreen:
```
scrcpy -f
```
To run without installing: To run without installing:
```bash ```bash

View File

@@ -18,9 +18,25 @@ static inline const char *get_adb_command() {
return 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) { process_t adb_execute(const char *serial, const char *const adb_cmd[], int len) {
const char *cmd[len + 4]; const char *cmd[len + 4];
int i; int i;
process_t process;
cmd[0] = get_adb_command(); cmd[0] = get_adb_command();
if (serial) { if (serial) {
cmd[1] = "-s"; 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 *)); memcpy(&cmd[i], adb_cmd, len * sizeof(const char *));
cmd[len + i] = NULL; 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) { process_t adb_forward(const char *serial, uint16_t local_port, const char *device_socket_name) {

View File

@@ -32,7 +32,13 @@
#endif #endif
# define NO_EXIT_CODE -1 # 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_terminate(process_t pid);
SDL_bool cmd_simple_wait(process_t pid, exit_code_t *exit_code); SDL_bool cmd_simple_wait(process_t pid, exit_code_t *exit_code);

View File

@@ -1,5 +1,5 @@
#ifndef FILE_HANDLER_H #ifndef FILE_HANDLER_H
#define FILE_HADNELR_H #define FILE_HANDLER_H
#include <SDL2/SDL_mutex.h> #include <SDL2/SDL_mutex.h>
#include <SDL2/SDL_stdinc.h> #include <SDL2/SDL_stdinc.h>

View File

@@ -11,6 +11,7 @@
struct args { struct args {
const char *serial; const char *serial;
const char *crop; const char *crop;
SDL_bool fullscreen;
SDL_bool help; SDL_bool help;
SDL_bool version; SDL_bool version;
SDL_bool show_touches; SDL_bool show_touches;
@@ -36,6 +37,9 @@ static void usage(const char *arg0) {
" (typically, portrait for a phone, landscape for a tablet).\n" " (typically, portrait for a phone, landscape for a tablet).\n"
" Any --max-size value is computed on the cropped size.\n" " Any --max-size value is computed on the cropped size.\n"
"\n" "\n"
" -f, --fullscreen\n"
" Start in fullscreen.\n"
"\n"
" -h, --help\n" " -h, --help\n"
" Print this help.\n" " Print this help.\n"
"\n" "\n"
@@ -200,6 +204,7 @@ static SDL_bool parse_args(struct args *args, int argc, char *argv[]) {
static const struct option long_options[] = { static const struct option long_options[] = {
{"bit-rate", required_argument, NULL, 'b'}, {"bit-rate", required_argument, NULL, 'b'},
{"crop", required_argument, NULL, 'c'}, {"crop", required_argument, NULL, 'c'},
{"fullscreen", no_argument, NULL, 'f'},
{"help", no_argument, NULL, 'h'}, {"help", no_argument, NULL, 'h'},
{"max-size", required_argument, NULL, 'm'}, {"max-size", required_argument, NULL, 'm'},
{"port", required_argument, NULL, 'p'}, {"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 }, {NULL, 0, NULL, 0 },
}; };
int c; 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) { switch (c) {
case 'b': case 'b':
if (!parse_bit_rate(optarg, &args->bit_rate)) { 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': case 'c':
args->crop = optarg; args->crop = optarg;
break; break;
case 'f':
args->fullscreen = SDL_TRUE;
break;
case 'h': case 'h':
args->help = SDL_TRUE; args->help = SDL_TRUE;
break; break;
@@ -305,6 +313,7 @@ int main(int argc, char *argv[]) {
.max_size = args.max_size, .max_size = args.max_size,
.bit_rate = args.bit_rate, .bit_rate = args.bit_rate,
.show_touches = args.show_touches, .show_touches = args.show_touches,
.fullscreen = args.fullscreen,
}; };
int res = scrcpy(&options) ? 0 : 1; int res = scrcpy(&options) ? 0 : 1;

View File

@@ -88,6 +88,7 @@ static SDL_bool event_loop(void) {
switch (event.window.event) { switch (event.window.event) {
case SDL_WINDOWEVENT_EXPOSED: case SDL_WINDOWEVENT_EXPOSED:
case SDL_WINDOWEVENT_SIZE_CHANGED: case SDL_WINDOWEVENT_SIZE_CHANGED:
screen_update_scale(&screen);
screen_render(&screen); screen_render(&screen);
break; break;
} }
@@ -223,6 +224,10 @@ SDL_bool scrcpy(const struct scrcpy_options *options) {
show_touches_waited = SDL_TRUE; show_touches_waited = SDL_TRUE;
} }
if (options->fullscreen) {
screen_switch_fullscreen(&screen);
}
ret = event_loop(); ret = event_loop();
LOGD("quit..."); LOGD("quit...");

View File

@@ -10,6 +10,7 @@ struct scrcpy_options {
Uint16 max_size; Uint16 max_size;
Uint32 bit_rate; Uint32 bit_rate;
SDL_bool show_touches; SDL_bool show_touches;
SDL_bool fullscreen;
}; };
SDL_bool scrcpy(const struct scrcpy_options *options); SDL_bool scrcpy(const struct scrcpy_options *options);

View File

@@ -132,6 +132,17 @@ static inline struct size get_initial_optimal_size(struct size frame_size) {
return get_optimal_size(frame_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) { void screen_init(struct screen *screen) {
*screen = (struct screen) SCREEN_INITIALIZER; *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; 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()); LOGE("Could not set renderer logical size: %s", SDL_GetError());
screen_destroy(screen); screen_destroy(screen);
return SDL_FALSE; 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 // 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) { 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 (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()); LOGE("Could not set renderer logical size: %s", SDL_GetError());
return SDL_FALSE; return SDL_FALSE;
} }
@@ -302,3 +313,7 @@ void screen_resize_to_pixel_perfect(struct screen *screen) {
LOGD("Resized to pixel-perfect"); LOGD("Resized to pixel-perfect");
} }
} }
void screen_update_scale(struct screen *screen) {
render_set_scaled_logical_size(screen, screen->frame_size);
}

View File

@@ -66,4 +66,8 @@ void screen_resize_to_fit(struct screen *screen);
// resize window to 1:1 (pixel-perfect) // resize window to 1:1 (pixel-perfect)
void screen_resize_to_pixel_perfect(struct screen *screen); 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 #endif

View File

@@ -1,5 +1,7 @@
#include "command.h" #include "command.h"
#include <errno.h>
#include <fcntl.h>
#include <signal.h> #include <signal.h>
#include <stdlib.h> #include <stdlib.h>
#include <sys/types.h> #include <sys/types.h>
@@ -7,18 +9,65 @@
#include <unistd.h> #include <unistd.h>
#include "log.h" #include "log.h"
pid_t cmd_execute(const char *path, const char *const argv[]) { enum process_result cmd_execute(const char *path, const char *const argv[], pid_t *pid) {
pid_t pid = fork(); int fd[2];
if (pid == -1) {
perror("fork"); if (pipe(fd) == -1) {
return -1; perror("pipe");
return PROCESS_ERROR_GENERIC;
} }
if (pid == 0) {
execvp(path, (char *const *)argv); enum process_result ret = PROCESS_SUCCESS;
perror("exec");
*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); _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) { SDL_bool cmd_terminate(pid_t pid) {

View File

@@ -4,7 +4,7 @@
#include "log.h" #include "log.h"
#include "str_util.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; STARTUPINFO si;
PROCESS_INFORMATION pi; PROCESS_INFORMATION pi;
memset(&si, 0, sizeof(si)); 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)); size_t ret = xstrjoin(cmd, argv, ' ', sizeof(cmd));
if (ret >= sizeof(cmd)) { if (ret >= sizeof(cmd)) {
LOGE("Command too long (%" PRIsizet " chars)", sizeof(cmd) - 1); LOGE("Command too long (%" PRIsizet " chars)", sizeof(cmd) - 1);
return NULL; *handle = NULL;
return PROCESS_ERROR_GENERIC;
} }
#ifdef WINDOWS_NOCONSOLE #ifdef WINDOWS_NOCONSOLE
@@ -27,10 +28,15 @@ HANDLE cmd_execute(const char *path, const char *const argv[]) {
int flags = 0; int flags = 0;
#endif #endif
if (!CreateProcess(NULL, cmd, NULL, NULL, FALSE, flags, NULL, NULL, &si, &pi)) { 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) { SDL_bool cmd_terminate(HANDLE handle) {

View File

@@ -5,9 +5,9 @@ import android.net.LocalSocket;
import android.net.LocalSocketAddress; import android.net.LocalSocketAddress;
import java.io.Closeable; import java.io.Closeable;
import java.io.FileDescriptor;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.io.OutputStream;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
public final class DesktopConnection implements Closeable { public final class DesktopConnection implements Closeable {
@@ -18,14 +18,14 @@ public final class DesktopConnection implements Closeable {
private final LocalSocket socket; private final LocalSocket socket;
private final InputStream inputStream; private final InputStream inputStream;
private final OutputStream outputStream; private final FileDescriptor fd;
private final ControlEventReader reader = new ControlEventReader(); private final ControlEventReader reader = new ControlEventReader();
private DesktopConnection(LocalSocket socket) throws IOException { private DesktopConnection(LocalSocket socket) throws IOException {
this.socket = socket; this.socket = socket;
inputStream = socket.getInputStream(); inputStream = socket.getInputStream();
outputStream = socket.getOutputStream(); fd = socket.getFileDescriptor();
} }
private static LocalSocket connect(String abstractName) throws IOException { 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 + 1] = (byte) width;
buffer[DEVICE_NAME_FIELD_LENGTH + 2] = (byte) (height >> 8); buffer[DEVICE_NAME_FIELD_LENGTH + 2] = (byte) (height >> 8);
buffer[DEVICE_NAME_FIELD_LENGTH + 3] = (byte) height; buffer[DEVICE_NAME_FIELD_LENGTH + 3] = (byte) height;
outputStream.write(buffer, 0, buffer.length); IO.writeFully(fd, buffer, 0, buffer.length);
} }
public OutputStream getOutputStream() { public FileDescriptor getFd() {
return outputStream; return fd;
} }
public ControlEvent receiveControlEvent() throws IOException { public ControlEvent receiveControlEvent() throws IOException {

View 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));
}
}

View File

@@ -9,8 +9,8 @@ import android.media.MediaFormat;
import android.os.IBinder; import android.os.IBinder;
import android.view.Surface; import android.view.Surface;
import java.io.FileDescriptor;
import java.io.IOException; import java.io.IOException;
import java.io.OutputStream;
import java.nio.ByteBuffer; import java.nio.ByteBuffer;
import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicBoolean;
@@ -48,7 +48,7 @@ public class ScreenEncoder implements Device.RotationListener {
return rotationChanged.getAndSet(false); 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); MediaFormat format = createFormat(bitRate, frameRate, iFrameInterval);
device.setRotationListener(this); device.setRotationListener(this);
boolean alive; boolean alive;
@@ -64,7 +64,7 @@ public class ScreenEncoder implements Device.RotationListener {
setDisplaySurface(display, surface, contentRect, videoRect); setDisplaySurface(display, surface, contentRect, videoRect);
codec.start(); codec.start();
try { try {
alive = encode(codec, outputStream); alive = encode(codec, fd);
} finally { } finally {
codec.stop(); codec.stop();
destroyDisplay(display); destroyDisplay(display);
@@ -77,9 +77,7 @@ public class ScreenEncoder implements Device.RotationListener {
} }
} }
private boolean encode(MediaCodec codec, OutputStream outputStream) throws IOException { private boolean encode(MediaCodec codec, FileDescriptor fd) throws IOException {
@SuppressWarnings("checkstyle:MagicNumber")
byte[] buf = new byte[bitRate / 8]; // may contain up to 1 second of video
boolean eof = false; boolean eof = false;
MediaCodec.BufferInfo bufferInfo = new MediaCodec.BufferInfo(); MediaCodec.BufferInfo bufferInfo = new MediaCodec.BufferInfo();
while (!consumeRotationChange() && !eof) { while (!consumeRotationChange() && !eof) {
@@ -91,15 +89,8 @@ public class ScreenEncoder implements Device.RotationListener {
break; break;
} }
if (outputBufferId >= 0) { if (outputBufferId >= 0) {
ByteBuffer outputBuffer = codec.getOutputBuffer(outputBufferId); ByteBuffer codecBuffer = codec.getOutputBuffer(outputBufferId);
while (outputBuffer.hasRemaining()) { IO.writeFully(fd, codecBuffer);
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);
}
} }
} finally { } finally {
if (outputBufferId >= 0) { if (outputBufferId >= 0) {

View File

@@ -21,7 +21,7 @@ public final class Server {
try { try {
// synchronous // synchronous
screenEncoder.streamScreen(device, connection.getOutputStream()); screenEncoder.streamScreen(device, connection.getFd());
} catch (IOException e) { } catch (IOException e) {
// this is expected on close // this is expected on close
Ln.d("Screen streaming stopped"); Ln.d("Screen streaming stopped");