Compare commits

..

1 Commits

Author SHA1 Message Date
Romain Vimont
5b56c57795 Screen off PoC 2019-03-15 20:24:28 +01:00
27 changed files with 144 additions and 276 deletions

View File

@@ -294,19 +294,6 @@ _¹Double-click on black borders to remove them._
_²Right-click turns the screen on if it was off, presses BACK otherwise._
## Custom paths
To use a specific _adb_ binary, configure its path in the environment variable
`ADB`:
ADB=/path/to/adb scrcpy
To override the path of the `scrcpy-server.jar` file (it can be [useful] on
Windows), configure its path in `SCRCPY_SERVER_PATH`.
[useful]: https://github.com/Genymobile/scrcpy/issues/278#issuecomment-429330345
## Why _scrcpy_?
A colleague challenged me to find a name as unpronounceable as [gnirehtet].

View File

@@ -143,34 +143,18 @@ else
link_args = []
endif
executable('scrcpy', src,
dependencies: dependencies,
include_directories: src_dir,
install: true,
c_args: c_args,
link_args: link_args)
executable('scrcpy', src, dependencies: dependencies, include_directories: src_dir, install: true, c_args: c_args, link_args: link_args)
### TESTS
tests = [
['test_control_event_queue', [
'tests/test_control_event_queue.c',
'src/control_event.c'
]],
['test_control_event_serialize', [
'tests/test_control_event_serialize.c',
'src/control_event.c'
]],
['test_strutil', [
'tests/test_strutil.c',
'src/str_util.c'
]],
['test_control_event_queue', ['tests/test_control_event_queue.c', 'src/control_event.c']],
['test_control_event_serialize', ['tests/test_control_event_serialize.c', 'src/control_event.c']],
['test_strutil', ['tests/test_strutil.c', 'src/str_util.c']],
]
foreach t : tests
exe = executable(t[0], t[1],
include_directories: src_dir,
dependencies: dependencies)
exe = executable(t[0], t[1], include_directories: src_dir, dependencies: dependencies)
test(t[0], exe)
endforeach

View File

