Compare commits
37 Commits
v1.8
...
twosockets
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
72c83b2ac2 | ||
|
|
7dcac5b8b3 | ||
|
|
f88345de0a | ||
|
|
2a1bdd0af3 | ||
|
|
2a7fee6c16 | ||
|
|
92f0266704 | ||
|
|
6cf134f31f | ||
|
|
b75f0e9427 | ||
|
|
5d473efeb5 | ||
|
|
a41dd6c79f | ||
|
|
c3779d8513 | ||
|
|
b3bd5f1b80 | ||
|
|
a920ba6471 | ||
|
|
3133d5d1c7 | ||
|
|
2dc1a59471 | ||
|
|
3068457b90 | ||
|
|
56f8e78f58 | ||
|
|
1630f923ef | ||
|
|
e443518ed9 | ||
|
|
eeb8e8420f | ||
|
|
39b5893c42 | ||
|
|
b941854c73 | ||
|
|
068253a3a2 | ||
|
|
c8338b2918 | ||
|
|
2837c6eaab | ||
|
|
668e54fd4b | ||
|
|
01664777c8 | ||
|
|
ffa8c66979 | ||
|
|
5254e585c6 | ||
|
|
66baf0f95b | ||
|
|
f11b0ec204 | ||
|
|
e2ef39fae5 | ||
|
|
3eda38e5fc | ||
|
|
a16cf95b8e | ||
|
|
71fd238b0a | ||
|
|
d795144a36 | ||
|
|
c287826f8e |
6
BUILD.md
6
BUILD.md
@@ -234,10 +234,10 @@ You can then [run](README.md#run) _scrcpy_.
|
||||
|
||||
## Prebuilt server
|
||||
|
||||
- [`scrcpy-server-v1.7.jar`][direct-scrcpy-server]
|
||||
_(SHA-256: ee86ec8424f7dc50cacdf927312bdb46e0aa0d68611da584dc4b16d8057bc25e)_
|
||||
- [`scrcpy-server-v1.8.jar`][direct-scrcpy-server]
|
||||
_(SHA-256: 839055ef905903bf98ead1b9b8a127fe402b39ad657a81f9a914b2dbcb2ce5c0)_
|
||||
|
||||
[direct-scrcpy-server]: https://github.com/Genymobile/scrcpy/releases/download/v1.7/scrcpy-server-v1.7.jar
|
||||
[direct-scrcpy-server]: https://github.com/Genymobile/scrcpy/releases/download/v1.8/scrcpy-server-v1.8.jar
|
||||
|
||||
Download the prebuilt server somewhere, and specify its path during the Meson
|
||||
configuration:
|
||||
|
||||
79
DEVELOP.md
79
DEVELOP.md
@@ -32,7 +32,7 @@ The server is a Java application (with a [`public static void main(String...
|
||||
args)`][main] method), compiled against the Android framework, and executed as
|
||||
`shell` on the Android device.
|
||||
|
||||
[main]: https://github.com/Genymobile/scrcpy/blob/v1.0/server/src/main/java/com/genymobile/scrcpy/Server.java#L61
|
||||
[main]: https://github.com/Genymobile/scrcpy/blob/v1.8/server/src/main/java/com/genymobile/scrcpy/Server.java#L100
|
||||
|
||||
To run such a Java application, the classes must be [_dexed_][dex] (typically,
|
||||
to `classes.dex`). If `my.package.MainClass` is the main class, compiled to
|
||||
@@ -65,8 +65,8 @@ They can be called using reflection though. The communication with hidden
|
||||
components is provided by [_wrappers_ classes][wrappers] and [aidl].
|
||||
|
||||
[hidden]: https://stackoverflow.com/a/31908373/1987178
|
||||
[wrappers]: https://github.com/Genymobile/scrcpy/blob/v1.0/server/src/main/java/com/genymobile/scrcpy/wrappers
|
||||
[aidl]: https://github.com/Genymobile/scrcpy/blob/v1.0/server/src/main/aidl/android/view
|
||||
[wrappers]: https://github.com/Genymobile/scrcpy/blob/v1.8/server/src/main/java/com/genymobile/scrcpy/wrappers
|
||||
[aidl]: https://github.com/Genymobile/scrcpy/blob/v1.8/server/src/main/aidl/android/view
|
||||
|
||||
|
||||
### Threading
|
||||
@@ -89,9 +89,9 @@ The video is encoded using the [`MediaCodec`] API. The codec takes its input
|
||||
from a [surface] associated to the display, and writes the resulting H.264
|
||||
stream to the provided output stream (the socket connected to the client).
|
||||
|
||||
[`ScreenEncoder`]: https://github.com/Genymobile/scrcpy/blob/v1.0/server/src/main/java/com/genymobile/scrcpy/ScreenEncoder.java
|
||||
[`ScreenEncoder`]: https://github.com/Genymobile/scrcpy/blob/v1.8/server/src/main/java/com/genymobile/scrcpy/ScreenEncoder.java
|
||||
[`MediaCodec`]: https://developer.android.com/reference/android/media/MediaCodec.html
|
||||
[surface]: https://github.com/Genymobile/scrcpy/blob/v1.0/server/src/main/java/com/genymobile/scrcpy/ScreenEncoder.java#L63-L64
|
||||
[surface]: https://github.com/Genymobile/scrcpy/blob/v1.8/server/src/main/java/com/genymobile/scrcpy/ScreenEncoder.java#L69-L70
|
||||
|
||||
On device [rotation], the codec, surface and display are reinitialized, and a
|
||||
new video stream is produced.
|
||||
@@ -105,8 +105,9 @@ because it avoids to send unnecessary frames, but there are drawbacks:
|
||||
Both problems are [solved][repeat] by the flag
|
||||
[`KEY_REPEAT_PREVIOUS_FRAME_AFTER`][repeat-flag].
|
||||
|
||||
[rotation]: https://github.com/Genymobile/scrcpy/blob/v1.0/server/src/main/java/com/genymobile/scrcpy/ScreenEncoder.java#L89-L92
|
||||
[repeat]: https://github.com/Genymobile/scrcpy/blob/v1.0/server/src/main/java/com/genymobile/scrcpy/ScreenEncoder.java#L125-L126
|
||||
[rotation]: https://github.com/Genymobile/scrcpy/blob/v1.8/server/src/main/java/com/genymobile/scrcpy/ScreenEncoder.java#L90
|
||||
[repeat]:
|
||||
https://github.com/Genymobile/scrcpy/blob/v1.8/server/src/main/java/com/genymobile/scrcpy/ScreenEncoder.java#L147-L148
|
||||
[repeat-flag]: https://developer.android.com/reference/android/media/MediaFormat.html#KEY_REPEAT_PREVIOUS_FRAME_AFTER
|
||||
|
||||
|
||||
@@ -124,11 +125,11 @@ All of them may need to inject input events to the system. To do so, they use
|
||||
the _hidden_ method [`InputManager.injectInputEvent`] (exposed by our
|
||||
[`InputManager` wrapper][inject-wrapper]).
|
||||
|
||||
[`EventController`]: https://github.com/Genymobile/scrcpy/blob/v1.0/server/src/main/java/com/genymobile/scrcpy/EventController.java#L70
|
||||
[`EventController`]: https://github.com/Genymobile/scrcpy/blob/v1.8/server/src/main/java/com/genymobile/scrcpy/EventController.java#L66
|
||||
[`KeyEvent`]: https://developer.android.com/reference/android/view/KeyEvent.html
|
||||
[`MotionEvent`]: https://developer.android.com/reference/android/view/MotionEvent.html
|
||||
[`InputManager.injectInputEvent`]: https://android.googlesource.com/platform/frameworks/base/+/oreo-release/core/java/android/hardware/input/InputManager.java#857
|
||||
[inject-wrapper]: https://github.com/Genymobile/scrcpy/blob/v1.0/server/src/main/java/com/genymobile/scrcpy/wrappers/InputManager.java#L27
|
||||
[inject-wrapper]: https://github.com/Genymobile/scrcpy/blob/v1.8/server/src/main/java/com/genymobile/scrcpy/wrappers/InputManager.java#L27
|
||||
|
||||
|
||||
|
||||
@@ -153,7 +154,7 @@ Note that the client-server roles are expressed at the application level:
|
||||
- the server _serves_ video stream and handle requests from the client,
|
||||
- the client _controls_ the device through the server.
|
||||
|
||||
However, the roles are inverted at the network level:
|
||||
However, the roles are reversed at the network level:
|
||||
|
||||
- the client opens a server socket and listen on a port before starting the
|
||||
server,
|
||||
@@ -162,6 +163,9 @@ However, the roles are inverted at the network level:
|
||||
This role inversion guarantees that the connection will not fail due to race
|
||||
conditions, and avoids polling.
|
||||
|
||||
_(Note that over TCP/IP, the roles are not reversed, due to a bug in `adb
|
||||
reverse`. See commit [1038bad] and [issue #5].)_
|
||||
|
||||
Once the server is connected, it sends the device information (name and initial
|
||||
screen dimensions). Thus, the client may init the window and renderer, before
|
||||
the first frame is available.
|
||||
@@ -169,6 +173,8 @@ the first frame is available.
|
||||
To minimize startup time, SDL initialization is performed while listening for
|
||||
the connection from the server (see commit [90a46b4]).
|
||||
|
||||
[1038bad]: https://github.com/Genymobile/scrcpy/commit/1038bad3850f18717a048a4d5c0f8110e54ee172
|
||||
[issue #5]: https://github.com/Genymobile/scrcpy/issues/5
|
||||
[90a46b4]: https://github.com/Genymobile/scrcpy/commit/90a46b4c45637d083e877020d85ade52a9a5fa8e
|
||||
|
||||
|
||||
@@ -177,17 +183,25 @@ the connection from the server (see commit [90a46b4]).
|
||||
The client uses 3 threads:
|
||||
|
||||
- the **main** thread, executing the SDL event loop,
|
||||
- the **decoder** thread, decoding video frames,
|
||||
- the **stream** thread, receiving the video and used for decoding and
|
||||
recording,
|
||||
- the **controller** thread, sending _control events_ to the server.
|
||||
|
||||
In addition, another thread can be started if necessary to handle APK
|
||||
installation or file push requests (via drag&drop on the main window).
|
||||
|
||||
### Decoder
|
||||
|
||||
The [decoder] runs in a separate thread. It uses _libav_ to decode the H.264
|
||||
stream from the socket, and notifies the main thread when a new frame is
|
||||
available.
|
||||
|
||||
There are two [frames] simultaneously in memory:
|
||||
### Stream
|
||||
|
||||
The video [stream] is received from the socket (connected to the server on the
|
||||
device) in a separate thread.
|
||||
|
||||
If a [decoder] is present (i.e. `--no-display` is not set), then it uses _libav_
|
||||
to decode the H.264 stream from the socket, and notifies the main thread when a
|
||||
new frame is available.
|
||||
|
||||
There are two [frames][video_buffer] simultaneously in memory:
|
||||
- the **decoding** frame, written by the decoder from the decoder thread,
|
||||
- the **rendering** frame, rendered in a texture from the main thread.
|
||||
|
||||
@@ -195,9 +209,23 @@ When a new decoded frame is available, the decoder _swaps_ the decoding and
|
||||
rendering frame (with proper synchronization). Thus, it immediatly starts
|
||||
to decode a new frame while the main thread renders the last one.
|
||||
|
||||
[decoder]: https://github.com/Genymobile/scrcpy/blob/v1.0/app/src/decoder.c
|
||||
[frames]: https://github.com/Genymobile/scrcpy/blob/v1.0/app/src/frames.h
|
||||
If a [recorder] is present (i.e. `--record` is enabled), then its muxes the raw
|
||||
H.264 packet to the output video file.
|
||||
|
||||
[stream]: https://github.com/Genymobile/scrcpy/blob/v1.8/app/src/stream.h
|
||||
[decoder]: https://github.com/Genymobile/scrcpy/blob/v1.8/app/src/decoder.h
|
||||
[video_buffer]: https://github.com/Genymobile/scrcpy/blob/v1.8/app/src/video_buffer.h
|
||||
[recorder]: https://github.com/Genymobile/scrcpy/blob/v1.8/app/src/recorder.h
|
||||
|
||||
```
|
||||
+----------+ +----------+
|
||||
---> | decoder | ---> | screen |
|
||||
+---------+ / +----------+ +----------+
|
||||
socket ---> | stream | ----
|
||||
+---------+ \ +----------+
|
||||
---> | recorder |
|
||||
+----------+
|
||||
```
|
||||
|
||||
### Controller
|
||||
|
||||
@@ -211,10 +239,10 @@ events_ to a blocking queue hold by the controller. On its own thread, the
|
||||
controller takes events from the queue, that it serializes and sends to the
|
||||
client.
|
||||
|
||||
[controller]: https://github.com/Genymobile/scrcpy/blob/v1.0/app/src/controller.h
|
||||
[controlevent]: https://github.com/Genymobile/scrcpy/blob/v1.0/app/src/controlevent.h
|
||||
[inputmanager]: https://github.com/Genymobile/scrcpy/blob/v1.0/app/src/inputmanager.h
|
||||
[convert]: https://github.com/Genymobile/scrcpy/blob/v1.0/app/src/convert.h
|
||||
[controller]: https://github.com/Genymobile/scrcpy/blob/v1.8/app/src/controller.h
|
||||
[controlevent]: https://github.com/Genymobile/scrcpy/blob/v1.8/app/src/control_event.h
|
||||
[inputmanager]: https://github.com/Genymobile/scrcpy/blob/v1.8/app/src/input_manager.h
|
||||
[convert]: https://github.com/Genymobile/scrcpy/blob/v1.8/app/src/convert.h
|
||||
|
||||
|
||||
### UI and event loop
|
||||
@@ -225,9 +253,10 @@ thread.
|
||||
Events are handled in the [event loop], which either updates the [screen] or
|
||||
delegates to the [input manager][inputmanager].
|
||||
|
||||
[scrcpy]: https://github.com/Genymobile/scrcpy/blob/v1.0/app/src/scrcpy.c
|
||||
[event loop]: https://github.com/Genymobile/scrcpy/blob/v1.0/app/src/scrcpy.c#L38
|
||||
[screen]: https://github.com/Genymobile/scrcpy/blob/v1.0/app/src/screen.h
|
||||
[scrcpy]: https://github.com/Genymobile/scrcpy/blob/v1.8/app/src/scrcpy.c
|
||||
[event loop]:
|
||||
https://github.com/Genymobile/scrcpy/blob/v1.8/app/src/scrcpy.c#L187
|
||||
[screen]: https://github.com/Genymobile/scrcpy/blob/v1.8/app/src/screen.h
|
||||
|
||||
|
||||
## Hack
|
||||
|
||||
34
README.md
34
README.md
@@ -1,4 +1,4 @@
|
||||
# scrcpy (v1.7)
|
||||
# scrcpy (v1.8)
|
||||
|
||||
This application provides display and control of Android devices connected on
|
||||
USB (or [over TCP/IP][article-tcpip]). It does not require any _root_ access.
|
||||
@@ -29,12 +29,10 @@ control it using keyboard and mouse.
|
||||
On Linux, you typically need to [build the app manually][BUILD]. Don't worry,
|
||||
it's not that hard.
|
||||
|
||||
For Arch Linux, two [AUR] packages have been created by users:
|
||||
|
||||
- [`scrcpy`](https://aur.archlinux.org/packages/scrcpy/)
|
||||
- [`scrcpy-prebuiltserver`](https://aur.archlinux.org/packages/scrcpy-prebuiltserver/)
|
||||
For Arch Linux, an [AUR] package is available: [`scrcpy`][aur-link].
|
||||
|
||||
[AUR]: https://wiki.archlinux.org/index.php/Arch_User_Repository
|
||||
[aur-link]: https://aur.archlinux.org/packages/scrcpy/
|
||||
|
||||
For Gentoo, an [Ebuild] is available: [`scrcpy/`][ebuild-link].
|
||||
|
||||
@@ -47,13 +45,13 @@ For Gentoo, an [Ebuild] is available: [`scrcpy/`][ebuild-link].
|
||||
For Windows, for simplicity, prebuilt archives with all the dependencies
|
||||
(including `adb`) are available:
|
||||
|
||||
- [`scrcpy-win32-v1.7.zip`][direct-win32]
|
||||
_(SHA-256: 98ae36f2da0b8212c07066fd93139650554274f863d4cee0781501a0c84f7c23)_
|
||||
- [`scrcpy-win64-v1.7.zip`][direct-win64]
|
||||
_(SHA-256: b41416547521062f19e3f3f539e89a70e713bd086e69ef1b29c128993f7aa462)_
|
||||
- [`scrcpy-win32-v1.8.zip`][direct-win32]
|
||||
_(SHA-256: c0c29ed1c66deaa73bdadacd09e598aafb3a117929cf7a314cce1cc45e34de53)_
|
||||
- [`scrcpy-win64-v1.8.zip`][direct-win64]
|
||||
_(SHA-256: 9cc980d07bd8f036ae4e91d0bc6fc3281d7fa8f9752d4913b643c0fb72a19fb7)_
|
||||
|
||||
[direct-win32]: https://github.com/Genymobile/scrcpy/releases/download/v1.7/scrcpy-win32-v1.7.zip
|
||||
[direct-win64]: https://github.com/Genymobile/scrcpy/releases/download/v1.7/scrcpy-win64-v1.7.zip
|
||||
[direct-win32]: https://github.com/Genymobile/scrcpy/releases/download/v1.8/scrcpy-win32-v1.8.zip
|
||||
[direct-win64]: https://github.com/Genymobile/scrcpy/releases/download/v1.8/scrcpy-win64-v1.8.zip
|
||||
|
||||
You can also [build the app manually][BUILD].
|
||||
|
||||
@@ -171,6 +169,7 @@ To disable mirroring while recording:
|
||||
scrcpy --no-display --record file.mp4
|
||||
scrcpy -Nr file.mkv
|
||||
# interrupt recording with Ctrl+C
|
||||
# Ctrl+C does not terminate properly on Windows, so disconnect the device
|
||||
```
|
||||
|
||||
"Skipped frames" are recorded, even if they are not displayed in real time (for
|
||||
@@ -295,6 +294,19 @@ _¹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].
|
||||
|
||||
@@ -143,18 +143,34 @@ 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
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
#include "command.h"
|
||||
|
||||
#include <assert.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
@@ -20,15 +21,52 @@ 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) {
|
||||
show_adb_err_msg(enum process_result err, const char *const argv[]) {
|
||||
char buf[512];
|
||||
switch (err) {
|
||||
case PROCESS_ERROR_GENERIC:
|
||||
LOGE("Failed to execute adb");
|
||||
argv_to_string(argv, buf, sizeof(buf));
|
||||
LOGE("Failed to execute: %s", buf);
|
||||
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)");
|
||||
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)");
|
||||
break;
|
||||
case PROCESS_SUCCESS:
|
||||
/* do nothing */
|
||||
@@ -37,7 +75,7 @@ show_adb_err_msg(enum process_result err) {
|
||||
}
|
||||
|
||||
process_t
|
||||
adb_execute(const char *serial, const char *const adb_cmd[], int len) {
|
||||
adb_execute(const char *serial, const char *const adb_cmd[], size_t len) {
|
||||
const char *cmd[len + 4];
|
||||
int i;
|
||||
process_t process;
|
||||
@@ -54,7 +92,7 @@ adb_execute(const char *serial, const char *const adb_cmd[], int len) {
|
||||
cmd[len + i] = NULL;
|
||||
enum process_result r = cmd_execute(cmd[0], cmd, &process);
|
||||
if (r != PROCESS_SUCCESS) {
|
||||
show_adb_err_msg(r);
|
||||
show_adb_err_msg(r, cmd);
|
||||
return PROCESS_NONE;
|
||||
}
|
||||
return process;
|
||||
|
||||
@@ -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[], int len);
|
||||
adb_execute(const char *serial, const char *const adb_cmd[], size_t len);
|
||||
|
||||
process_t
|
||||
adb_forward(const char *serial, uint16_t local_port,
|
||||
|
||||
@@ -43,4 +43,9 @@
|
||||
# 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
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -10,11 +10,11 @@
|
||||
|
||||
struct request {
|
||||
file_handler_action_t action;
|
||||
const char *file;
|
||||
char *file;
|
||||
};
|
||||
|
||||
static struct request *
|
||||
request_new(file_handler_action_t action, const char *file) {
|
||||
request_new(file_handler_action_t action, 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((void *) req->file);
|
||||
SDL_free((void *) req);
|
||||
SDL_free(req->file);
|
||||
SDL_free(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((void *) file_handler->serial);
|
||||
SDL_free(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,
|
||||
const char *file) {
|
||||
char *file) {
|
||||
bool res;
|
||||
|
||||
// start file_handler if it's used for the first time
|
||||
|
||||
@@ -21,7 +21,7 @@ struct request_queue {
|
||||
};
|
||||
|
||||
struct file_handler {
|
||||
const char *serial;
|
||||
char *serial;
|
||||
SDL_Thread *thread;
|
||||
SDL_mutex *mutex;
|
||||
SDL_cond *event_cond;
|
||||
@@ -46,9 +46,10 @@ 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,
|
||||
const char *file);
|
||||
char *file);
|
||||
|
||||
#endif
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
#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"
|
||||
@@ -104,7 +105,6 @@ 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"
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
|
||||
#include "command.h"
|
||||
#include "common.h"
|
||||
#include "compat.h"
|
||||
#include "controller.h"
|
||||
#include "decoder.h"
|
||||
#include "device.h"
|
||||
@@ -68,6 +69,13 @@ 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();
|
||||
|
||||
@@ -282,8 +290,7 @@ scrcpy(const struct scrcpy_options *options) {
|
||||
goto finally_destroy_server;
|
||||
}
|
||||
|
||||
socket_t device_socket = server_connect_to(&server);
|
||||
if (device_socket == INVALID_SOCKET) {
|
||||
if (!server_connect_to(&server)) {
|
||||
server_stop(&server);
|
||||
ret = false;
|
||||
goto finally_destroy_server;
|
||||
@@ -295,7 +302,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(device_socket, device_name, &frame_size)) {
|
||||
if (!device_read_info(server.video_socket, device_name, &frame_size)) {
|
||||
server_stop(&server);
|
||||
ret = false;
|
||||
goto finally_destroy_server;
|
||||
@@ -334,7 +341,7 @@ scrcpy(const struct scrcpy_options *options) {
|
||||
|
||||
av_log_set_callback(av_log_callback);
|
||||
|
||||
stream_init(&stream, device_socket, dec, rec);
|
||||
stream_init(&stream, server.video_socket, dec, rec);
|
||||
|
||||
// now we consumed the header values, the socket receives the video stream
|
||||
// start the stream
|
||||
@@ -346,7 +353,7 @@ scrcpy(const struct scrcpy_options *options) {
|
||||
|
||||
if (display) {
|
||||
if (control) {
|
||||
if (!controller_init(&controller, device_socket)) {
|
||||
if (!controller_init(&controller, server.control_socket)) {
|
||||
ret = false;
|
||||
goto finally_stop_stream;
|
||||
}
|
||||
|
||||
@@ -177,13 +177,12 @@ screen_init_rendering(struct screen *screen, const char *device_name,
|
||||
}
|
||||
|
||||
SDL_Surface *icon = read_xpm(icon_xpm);
|
||||
if (!icon) {
|
||||
LOGE("Could not load icon: %s", SDL_GetError());
|
||||
screen_destroy(screen);
|
||||
return false;
|
||||
if (icon) {
|
||||
SDL_SetWindowIcon(screen->window, icon);
|
||||
SDL_FreeSurface(icon);
|
||||
} else {
|
||||
LOGW("Could not load icon");
|
||||
}
|
||||
SDL_SetWindowIcon(screen->window, icon);
|
||||
SDL_FreeSurface(icon);
|
||||
|
||||
LOGI("Initial texture: %" PRIu16 "x%" PRIu16, frame_size.width,
|
||||
frame_size.height);
|
||||
|
||||
@@ -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_all(socket, &byte, 1) != 1) {
|
||||
if (net_recv(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((void *) server->serial);
|
||||
SDL_free(server->serial);
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -219,31 +219,44 @@ server_start(struct server *server, const char *serial,
|
||||
return true;
|
||||
}
|
||||
|
||||
socket_t
|
||||
bool
|
||||
server_connect_to(struct server *server) {
|
||||
if (!server->tunnel_forward) {
|
||||
server->device_socket = net_accept(server->server_socket);
|
||||
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);
|
||||
} else {
|
||||
uint32_t attempts = 100;
|
||||
uint32_t delay = 100; // ms
|
||||
server->device_socket = connect_to_server(server->local_port, attempts,
|
||||
delay);
|
||||
}
|
||||
server->video_socket =
|
||||
connect_to_server(server->local_port, attempts, delay);
|
||||
if (server->video_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 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;
|
||||
}
|
||||
}
|
||||
|
||||
// we don't need the adb tunnel anymore
|
||||
disable_tunnel(server); // ignore failure
|
||||
server->tunnel_enabled = false;
|
||||
|
||||
return server->device_socket;
|
||||
return true;
|
||||
}
|
||||
|
||||
void
|
||||
@@ -268,8 +281,11 @@ server_destroy(struct server *server) {
|
||||
if (server->server_socket != INVALID_SOCKET) {
|
||||
close_socket(&server->server_socket);
|
||||
}
|
||||
if (server->device_socket != INVALID_SOCKET) {
|
||||
close_socket(&server->device_socket);
|
||||
if (server->video_socket != INVALID_SOCKET) {
|
||||
close_socket(&server->video_socket);
|
||||
}
|
||||
if (server->control_socket != INVALID_SOCKET) {
|
||||
close_socket(&server->control_socket);
|
||||
}
|
||||
SDL_free(server->serial);
|
||||
}
|
||||
|
||||
@@ -11,19 +11,21 @@ struct server {
|
||||
char *serial;
|
||||
process_t process;
|
||||
socket_t server_socket; // only used if !tunnel_forward
|
||||
socket_t device_socket;
|
||||
socket_t video_socket;
|
||||
socket_t control_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, \
|
||||
.device_socket = INVALID_SOCKET, \
|
||||
.local_port = 0, \
|
||||
#define SERVER_INITIALIZER { \
|
||||
.serial = NULL, \
|
||||
.process = PROCESS_NONE, \
|
||||
.server_socket = INVALID_SOCKET, \
|
||||
.video_socket = INVALID_SOCKET, \
|
||||
.control_socket = INVALID_SOCKET, \
|
||||
.local_port = 0, \
|
||||
.tunnel_enabled = false, \
|
||||
.tunnel_forward = false, \
|
||||
.send_frame_meta = false, \
|
||||
@@ -36,11 +38,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
|
||||
socket_t
|
||||
bool
|
||||
server_connect_to(struct server *server);
|
||||
|
||||
// disconnect and kill the server process
|
||||
|
||||
@@ -105,6 +105,10 @@ 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;
|
||||
|
||||
@@ -5,21 +5,21 @@
|
||||
|
||||
static void test_control_event_queue_empty(void) {
|
||||
struct control_event_queue queue;
|
||||
SDL_bool init_ok = control_event_queue_init(&queue);
|
||||
bool init_ok = control_event_queue_init(&queue);
|
||||
assert(init_ok);
|
||||
|
||||
assert(control_event_queue_is_empty(&queue));
|
||||
|
||||
struct control_event dummy_event;
|
||||
SDL_bool push_ok = control_event_queue_push(&queue, &dummy_event);
|
||||
bool push_ok = control_event_queue_push(&queue, &dummy_event);
|
||||
assert(push_ok);
|
||||
assert(!control_event_queue_is_empty(&queue));
|
||||
|
||||
SDL_bool take_ok = control_event_queue_take(&queue, &dummy_event);
|
||||
bool take_ok = control_event_queue_take(&queue, &dummy_event);
|
||||
assert(take_ok);
|
||||
assert(control_event_queue_is_empty(&queue));
|
||||
|
||||
SDL_bool take_empty_ok = control_event_queue_take(&queue, &dummy_event);
|
||||
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;
|
||||
SDL_bool init_ok = control_event_queue_init(&queue);
|
||||
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));
|
||||
|
||||
SDL_bool take_ok = control_event_queue_take(&queue, &dummy_event);
|
||||
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;
|
||||
SDL_bool init_ok = control_event_queue_init(&queue);
|
||||
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) {
|
||||
},
|
||||
};
|
||||
|
||||
SDL_bool push1_ok = control_event_queue_push(&queue, &event);
|
||||
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) {
|
||||
},
|
||||
};
|
||||
|
||||
SDL_bool push2_ok = control_event_queue_push(&queue, &event);
|
||||
bool push2_ok = control_event_queue_push(&queue, &event);
|
||||
assert(push2_ok);
|
||||
|
||||
// overwrite event
|
||||
SDL_bool take1_ok = control_event_queue_take(&queue, &event);
|
||||
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
|
||||
SDL_bool take2_ok = control_event_queue_take(&queue, &event);
|
||||
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"));
|
||||
|
||||
@@ -49,13 +49,14 @@ 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];
|
||||
char text[TEXT_MAX_LENGTH + 1];
|
||||
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 + sizeof(text));
|
||||
assert(size == 3 + TEXT_MAX_LENGTH);
|
||||
|
||||
unsigned char expected[3 + TEXT_MAX_LENGTH];
|
||||
expected[0] = 0x01; // CONTROL_EVENT_TYPE_KEYCODE
|
||||
|
||||
@@ -7,7 +7,7 @@ buildscript {
|
||||
jcenter()
|
||||
}
|
||||
dependencies {
|
||||
classpath 'com.android.tools.build:gradle:3.1.1'
|
||||
classpath 'com.android.tools.build:gradle:3.3.0'
|
||||
|
||||
// NOTE: Do not place your application dependencies here; they belong
|
||||
// in the individual module build.gradle files
|
||||
|
||||
4
gradle/wrapper/gradle-wrapper.properties
vendored
4
gradle/wrapper/gradle-wrapper.properties
vendored
@@ -1,6 +1,6 @@
|
||||
#Mon Jun 04 11:48:32 CEST 2018
|
||||
#Thu Apr 18 11:45:59 CEST 2019
|
||||
distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
zipStorePath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-4.4-all.zip
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-4.10.1-all.zip
|
||||
|
||||
13
release.sh
13
release.sh
@@ -1,13 +1,22 @@
|
||||
#!/bin/bash
|
||||
set -e
|
||||
|
||||
# build and test locally
|
||||
# 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
|
||||
|
||||
BUILDDIR=build_release
|
||||
rm -rf "$BUILDDIR"
|
||||
meson "$BUILDDIR" --buildtype release --strip -Db_lto=true
|
||||
cd "$BUILDDIR"
|
||||
ninja
|
||||
ninja test
|
||||
cd -
|
||||
|
||||
# build Windows releases
|
||||
|
||||
@@ -16,16 +16,20 @@ public final class DesktopConnection implements Closeable {
|
||||
|
||||
private static final String SOCKET_NAME = "scrcpy";
|
||||
|
||||
private final LocalSocket socket;
|
||||
private final InputStream inputStream;
|
||||
private final FileDescriptor fd;
|
||||
private final LocalSocket videoSocket;
|
||||
private final FileDescriptor videoFd;
|
||||
|
||||
private final LocalSocket controlSocket;
|
||||
private final InputStream controlInputStream;
|
||||
|
||||
|
||||
private final ControlEventReader reader = new ControlEventReader();
|
||||
|
||||
private DesktopConnection(LocalSocket socket) throws IOException {
|
||||
this.socket = socket;
|
||||
inputStream = socket.getInputStream();
|
||||
fd = socket.getFileDescriptor();
|
||||
private DesktopConnection(LocalSocket videoSocket, LocalSocket controlSocket) throws IOException {
|
||||
this.videoSocket = videoSocket;
|
||||
this.controlSocket = controlSocket;
|
||||
controlInputStream = controlSocket.getInputStream();
|
||||
videoFd = videoSocket.getFileDescriptor();
|
||||
}
|
||||
|
||||
private static LocalSocket connect(String abstractName) throws IOException {
|
||||
@@ -44,25 +48,46 @@ public final class DesktopConnection implements Closeable {
|
||||
}
|
||||
|
||||
public static DesktopConnection open(Device device, boolean tunnelForward) throws IOException {
|
||||
LocalSocket socket;
|
||||
LocalSocket videoSocket;
|
||||
LocalSocket controlSocket;
|
||||
if (tunnelForward) {
|
||||
socket = listenAndAccept(SOCKET_NAME);
|
||||
// send one byte so the client may read() to detect a connection error
|
||||
socket.getOutputStream().write(0);
|
||||
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();
|
||||
}
|
||||
} else {
|
||||
socket = connect(SOCKET_NAME);
|
||||
videoSocket = connect(SOCKET_NAME);
|
||||
try {
|
||||
controlSocket = connect(SOCKET_NAME);
|
||||
} catch (IOException | RuntimeException e) {
|
||||
videoSocket.close();
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
DesktopConnection connection = new DesktopConnection(socket);
|
||||
DesktopConnection connection = new DesktopConnection(videoSocket, controlSocket);
|
||||
Size videoSize = device.getScreenInfo().getVideoSize();
|
||||
connection.send(Device.getDeviceName(), videoSize.getWidth(), videoSize.getHeight());
|
||||
return connection;
|
||||
}
|
||||
|
||||
public void close() throws IOException {
|
||||
socket.shutdownInput();
|
||||
socket.shutdownOutput();
|
||||
socket.close();
|
||||
videoSocket.shutdownInput();
|
||||
videoSocket.shutdownOutput();
|
||||
videoSocket.close();
|
||||
controlSocket.shutdownInput();
|
||||
controlSocket.shutdownOutput();
|
||||
controlSocket.close();
|
||||
}
|
||||
|
||||
@SuppressWarnings("checkstyle:MagicNumber")
|
||||
@@ -78,17 +103,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(fd, buffer, 0, buffer.length);
|
||||
IO.writeFully(videoFd, buffer, 0, buffer.length);
|
||||
}
|
||||
|
||||
public FileDescriptor getFd() {
|
||||
return fd;
|
||||
public FileDescriptor getVideoFd() {
|
||||
return videoFd;
|
||||
}
|
||||
|
||||
public ControlEvent receiveControlEvent() throws IOException {
|
||||
ControlEvent event = reader.next();
|
||||
while (event == null) {
|
||||
reader.readFrom(inputStream);
|
||||
reader.readFrom(controlInputStream);
|
||||
event = reader.next();
|
||||
}
|
||||
return event;
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
package com.genymobile.scrcpy;
|
||||
|
||||
import com.genymobile.scrcpy.wrappers.InputManager;
|
||||
import com.genymobile.scrcpy.wrappers.ServiceManager;
|
||||
|
||||
import android.graphics.Point;
|
||||
import android.os.SystemClock;
|
||||
@@ -105,13 +104,16 @@ public class EventController {
|
||||
return true;
|
||||
}
|
||||
|
||||
private boolean injectText(String text) {
|
||||
private int injectText(String text) {
|
||||
int successCount = 0;
|
||||
for (char c : text.toCharArray()) {
|
||||
if (!injectChar(c)) {
|
||||
return false;
|
||||
Ln.w("Could not inject char u+" + String.format("%04x", (int) c));
|
||||
continue;
|
||||
}
|
||||
successCount++;
|
||||
}
|
||||
return true;
|
||||
return successCount;
|
||||
}
|
||||
|
||||
private boolean injectMouse(int action, int buttons, Position position) {
|
||||
|
||||
@@ -8,7 +8,7 @@ import java.io.FileDescriptor;
|
||||
import java.io.IOException;
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
public class IO {
|
||||
public final class IO {
|
||||
private IO() {
|
||||
// not instantiable
|
||||
}
|
||||
|
||||
@@ -52,7 +52,13 @@ public final class Ln {
|
||||
if (isEnabled(Level.ERROR)) {
|
||||
Log.e(TAG, message, throwable);
|
||||
System.out.println("ERROR: " + message);
|
||||
throwable.printStackTrace();
|
||||
if (throwable != null) {
|
||||
throwable.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static void e(String message) {
|
||||
e(message, null);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,7 +3,6 @@ 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;
|
||||
@@ -71,8 +70,9 @@ public class ScreenEncoder implements Device.RotationListener {
|
||||
codec.start();
|
||||
try {
|
||||
alive = encode(codec, fd);
|
||||
} finally {
|
||||
// do not call stop() on exception, it would trigger an IllegalStateException
|
||||
codec.stop();
|
||||
} finally {
|
||||
destroyDisplay(display);
|
||||
codec.release();
|
||||
surface.release();
|
||||
|
||||
@@ -4,7 +4,6 @@ import android.graphics.Rect;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.Arrays;
|
||||
|
||||
public final class Server {
|
||||
|
||||
@@ -25,7 +24,7 @@ public final class Server {
|
||||
|
||||
try {
|
||||
// synchronous
|
||||
screenEncoder.streamScreen(device, connection.getFd());
|
||||
screenEncoder.streamScreen(device, connection.getVideoFd());
|
||||
} catch (IOException e) {
|
||||
// this is expected on close
|
||||
Ln.d("Screen streaming stopped");
|
||||
@@ -49,8 +48,9 @@ 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,6 +73,7 @@ public final class Server {
|
||||
return options;
|
||||
}
|
||||
|
||||
@SuppressWarnings("checkstyle:MagicNumber")
|
||||
private static Rect parseCrop(String crop) {
|
||||
if ("-".equals(crop)) {
|
||||
return null;
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
package com.genymobile.scrcpy.wrappers;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.os.IInterface;
|
||||
import android.view.InputEvent;
|
||||
|
||||
import com.genymobile.scrcpy.Ln;
|
||||
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.lang.reflect.Method;
|
||||
@@ -10,32 +10,42 @@ import java.lang.reflect.Method;
|
||||
public class StatusBarManager {
|
||||
|
||||
private final IInterface manager;
|
||||
private final Method expandNotificationsPanelMethod;
|
||||
private final Method collapsePanelsMethod;
|
||||
private Method expandNotificationsPanelMethod;
|
||||
private 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) {
|
||||
throw new AssertionError(e);
|
||||
Ln.e("Cannot invoke ServiceBarManager.expandNotificationsPanel()", 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) {
|
||||
throw new AssertionError(e);
|
||||
Ln.e("Cannot invoke ServiceBarManager.collapsePanels()", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user