mirror of
https://github.com/Genymobile/scrcpy.git
synced 2026-03-16 09:04:27 +01:00
Compare commits
20 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
259d3aee93 | ||
|
|
90859f1dcf | ||
|
|
1afe9ce2ee | ||
|
|
273cec8a92 | ||
|
|
02f189b1de | ||
|
|
4abe163233 | ||
|
|
5ffdcbb7be | ||
|
|
ffe0417228 | ||
|
|
e3afb67e7f | ||
|
|
4ee1391361 | ||
|
|
2755bfc255 | ||
|
|
3b17ff7c86 | ||
|
|
4eb6b26c93 | ||
|
|
eb34098add | ||
|
|
b777760bca | ||
|
|
72bdfbc7a6 | ||
|
|
8604f16b30 | ||
|
|
5d11339259 | ||
|
|
e2a272bf99 | ||
|
|
d104d3bda9 |
75
DEVELOP.md
75
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.8/server/src/main/java/com/genymobile/scrcpy/Server.java#L100
|
||||
[main]: https://github.com/Genymobile/scrcpy/blob/ffe0417228fb78ab45b7ee4e202fc06fc8875bf3/server/src/main/java/com/genymobile/scrcpy/Server.java#L123
|
||||
|
||||
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,18 +65,18 @@ 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.8/server/src/main/java/com/genymobile/scrcpy/wrappers
|
||||
[aidl]: https://github.com/Genymobile/scrcpy/blob/v1.8/server/src/main/aidl/android/view
|
||||
[wrappers]: https://github.com/Genymobile/scrcpy/tree/ffe0417228fb78ab45b7ee4e202fc06fc8875bf3/server/src/main/java/com/genymobile/scrcpy/wrappers
|
||||
[aidl]: https://github.com/Genymobile/scrcpy/tree/ffe0417228fb78ab45b7ee4e202fc06fc8875bf3/server/src/main/aidl/android/view
|
||||
|
||||
|
||||
### Threading
|
||||
|
||||
The server uses 2 threads:
|
||||
The server uses 3 threads:
|
||||
|
||||
- the **main** thread, encoding and streaming the video to the client;
|
||||
- the **controller** thread, listening for _control events_ (typically,
|
||||
keyboard and mouse events) from the client.
|
||||
- the **receiver** thread (managed by the controller), sending _device events_
|
||||
- the **controller** thread, listening for _control messages_ (typically,
|
||||
keyboard and mouse events) from the client;
|
||||
- the **receiver** thread (managed by the controller), sending _device messges_
|
||||
to the clients (currently, it is only used to send the device clipboard
|
||||
content).
|
||||
|
||||
@@ -92,9 +92,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.8/server/src/main/java/com/genymobile/scrcpy/ScreenEncoder.java
|
||||
[`ScreenEncoder`]: https://github.com/Genymobile/scrcpy/blob/ffe0417228fb78ab45b7ee4e202fc06fc8875bf3/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.8/server/src/main/java/com/genymobile/scrcpy/ScreenEncoder.java#L69-L70
|
||||
[surface]: https://github.com/Genymobile/scrcpy/blob/ffe0417228fb78ab45b7ee4e202fc06fc8875bf3/server/src/main/java/com/genymobile/scrcpy/ScreenEncoder.java#L68-L69
|
||||
|
||||
On device [rotation], the codec, surface and display are reinitialized, and a
|
||||
new video stream is produced.
|
||||
@@ -108,31 +108,30 @@ 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.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
|
||||
[rotation]: https://github.com/Genymobile/scrcpy/blob/ffe0417228fb78ab45b7ee4e202fc06fc8875bf3/server/src/main/java/com/genymobile/scrcpy/ScreenEncoder.java#L90
|
||||
[repeat]: https://github.com/Genymobile/scrcpy/blob/ffe0417228fb78ab45b7ee4e202fc06fc8875bf3/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
|
||||
|
||||
|
||||
### Input events injection
|
||||
|
||||
_Control events_ are received from the client by the [`EventController`] (run in
|
||||
a separate thread). There are several types of input events:
|
||||
_Control messages_ are received from the client by the [`Controller`] (run in a
|
||||
separate thread). There are several types of input events:
|
||||
- keycode (cf [`KeyEvent`]),
|
||||
- text (special characters may not be handled by keycodes directly),
|
||||
- mouse motion/click,
|
||||
- mouse scroll,
|
||||
- other commands (e.g. to switch the screen on or to copy the clipboard).
|
||||
|
||||
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
|
||||
Some of them 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.8/server/src/main/java/com/genymobile/scrcpy/EventController.java#L66
|
||||
[`Controller`]: https://github.com/Genymobile/scrcpy/blob/ffe0417228fb78ab45b7ee4e202fc06fc8875bf3/server/src/main/java/com/genymobile/scrcpy/Controller.java#L81
|
||||
[`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.8/server/src/main/java/com/genymobile/scrcpy/wrappers/InputManager.java#L27
|
||||
[inject-wrapper]: https://github.com/Genymobile/scrcpy/blob/ffe0417228fb78ab45b7ee4e202fc06fc8875bf3/server/src/main/java/com/genymobile/scrcpy/wrappers/InputManager.java#L27
|
||||
|
||||
|
||||
|
||||
@@ -188,12 +187,13 @@ The client uses 4 threads:
|
||||
- the **main** thread, executing the SDL event loop,
|
||||
- the **stream** thread, receiving the video and used for decoding and
|
||||
recording,
|
||||
- the **controller** thread, sending _control events_ to the server.
|
||||
- the **controller** thread, sending _control messages_ to the server,
|
||||
- the **receiver** thread (managed by the controller), receiving _device
|
||||
events_ from the client.
|
||||
messages_ from the client.
|
||||
|
||||
In addition, another thread can be started if necessary to handle APK
|
||||
installation or file push requests (via drag&drop on the main window).
|
||||
installation or file push requests (via drag&drop on the main window) or to
|
||||
print the framerate regularly in the console.
|
||||
|
||||
|
||||
|
||||
@@ -217,10 +217,10 @@ to decode a new frame while the main thread renders the last one.
|
||||
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
|
||||
[stream]: https://github.com/Genymobile/scrcpy/blob/ffe0417228fb78ab45b7ee4e202fc06fc8875bf3/app/src/stream.h
|
||||
[decoder]: https://github.com/Genymobile/scrcpy/blob/ffe0417228fb78ab45b7ee4e202fc06fc8875bf3/app/src/decoder.h
|
||||
[video_buffer]: https://github.com/Genymobile/scrcpy/blob/ffe0417228fb78ab45b7ee4e202fc06fc8875bf3/app/src/video_buffer.h
|
||||
[recorder]: https://github.com/Genymobile/scrcpy/blob/ffe0417228fb78ab45b7ee4e202fc06fc8875bf3/app/src/recorder.h
|
||||
|
||||
```
|
||||
+----------+ +----------+
|
||||
@@ -234,19 +234,19 @@ H.264 packet to the output video file.
|
||||
|
||||
### Controller
|
||||
|
||||
The [controller] is responsible to send _control events_ to the device. It runs
|
||||
in a separate thread, to avoid I/O on the main thread.
|
||||
The [controller] is responsible to send _control messages_ to the device. It
|
||||
runs in a separate thread, to avoid I/O on the main thread.
|
||||
|
||||
On SDL event, received on the main thread, the [input manager][inputmanager]
|
||||
creates appropriate [_control events_][controlevent]. It is responsible to
|
||||
creates appropriate [_control messages_][controlmsg]. It is responsible to
|
||||
convert SDL events to Android events (using [convert]). It pushes the _control
|
||||
events_ to a queue hold by the controller. On its own thread, the controller
|
||||
takes events from the queue, that it serializes and sends to the client.
|
||||
messages_ to a queue hold by the controller. On its own thread, the controller
|
||||
takes messages from the queue, that it serializes and sends to the client.
|
||||
|
||||
[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
|
||||
[controller]: https://github.com/Genymobile/scrcpy/blob/ffe0417228fb78ab45b7ee4e202fc06fc8875bf3/app/src/controller.h
|
||||
[controlmsg]: https://github.com/Genymobile/scrcpy/blob/ffe0417228fb78ab45b7ee4e202fc06fc8875bf3/app/src/control_msg.h
|
||||
[inputmanager]: https://github.com/Genymobile/scrcpy/blob/ffe0417228fb78ab45b7ee4e202fc06fc8875bf3/app/src/input_manager.h
|
||||
[convert]: https://github.com/Genymobile/scrcpy/blob/ffe0417228fb78ab45b7ee4e202fc06fc8875bf3/app/src/convert.h
|
||||
|
||||
|
||||
### UI and event loop
|
||||
@@ -257,10 +257,9 @@ 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.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
|
||||
[scrcpy]: https://github.com/Genymobile/scrcpy/blob/ffe0417228fb78ab45b7ee4e202fc06fc8875bf3/app/src/scrcpy.c
|
||||
[event loop]: https://github.com/Genymobile/scrcpy/blob/ffe0417228fb78ab45b7ee4e202fc06fc8875bf3/app/src/scrcpy.c#L201
|
||||
[screen]: https://github.com/Genymobile/scrcpy/blob/ffe0417228fb78ab45b7ee4e202fc06fc8875bf3/app/src/screen.h
|
||||
|
||||
|
||||
## Hack
|
||||
|
||||
4
FAQ.md
4
FAQ.md
@@ -19,10 +19,6 @@ Windows may need some [drivers] to detect your device.
|
||||
|
||||
[drivers]: https://developer.android.com/studio/run/oem-usb.html
|
||||
|
||||
If you still encounter problems, please see [issue 9].
|
||||
|
||||
[issue 9]: https://github.com/Genymobile/scrcpy/issues/9
|
||||
|
||||
|
||||
### Mouse clicks do not work
|
||||
|
||||
|
||||
@@ -57,7 +57,7 @@ build-win32: prepare-deps-win32
|
||||
--buildtype release --strip -Db_lto=true \
|
||||
-Dcrossbuild_windows=true \
|
||||
-Dbuild_server=false \
|
||||
-Doverride_server_path=scrcpy-server.jar )
|
||||
-Dportable=true )
|
||||
ninja -C "$(WIN32_BUILD_DIR)"
|
||||
|
||||
build-win32-noconsole: prepare-deps-win32
|
||||
@@ -68,7 +68,7 @@ build-win32-noconsole: prepare-deps-win32
|
||||
-Dcrossbuild_windows=true \
|
||||
-Dbuild_server=false \
|
||||
-Dwindows_noconsole=true \
|
||||
-Doverride_server_path=scrcpy-server.jar )
|
||||
-Dportable=true )
|
||||
ninja -C "$(WIN32_NOCONSOLE_BUILD_DIR)"
|
||||
|
||||
prepare-deps-win64:
|
||||
@@ -81,7 +81,7 @@ build-win64: prepare-deps-win64
|
||||
--buildtype release --strip -Db_lto=true \
|
||||
-Dcrossbuild_windows=true \
|
||||
-Dbuild_server=false \
|
||||
-Doverride_server_path=scrcpy-server.jar )
|
||||
-Dportable=true )
|
||||
ninja -C "$(WIN64_BUILD_DIR)"
|
||||
|
||||
build-win64-noconsole: prepare-deps-win64
|
||||
@@ -92,7 +92,7 @@ build-win64-noconsole: prepare-deps-win64
|
||||
-Dcrossbuild_windows=true \
|
||||
-Dbuild_server=false \
|
||||
-Dwindows_noconsole=true \
|
||||
-Doverride_server_path=scrcpy-server.jar )
|
||||
-Dportable=true )
|
||||
ninja -C "$(WIN64_NOCONSOLE_BUILD_DIR)"
|
||||
|
||||
dist-win32: build-server build-win32 build-win32-noconsole
|
||||
@@ -100,28 +100,29 @@ dist-win32: build-server build-win32 build-win32-noconsole
|
||||
cp "$(SERVER_BUILD_DIR)"/server/scrcpy-server.jar "$(DIST)/$(WIN32_TARGET_DIR)/"
|
||||
cp "$(WIN32_BUILD_DIR)"/app/scrcpy.exe "$(DIST)/$(WIN32_TARGET_DIR)/"
|
||||
cp "$(WIN32_NOCONSOLE_BUILD_DIR)"/app/scrcpy.exe "$(DIST)/$(WIN32_TARGET_DIR)/scrcpy-noconsole.exe"
|
||||
cp prebuilt-deps/ffmpeg-4.1-win32-shared/bin/avutil-56.dll "$(DIST)/$(WIN32_TARGET_DIR)/"
|
||||
cp prebuilt-deps/ffmpeg-4.1-win32-shared/bin/avcodec-58.dll "$(DIST)/$(WIN32_TARGET_DIR)/"
|
||||
cp prebuilt-deps/ffmpeg-4.1-win32-shared/bin/avformat-58.dll "$(DIST)/$(WIN32_TARGET_DIR)/"
|
||||
cp prebuilt-deps/ffmpeg-4.1-win32-shared/bin/swresample-3.dll "$(DIST)/$(WIN32_TARGET_DIR)/"
|
||||
cp prebuilt-deps/ffmpeg-4.1.3-win32-shared/bin/avutil-56.dll "$(DIST)/$(WIN32_TARGET_DIR)/"
|
||||
cp prebuilt-deps/ffmpeg-4.1.3-win32-shared/bin/avcodec-58.dll "$(DIST)/$(WIN32_TARGET_DIR)/"
|
||||
cp prebuilt-deps/ffmpeg-4.1.3-win32-shared/bin/avformat-58.dll "$(DIST)/$(WIN32_TARGET_DIR)/"
|
||||
cp prebuilt-deps/ffmpeg-4.1.3-win32-shared/bin/swresample-3.dll "$(DIST)/$(WIN32_TARGET_DIR)/"
|
||||
cp prebuilt-deps/platform-tools/adb.exe "$(DIST)/$(WIN32_TARGET_DIR)/"
|
||||
cp prebuilt-deps/platform-tools/AdbWinApi.dll "$(DIST)/$(WIN32_TARGET_DIR)/"
|
||||
cp prebuilt-deps/platform-tools/AdbWinUsbApi.dll "$(DIST)/$(WIN32_TARGET_DIR)/"
|
||||
cp prebuilt-deps/SDL2-2.0.9/i686-w64-mingw32/bin/SDL2.dll "$(DIST)/$(WIN32_TARGET_DIR)/"
|
||||
cp prebuilt-deps/SDL2-2.0.8/i686-w64-mingw32/bin/SDL2.dll "$(DIST)/$(WIN32_TARGET_DIR)/"
|
||||
|
||||
dist-win64: build-server build-win64 build-win64-noconsole
|
||||
mkdir -p "$(DIST)/$(WIN64_TARGET_DIR)"
|
||||
cp "$(SERVER_BUILD_DIR)"/server/scrcpy-server.jar "$(DIST)/$(WIN64_TARGET_DIR)/"
|
||||
cp "$(WIN64_BUILD_DIR)"/app/scrcpy.exe "$(DIST)/$(WIN64_TARGET_DIR)/"
|
||||
cp "$(WIN64_NOCONSOLE_BUILD_DIR)"/app/scrcpy.exe "$(DIST)/$(WIN64_TARGET_DIR)/scrcpy-noconsole.exe"
|
||||
cp prebuilt-deps/ffmpeg-4.1-win64-shared/bin/avutil-56.dll "$(DIST)/$(WIN64_TARGET_DIR)/"
|
||||
cp prebuilt-deps/ffmpeg-4.1-win64-shared/bin/avcodec-58.dll "$(DIST)/$(WIN64_TARGET_DIR)/"
|
||||
cp prebuilt-deps/ffmpeg-4.1-win64-shared/bin/avformat-58.dll "$(DIST)/$(WIN64_TARGET_DIR)/"
|
||||
cp prebuilt-deps/ffmpeg-4.1-win64-shared/bin/swresample-3.dll "$(DIST)/$(WIN64_TARGET_DIR)/"
|
||||
cp prebuilt-deps/ffmpeg-4.1.3-win64-shared/bin/avutil-56.dll "$(DIST)/$(WIN64_TARGET_DIR)/"
|
||||
cp prebuilt-deps/ffmpeg-4.1.3-win64-shared/bin/avcodec-58.dll "$(DIST)/$(WIN64_TARGET_DIR)/"
|
||||
cp prebuilt-deps/ffmpeg-4.1.3-win64-shared/bin/avformat-58.dll "$(DIST)/$(WIN64_TARGET_DIR)/"
|
||||
cp prebuilt-deps/ffmpeg-4.1.3-win64-shared/bin/swresample-3.dll "$(DIST)/$(WIN64_TARGET_DIR)/"
|
||||
cp prebuilt-deps/ffmpeg-4.1.3-win64-shared/bin/swscale-5.dll "$(DIST)/$(WIN64_TARGET_DIR)/"
|
||||
cp prebuilt-deps/platform-tools/adb.exe "$(DIST)/$(WIN64_TARGET_DIR)/"
|
||||
cp prebuilt-deps/platform-tools/AdbWinApi.dll "$(DIST)/$(WIN64_TARGET_DIR)/"
|
||||
cp prebuilt-deps/platform-tools/AdbWinUsbApi.dll "$(DIST)/$(WIN64_TARGET_DIR)/"
|
||||
cp prebuilt-deps/SDL2-2.0.9/x86_64-w64-mingw32/bin/SDL2.dll "$(DIST)/$(WIN64_TARGET_DIR)/"
|
||||
cp prebuilt-deps/SDL2-2.0.8/x86_64-w64-mingw32/bin/SDL2.dll "$(DIST)/$(WIN64_TARGET_DIR)/"
|
||||
|
||||
zip-win32: dist-win32
|
||||
cd "$(DIST)"; \
|
||||
|
||||
@@ -331,8 +331,8 @@ To use a specific _adb_ binary, configure its path in the environment variable
|
||||
|
||||
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`.
|
||||
To override the path of the `scrcpy-server.jar` file, configure its path in
|
||||
`SCRCPY_SERVER_PATH`.
|
||||
|
||||
[useful]: https://github.com/Genymobile/scrcpy/issues/278#issuecomment-429330345
|
||||
|
||||
|
||||
@@ -10,7 +10,6 @@ src = [
|
||||
'src/file_handler.c',
|
||||
'src/fps_counter.c',
|
||||
'src/input_manager.c',
|
||||
'src/lock_util.c',
|
||||
'src/net.c',
|
||||
'src/receiver.c',
|
||||
'src/recorder.c',
|
||||
@@ -94,21 +93,9 @@ conf.set_quoted('SCRCPY_VERSION', meson.project_version())
|
||||
# the prefix used during configuration (meson --prefix=PREFIX)
|
||||
conf.set_quoted('PREFIX', get_option('prefix'))
|
||||
|
||||
# the path of the server, which will be appended to the prefix
|
||||
# ignored if OVERRIDE_SERVER_PATH if defined
|
||||
# must be consistent with the install_dir in server/meson.build
|
||||
conf.set_quoted('PREFIXED_SERVER_PATH', '/share/scrcpy/scrcpy-server.jar')
|
||||
|
||||
# the path of the server to be used "as is"
|
||||
# this is useful for building a "portable" version (with the server in the same
|
||||
# directory as the client)
|
||||
override_server_path = get_option('override_server_path')
|
||||
if override_server_path != ''
|
||||
conf.set_quoted('OVERRIDE_SERVER_PATH', override_server_path)
|
||||
else
|
||||
# undefine it
|
||||
conf.set('OVERRIDE_SERVER_PATH', false)
|
||||
endif
|
||||
# build a "portable" version (with scrcpy-server.jar accessible from the same
|
||||
# directory as the executable)
|
||||
conf.set('PORTABLE', get_option('portable'))
|
||||
|
||||
# the default client TCP port for the "adb reverse" tunnel
|
||||
# overridden by option --port
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
// not needed here, but winsock2.h must never be included AFTER windows.h
|
||||
# include <winsock2.h>
|
||||
# include <windows.h>
|
||||
# define PATH_SEPARATOR '\\'
|
||||
# define PRIexitcode "lu"
|
||||
// <https://stackoverflow.com/a/44383330/1987178>
|
||||
# ifdef _WIN64
|
||||
@@ -23,6 +24,7 @@
|
||||
#else
|
||||
|
||||
# include <sys/types.h>
|
||||
# define PATH_SEPARATOR '/'
|
||||
# define PRIsizet "zu"
|
||||
# define PRIexitcode "d"
|
||||
# define PROCESS_NONE -1
|
||||
@@ -76,4 +78,9 @@ adb_install(const char *serial, const char *local);
|
||||
bool
|
||||
process_check_success(process_t proc, const char *name);
|
||||
|
||||
// return the absolute path of the executable (the scrcpy binary)
|
||||
// may be NULL on error; to be freed by SDL_free
|
||||
char *
|
||||
get_executable_path(void);
|
||||
|
||||
#endif
|
||||
|
||||
@@ -1,60 +1,169 @@
|
||||
#include "fps_counter.h"
|
||||
|
||||
#include <SDL2/SDL_assert.h>
|
||||
#include <SDL2/SDL_timer.h>
|
||||
|
||||
#include "lock_util.h"
|
||||
#include "log.h"
|
||||
|
||||
void
|
||||
#define FPS_COUNTER_INTERVAL_MS 1000
|
||||
|
||||
bool
|
||||
fps_counter_init(struct fps_counter *counter) {
|
||||
counter->started = false;
|
||||
// no need to initialize the other fields, they are meaningful only when
|
||||
// started is true
|
||||
counter->mutex = SDL_CreateMutex();
|
||||
if (!counter->mutex) {
|
||||
return false;
|
||||
}
|
||||
|
||||
counter->state_cond = SDL_CreateCond();
|
||||
if (!counter->state_cond) {
|
||||
SDL_DestroyMutex(counter->mutex);
|
||||
return false;
|
||||
}
|
||||
|
||||
counter->thread = NULL;
|
||||
SDL_AtomicSet(&counter->started, 0);
|
||||
// no need to initialize the other fields, they are unused until started
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void
|
||||
fps_counter_start(struct fps_counter *counter) {
|
||||
counter->started = true;
|
||||
counter->slice_start = SDL_GetTicks();
|
||||
fps_counter_destroy(struct fps_counter *counter) {
|
||||
SDL_DestroyCond(counter->state_cond);
|
||||
SDL_DestroyMutex(counter->mutex);
|
||||
}
|
||||
|
||||
// must be called with mutex locked
|
||||
static void
|
||||
display_fps(struct fps_counter *counter) {
|
||||
unsigned rendered_per_second =
|
||||
counter->nr_rendered * 1000 / FPS_COUNTER_INTERVAL_MS;
|
||||
if (counter->nr_skipped) {
|
||||
LOGI("%u fps (+%u frames skipped)", rendered_per_second,
|
||||
counter->nr_skipped);
|
||||
} else {
|
||||
LOGI("%u fps", rendered_per_second);
|
||||
}
|
||||
}
|
||||
|
||||
// must be called with mutex locked
|
||||
static void
|
||||
check_interval_expired(struct fps_counter *counter, uint32_t now) {
|
||||
if (now < counter->next_timestamp) {
|
||||
return;
|
||||
}
|
||||
|
||||
display_fps(counter);
|
||||
counter->nr_rendered = 0;
|
||||
counter->nr_skipped = 0;
|
||||
// add a multiple of the interval
|
||||
uint32_t elapsed_slices =
|
||||
(now - counter->next_timestamp) / FPS_COUNTER_INTERVAL_MS + 1;
|
||||
counter->next_timestamp += FPS_COUNTER_INTERVAL_MS * elapsed_slices;
|
||||
}
|
||||
|
||||
static int
|
||||
run_fps_counter(void *data) {
|
||||
struct fps_counter *counter = data;
|
||||
|
||||
mutex_lock(counter->mutex);
|
||||
while (!counter->interrupted) {
|
||||
while (!counter->interrupted && !SDL_AtomicGet(&counter->started)) {
|
||||
cond_wait(counter->state_cond, counter->mutex);
|
||||
}
|
||||
while (!counter->interrupted && SDL_AtomicGet(&counter->started)) {
|
||||
uint32_t now = SDL_GetTicks();
|
||||
check_interval_expired(counter, now);
|
||||
|
||||
SDL_assert(counter->next_timestamp > now);
|
||||
uint32_t remaining = counter->next_timestamp - now;
|
||||
|
||||
// ignore the reason (timeout or signaled), we just loop anyway
|
||||
cond_wait_timeout(counter->state_cond, counter->mutex, remaining);
|
||||
}
|
||||
}
|
||||
mutex_unlock(counter->mutex);
|
||||
return 0;
|
||||
}
|
||||
|
||||
bool
|
||||
fps_counter_start(struct fps_counter *counter) {
|
||||
mutex_lock(counter->mutex);
|
||||
counter->next_timestamp = SDL_GetTicks() + FPS_COUNTER_INTERVAL_MS;
|
||||
counter->nr_rendered = 0;
|
||||
counter->nr_skipped = 0;
|
||||
mutex_unlock(counter->mutex);
|
||||
|
||||
SDL_AtomicSet(&counter->started, 1);
|
||||
cond_signal(counter->state_cond);
|
||||
|
||||
// counter->thread is always accessed from the same thread, no need to lock
|
||||
if (!counter->thread) {
|
||||
counter->thread =
|
||||
SDL_CreateThread(run_fps_counter, "fps counter", counter);
|
||||
if (!counter->thread) {
|
||||
LOGE("Could not start FPS counter thread");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void
|
||||
fps_counter_stop(struct fps_counter *counter) {
|
||||
counter->started = false;
|
||||
SDL_AtomicSet(&counter->started, 0);
|
||||
cond_signal(counter->state_cond);
|
||||
}
|
||||
|
||||
static void
|
||||
display_fps(struct fps_counter *counter) {
|
||||
if (counter->nr_skipped) {
|
||||
LOGI("%d fps (+%d frames skipped)", counter->nr_rendered,
|
||||
counter->nr_skipped);
|
||||
} else {
|
||||
LOGI("%d fps", counter->nr_rendered);
|
||||
bool
|
||||
fps_counter_is_started(struct fps_counter *counter) {
|
||||
return SDL_AtomicGet(&counter->started);
|
||||
}
|
||||
|
||||
void
|
||||
fps_counter_interrupt(struct fps_counter *counter) {
|
||||
if (!counter->thread) {
|
||||
return;
|
||||
}
|
||||
|
||||
mutex_lock(counter->mutex);
|
||||
counter->interrupted = true;
|
||||
mutex_unlock(counter->mutex);
|
||||
// wake up blocking wait
|
||||
cond_signal(counter->state_cond);
|
||||
}
|
||||
|
||||
static void
|
||||
check_expired(struct fps_counter *counter) {
|
||||
uint32_t now = SDL_GetTicks();
|
||||
if (now - counter->slice_start >= 1000) {
|
||||
display_fps(counter);
|
||||
// add a multiple of one second
|
||||
uint32_t elapsed_slices = (now - counter->slice_start) / 1000;
|
||||
counter->slice_start += 1000 * elapsed_slices;
|
||||
counter->nr_rendered = 0;
|
||||
counter->nr_skipped = 0;
|
||||
void
|
||||
fps_counter_join(struct fps_counter *counter) {
|
||||
if (counter->thread) {
|
||||
SDL_WaitThread(counter->thread, NULL);
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
fps_counter_add_rendered_frame(struct fps_counter *counter) {
|
||||
check_expired(counter);
|
||||
if (!SDL_AtomicGet(&counter->started)) {
|
||||
return;
|
||||
}
|
||||
|
||||
mutex_lock(counter->mutex);
|
||||
uint32_t now = SDL_GetTicks();
|
||||
check_interval_expired(counter, now);
|
||||
++counter->nr_rendered;
|
||||
mutex_unlock(counter->mutex);
|
||||
}
|
||||
|
||||
void
|
||||
fps_counter_add_skipped_frame(struct fps_counter *counter) {
|
||||
check_expired(counter);
|
||||
if (!SDL_AtomicGet(&counter->started)) {
|
||||
return;
|
||||
}
|
||||
|
||||
mutex_lock(counter->mutex);
|
||||
uint32_t now = SDL_GetTicks();
|
||||
check_interval_expired(counter, now);
|
||||
++counter->nr_skipped;
|
||||
mutex_unlock(counter->mutex);
|
||||
}
|
||||
|
||||
@@ -3,23 +3,49 @@
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
#include <SDL2/SDL_atomic.h>
|
||||
#include <SDL2/SDL_mutex.h>
|
||||
#include <SDL2/SDL_thread.h>
|
||||
|
||||
struct fps_counter {
|
||||
bool started;
|
||||
uint32_t slice_start; // initialized by SDL_GetTicks()
|
||||
int nr_rendered;
|
||||
int nr_skipped;
|
||||
SDL_Thread *thread;
|
||||
SDL_mutex *mutex;
|
||||
SDL_cond *state_cond;
|
||||
|
||||
// atomic so that we can check without locking the mutex
|
||||
// if the FPS counter is disabled, we don't want to lock unnecessarily
|
||||
SDL_atomic_t started;
|
||||
|
||||
// the following fields are protected by the mutex
|
||||
bool interrupted;
|
||||
unsigned nr_rendered;
|
||||
unsigned nr_skipped;
|
||||
uint32_t next_timestamp;
|
||||
};
|
||||
|
||||
void
|
||||
bool
|
||||
fps_counter_init(struct fps_counter *counter);
|
||||
|
||||
void
|
||||
fps_counter_destroy(struct fps_counter *counter);
|
||||
|
||||
bool
|
||||
fps_counter_start(struct fps_counter *counter);
|
||||
|
||||
void
|
||||
fps_counter_stop(struct fps_counter *counter);
|
||||
|
||||
bool
|
||||
fps_counter_is_started(struct fps_counter *counter);
|
||||
|
||||
// request to stop the thread (on quit)
|
||||
// must be called before fps_counter_join()
|
||||
void
|
||||
fps_counter_interrupt(struct fps_counter *counter);
|
||||
|
||||
void
|
||||
fps_counter_join(struct fps_counter *counter);
|
||||
|
||||
void
|
||||
fps_counter_add_rendered_frame(struct fps_counter *counter);
|
||||
|
||||
|
||||
@@ -172,16 +172,19 @@ set_screen_power_mode(struct controller *controller,
|
||||
}
|
||||
|
||||
static void
|
||||
switch_fps_counter_state(struct video_buffer *vb) {
|
||||
mutex_lock(vb->mutex);
|
||||
if (vb->fps_counter.started) {
|
||||
switch_fps_counter_state(struct fps_counter *fps_counter) {
|
||||
// the started state can only be written from the current thread, so there
|
||||
// is no ToCToU issue
|
||||
if (fps_counter_is_started(fps_counter)) {
|
||||
fps_counter_stop(fps_counter);
|
||||
LOGI("FPS counter stopped");
|
||||
fps_counter_stop(&vb->fps_counter);
|
||||
} else {
|
||||
LOGI("FPS counter started");
|
||||
fps_counter_start(&vb->fps_counter);
|
||||
if (fps_counter_start(fps_counter)) {
|
||||
LOGI("FPS counter started");
|
||||
} else {
|
||||
LOGE("FPS counter starting failed");
|
||||
}
|
||||
}
|
||||
mutex_unlock(vb->mutex);
|
||||
}
|
||||
|
||||
static void
|
||||
@@ -339,7 +342,9 @@ input_manager_process_key(struct input_manager *input_manager,
|
||||
return;
|
||||
case SDLK_i:
|
||||
if (ctrl && !meta && !shift && !repeat && down) {
|
||||
switch_fps_counter_state(input_manager->video_buffer);
|
||||
struct fps_counter *fps_counter =
|
||||
input_manager->video_buffer->fps_counter;
|
||||
switch_fps_counter_state(fps_counter);
|
||||
}
|
||||
return;
|
||||
case SDLK_n:
|
||||
|
||||
@@ -1,38 +0,0 @@
|
||||
#include <lock_util.h>
|
||||
#include <stdlib.h>
|
||||
#include <SDL2/SDL_mutex.h>
|
||||
|
||||
#include "log.h"
|
||||
|
||||
void
|
||||
mutex_lock(SDL_mutex *mutex) {
|
||||
if (SDL_LockMutex(mutex)) {
|
||||
LOGC("Could not lock mutex");
|
||||
abort();
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
mutex_unlock(SDL_mutex *mutex) {
|
||||
if (SDL_UnlockMutex(mutex)) {
|
||||
LOGC("Could not unlock mutex");
|
||||
abort();
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
cond_wait(SDL_cond *cond, SDL_mutex *mutex) {
|
||||
if (SDL_CondWait(cond, mutex)) {
|
||||
LOGC("Could not wait on condition");
|
||||
abort();
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
cond_signal(SDL_cond *cond) {
|
||||
if (SDL_CondSignal(cond)) {
|
||||
LOGC("Could not signal a condition");
|
||||
abort();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,20 +1,51 @@
|
||||
#ifndef LOCKUTIL_H
|
||||
#define LOCKUTIL_H
|
||||
|
||||
// forward declarations
|
||||
typedef struct SDL_mutex SDL_mutex;
|
||||
typedef struct SDL_cond SDL_cond;
|
||||
#include <stdint.h>
|
||||
#include <SDL2/SDL_mutex.h>
|
||||
|
||||
void
|
||||
mutex_lock(SDL_mutex *mutex);
|
||||
#include "log.h"
|
||||
|
||||
void
|
||||
mutex_unlock(SDL_mutex *mutex);
|
||||
static inline void
|
||||
mutex_lock(SDL_mutex *mutex) {
|
||||
if (SDL_LockMutex(mutex)) {
|
||||
LOGC("Could not lock mutex");
|
||||
abort();
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
cond_wait(SDL_cond *cond, SDL_mutex *mutex);
|
||||
static inline void
|
||||
mutex_unlock(SDL_mutex *mutex) {
|
||||
if (SDL_UnlockMutex(mutex)) {
|
||||
LOGC("Could not unlock mutex");
|
||||
abort();
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
cond_signal(SDL_cond *cond);
|
||||
static inline void
|
||||
cond_wait(SDL_cond *cond, SDL_mutex *mutex) {
|
||||
if (SDL_CondWait(cond, mutex)) {
|
||||
LOGC("Could not wait on condition");
|
||||
abort();
|
||||
}
|
||||
}
|
||||
|
||||
static inline int
|
||||
cond_wait_timeout(SDL_cond *cond, SDL_mutex *mutex, uint32_t ms) {
|
||||
int r = SDL_CondWaitTimeout(cond, mutex, ms);
|
||||
if (r < 0) {
|
||||
LOGC("Could not wait on condition with timeout");
|
||||
abort();
|
||||
}
|
||||
return r;
|
||||
}
|
||||
|
||||
static inline void
|
||||
cond_signal(SDL_cond *cond) {
|
||||
if (SDL_CondSignal(cond)) {
|
||||
LOGC("Could not signal a condition");
|
||||
abort();
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
@@ -29,6 +29,7 @@
|
||||
|
||||
static struct server server = SERVER_INITIALIZER;
|
||||
static struct screen screen = SCREEN_INITIALIZER;
|
||||
static struct fps_counter fps_counter;
|
||||
static struct video_buffer video_buffer;
|
||||
static struct stream stream;
|
||||
static struct decoder decoder;
|
||||
@@ -293,6 +294,7 @@ scrcpy(const struct scrcpy_options *options) {
|
||||
|
||||
bool ret = false;
|
||||
|
||||
bool fps_counter_initialized = false;
|
||||
bool video_buffer_initialized = false;
|
||||
bool file_handler_initialized = false;
|
||||
bool recorder_initialized = false;
|
||||
@@ -320,7 +322,13 @@ scrcpy(const struct scrcpy_options *options) {
|
||||
|
||||
struct decoder *dec = NULL;
|
||||
if (options->display) {
|
||||
if (!video_buffer_init(&video_buffer, options->render_expired_frames)) {
|
||||
if (!fps_counter_init(&fps_counter)) {
|
||||
goto end;
|
||||
}
|
||||
fps_counter_initialized = true;
|
||||
|
||||
if (!video_buffer_init(&video_buffer, &fps_counter,
|
||||
options->render_expired_frames)) {
|
||||
goto end;
|
||||
}
|
||||
video_buffer_initialized = true;
|
||||
@@ -414,6 +422,9 @@ end:
|
||||
if (file_handler_initialized) {
|
||||
file_handler_stop(&file_handler);
|
||||
}
|
||||
if (fps_counter_initialized) {
|
||||
fps_counter_interrupt(&fps_counter);
|
||||
}
|
||||
|
||||
// shutdown the sockets and kill the server
|
||||
server_stop(&server);
|
||||
@@ -443,6 +454,11 @@ end:
|
||||
video_buffer_destroy(&video_buffer);
|
||||
}
|
||||
|
||||
if (fps_counter_initialized) {
|
||||
fps_counter_join(&fps_counter);
|
||||
fps_counter_destroy(&fps_counter);
|
||||
}
|
||||
|
||||
if (options->show_touches) {
|
||||
if (!show_touches_waited) {
|
||||
// wait the process which enabled "show touches"
|
||||
|
||||
@@ -2,31 +2,67 @@
|
||||
|
||||
#include <errno.h>
|
||||
#include <inttypes.h>
|
||||
#include <libgen.h>
|
||||
#include <stdio.h>
|
||||
#include <SDL2/SDL_assert.h>
|
||||
#include <SDL2/SDL_timer.h>
|
||||
|
||||
#include "config.h"
|
||||
#include "command.h"
|
||||
#include "log.h"
|
||||
#include "net.h"
|
||||
|
||||
#define SOCKET_NAME "scrcpy"
|
||||
#define SERVER_FILENAME "scrcpy-server.jar"
|
||||
|
||||
#ifdef OVERRIDE_SERVER_PATH
|
||||
# define DEFAULT_SERVER_PATH OVERRIDE_SERVER_PATH
|
||||
#else
|
||||
# define DEFAULT_SERVER_PATH PREFIX PREFIXED_SERVER_PATH
|
||||
#endif
|
||||
|
||||
#define DEVICE_SERVER_PATH "/data/local/tmp/scrcpy-server.jar"
|
||||
#define DEFAULT_SERVER_PATH PREFIX "/share/scrcpy/" SERVER_FLENAME
|
||||
#define DEVICE_SERVER_PATH "/data/local/tmp/" SERVER_FILENAME
|
||||
|
||||
static const char *
|
||||
get_server_path(void) {
|
||||
const char *server_path = getenv("SCRCPY_SERVER_PATH");
|
||||
if (!server_path) {
|
||||
server_path = DEFAULT_SERVER_PATH;
|
||||
const char *server_path_env = getenv("SCRCPY_SERVER_PATH");
|
||||
if (server_path_env) {
|
||||
LOGD("Using SCRCPY_SERVER_PATH: %s", server_path_env);
|
||||
// if the envvar is set, use it
|
||||
return server_path_env;
|
||||
}
|
||||
|
||||
#ifndef PORTABLE
|
||||
LOGD("Using server: " DEFAULT_SERVER_PATH);
|
||||
// the absolute path is hardcoded
|
||||
return DEFAULT_SERVER_PATH;
|
||||
#else
|
||||
// use scrcpy-server.jar in the same directory as the executable
|
||||
char *executable_path = get_executable_path();
|
||||
if (!executable_path) {
|
||||
LOGE("Cannot get executable path, "
|
||||
"using " SERVER_FILENAME " from current directory");
|
||||
// not found, use current directory
|
||||
return SERVER_FILENAME;
|
||||
}
|
||||
char *dir = dirname(executable_path);
|
||||
size_t dirlen = strlen(dir);
|
||||
|
||||
// sizeof(SERVER_FILENAME) gives statically the size including the null byte
|
||||
size_t len = dirlen + 1 + sizeof(SERVER_FILENAME);
|
||||
char *server_path = SDL_malloc(len);
|
||||
if (!server_path) {
|
||||
LOGE("Cannot alloc server path string, "
|
||||
"using " SERVER_FILENAME " from current directory");
|
||||
SDL_free(executable_path);
|
||||
return SERVER_FILENAME;
|
||||
}
|
||||
|
||||
memcpy(server_path, dir, dirlen);
|
||||
server_path[dirlen] = PATH_SEPARATOR;
|
||||
memcpy(&server_path[dirlen + 1], SERVER_FILENAME, sizeof(SERVER_FILENAME));
|
||||
// the final null byte has been copied with SERVER_FILENAME
|
||||
|
||||
SDL_free(executable_path);
|
||||
|
||||
LOGD("Using server (portable): %s", server_path);
|
||||
return server_path;
|
||||
#endif
|
||||
}
|
||||
|
||||
static bool
|
||||
@@ -86,7 +122,7 @@ execute_server(struct server *server, const struct server_params *params) {
|
||||
sprintf(bit_rate_string, "%"PRIu32, params->bit_rate);
|
||||
const char *const cmd[] = {
|
||||
"shell",
|
||||
"CLASSPATH=/data/local/tmp/scrcpy-server.jar",
|
||||
"CLASSPATH=/data/local/tmp/" SERVER_FILENAME,
|
||||
"app_process",
|
||||
"/", // unused
|
||||
"com.genymobile.scrcpy.Server",
|
||||
|
||||
@@ -92,4 +92,20 @@ utf8_to_wide_char(const char *utf8) {
|
||||
return wide;
|
||||
}
|
||||
|
||||
char *
|
||||
utf8_from_wide_char(const wchar_t *ws) {
|
||||
int len = WideCharToMultiByte(CP_UTF8, 0, ws, -1, NULL, 0, NULL, NULL);
|
||||
if (!len) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
char *utf8 = SDL_malloc(len);
|
||||
if (!utf8) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
WideCharToMultiByte(CP_UTF8, 0, ws, -1, utf8, len, NULL, NULL);
|
||||
return utf8;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
@@ -32,6 +32,9 @@ utf8_truncation_index(const char *utf8, size_t max_len);
|
||||
// returns the new allocated string, to be freed by the caller
|
||||
wchar_t *
|
||||
utf8_to_wide_char(const char *utf8);
|
||||
|
||||
char *
|
||||
utf8_from_wide_char(const wchar_t *s);
|
||||
#endif
|
||||
|
||||
#endif
|
||||
|
||||
@@ -113,7 +113,7 @@ read_packet_with_meta(void *opaque, uint8_t *buf, int buf_size) {
|
||||
|
||||
ssize_t r = net_recv(stream->socket, buf, buf_size);
|
||||
if (r == -1) {
|
||||
return AVERROR(errno);
|
||||
return errno ? AVERROR(errno) : AVERROR_EOF;
|
||||
}
|
||||
if (r == 0) {
|
||||
return AVERROR_EOF;
|
||||
@@ -130,7 +130,7 @@ read_raw_packet(void *opaque, uint8_t *buf, int buf_size) {
|
||||
struct stream *stream = opaque;
|
||||
ssize_t r = net_recv(stream->socket, buf, buf_size);
|
||||
if (r == -1) {
|
||||
return AVERROR(errno);
|
||||
return errno ? AVERROR(errno) : AVERROR_EOF;
|
||||
}
|
||||
if (r == 0) {
|
||||
return AVERROR_EOF;
|
||||
|
||||
@@ -1,9 +1,15 @@
|
||||
// for portability
|
||||
#define _POSIX_SOURCE // for kill()
|
||||
#define _BSD_SOURCE // for readlink()
|
||||
|
||||
// modern glibc will complain without this
|
||||
#define _DEFAULT_SOURCE
|
||||
|
||||
#include "command.h"
|
||||
|
||||
#include <errno.h>
|
||||
#include <fcntl.h>
|
||||
#include <limits.h>
|
||||
#include <signal.h>
|
||||
#include <stdlib.h>
|
||||
#include <sys/types.h>
|
||||
@@ -98,3 +104,23 @@ cmd_simple_wait(pid_t pid, int *exit_code) {
|
||||
}
|
||||
return !code;
|
||||
}
|
||||
|
||||
char *
|
||||
get_executable_path(void) {
|
||||
// <https://stackoverflow.com/a/1024937/1987178>
|
||||
#ifdef __linux__
|
||||
char buf[PATH_MAX + 1]; // +1 for the null byte
|
||||
ssize_t len = readlink("/proc/self/exe", buf, PATH_MAX);
|
||||
if (len == -1) {
|
||||
perror("readlink");
|
||||
return NULL;
|
||||
}
|
||||
buf[len] = '\0';
|
||||
return SDL_strdup(buf);
|
||||
#else
|
||||
// in practice, we only need this feature for portable builds, only used on
|
||||
// Windows, so we don't care implementing it for every platform
|
||||
// (it's useful to have a working version on Linux for debugging though)
|
||||
return NULL;
|
||||
#endif
|
||||
}
|
||||
|
||||
@@ -75,3 +75,18 @@ cmd_simple_wait(HANDLE handle, DWORD *exit_code) {
|
||||
}
|
||||
return !code;
|
||||
}
|
||||
|
||||
char *
|
||||
get_executable_path(void) {
|
||||
HMODULE hModule = GetModuleHandleW(NULL);
|
||||
if (!hModule) {
|
||||
return NULL;
|
||||
}
|
||||
WCHAR buf[MAX_PATH + 1]; // +1 for the null byte
|
||||
int len = GetModuleFileNameW(hModule, buf, MAX_PATH);
|
||||
if (!len) {
|
||||
return NULL;
|
||||
}
|
||||
buf[len] = '\0';
|
||||
return utf8_from_wide_char(buf);
|
||||
}
|
||||
|
||||
@@ -10,7 +10,10 @@
|
||||
#include "log.h"
|
||||
|
||||
bool
|
||||
video_buffer_init(struct video_buffer *vb, bool render_expired_frames) {
|
||||
video_buffer_init(struct video_buffer *vb, struct fps_counter *fps_counter,
|
||||
bool render_expired_frames) {
|
||||
vb->fps_counter = fps_counter;
|
||||
|
||||
if (!(vb->decoding_frame = av_frame_alloc())) {
|
||||
goto error_0;
|
||||
}
|
||||
@@ -37,7 +40,6 @@ video_buffer_init(struct video_buffer *vb, bool render_expired_frames) {
|
||||
// there is initially no rendering frame, so consider it has already been
|
||||
// consumed
|
||||
vb->rendering_frame_consumed = true;
|
||||
fps_counter_init(&vb->fps_counter);
|
||||
|
||||
return true;
|
||||
|
||||
@@ -75,10 +77,8 @@ video_buffer_offer_decoded_frame(struct video_buffer *vb,
|
||||
while (!vb->rendering_frame_consumed && !vb->interrupted) {
|
||||
cond_wait(vb->rendering_frame_consumed_cond, vb->mutex);
|
||||
}
|
||||
} else {
|
||||
if (vb->fps_counter.started && !vb->rendering_frame_consumed) {
|
||||
fps_counter_add_skipped_frame(&vb->fps_counter);
|
||||
}
|
||||
} else if (!vb->rendering_frame_consumed) {
|
||||
fps_counter_add_skipped_frame(vb->fps_counter);
|
||||
}
|
||||
|
||||
video_buffer_swap_frames(vb);
|
||||
@@ -93,9 +93,7 @@ const AVFrame *
|
||||
video_buffer_consume_rendered_frame(struct video_buffer *vb) {
|
||||
SDL_assert(!vb->rendering_frame_consumed);
|
||||
vb->rendering_frame_consumed = true;
|
||||
if (vb->fps_counter.started) {
|
||||
fps_counter_add_rendered_frame(&vb->fps_counter);
|
||||
}
|
||||
fps_counter_add_rendered_frame(vb->fps_counter);
|
||||
if (vb->render_expired_frames) {
|
||||
// unblock video_buffer_offer_decoded_frame()
|
||||
cond_signal(vb->rendering_frame_consumed_cond);
|
||||
|
||||
@@ -17,11 +17,12 @@ struct video_buffer {
|
||||
bool interrupted;
|
||||
SDL_cond *rendering_frame_consumed_cond;
|
||||
bool rendering_frame_consumed;
|
||||
struct fps_counter fps_counter;
|
||||
struct fps_counter *fps_counter;
|
||||
};
|
||||
|
||||
bool
|
||||
video_buffer_init(struct video_buffer *vb, bool render_expired_frames);
|
||||
video_buffer_init(struct video_buffer *vb, struct fps_counter *fps_counter,
|
||||
bool render_expired_frames);
|
||||
|
||||
void
|
||||
video_buffer_destroy(struct video_buffer *vb);
|
||||
|
||||
@@ -15,6 +15,6 @@ cpu = 'i686'
|
||||
endian = 'little'
|
||||
|
||||
[properties]
|
||||
prebuilt_ffmpeg_shared = 'ffmpeg-4.1-win32-shared'
|
||||
prebuilt_ffmpeg_dev = 'ffmpeg-4.1-win32-dev'
|
||||
prebuilt_sdl2 = 'SDL2-2.0.9/i686-w64-mingw32'
|
||||
prebuilt_ffmpeg_shared = 'ffmpeg-4.1.3-win32-shared'
|
||||
prebuilt_ffmpeg_dev = 'ffmpeg-4.1.3-win32-dev'
|
||||
prebuilt_sdl2 = 'SDL2-2.0.8/i686-w64-mingw32'
|
||||
|
||||
@@ -15,6 +15,6 @@ cpu = 'x86_64'
|
||||
endian = 'little'
|
||||
|
||||
[properties]
|
||||
prebuilt_ffmpeg_shared = 'ffmpeg-4.1-win64-shared'
|
||||
prebuilt_ffmpeg_dev = 'ffmpeg-4.1-win64-dev'
|
||||
prebuilt_sdl2 = 'SDL2-2.0.9/x86_64-w64-mingw32'
|
||||
prebuilt_ffmpeg_shared = 'ffmpeg-4.1.3-win64-shared'
|
||||
prebuilt_ffmpeg_dev = 'ffmpeg-4.1.3-win64-dev'
|
||||
prebuilt_sdl2 = 'SDL2-2.0.8/x86_64-w64-mingw32'
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
project('scrcpy', 'c',
|
||||
version: '1.8',
|
||||
version: '1.9',
|
||||
meson_version: '>= 0.37',
|
||||
default_options: 'c_std=c11')
|
||||
|
||||
|
||||
@@ -3,6 +3,6 @@ option('build_server', type: 'boolean', value: true, description: 'Build the ser
|
||||
option('crossbuild_windows', type: 'boolean', value: false, description: 'Build for Windows from Linux')
|
||||
option('windows_noconsole', type: 'boolean', value: false, description: 'Disable console on Windows (pass -mwindows flag)')
|
||||
option('prebuilt_server', type: 'string', description: 'Path of the prebuilt server')
|
||||
option('override_server_path', type: 'string', description: 'Hardcoded path to find the server at runtime')
|
||||
option('portable', type: 'boolean', description: 'Use scrcpy-server.jar from the same directory as the scrcpy executable')
|
||||
option('skip_frames', type: 'boolean', value: true, description: 'Always display the most recent frame')
|
||||
option('hidpi_support', type: 'boolean', value: true, description: 'Enable High DPI support')
|
||||
|
||||
@@ -10,31 +10,31 @@ prepare-win32: prepare-sdl2 prepare-ffmpeg-shared-win32 prepare-ffmpeg-dev-win32
|
||||
prepare-win64: prepare-sdl2 prepare-ffmpeg-shared-win64 prepare-ffmpeg-dev-win64 prepare-adb
|
||||
|
||||
prepare-ffmpeg-shared-win32:
|
||||
@./prepare-dep https://ffmpeg.zeranoe.com/builds/win32/shared/ffmpeg-4.1-win32-shared.zip \
|
||||
e692b18c01745d262c03294b382fd64df68fabe3c66aa4546a3ad3935175cde3 \
|
||||
ffmpeg-4.1-win32-shared
|
||||
@./prepare-dep https://ffmpeg.zeranoe.com/builds/win32/shared/ffmpeg-4.1.3-win32-shared.zip \
|
||||
8ea472d673370d5e87517a75587abfa6f189ee4f82e8da21fdbc49d0db0c1a89 \
|
||||
ffmpeg-4.1.3-win32-shared
|
||||
|
||||
prepare-ffmpeg-dev-win32:
|
||||
@./prepare-dep https://ffmpeg.zeranoe.com/builds/win32/dev/ffmpeg-4.1-win32-dev.zip \
|
||||
34bc5e471fb9160609abd6bc271e361050f3ff7376b1b8a0873cca02b38277c8 \
|
||||
ffmpeg-4.1-win32-dev
|
||||
@./prepare-dep https://ffmpeg.zeranoe.com/builds/win32/dev/ffmpeg-4.1.3-win32-dev.zip \
|
||||
e16d3150b6ccf0b71908f5b964cb8c051d79053c8f5cd6d777d617ab4f03613a \
|
||||
ffmpeg-4.1.3-win32-dev
|
||||
|
||||
prepare-ffmpeg-shared-win64:
|
||||
@./prepare-dep https://ffmpeg.zeranoe.com/builds/win64/shared/ffmpeg-4.1-win64-shared.zip \
|
||||
c4908c97436c946509dc365e421159274fa4b1e66dce6fb5b63d82a6294d5357 \
|
||||
ffmpeg-4.1-win64-shared
|
||||
@./prepare-dep https://ffmpeg.zeranoe.com/builds/win64/shared/ffmpeg-4.1.3-win64-shared.zip \
|
||||
0b974578e07d974c4bafb36c7ed0b46e46b001d38b149455089c13b57ddefe5d \
|
||||
ffmpeg-4.1.3-win64-shared
|
||||
|
||||
prepare-ffmpeg-dev-win64:
|
||||
@./prepare-dep https://ffmpeg.zeranoe.com/builds/win64/dev/ffmpeg-4.1-win64-dev.zip \
|
||||
761ec79aa3dae66698c9791a2f0bb9da8794246f8356cadc741ddc0eabab0471 \
|
||||
ffmpeg-4.1-win64-dev
|
||||
@./prepare-dep https://ffmpeg.zeranoe.com/builds/win64/dev/ffmpeg-4.1.3-win64-dev.zip \
|
||||
334b473467db096a5b74242743592a73e120a137232794508e4fc55593696a5b \
|
||||
ffmpeg-4.1.3-win64-dev
|
||||
|
||||
prepare-sdl2:
|
||||
@./prepare-dep https://libsdl.org/release/SDL2-devel-2.0.9-mingw.tar.gz \
|
||||
0f9f00d0f2a9a95dfb5cce929718210c3f85432cc2e9d4abade4adcb7f6bb39d \
|
||||
SDL2-2.0.9
|
||||
@./prepare-dep https://libsdl.org/release/SDL2-devel-2.0.8-mingw.tar.gz \
|
||||
ffff7305d634aff5e1df5b7bb935435c3a02c8b03ad94a1a2be9169a558a7961 \
|
||||
SDL2-2.0.8
|
||||
|
||||
prepare-adb:
|
||||
@./prepare-dep https://dl.google.com/android/repository/platform-tools_r28.0.1-windows.zip \
|
||||
db78f726d5dc653706dcd15a462ab1b946c643f598df76906c4c1858411c54df \
|
||||
@./prepare-dep https://dl.google.com/android/repository/platform-tools_r29.0.1-windows.zip \
|
||||
2334f92cf571fd2d9bf6ff7c637765bee5d8323e0bd8e051e15927d87b54b4e8 \
|
||||
platform-tools
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
apply plugin: 'com.android.application'
|
||||
|
||||
android {
|
||||
compileSdkVersion 27
|
||||
compileSdkVersion 29
|
||||
defaultConfig {
|
||||
applicationId "com.genymobile.scrcpy"
|
||||
minSdkVersion 21
|
||||
targetSdkVersion 27
|
||||
versionCode 9
|
||||
versionName "1.8"
|
||||
targetSdkVersion 29
|
||||
versionCode 10
|
||||
versionName "1.9"
|
||||
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
|
||||
}
|
||||
buildTypes {
|
||||
|
||||
@@ -89,7 +89,7 @@ public final class DesktopConnection implements Closeable {
|
||||
byte[] buffer = new byte[DEVICE_NAME_FIELD_LENGTH + 4];
|
||||
|
||||
byte[] deviceNameBytes = deviceName.getBytes(StandardCharsets.UTF_8);
|
||||
int len = Math.min(DEVICE_NAME_FIELD_LENGTH - 1, deviceNameBytes.length);
|
||||
int len = StringUtils.getUtf8TruncationIndex(deviceNameBytes, DEVICE_NAME_FIELD_LENGTH - 1);
|
||||
System.arraycopy(deviceNameBytes, 0, buffer, 0, len);
|
||||
// byte[] are always 0-initialized in java, no need to set '\0' explicitly
|
||||
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
package com.genymobile.scrcpy;
|
||||
|
||||
import junit.framework.Assert;
|
||||
|
||||
import org.junit.Assert;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
@@ -10,7 +9,7 @@ public class StringUtilsTest {
|
||||
|
||||
@Test
|
||||
@SuppressWarnings("checkstyle:MagicNumber")
|
||||
public void testUtf8Trucate() {
|
||||
public void testUtf8Truncate() {
|
||||
String s = "aÉbÔc";
|
||||
byte[] utf8 = s.getBytes(StandardCharsets.UTF_8);
|
||||
Assert.assertEquals(7, utf8.length);
|
||||
|
||||
Reference in New Issue
Block a user