@@ -1,6 +1,5 @@
#include "command.h"
#include <assert.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
@@ -21,52 +20,15 @@ get_adb_command(void) {
return adb_command;
}
// serialize argv to string "[arg1], [arg2], [arg3]"
static size_t
argv_to_string(const char *const *argv, char *buf, size_t bufsize) {
size_t idx = 0;
bool first = true;
while (*argv) {
const char *arg = *argv;
size_t len = strlen(arg);
// count space for "[], ...\0"
if (idx + len + 8 >= bufsize) {
// not enough space, truncate
assert(idx < bufsize - 4);
memcpy(&buf[idx], "...", 3);
idx += 3;
break;
}
if (first) {
first = false;
} else {
buf[idx++] = ',';
buf[idx++] = ' ';
}
buf[idx++] = '[';
memcpy(&buf[idx], arg, len);
idx += len;
buf[idx++] = ']';
argv++;
}
assert(idx < bufsize);
buf[idx] = '\0';
return idx;
}
static void
show_adb_err_msg(enum process_result err, const char *const argv[]) {
char buf[512];
show_adb_err_msg(enum process_result err) {
switch (err) {
case PROCESS_ERROR_GENERIC:
argv_to_string(argv, buf, sizeof(buf));
LOGE("Failed to execute: %s", buf);
LOGE("Failed to execute adb");
break;
case PROCESS_ERROR_MISSING_BINARY:
argv_to_string(argv, buf, sizeof(buf));
LOGE("Command not found: %s", buf);
LOGE("(make 'adb' accessible from your PATH or define its full"
"path in the ADB environment variable)");
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 */
@@ -75,7 +37,7 @@ 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(const char *serial, const char *const adb_cmd[], int len) {
const char *cmd[len + 4];
int i;
process_t process;
@@ -92,7 +54,7 @@ adb_execute(const char *serial, const char *const adb_cmd[], size_t len) {
cmd[len + i] = NULL;
enum process_result r = cmd_execute(cmd[0], cmd, &process);
if (r != PROCESS_SUCCESS) {
show_adb_err_msg(r, cmd);
show_adb_err_msg(r);
return PROCESS_NONE;
}
return process;

View File

@@ -49,7 +49,7 @@ bool
cmd_simple_wait(process_t pid, exit_code_t *exit_code);
process_t
adb_execute(const char *serial, const char *const adb_cmd[], size_t len);
adb_execute(const char *serial, const char *const adb_cmd[], int len);
process_t
adb_forward(const char *serial, uint16_t local_port,

View File

@@ -43,9 +43,4 @@
# define SCRCPY_SDL_HAS_WINDOW_ALWAYS_ON_TOP
#endif
#if SDL_VERSION_ATLEAST(2, 0, 8)
// <https://hg.libsdl.org/SDL/rev/dfde5d3f9781>
# define SCRCPY_SDL_HAS_HINT_VIDEO_X11_NET_WM_BYPASS_COMPOSITOR
#endif
#endif

View File

@@ -81,9 +81,9 @@ convert_keycode(SDL_Keycode from, enum android_keycode *to, uint16_t mod) {
MAP(SDLK_ESCAPE, AKEYCODE_ESCAPE);
MAP(SDLK_BACKSPACE, AKEYCODE_DEL);
MAP(SDLK_TAB, AKEYCODE_TAB);
MAP(SDLK_HOME, AKEYCODE_HOME);
MAP(SDLK_PAGEUP, AKEYCODE_PAGE_UP);
MAP(SDLK_DELETE, AKEYCODE_FORWARD_DEL);
MAP(SDLK_HOME, AKEYCODE_MOVE_HOME);
MAP(SDLK_END, AKEYCODE_MOVE_END);
MAP(SDLK_PAGEDOWN, AKEYCODE_PAGE_DOWN);
MAP(SDLK_RIGHT, AKEYCODE_DPAD_RIGHT);

View File

@@ -10,11 +10,11 @@
struct request {
file_handler_action_t action;
char *file;
const char *file;
};
static struct request *
request_new(file_handler_action_t action, char *file) {
request_new(file_handler_action_t action, const char *file) {
struct request *req = SDL_malloc(sizeof(*req));
if (!req) {
return NULL;
@@ -29,8 +29,8 @@ request_free(struct request *req) {
if (!req) {
return;
}
SDL_free(req->file);
SDL_free(req);
SDL_free((void *) req->file);
SDL_free((void *) req);
}
static bool
@@ -121,7 +121,7 @@ file_handler_destroy(struct file_handler *file_handler) {
SDL_DestroyCond(file_handler->event_cond);
SDL_DestroyMutex(file_handler->mutex);
request_queue_destroy(&file_handler->queue);
SDL_free(file_handler->serial);
SDL_free((void *) file_handler->serial);
}
static process_t
@@ -137,7 +137,7 @@ push_file(const char *serial, const char *file) {
bool
file_handler_request(struct file_handler *file_handler,
file_handler_action_t action,
char *file) {
const char *file) {
bool res;
// start file_handler if it's used for the first time

View File

@@ -21,7 +21,7 @@ struct request_queue {
};
struct file_handler {
char *serial;
const char *serial;
SDL_Thread *thread;
SDL_mutex *mutex;
SDL_cond *event_cond;
@@ -46,10 +46,9 @@ file_handler_stop(struct file_handler *file_handler);
void
file_handler_join(struct file_handler *file_handler);
// take ownership of file, and will SDL_free() it
bool
file_handler_request(struct file_handler *file_handler,
file_handler_action_t action,
char *file);
const char *file);
#endif

View File

@@ -5,7 +5,6 @@
#include <stdint.h>
#include <unistd.h>
#include <libavformat/avformat.h>
#define SDL_MAIN_HANDLED // avoid link error on Linux Windows Subsystem
#include <SDL2/SDL.h>
#include "compat.h"
@@ -105,6 +104,7 @@ static void usage(const char *arg0) {
" resize window to remove black borders\n"
"\n"
" Ctrl+h\n"
" Home\n"
" Middle-click\n"
" click on HOME\n"
"\n"

View File

@@ -9,7 +9,6 @@
#include "command.h"
#include "common.h"
#include "compat.h"
#include "controller.h"
#include "decoder.h"
#include "device.h"
@@ -69,13 +68,6 @@ sdl_init_and_configure(bool display) {
}
#endif
#ifdef SCRCPY_SDL_HAS_HINT_VIDEO_X11_NET_WM_BYPASS_COMPOSITOR
// Disable compositor bypassing on X11
if (!SDL_SetHint(SDL_HINT_VIDEO_X11_NET_WM_BYPASS_COMPOSITOR, "0")) {
LOGW("Could not disable X11 compositor bypass");
}
#endif
// Do not disable the screensaver when scrcpy is running
SDL_EnableScreenSaver();
@@ -290,7 +282,8 @@ scrcpy(const struct scrcpy_options *options) {
goto finally_destroy_server;
}
if (!server_connect_to(&server)) {
socket_t device_socket = server_connect_to(&server);
if (device_socket == INVALID_SOCKET) {
server_stop(&server);
ret = false;
goto finally_destroy_server;
@@ -302,7 +295,7 @@ scrcpy(const struct scrcpy_options *options) {
// screenrecord does not send frames when the screen content does not
// change therefore, we transmit the screen size before the video stream,
// to be able to init the window immediately
if (!device_read_info(server.video_socket, device_name, &frame_size)) {
if (!device_read_info(device_socket, device_name, &frame_size)) {
server_stop(&server);
ret = false;
goto finally_destroy_server;
@@ -341,7 +334,7 @@ scrcpy(const struct scrcpy_options *options) {
av_log_set_callback(av_log_callback);
stream_init(&stream, server.video_socket, dec, rec);
stream_init(&stream, device_socket, dec, rec);
// now we consumed the header values, the socket receives the video stream
// start the stream
@@ -353,7 +346,7 @@ scrcpy(const struct scrcpy_options *options) {
if (display) {
if (control) {
if (!controller_init(&controller, server.control_socket)) {
if (!controller_init(&controller, device_socket)) {
ret = false;
goto finally_stop_stream;
}

View File

@@ -177,12 +177,13 @@ screen_init_rendering(struct screen *screen, const char *device_name,
}
SDL_Surface *icon = read_xpm(icon_xpm);
if (icon) {
SDL_SetWindowIcon(screen->window, icon);
SDL_FreeSurface(icon);
} else {
LOGW("Could not load icon");
if (!icon) {
LOGE("Could not load icon: %s", SDL_GetError());
screen_destroy(screen);
return false;
}
SDL_SetWindowIcon(screen->window, icon);
SDL_FreeSurface(icon);
LOGI("Initial texture: %" PRIu16 "x%" PRIu16, frame_size.width,
frame_size.height);

View File

@@ -119,7 +119,7 @@ connect_and_read_byte(uint16_t port) {
char byte;
// the connection may succeed even if the server behind the "adb tunnel"
// is not listening, so read one byte to detect a working connection
if (net_recv(socket, &byte, 1) != 1) {
if (net_recv_all(socket, &byte, 1) != 1) {
// the server is not listening yet behind the adb tunnel
return INVALID_SOCKET;
}
@@ -210,7 +210,7 @@ server_start(struct server *server, const char *serial,
close_socket(&server->server_socket);
}
disable_tunnel(server);
SDL_free(server->serial);
SDL_free((void *) server->serial);
return false;
}
@@ -219,44 +219,31 @@ server_start(struct server *server, const char *serial,
return true;
}
bool
socket_t
server_connect_to(struct server *server) {
if (!server->tunnel_forward) {
server->video_socket = net_accept(server->server_socket);
if (server->video_socket == INVALID_SOCKET) {
return false;
}
server->control_socket = net_accept(server->server_socket);
if (server->control_socket == INVALID_SOCKET) {
// the video_socket will be clean up on destroy
return false;
}
// we don't need the server socket anymore
close_socket(&server->server_socket);
server->device_socket = net_accept(server->server_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) {
return false;
}
server->device_socket = connect_to_server(server->local_port, attempts,
delay);
}
// 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) {
return false;
}
if (server->device_socket == INVALID_SOCKET) {
return INVALID_SOCKET;
}
if (!server->tunnel_forward) {
// we don't need the server socket anymore
close_socket(&server->server_socket);
}
// we don't need the adb tunnel anymore
disable_tunnel(server); // ignore failure
server->tunnel_enabled = false;
return true;
return server->device_socket;
}
void
@@ -281,11 +268,8 @@ server_destroy(struct server *server) {
if (server->server_socket != INVALID_SOCKET) {
close_socket(&server->server_socket);
}
if (server->video_socket != INVALID_SOCKET) {
close_socket(&server->video_socket);
}
if (server->control_socket != INVALID_SOCKET) {
close_socket(&server->control_socket);
if (server->device_socket != INVALID_SOCKET) {
close_socket(&server->device_socket);
}
SDL_free(server->serial);
}

View File

@@ -11,21 +11,19 @@ struct server {
char *serial;
process_t process;
socket_t server_socket; // only used if !tunnel_forward
socket_t video_socket;
socket_t control_socket;
socket_t device_socket;
uint16_t local_port;
bool tunnel_enabled;
bool tunnel_forward; // use "adb forward" instead of "adb reverse"
bool send_frame_meta; // request frame PTS to be able to record properly
};
#define SERVER_INITIALIZER { \
.serial = NULL, \
.process = PROCESS_NONE, \
.server_socket = INVALID_SOCKET, \
.video_socket = INVALID_SOCKET, \
.control_socket = INVALID_SOCKET, \
.local_port = 0, \
#define SERVER_INITIALIZER { \
.serial = NULL, \
.process = PROCESS_NONE, \
.server_socket = INVALID_SOCKET, \
.device_socket = INVALID_SOCKET, \
.local_port = 0, \
.tunnel_enabled = false, \
.tunnel_forward = false, \
.send_frame_meta = false, \
@@ -38,11 +36,11 @@ server_init(struct server *server);
// push, enable tunnel et start the server
bool
server_start(struct server *server, const char *serial,
uint16_t local_port, uint16_t max_size, uint32_t bit_rate,
const char *crop, bool send_frame_meta);
uint16_t local_port, uint16_t max_size, uint32_t bit_rate,
const char *crop, bool send_frame_meta);
// block until the communication with the server is established
bool
socket_t
server_connect_to(struct server *server);
// disconnect and kill the server process

View File

@@ -105,10 +105,6 @@ read_xpm(char *xpm[]) {
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

@@ -5,21 +5,21 @@
static void test_control_event_queue_empty(void) {
struct control_event_queue queue;
bool init_ok = control_event_queue_init(&queue);
SDL_bool init_ok = control_event_queue_init(&queue);
assert(init_ok);
assert(control_event_queue_is_empty(&queue));
struct control_event dummy_event;
bool push_ok = control_event_queue_push(&queue, &dummy_event);
SDL_bool push_ok = control_event_queue_push(&queue, &dummy_event);
assert(push_ok);
assert(!control_event_queue_is_empty(&queue));
bool take_ok = control_event_queue_take(&queue, &dummy_event);
SDL_bool take_ok = control_event_queue_take(&queue, &dummy_event);
assert(take_ok);
assert(control_event_queue_is_empty(&queue));
bool take_empty_ok = control_event_queue_take(&queue, &dummy_event);
SDL_bool take_empty_ok = control_event_queue_take(&queue, &dummy_event);
assert(!take_empty_ok); // the queue is empty
control_event_queue_destroy(&queue);
@@ -27,7 +27,7 @@ static void test_control_event_queue_empty(void) {
static void test_control_event_queue_full(void) {
struct control_event_queue queue;
bool init_ok = control_event_queue_init(&queue);
SDL_bool init_ok = control_event_queue_init(&queue);
assert(init_ok);
assert(!control_event_queue_is_full(&queue));
@@ -36,7 +36,7 @@ static void test_control_event_queue_full(void) {
// fill the queue
while (control_event_queue_push(&queue, &dummy_event));
bool take_ok = control_event_queue_take(&queue, &dummy_event);
SDL_bool take_ok = control_event_queue_take(&queue, &dummy_event);
assert(take_ok);
assert(!control_event_queue_is_full(&queue));
@@ -45,7 +45,7 @@ static void test_control_event_queue_full(void) {
static void test_control_event_queue_push_take(void) {
struct control_event_queue queue;
bool init_ok = control_event_queue_init(&queue);
SDL_bool init_ok = control_event_queue_init(&queue);
assert(init_ok);
struct control_event event = {
@@ -57,7 +57,7 @@ static void test_control_event_queue_push_take(void) {
},
};
bool push1_ok = control_event_queue_push(&queue, &event);
SDL_bool push1_ok = control_event_queue_push(&queue, &event);
assert(push1_ok);
event = (struct control_event) {
@@ -67,11 +67,11 @@ static void test_control_event_queue_push_take(void) {
},
};
bool push2_ok = control_event_queue_push(&queue, &event);
SDL_bool push2_ok = control_event_queue_push(&queue, &event);
assert(push2_ok);
// overwrite event
bool take1_ok = control_event_queue_take(&queue, &event);
SDL_bool take1_ok = control_event_queue_take(&queue, &event);
assert(take1_ok);
assert(event.type == CONTROL_EVENT_TYPE_KEYCODE);
assert(event.keycode_event.action == AKEY_EVENT_ACTION_DOWN);
@@ -79,7 +79,7 @@ static void test_control_event_queue_push_take(void) {
assert(event.keycode_event.metastate == (AMETA_CTRL_LEFT_ON | AMETA_CTRL_ON));
// overwrite event
bool take2_ok = control_event_queue_take(&queue, &event);
SDL_bool take2_ok = control_event_queue_take(&queue, &event);
assert(take2_ok);
assert(event.type == CONTROL_EVENT_TYPE_TEXT);
assert(!strcmp(event.text_event.text, "abc"));

View File

@@ -49,14 +49,13 @@ static void test_serialize_text_event(void) {
static void test_serialize_long_text_event(void) {
struct control_event event;
event.type = CONTROL_EVENT_TYPE_TEXT;
char text[TEXT_MAX_LENGTH + 1];
char text[TEXT_MAX_LENGTH];
memset(text, 'a', sizeof(text));
text[TEXT_MAX_LENGTH] = '\0';
event.text_event.text = text;
unsigned char buf[SERIALIZED_EVENT_MAX_SIZE];
int size = control_event_serialize(&event, buf);
assert(size == 3 + TEXT_MAX_LENGTH);
assert(size == 3 + sizeof(text));
unsigned char expected[3 + TEXT_MAX_LENGTH];
expected[0] = 0x01; // CONTROL_EVENT_TYPE_KEYCODE

View File

@@ -7,7 +7,7 @@ buildscript {
jcenter()
}
dependencies {
classpath 'com.android.tools.build:gradle:3.3.0'
classpath 'com.android.tools.build:gradle:3.1.1'
// NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files

View File

@@ -1,6 +1,6 @@
#Thu Apr 18 11:45:59 CEST 2019
#Mon Jun 04 11:48:32 CEST 2018
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-4.10.1-all.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-4.4-all.zip

View File

@@ -1,22 +1,13 @@
#!/bin/bash
set -e
# test locally
TESTDIR=build_test
rm -rf "$TESTDIR"
# run client tests with ASAN enabled
meson "$TESTDIR" -Db_sanitize=address
ninja -C"$TESTDIR" test
# test server
GRADLE=${GRADLE:-./gradlew}
$GRADLE -p server check
# build and test locally
BUILDDIR=build_release
rm -rf "$BUILDDIR"
meson "$BUILDDIR" --buildtype release --strip -Db_lto=true
cd "$BUILDDIR"
ninja
ninja test
cd -
# build Windows releases

View File

@@ -16,20 +16,16 @@ public final class DesktopConnection implements Closeable {
private static final String SOCKET_NAME = "scrcpy";
private final LocalSocket videoSocket;
private final FileDescriptor videoFd;
private final LocalSocket controlSocket;
private final InputStream controlInputStream;
private final LocalSocket socket;
private final InputStream inputStream;
private final FileDescriptor fd;
private final ControlEventReader reader = new ControlEventReader();
private DesktopConnection(LocalSocket videoSocket, LocalSocket controlSocket) throws IOException {
this.videoSocket = videoSocket;
this.controlSocket = controlSocket;
controlInputStream = controlSocket.getInputStream();
videoFd = videoSocket.getFileDescriptor();
private DesktopConnection(LocalSocket socket) throws IOException {
this.socket = socket;
inputStream = socket.getInputStream();
fd = socket.getFileDescriptor();
}
private static LocalSocket connect(String abstractName) throws IOException {
@@ -48,46 +44,25 @@ public final class DesktopConnection implements Closeable {
}
public static DesktopConnection open(Device device, boolean tunnelForward) throws IOException {
LocalSocket videoSocket;
LocalSocket controlSocket;
LocalSocket socket;
if (tunnelForward) {
LocalServerSocket localServerSocket = new LocalServerSocket(SOCKET_NAME);
try {
videoSocket = localServerSocket.accept();
// send one byte so the client may read() to detect a connection error
videoSocket.getOutputStream().write(0);
try {
controlSocket = localServerSocket.accept();
} catch (IOException | RuntimeException e) {
videoSocket.close();
throw e;
}
} finally {
localServerSocket.close();
}
socket = listenAndAccept(SOCKET_NAME);
// send one byte so the client may read() to detect a connection error
socket.getOutputStream().write(0);
} else {
videoSocket = connect(SOCKET_NAME);
try {
controlSocket = connect(SOCKET_NAME);
} catch (IOException | RuntimeException e) {
videoSocket.close();
throw e;
}
socket = connect(SOCKET_NAME);
}
DesktopConnection connection = new DesktopConnection(videoSocket, controlSocket);
DesktopConnection connection = new DesktopConnection(socket);
Size videoSize = device.getScreenInfo().getVideoSize();
connection.send(Device.getDeviceName(), videoSize.getWidth(), videoSize.getHeight());
return connection;
}
public void close() throws IOException {
videoSocket.shutdownInput();
videoSocket.shutdownOutput();
videoSocket.close();
controlSocket.shutdownInput();
controlSocket.shutdownOutput();
controlSocket.close();
socket.shutdownInput();
socket.shutdownOutput();
socket.close();
}
@SuppressWarnings("checkstyle:MagicNumber")
@@ -103,17 +78,17 @@ 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;
IO.writeFully(videoFd, buffer, 0, buffer.length);
IO.writeFully(fd, buffer, 0, buffer.length);
}
public FileDescriptor getVideoFd() {
return videoFd;
public FileDescriptor getFd() {
return fd;
}
public ControlEvent receiveControlEvent() throws IOException {
ControlEvent event = reader.next();
while (event == null) {
reader.readFrom(controlInputStream);
reader.readFrom(inputStream);
event = reader.next();
}
return event;

View File

@@ -1,6 +1,7 @@
package com.genymobile.scrcpy;
import com.genymobile.scrcpy.wrappers.InputManager;
import com.genymobile.scrcpy.wrappers.ServiceManager;
import android.graphics.Point;
import android.os.SystemClock;
@@ -104,16 +105,13 @@ public class EventController {
return true;
}
private int injectText(String text) {
int successCount = 0;
private boolean injectText(String text) {
for (char c : text.toCharArray()) {
if (!injectChar(c)) {
Ln.w("Could not inject char u+" + String.format("%04x", (int) c));
continue;
return false;
}
successCount++;
}
return successCount;
return true;
}
private boolean injectMouse(int action, int buttons, Position position) {

View File

@@ -8,7 +8,7 @@ import java.io.FileDescriptor;
import java.io.IOException;
import java.nio.ByteBuffer;
public final class IO {
public class IO {
private IO() {
// not instantiable
}

View File

@@ -52,13 +52,7 @@ public final class Ln {
if (isEnabled(Level.ERROR)) {
Log.e(TAG, message, throwable);
System.out.println("ERROR: " + message);
if (throwable != null) {
throwable.printStackTrace();
}
throwable.printStackTrace();
}
}
public static void e(String message) {
e(message, null);
}
}

View File

@@ -3,6 +3,7 @@ package com.genymobile.scrcpy;
import com.genymobile.scrcpy.wrappers.SurfaceControl;
import android.graphics.Rect;
import android.media.MediaMuxer;
import android.media.MediaCodec;
import android.media.MediaCodecInfo;
import android.media.MediaFormat;
@@ -56,6 +57,8 @@ public class ScreenEncoder implements Device.RotationListener {
public void streamScreen(Device device, FileDescriptor fd) throws IOException {
MediaFormat format = createFormat(bitRate, frameRate, iFrameInterval);
device.setRotationListener(this);
IBinder d = SurfaceControl.getBuiltInDisplay(0);
SurfaceControl.setDisplayPowerMode(d, SurfaceControl.POWER_MODE_OFF);
boolean alive;
try {
do {
@@ -70,9 +73,8 @@ public class ScreenEncoder implements Device.RotationListener {
codec.start();
try {
alive = encode(codec, fd);
// do not call stop() on exception, it would trigger an IllegalStateException
codec.stop();
} finally {
codec.stop();
destroyDisplay(display);
codec.release();
surface.release();

View File

@@ -4,6 +4,7 @@ import android.graphics.Rect;
import java.io.File;
import java.io.IOException;
import java.util.Arrays;
public final class Server {
@@ -24,7 +25,7 @@ public final class Server {
try {
// synchronous
screenEncoder.streamScreen(device, connection.getVideoFd());
screenEncoder.streamScreen(device, connection.getFd());
} catch (IOException e) {
// this is expected on close
Ln.d("Screen streaming stopped");
@@ -48,9 +49,8 @@ public final class Server {
@SuppressWarnings("checkstyle:MagicNumber")
private static Options createOptions(String... args) {
if (args.length != 5) {
if (args.length != 5)
throw new IllegalArgumentException("Expecting 5 parameters");
}
Options options = new Options();
@@ -73,7 +73,6 @@ public final class Server {
return options;
}
@SuppressWarnings("checkstyle:MagicNumber")
private static Rect parseCrop(String crop) {
if ("-".equals(crop)) {
return null;

View File

@@ -1,8 +1,8 @@
package com.genymobile.scrcpy.wrappers;
import android.annotation.SuppressLint;
import android.os.IInterface;
import com.genymobile.scrcpy.Ln;
import android.view.InputEvent;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
@@ -10,42 +10,32 @@ import java.lang.reflect.Method;
public class StatusBarManager {
private final IInterface manager;
private Method expandNotificationsPanelMethod;
private Method collapsePanelsMethod;
private final Method expandNotificationsPanelMethod;
private final Method collapsePanelsMethod;
public StatusBarManager(IInterface manager) {
this.manager = manager;
try {
expandNotificationsPanelMethod = manager.getClass().getMethod("expandNotificationsPanel");
collapsePanelsMethod = manager.getClass().getMethod("collapsePanels");
} catch (NoSuchMethodException e) {
throw new AssertionError(e);
}
}
public void expandNotificationsPanel() {
if (expandNotificationsPanelMethod == null) {
try {
expandNotificationsPanelMethod = manager.getClass().getMethod("expandNotificationsPanel");
} catch (NoSuchMethodException e) {
Ln.e("ServiceBarManager.expandNotificationsPanel() is not available on this device");
return;
}
}
try {
expandNotificationsPanelMethod.invoke(manager);
} catch (InvocationTargetException | IllegalAccessException e) {
Ln.e("Cannot invoke ServiceBarManager.expandNotificationsPanel()", e);
throw new AssertionError(e);
}
}
public void collapsePanels() {
if (collapsePanelsMethod == null) {
try {
collapsePanelsMethod = manager.getClass().getMethod("collapsePanels");
} catch (NoSuchMethodException e) {
Ln.e("ServiceBarManager.collapsePanels() is not available on this device");
return;
}
}
try {
collapsePanelsMethod.invoke(manager);
} catch (InvocationTargetException | IllegalAccessException e) {
Ln.e("Cannot invoke ServiceBarManager.collapsePanels()", e);
throw new AssertionError(e);
}
}
}

View File

@@ -10,6 +10,11 @@ public final class SurfaceControl {
private static final Class<?> CLASS;
public static final int POWER_MODE_OFF = 0;
public static final int POWER_MODE_DOZE = 1;
public static final int POWER_MODE_NORMAL = 2;
public static final int POWER_MODE_DOZE_SUSPEND = 3;
static {
try {
CLASS = Class.forName("android.view.SurfaceControl");
@@ -71,6 +76,22 @@ public final class SurfaceControl {
}
}
public static IBinder getBuiltInDisplay(int builtInDisplayId) {
try {
return (IBinder) CLASS.getMethod("getBuiltInDisplay", int.class).invoke(null, builtInDisplayId);
} catch (Exception e) {
throw new AssertionError(e);
}
}
public static void setDisplayPowerMode(IBinder displayToken, int mode) {
try {
CLASS.getMethod("setDisplayPowerMode", IBinder.class, int.class).invoke(null, displayToken, mode);
} catch (Exception e) {
throw new AssertionError(e);
}
}
public static void destroyDisplay(IBinder displayToken) {
try {
CLASS.getMethod("destroyDisplay", IBinder.class).invoke(null, displayToken);