Compare commits

..

1 Commits

Author SHA1 Message Date
Romain Vimont
cc6db6776c Document Windows command line usage 2020-12-20 08:18:32 +01:00
94 changed files with 1516 additions and 1924 deletions

View File

@@ -254,10 +254,10 @@ You can then [run](README.md#run) _scrcpy_.
## Prebuilt server ## Prebuilt server
- [`scrcpy-server-v1.17`][direct-scrcpy-server] - [`scrcpy-server-v1.16`][direct-scrcpy-server]
_(SHA-256: 11b5ad2d1bc9b9730fb7254a78efd71a8ff46b1938ff468e47a21b653a1b6725_ _(SHA-256: 94a79e05b4498d0460ab7bd9d12cbf05156e3a47bf0c5d1420cee1d4493b3832)_
[direct-scrcpy-server]: https://github.com/Genymobile/scrcpy/releases/download/v1.17/scrcpy-server-v1.17 [direct-scrcpy-server]: https://github.com/Genymobile/scrcpy/releases/download/v1.16/scrcpy-server-v1.16
Download the prebuilt server somewhere, and specify its path during the Meson Download the prebuilt server somewhere, and specify its path during the Meson
configuration: configuration:

3
FAQ.md
View File

@@ -233,7 +233,4 @@ scrcpy --prefer-text --turn-screen-off --stay-awake
Then just double-click on that file. Then just double-click on that file.
You could also edit (a copy of) `scrcpy-console.bat` or `scrcpy-noconsole.vbs`
to add some arguments.
[show file extensions]: https://www.howtogeek.com/205086/beginner-how-to-make-windows-show-file-extensions/ [show file extensions]: https://www.howtogeek.com/205086/beginner-how-to-make-windows-show-file-extensions/

View File

@@ -188,7 +188,7 @@
identification within third-party archives. identification within third-party archives.
Copyright (C) 2018 Genymobile Copyright (C) 2018 Genymobile
Copyright (C) 2018-2021 Romain Vimont Copyright (C) 2018-2020 Romain Vimont
Licensed under the Apache License, Version 2.0 (the "License"); Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License. you may not use this file except in compliance with the License.

View File

@@ -9,20 +9,21 @@
# the server to the device. # the server to the device.
.PHONY: default clean \ .PHONY: default clean \
test \
build-server \ build-server \
prepare-deps-win32 prepare-deps-win64 \ prepare-deps-win32 prepare-deps-win64 \
build-win32 build-win64 \ build-win32 build-win32-noconsole \
build-win64 build-win64-noconsole \
dist-win32 dist-win64 \ dist-win32 dist-win64 \
zip-win32 zip-win64 \ zip-win32 zip-win64 \
release sums release
GRADLE ?= ./gradlew GRADLE ?= ./gradlew
TEST_BUILD_DIR := build-test
SERVER_BUILD_DIR := build-server SERVER_BUILD_DIR := build-server
WIN32_BUILD_DIR := build-win32 WIN32_BUILD_DIR := build-win32
WIN32_NOCONSOLE_BUILD_DIR := build-win32-noconsole
WIN64_BUILD_DIR := build-win64 WIN64_BUILD_DIR := build-win64
WIN64_NOCONSOLE_BUILD_DIR := build-win64-noconsole
DIST := dist DIST := dist
WIN32_TARGET_DIR := scrcpy-win32 WIN32_TARGET_DIR := scrcpy-win32
@@ -32,35 +33,19 @@ VERSION := $(shell git describe --tags --always)
WIN32_TARGET := $(WIN32_TARGET_DIR)-$(VERSION).zip WIN32_TARGET := $(WIN32_TARGET_DIR)-$(VERSION).zip
WIN64_TARGET := $(WIN64_TARGET_DIR)-$(VERSION).zip WIN64_TARGET := $(WIN64_TARGET_DIR)-$(VERSION).zip
RELEASE_DIR := release-$(VERSION) release: clean zip-win32 zip-win64 sums
@echo "Windows archives generated in $(DIST)/"
release: clean test build-server zip-win32 zip-win64
mkdir -p "$(RELEASE_DIR)"
cp "$(SERVER_BUILD_DIR)/server/scrcpy-server" \
"$(RELEASE_DIR)/scrcpy-server-$(VERSION)"
cp "$(DIST)/$(WIN32_TARGET)" "$(RELEASE_DIR)"
cp "$(DIST)/$(WIN64_TARGET)" "$(RELEASE_DIR)"
cd "$(RELEASE_DIR)" && \
sha256sum "scrcpy-server-$(VERSION)" \
"scrcpy-win32-$(VERSION).zip" \
"scrcpy-win64-$(VERSION).zip" > SHA256SUMS.txt
@echo "Release generated in $(RELEASE_DIR)/"
clean: clean:
$(GRADLE) clean $(GRADLE) clean
rm -rf "$(DIST)" "$(TEST_BUILD_DIR)" "$(SERVER_BUILD_DIR)" \ rm -rf "$(SERVER_BUILD_DIR)" "$(WIN32_BUILD_DIR)" "$(WIN64_BUILD_DIR)" \
"$(WIN32_BUILD_DIR)" "$(WIN64_BUILD_DIR)" "$(WIN32_NOCONSOLE_BUILD_DIR)" "$(WIN64_NOCONSOLE_BUILD_DIR)" "$(DIST)"
test:
[ -d "$(TEST_BUILD_DIR)" ] || ( mkdir "$(TEST_BUILD_DIR)" && \
meson "$(TEST_BUILD_DIR)" -Db_sanitize=address )
ninja -C "$(TEST_BUILD_DIR)"
$(GRADLE) -p server check
build-server: build-server:
[ -d "$(SERVER_BUILD_DIR)" ] || ( mkdir "$(SERVER_BUILD_DIR)" && \ [ -d "$(SERVER_BUILD_DIR)" ] || ( mkdir "$(SERVER_BUILD_DIR)" && \
meson "$(SERVER_BUILD_DIR)" --buildtype release -Dcompile_app=false ) meson "$(SERVER_BUILD_DIR)" \
ninja -C "$(SERVER_BUILD_DIR)" --buildtype release -Dcompile_app=false )
ninja -C "$(SERVER_BUILD_DIR)"
prepare-deps-win32: prepare-deps-win32:
-$(MAKE) -C prebuilt-deps prepare-win32 -$(MAKE) -C prebuilt-deps prepare-win32
@@ -75,6 +60,17 @@ build-win32: prepare-deps-win32
-Dportable=true ) -Dportable=true )
ninja -C "$(WIN32_BUILD_DIR)" ninja -C "$(WIN32_BUILD_DIR)"
build-win32-noconsole: prepare-deps-win32
[ -d "$(WIN32_NOCONSOLE_BUILD_DIR)" ] || ( mkdir "$(WIN32_NOCONSOLE_BUILD_DIR)" && \
meson "$(WIN32_NOCONSOLE_BUILD_DIR)" \
--cross-file cross_win32.txt \
--buildtype release --strip -Db_lto=true \
-Dcrossbuild_windows=true \
-Dcompile_server=false \
-Dwindows_noconsole=true \
-Dportable=true )
ninja -C "$(WIN32_NOCONSOLE_BUILD_DIR)"
prepare-deps-win64: prepare-deps-win64:
-$(MAKE) -C prebuilt-deps prepare-win64 -$(MAKE) -C prebuilt-deps prepare-win64
@@ -88,12 +84,23 @@ build-win64: prepare-deps-win64
-Dportable=true ) -Dportable=true )
ninja -C "$(WIN64_BUILD_DIR)" ninja -C "$(WIN64_BUILD_DIR)"
dist-win32: build-server build-win32 build-win64-noconsole: prepare-deps-win64
[ -d "$(WIN64_NOCONSOLE_BUILD_DIR)" ] || ( mkdir "$(WIN64_NOCONSOLE_BUILD_DIR)" && \
meson "$(WIN64_NOCONSOLE_BUILD_DIR)" \
--cross-file cross_win64.txt \
--buildtype release --strip -Db_lto=true \
-Dcrossbuild_windows=true \
-Dcompile_server=false \
-Dwindows_noconsole=true \
-Dportable=true )
ninja -C "$(WIN64_NOCONSOLE_BUILD_DIR)"
dist-win32: build-server build-win32 build-win32-noconsole
mkdir -p "$(DIST)/$(WIN32_TARGET_DIR)" mkdir -p "$(DIST)/$(WIN32_TARGET_DIR)"
cp "$(SERVER_BUILD_DIR)"/server/scrcpy-server "$(DIST)/$(WIN32_TARGET_DIR)/" cp "$(SERVER_BUILD_DIR)"/server/scrcpy-server "$(DIST)/$(WIN32_TARGET_DIR)/"
cp "$(WIN32_BUILD_DIR)"/app/scrcpy.exe "$(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 data/scrcpy-console.bat "$(DIST)/$(WIN32_TARGET_DIR)" cp data/scrcpy-console.bat "$(DIST)/$(WIN32_TARGET_DIR)"
cp data/scrcpy-noconsole.vbs "$(DIST)/$(WIN32_TARGET_DIR)"
cp prebuilt-deps/ffmpeg-4.3.1-win32-shared/bin/avutil-56.dll "$(DIST)/$(WIN32_TARGET_DIR)/" cp prebuilt-deps/ffmpeg-4.3.1-win32-shared/bin/avutil-56.dll "$(DIST)/$(WIN32_TARGET_DIR)/"
cp prebuilt-deps/ffmpeg-4.3.1-win32-shared/bin/avcodec-58.dll "$(DIST)/$(WIN32_TARGET_DIR)/" cp prebuilt-deps/ffmpeg-4.3.1-win32-shared/bin/avcodec-58.dll "$(DIST)/$(WIN32_TARGET_DIR)/"
cp prebuilt-deps/ffmpeg-4.3.1-win32-shared/bin/avformat-58.dll "$(DIST)/$(WIN32_TARGET_DIR)/" cp prebuilt-deps/ffmpeg-4.3.1-win32-shared/bin/avformat-58.dll "$(DIST)/$(WIN32_TARGET_DIR)/"
@@ -102,14 +109,14 @@ dist-win32: build-server build-win32
cp prebuilt-deps/platform-tools/adb.exe "$(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/AdbWinApi.dll "$(DIST)/$(WIN32_TARGET_DIR)/"
cp prebuilt-deps/platform-tools/AdbWinUsbApi.dll "$(DIST)/$(WIN32_TARGET_DIR)/" cp prebuilt-deps/platform-tools/AdbWinUsbApi.dll "$(DIST)/$(WIN32_TARGET_DIR)/"
cp prebuilt-deps/SDL2-2.0.14/i686-w64-mingw32/bin/SDL2.dll "$(DIST)/$(WIN32_TARGET_DIR)/" cp prebuilt-deps/SDL2-2.0.12/i686-w64-mingw32/bin/SDL2.dll "$(DIST)/$(WIN32_TARGET_DIR)/"
dist-win64: build-server build-win64 dist-win64: build-server build-win64 build-win64-noconsole
mkdir -p "$(DIST)/$(WIN64_TARGET_DIR)" mkdir -p "$(DIST)/$(WIN64_TARGET_DIR)"
cp "$(SERVER_BUILD_DIR)"/server/scrcpy-server "$(DIST)/$(WIN64_TARGET_DIR)/" cp "$(SERVER_BUILD_DIR)"/server/scrcpy-server "$(DIST)/$(WIN64_TARGET_DIR)/"
cp "$(WIN64_BUILD_DIR)"/app/scrcpy.exe "$(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 data/scrcpy-console.bat "$(DIST)/$(WIN64_TARGET_DIR)" cp data/scrcpy-console.bat "$(DIST)/$(WIN64_TARGET_DIR)"
cp data/scrcpy-noconsole.vbs "$(DIST)/$(WIN64_TARGET_DIR)"
cp prebuilt-deps/ffmpeg-4.3.1-win64-shared/bin/avutil-56.dll "$(DIST)/$(WIN64_TARGET_DIR)/" cp prebuilt-deps/ffmpeg-4.3.1-win64-shared/bin/avutil-56.dll "$(DIST)/$(WIN64_TARGET_DIR)/"
cp prebuilt-deps/ffmpeg-4.3.1-win64-shared/bin/avcodec-58.dll "$(DIST)/$(WIN64_TARGET_DIR)/" cp prebuilt-deps/ffmpeg-4.3.1-win64-shared/bin/avcodec-58.dll "$(DIST)/$(WIN64_TARGET_DIR)/"
cp prebuilt-deps/ffmpeg-4.3.1-win64-shared/bin/avformat-58.dll "$(DIST)/$(WIN64_TARGET_DIR)/" cp prebuilt-deps/ffmpeg-4.3.1-win64-shared/bin/avformat-58.dll "$(DIST)/$(WIN64_TARGET_DIR)/"
@@ -118,7 +125,7 @@ dist-win64: build-server build-win64
cp prebuilt-deps/platform-tools/adb.exe "$(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/AdbWinApi.dll "$(DIST)/$(WIN64_TARGET_DIR)/"
cp prebuilt-deps/platform-tools/AdbWinUsbApi.dll "$(DIST)/$(WIN64_TARGET_DIR)/" cp prebuilt-deps/platform-tools/AdbWinUsbApi.dll "$(DIST)/$(WIN64_TARGET_DIR)/"
cp prebuilt-deps/SDL2-2.0.14/x86_64-w64-mingw32/bin/SDL2.dll "$(DIST)/$(WIN64_TARGET_DIR)/" cp prebuilt-deps/SDL2-2.0.12/x86_64-w64-mingw32/bin/SDL2.dll "$(DIST)/$(WIN64_TARGET_DIR)/"
zip-win32: dist-win32 zip-win32: dist-win32
cd "$(DIST)/$(WIN32_TARGET_DIR)"; \ cd "$(DIST)/$(WIN32_TARGET_DIR)"; \
@@ -127,3 +134,7 @@ zip-win32: dist-win32
zip-win64: dist-win64 zip-win64: dist-win64
cd "$(DIST)/$(WIN64_TARGET_DIR)"; \ cd "$(DIST)/$(WIN64_TARGET_DIR)"; \
zip -r "../$(WIN64_TARGET)" . zip -r "../$(WIN64_TARGET)" .
sums:
cd "$(DIST)"; \
sha256sum *.zip > SHA256SUMS.txt

View File

@@ -675,7 +675,7 @@ Baca [halaman pengembang].
## Lisensi ## Lisensi
Copyright (C) 2018 Genymobile Copyright (C) 2018 Genymobile
Copyright (C) 2018-2021 Romain Vimont Copyright (C) 2018-2020 Romain Vimont
Licensed under the Apache License, Version 2.0 (the "License"); Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License. you may not use this file except in compliance with the License.

View File

@@ -477,7 +477,7 @@ _²화면이 꺼진 상태에서 우클릭 시 다시 켜지며, 그 외의 상
## 라이선스 ## 라이선스
Copyright (C) 2018 Genymobile Copyright (C) 2018 Genymobile
Copyright (C) 2018-2021 Romain Vimont Copyright (C) 2018-2020 Romain Vimont
Licensed under the Apache License, Version 2.0 (the "License"); Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License. you may not use this file except in compliance with the License.

View File

@@ -1,4 +1,4 @@
# scrcpy (v1.17) # scrcpy (v1.16)
[Read in another language](#translations) [Read in another language](#translations)
@@ -77,10 +77,10 @@ hard).
For Windows, for simplicity, a prebuilt archive with all the dependencies For Windows, for simplicity, a prebuilt archive with all the dependencies
(including `adb`) is available: (including `adb`) is available:
- [`scrcpy-win64-v1.17.zip`][direct-win64] - [`scrcpy-win64-v1.16.zip`][direct-win64]
_(SHA-256: 8b9e57993c707367ed10ebfe0e1ef563c7a29d9af4a355cd8b6a52a317c73eea)_ _(SHA-256: 3f30dc5db1a2f95c2b40a0f5de91ec1642d9f53799250a8c529bc882bc0918f0)_
[direct-win64]: https://github.com/Genymobile/scrcpy/releases/download/v1.17/scrcpy-win64-v1.17.zip [direct-win64]: https://github.com/Genymobile/scrcpy/releases/download/v1.16/scrcpy-win64-v1.16.zip
It is also available in [Chocolatey]: It is also available in [Chocolatey]:
@@ -116,10 +116,6 @@ brew install scrcpy
You need `adb`, accessible from your `PATH`. If you don't have it yet: You need `adb`, accessible from your `PATH`. If you don't have it yet:
```bash ```bash
# Homebrew >= 2.6.0
brew install --cask android-platform-tools
# Homebrew < 2.6.0
brew cask install android-platform-tools brew cask install android-platform-tools
``` ```
@@ -206,8 +202,6 @@ scrcpy --lock-video-orientation 3 # 90° clockwise
This affects recording orientation. This affects recording orientation.
The [window may also be rotated](#rotation) independently.
#### Encoder #### Encoder
@@ -413,9 +407,9 @@ Note that _scrcpy_ manages 3 different rotations:
- <kbd>MOD</kbd>+<kbd>r</kbd> requests the device to switch between portrait - <kbd>MOD</kbd>+<kbd>r</kbd> requests the device to switch between portrait
and landscape (the current running app may refuse, if it does support the and landscape (the current running app may refuse, if it does support the
requested orientation). requested orientation).
- [`--lock-video-orientation`](#lock-video-orientation) changes the mirroring - `--lock-video-orientation` changes the mirroring orientation (the orientation
orientation (the orientation of the video sent from the device to the of the video sent from the device to the computer). This affects the
computer). This affects the recording. recording.
- `--rotation` (or <kbd>MOD</kbd>+<kbd>←</kbd>/<kbd>MOD</kbd>+<kbd>→</kbd>) - `--rotation` (or <kbd>MOD</kbd>+<kbd>←</kbd>/<kbd>MOD</kbd>+<kbd>→</kbd>)
rotates only the window content. This affects only the display, not the rotates only the window content. This affects only the display, not the
recording. recording.
@@ -491,6 +485,18 @@ scrcpy -Sw
``` ```
#### Render expired frames
By default, to minimize latency, _scrcpy_ always renders the last decoded frame
available, and drops any previous one.
To force the rendering of all frames (at a cost of a possible increased
latency), use:
```bash
scrcpy --render-expired-frames
```
#### Show touches #### Show touches
For presentations, it may be useful to show physical touches (on the physical For presentations, it may be useful to show physical touches (on the physical
@@ -760,7 +766,7 @@ Read the [developers page].
## Licence ## Licence
Copyright (C) 2018 Genymobile Copyright (C) 2018 Genymobile
Copyright (C) 2018-2021 Romain Vimont Copyright (C) 2018-2020 Romain Vimont
Licensed under the Apache License, Version 2.0 (the "License"); Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License. you may not use this file except in compliance with the License.

View File

@@ -508,7 +508,7 @@ Leia a [developers page].
## Licença ## Licença
Copyright (C) 2018 Genymobile Copyright (C) 2018 Genymobile
Copyright (C) 2018-2021 Romain Vimont Copyright (C) 2018-2020 Romain Vimont
Licensed under the Apache License, Version 2.0 (the "License"); Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License. you may not use this file except in compliance with the License.

View File

@@ -703,7 +703,7 @@ _³需要安卓版本 Android >= 7。_
## 许可协议 ## 许可协议
Copyright (C) 2018 Genymobile Copyright (C) 2018 Genymobile
Copyright (C) 2018-2021 Romain Vimont Copyright (C) 2018-2020 Romain Vimont
Licensed under the Apache License, Version 2.0 (the "License"); Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License. you may not use this file except in compliance with the License.

View File

@@ -682,7 +682,7 @@ _³只支援 Android 7+。_
## Licence ## Licence
Copyright (C) 2018 Genymobile Copyright (C) 2018 Genymobile
Copyright (C) 2018-2021 Romain Vimont Copyright (C) 2018-2020 Romain Vimont
Licensed under the Apache License, Version 2.0 (the "License"); Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License. you may not use this file except in compliance with the License.

View File

@@ -1,8 +1,7 @@
src = [ src = [
'src/main.c', 'src/main.c',
'src/adb.c',
'src/cli.c', 'src/cli.c',
'src/compat.c', 'src/command.c',
'src/control_msg.c', 'src/control_msg.c',
'src/controller.c', 'src/controller.c',
'src/decoder.c', 'src/decoder.c',
@@ -22,23 +21,9 @@ src = [
'src/tiny_xpm.c', 'src/tiny_xpm.c',
'src/video_buffer.c', 'src/video_buffer.c',
'src/util/net.c', 'src/util/net.c',
'src/util/process.c', 'src/util/str_util.c'
'src/util/str_util.c',
'src/util/thread.c',
] ]
if host_machine.system() == 'windows'
src += [ 'src/sys/win/process.c' ]
else
src += [ 'src/sys/unix/process.c' ]
endif
check_functions = [
'strdup'
]
cc = meson.get_compiler('c')
if not get_option('crossbuild_windows') if not get_option('crossbuild_windows')
# native build # native build
@@ -52,6 +37,8 @@ if not get_option('crossbuild_windows')
else else
# cross-compile mingw32 build (from Linux to Windows) # cross-compile mingw32 build (from Linux to Windows)
cc = meson.get_compiler('c')
prebuilt_sdl2 = meson.get_cross_property('prebuilt_sdl2') prebuilt_sdl2 = meson.get_cross_property('prebuilt_sdl2')
sdl2_bin_dir = meson.current_source_dir() + '/../prebuilt-deps/' + prebuilt_sdl2 + '/bin' sdl2_bin_dir = meson.current_source_dir() + '/../prebuilt-deps/' + prebuilt_sdl2 + '/bin'
sdl2_lib_dir = meson.current_source_dir() + '/../prebuilt-deps/' + prebuilt_sdl2 + '/lib' sdl2_lib_dir = meson.current_source_dir() + '/../prebuilt-deps/' + prebuilt_sdl2 + '/lib'
@@ -86,18 +73,19 @@ else
endif endif
cc = meson.get_compiler('c')
if host_machine.system() == 'windows' if host_machine.system() == 'windows'
src += [ 'src/sys/win/command.c' ]
dependencies += cc.find_library('ws2_32') dependencies += cc.find_library('ws2_32')
else
src += [ 'src/sys/unix/command.c' ]
endif endif
conf = configuration_data() conf = configuration_data()
foreach f : check_functions # expose the build type
if cc.has_function(f) conf.set('NDEBUG', get_option('buildtype') != 'debug')
define = 'HAVE_' + f.underscorify().to_upper()
conf.set(define, true)
endif
endforeach
# the version, updated on release # the version, updated on release
conf.set_quoted('SCRCPY_VERSION', meson.project_version()) conf.set_quoted('SCRCPY_VERSION', meson.project_version())
@@ -114,10 +102,26 @@ conf.set('PORTABLE', get_option('portable'))
conf.set('DEFAULT_LOCAL_PORT_RANGE_FIRST', '27183') conf.set('DEFAULT_LOCAL_PORT_RANGE_FIRST', '27183')
conf.set('DEFAULT_LOCAL_PORT_RANGE_LAST', '27199') conf.set('DEFAULT_LOCAL_PORT_RANGE_LAST', '27199')
# the default max video size for both dimensions, in pixels
# overridden by option --max-size
conf.set('DEFAULT_MAX_SIZE', '0') # 0: unlimited
# the default video orientation
# natural device orientation is 0 and each increment adds 90 degrees
# counterclockwise
# overridden by option --lock-video-orientation
conf.set('DEFAULT_LOCK_VIDEO_ORIENTATION', '-1') # -1: unlocked
# the default video bitrate, in bits/second # the default video bitrate, in bits/second
# overridden by option --bit-rate # overridden by option --bit-rate
conf.set('DEFAULT_BIT_RATE', '8000000') # 8Mbps conf.set('DEFAULT_BIT_RATE', '8000000') # 8Mbps
# enable High DPI support
conf.set('HIDPI_SUPPORT', get_option('hidpi_support'))
# disable console on Windows
conf.set('WINDOWS_NOCONSOLE', get_option('windows_noconsole'))
# run a server debugger and wait for a client to be attached # run a server debugger and wait for a client to be attached
conf.set('SERVER_DEBUGGER', get_option('server_debugger')) conf.set('SERVER_DEBUGGER', get_option('server_debugger'))
@@ -128,11 +132,18 @@ configure_file(configuration: conf, output: 'config.h')
src_dir = include_directories('src') src_dir = include_directories('src')
if get_option('windows_noconsole')
link_args = [ '-Wl,--subsystem,windows' ]
else
link_args = []
endif
executable('scrcpy', src, executable('scrcpy', src,
dependencies: dependencies, dependencies: dependencies,
include_directories: src_dir, include_directories: src_dir,
install: true, install: true,
c_args: []) c_args: [],
link_args: link_args)
install_man('scrcpy.1') install_man('scrcpy.1')

View File

@@ -155,6 +155,10 @@ Supported names are currently "direct3d", "opengl", "opengles2", "opengles", "me
.UR https://wiki.libsdl.org/SDL_HINT_RENDER_DRIVER .UR https://wiki.libsdl.org/SDL_HINT_RENDER_DRIVER
.UE .UE
.TP
.B \-\-render\-expired\-frames
By default, to minimize latency, scrcpy always renders the last available decoded frame, and drops any previous ones. This flag forces to render all frames, at a cost of a possible increased latency.
.TP .TP
.BI "\-\-rotation " value .BI "\-\-rotation " value
Set the initial display rotation. Possibles values are 0, 1, 2 and 3. Each increment adds a 90 degrees rotation counterclockwise. Set the initial display rotation. Possibles values are 0, 1, 2 and 3. Each increment adds a 90 degrees rotation counterclockwise.

View File

@@ -1,34 +0,0 @@
#ifndef SC_ADB_H
#define SC_ADB_H
#include "common.h"
#include <stdbool.h>
#include <inttypes.h>
#include "util/process.h"
process_t
adb_execute(const char *serial, const char *const adb_cmd[], size_t len);
process_t
adb_forward(const char *serial, uint16_t local_port,
const char *device_socket_name);
process_t
adb_forward_remove(const char *serial, uint16_t local_port);
process_t
adb_reverse(const char *serial, const char *device_socket_name,
uint16_t local_port);
process_t
adb_reverse_remove(const char *serial, const char *device_socket_name);
process_t
adb_push(const char *serial, const char *local, const char *remote);
process_t
adb_install(const char *serial, const char *local);
#endif

View File

@@ -6,13 +6,11 @@
#include <stdio.h> #include <stdio.h>
#include <unistd.h> #include <unistd.h>
#include "config.h"
#include "scrcpy.h" #include "scrcpy.h"
#include "util/log.h" #include "util/log.h"
#include "util/str_util.h" #include "util/str_util.h"
#define STR_IMPL_(x) #x
#define STR(x) STR_IMPL_(x)
void void
scrcpy_print_usage(const char *arg0) { scrcpy_print_usage(const char *arg0) {
fprintf(stderr, fprintf(stderr,
@@ -26,7 +24,7 @@ scrcpy_print_usage(const char *arg0) {
" -b, --bit-rate value\n" " -b, --bit-rate value\n"
" Encode the video at the given bit-rate, expressed in bits/s.\n" " Encode the video at the given bit-rate, expressed in bits/s.\n"
" Unit suffixes are supported: 'K' (x1000) and 'M' (x1000000).\n" " Unit suffixes are supported: 'K' (x1000) and 'M' (x1000000).\n"
" Default is " STR(DEFAULT_BIT_RATE) ".\n" " Default is %d.\n"
"\n" "\n"
" --codec-options key[:type]=value[,...]\n" " --codec-options key[:type]=value[,...]\n"
" Set a list of comma-separated key:type=value options for the\n" " Set a list of comma-separated key:type=value options for the\n"
@@ -84,7 +82,7 @@ scrcpy_print_usage(const char *arg0) {
" Possible values are -1 (unlocked), 0, 1, 2 and 3.\n" " Possible values are -1 (unlocked), 0, 1, 2 and 3.\n"
" Natural device orientation is 0, and each increment adds a\n" " Natural device orientation is 0, and each increment adds a\n"
" 90 degrees rotation counterclockwise.\n" " 90 degrees rotation counterclockwise.\n"
" Default is -1 (unlocked).\n" " Default is %d%s.\n"
"\n" "\n"
" --max-fps value\n" " --max-fps value\n"
" Limit the frame rate of screen capture (officially supported\n" " Limit the frame rate of screen capture (officially supported\n"
@@ -94,7 +92,7 @@ scrcpy_print_usage(const char *arg0) {
" Limit both the width and height of the video to value. The\n" " Limit both the width and height of the video to value. The\n"
" other dimension is computed so that the device aspect-ratio\n" " other dimension is computed so that the device aspect-ratio\n"
" is preserved.\n" " is preserved.\n"
" Default is 0 (unlimited).\n" " Default is %d%s.\n"
"\n" "\n"
" -n, --no-control\n" " -n, --no-control\n"
" Disable device control (mirror the device in read-only).\n" " Disable device control (mirror the device in read-only).\n"
@@ -113,8 +111,7 @@ scrcpy_print_usage(const char *arg0) {
"\n" "\n"
" -p, --port port[:port]\n" " -p, --port port[:port]\n"
" Set the TCP port (range) used by the client to listen.\n" " Set the TCP port (range) used by the client to listen.\n"
" Default is " STR(DEFAULT_LOCAL_PORT_RANGE_FIRST) ":" " Default is %d:%d.\n"
STR(DEFAULT_LOCAL_PORT_RANGE_LAST) ".\n"
"\n" "\n"
" --prefer-text\n" " --prefer-text\n"
" Inject alpha characters and space as text events instead of\n" " Inject alpha characters and space as text events instead of\n"
@@ -143,6 +140,12 @@ scrcpy_print_usage(const char *arg0) {
" \"opengles2\", \"opengles\", \"metal\" and \"software\".\n" " \"opengles2\", \"opengles\", \"metal\" and \"software\".\n"
" <https://wiki.libsdl.org/SDL_HINT_RENDER_DRIVER>\n" " <https://wiki.libsdl.org/SDL_HINT_RENDER_DRIVER>\n"
"\n" "\n"
" --render-expired-frames\n"
" By default, to minimize latency, scrcpy always renders the\n"
" last available decoded frame, and drops any previous ones.\n"
" This flag forces to render all frames, at a cost of a\n"
" possible increased latency.\n"
"\n"
" --rotation value\n" " --rotation value\n"
" Set the initial display rotation.\n" " Set the initial display rotation.\n"
" Possibles values are 0, 1, 2 and 3. Each increment adds a 90\n" " Possibles values are 0, 1, 2 and 3. Each increment adds a 90\n"
@@ -295,7 +298,12 @@ scrcpy_print_usage(const char *arg0) {
"\n" "\n"
" Drag & drop APK file\n" " Drag & drop APK file\n"
" Install APK from computer\n" " Install APK from computer\n"
"\n", arg0); "\n",
arg0,
DEFAULT_BIT_RATE,
DEFAULT_LOCK_VIDEO_ORIENTATION, DEFAULT_LOCK_VIDEO_ORIENTATION >= 0 ? "" : " (unlocked)",
DEFAULT_MAX_SIZE, DEFAULT_MAX_SIZE ? "" : " (unlimited)",
DEFAULT_LOCAL_PORT_RANGE_FIRST, DEFAULT_LOCAL_PORT_RANGE_LAST);
} }
static bool static bool
@@ -470,14 +478,14 @@ parse_port_range(const char *s, struct sc_port_range *port_range) {
} }
static bool static bool
parse_display_id(const char *s, uint32_t *display_id) { parse_display_id(const char *s, uint16_t *display_id) {
long value; long value;
bool ok = parse_integer_arg(s, &value, false, 0, 0x7FFFFFFF, "display id"); bool ok = parse_integer_arg(s, &value, false, 0, 0xFFFF, "display id");
if (!ok) { if (!ok) {
return false; return false;
} }
*display_id = (uint32_t) value; *display_id = (uint16_t) value;
return true; return true;
} }
@@ -660,7 +668,6 @@ guess_record_format(const char *filename) {
#define OPT_FORWARD_ALL_CLICKS 1023 #define OPT_FORWARD_ALL_CLICKS 1023
#define OPT_LEGACY_PASTE 1024 #define OPT_LEGACY_PASTE 1024
#define OPT_ENCODER_NAME 1025 #define OPT_ENCODER_NAME 1025
#define OPT_POWER_OFF_ON_CLOSE 1026
bool bool
scrcpy_parse_args(struct scrcpy_cli_args *args, int argc, char *argv[]) { scrcpy_parse_args(struct scrcpy_cli_args *args, int argc, char *argv[]) {
@@ -711,8 +718,6 @@ scrcpy_parse_args(struct scrcpy_cli_args *args, int argc, char *argv[]) {
{"window-height", required_argument, NULL, OPT_WINDOW_HEIGHT}, {"window-height", required_argument, NULL, OPT_WINDOW_HEIGHT},
{"window-borderless", no_argument, NULL, {"window-borderless", no_argument, NULL,
OPT_WINDOW_BORDERLESS}, OPT_WINDOW_BORDERLESS},
{"power-off-on-close", no_argument, NULL,
OPT_POWER_OFF_ON_CLOSE},
{NULL, 0, NULL, 0 }, {NULL, 0, NULL, 0 },
}; };
@@ -810,8 +815,7 @@ scrcpy_parse_args(struct scrcpy_cli_args *args, int argc, char *argv[]) {
opts->stay_awake = true; opts->stay_awake = true;
break; break;
case OPT_RENDER_EXPIRED_FRAMES: case OPT_RENDER_EXPIRED_FRAMES:
LOGW("Option --render-expired-frames has been removed. This " opts->render_expired_frames = true;
"flag has been ignored.");
break; break;
case OPT_WINDOW_TITLE: case OPT_WINDOW_TITLE:
opts->window_title = optarg; opts->window_title = optarg;
@@ -882,9 +886,6 @@ scrcpy_parse_args(struct scrcpy_cli_args *args, int argc, char *argv[]) {
case OPT_LEGACY_PASTE: case OPT_LEGACY_PASTE:
opts->legacy_paste = true; opts->legacy_paste = true;
break; break;
case OPT_POWER_OFF_ON_CLOSE:
opts->power_off_on_close = true;
break;
default: default:
// getopt prints the error message on stderr // getopt prints the error message on stderr
return false; return false;

View File

@@ -1,10 +1,9 @@
#ifndef SCRCPY_CLI_H #ifndef SCRCPY_CLI_H
#define SCRCPY_CLI_H #define SCRCPY_CLI_H
#include "common.h"
#include <stdbool.h> #include <stdbool.h>
#include "config.h"
#include "scrcpy.h" #include "scrcpy.h"
struct scrcpy_cli_args { struct scrcpy_cli_args {

View File

@@ -1,10 +1,12 @@
#include "adb.h" #include "command.h"
#include <assert.h> #include <assert.h>
#include <stdio.h> #include <stdio.h>
#include <stdlib.h> #include <stdlib.h>
#include <string.h> #include <string.h>
#include "config.h"
#include "common.h"
#include "util/log.h" #include "util/log.h"
#include "util/str_util.h" #include "util/str_util.h"
@@ -68,7 +70,7 @@ show_adb_installation_msg() {
{"pacman", "pacman -S android-tools"}, {"pacman", "pacman -S android-tools"},
}; };
for (size_t i = 0; i < ARRAY_LEN(pkg_managers); ++i) { for (size_t i = 0; i < ARRAY_LEN(pkg_managers); ++i) {
if (search_executable(pkg_managers[i].binary)) { if (cmd_search(pkg_managers[i].binary)) {
LOGI("You may install 'adb' by \"%s\"", pkg_managers[i].command); LOGI("You may install 'adb' by \"%s\"", pkg_managers[i].command);
return; return;
} }
@@ -116,7 +118,7 @@ adb_execute(const char *serial, const char *const adb_cmd[], size_t len) {
memcpy(&cmd[i], adb_cmd, len * sizeof(const char *)); memcpy(&cmd[i], adb_cmd, len * sizeof(const char *));
cmd[len + i] = NULL; cmd[len + i] = NULL;
enum process_result r = process_execute(cmd, &process); enum process_result r = cmd_execute(cmd, &process);
if (r != PROCESS_SUCCESS) { if (r != PROCESS_SUCCESS) {
show_adb_err_msg(r, cmd); show_adb_err_msg(r, cmd);
return PROCESS_NONE; return PROCESS_NONE;
@@ -173,7 +175,7 @@ adb_push(const char *serial, const char *local, const char *remote) {
} }
remote = strquote(remote); remote = strquote(remote);
if (!remote) { if (!remote) {
free((void *) local); SDL_free((void *) local);
return PROCESS_NONE; return PROCESS_NONE;
} }
#endif #endif
@@ -182,8 +184,8 @@ adb_push(const char *serial, const char *local, const char *remote) {
process_t proc = adb_execute(serial, adb_cmd, ARRAY_LEN(adb_cmd)); process_t proc = adb_execute(serial, adb_cmd, ARRAY_LEN(adb_cmd));
#ifdef __WINDOWS__ #ifdef __WINDOWS__
free((void *) remote); SDL_free((void *) remote);
free((void *) local); SDL_free((void *) local);
#endif #endif
return proc; return proc;
@@ -204,8 +206,26 @@ adb_install(const char *serial, const char *local) {
process_t proc = adb_execute(serial, adb_cmd, ARRAY_LEN(adb_cmd)); process_t proc = adb_execute(serial, adb_cmd, ARRAY_LEN(adb_cmd));
#ifdef __WINDOWS__ #ifdef __WINDOWS__
free((void *) local); SDL_free((void *) local);
#endif #endif
return proc; return proc;
} }
bool
process_check_success(process_t proc, const char *name) {
if (proc == PROCESS_NONE) {
LOGE("Could not execute \"%s\"", name);
return false;
}
exit_code_t exit_code;
if (!cmd_simple_wait(proc, &exit_code)) {
if (exit_code != NO_EXIT_CODE) {
LOGE("\"%s\" returned with value %" PRIexitcode, name, exit_code);
} else {
LOGE("\"%s\" exited unexpectedly", name);
}
return false;
}
return true;
}

View File

@@ -1,9 +1,8 @@
#ifndef SC_PROCESS_H #ifndef COMMAND_H
#define SC_PROCESS_H #define COMMAND_H
#include "common.h"
#include <stdbool.h> #include <stdbool.h>
#include <inttypes.h>
#ifdef _WIN32 #ifdef _WIN32
@@ -32,45 +31,58 @@
#endif #endif
#include "config.h"
enum process_result { enum process_result {
PROCESS_SUCCESS, PROCESS_SUCCESS,
PROCESS_ERROR_GENERIC, PROCESS_ERROR_GENERIC,
PROCESS_ERROR_MISSING_BINARY, PROCESS_ERROR_MISSING_BINARY,
}; };
// execute the command and write the result to the output parameter "process" #ifndef __WINDOWS__
enum process_result
process_execute(const char *const argv[], process_t *process);
// kill the process
bool bool
process_terminate(process_t pid); cmd_search(const char *file);
#endif
// wait and close the process (like waitpid()) enum process_result
// the "close" flag indicates if the process must be "closed" (reaped) cmd_execute(const char *const argv[], process_t *process);
// (passing false is equivalent to enable WNOWAIT in waitid())
exit_code_t
process_wait(process_t pid, bool close);
// close the process bool
// cmd_terminate(process_t pid);
// Semantically, process_wait(close) = process_wait(noclose) + process_close
void bool
process_close(process_t pid); cmd_simple_wait(process_t pid, exit_code_t *exit_code);
process_t
adb_execute(const char *serial, const char *const adb_cmd[], size_t len);
process_t
adb_forward(const char *serial, uint16_t local_port,
const char *device_socket_name);
process_t
adb_forward_remove(const char *serial, uint16_t local_port);
process_t
adb_reverse(const char *serial, const char *device_socket_name,
uint16_t local_port);
process_t
adb_reverse_remove(const char *serial, const char *device_socket_name);
process_t
adb_push(const char *serial, const char *local, const char *remote);
process_t
adb_install(const char *serial, const char *local);
// convenience function to wait for a successful process execution // convenience function to wait for a successful process execution
// automatically log process errors with the provided process name // automatically log process errors with the provided process name
bool bool
process_check_success(process_t proc, const char *name, bool close); process_check_success(process_t proc, const char *name);
#ifndef _WIN32
// only used to find package manager, not implemented for Windows
bool
search_executable(const char *file);
#endif
// return the absolute path of the executable (the scrcpy binary) // return the absolute path of the executable (the scrcpy binary)
// may be NULL on error; to be freed by free() // may be NULL on error; to be freed by SDL_free
char * char *
get_executable_path(void); get_executable_path(void);

View File

@@ -1,14 +1,35 @@
#ifndef COMMON_H #ifndef COMMON_H
#define COMMON_H #define COMMON_H
#include <stdint.h>
#include "config.h" #include "config.h"
#include "compat.h"
#define ARRAY_LEN(a) (sizeof(a) / sizeof(a[0])) #define ARRAY_LEN(a) (sizeof(a) / sizeof(a[0]))
#define MIN(X,Y) (X) < (Y) ? (X) : (Y) #define MIN(X,Y) (X) < (Y) ? (X) : (Y)
#define MAX(X,Y) (X) > (Y) ? (X) : (Y) #define MAX(X,Y) (X) > (Y) ? (X) : (Y)
#define container_of(ptr, type, member) \ struct size {
((type *) (((char *) (ptr)) - offsetof(type, member))) uint16_t width;
uint16_t height;
};
struct point {
int32_t x;
int32_t y;
};
struct position {
// The video screen size may be different from the real device screen size,
// so store to which size the absolute position apply, to scale it
// accordingly.
struct size screen_size;
struct point point;
};
struct port_range {
uint16_t first;
uint16_t last;
};
#endif #endif

View File

@@ -1,14 +0,0 @@
#include "compat.h"
#include "config.h"
#ifndef HAVE_STRDUP
char *strdup(const char *s) {
size_t size = strlen(s) + 1;
char *dup = malloc(size);
if (dup) {
memcpy(dup, s, size);
}
return dup;
}
#endif

View File

@@ -1,16 +1,19 @@
#ifndef COMPAT_H #ifndef COMPAT_H
#define COMPAT_H #define COMPAT_H
#define _POSIX_C_SOURCE 200809L
#define _XOPEN_SOURCE 700
#define _GNU_SOURCE
#ifdef __APPLE__
# define _DARWIN_C_SOURCE
#endif
#include <libavformat/version.h> #include <libavformat/version.h>
#include <SDL2/SDL_version.h> #include <SDL2/SDL_version.h>
// In ffmpeg/doc/APIchanges:
// 2016-04-11 - 6f69f7a / 9200514 - lavf 57.33.100 / 57.5.0 - avformat.h
// Add AVStream.codecpar, deprecate AVStream.codec.
#if (LIBAVFORMAT_VERSION_MICRO >= 100 /* FFmpeg */ && \
LIBAVFORMAT_VERSION_INT >= AV_VERSION_INT(57, 33, 100)) \
|| (LIBAVFORMAT_VERSION_MICRO < 100 && /* Libav */ \
LIBAVFORMAT_VERSION_INT >= AV_VERSION_INT(57, 5, 0))
# define SCRCPY_LAVF_HAS_NEW_CODEC_PARAMS_API
#endif
// In ffmpeg/doc/APIchanges: // In ffmpeg/doc/APIchanges:
// 2018-02-06 - 0694d87024 - lavf 58.9.100 - avformat.h // 2018-02-06 - 0694d87024 - lavf 58.9.100 - avformat.h
// Deprecate use of av_register_input_format(), av_register_output_format(), // Deprecate use of av_register_input_format(), av_register_output_format(),
@@ -22,6 +25,15 @@
# define SCRCPY_LAVF_REQUIRES_REGISTER_ALL # define SCRCPY_LAVF_REQUIRES_REGISTER_ALL
#endif #endif
// In ffmpeg/doc/APIchanges:
// 2016-04-21 - 7fc329e - lavc 57.37.100 - avcodec.h
// Add a new audio/video encoding and decoding API with decoupled input
// and output -- avcodec_send_packet(), avcodec_receive_frame(),
// avcodec_send_frame() and avcodec_receive_packet().
#if LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(57, 37, 100)
# define SCRCPY_LAVF_HAS_NEW_ENCODING_DECODING_API
#endif
#if SDL_VERSION_ATLEAST(2, 0, 5) #if SDL_VERSION_ATLEAST(2, 0, 5)
// <https://wiki.libsdl.org/SDL_HINT_MOUSE_FOCUS_CLICKTHROUGH> // <https://wiki.libsdl.org/SDL_HINT_MOUSE_FOCUS_CLICKTHROUGH>
# define SCRCPY_SDL_HAS_HINT_MOUSE_FOCUS_CLICKTHROUGH # define SCRCPY_SDL_HAS_HINT_MOUSE_FOCUS_CLICKTHROUGH
@@ -36,8 +48,4 @@
# define SCRCPY_SDL_HAS_HINT_VIDEO_X11_NET_WM_BYPASS_COMPOSITOR # define SCRCPY_SDL_HAS_HINT_VIDEO_X11_NET_WM_BYPASS_COMPOSITOR
#endif #endif
#ifndef HAVE_STRDUP
char *strdup(const char *s);
#endif
#endif #endif

View File

@@ -1,9 +1,9 @@
#include "control_msg.h" #include "control_msg.h"
#include <assert.h> #include <assert.h>
#include <stdlib.h>
#include <string.h> #include <string.h>
#include "config.h"
#include "util/buffer_util.h" #include "util/buffer_util.h"
#include "util/log.h" #include "util/log.h"
#include "util/str_util.h" #include "util/str_util.h"
@@ -94,10 +94,10 @@ void
control_msg_destroy(struct control_msg *msg) { control_msg_destroy(struct control_msg *msg) {
switch (msg->type) { switch (msg->type) {
case CONTROL_MSG_TYPE_INJECT_TEXT: case CONTROL_MSG_TYPE_INJECT_TEXT:
free(msg->inject_text.text); SDL_free(msg->inject_text.text);
break; break;
case CONTROL_MSG_TYPE_SET_CLIPBOARD: case CONTROL_MSG_TYPE_SET_CLIPBOARD:
free(msg->set_clipboard.text); SDL_free(msg->set_clipboard.text);
break; break;
default: default:
// do nothing // do nothing

View File

@@ -1,15 +1,14 @@
#ifndef CONTROLMSG_H #ifndef CONTROLMSG_H
#define CONTROLMSG_H #define CONTROLMSG_H
#include "common.h"
#include <stdbool.h> #include <stdbool.h>
#include <stddef.h> #include <stddef.h>
#include <stdint.h> #include <stdint.h>
#include "config.h"
#include "android/input.h" #include "android/input.h"
#include "android/keycodes.h" #include "android/keycodes.h"
#include "coords.h" #include "common.h"
#define CONTROL_MSG_MAX_SIZE (1 << 18) // 256k #define CONTROL_MSG_MAX_SIZE (1 << 18) // 256k
@@ -50,7 +49,7 @@ struct control_msg {
enum android_metastate metastate; enum android_metastate metastate;
} inject_keycode; } inject_keycode;
struct { struct {
char *text; // owned, to be freed by free() char *text; // owned, to be freed by SDL_free()
} inject_text; } inject_text;
struct { struct {
enum android_motionevent_action action; enum android_motionevent_action action;
@@ -65,7 +64,7 @@ struct control_msg {
int32_t vscroll; int32_t vscroll;
} inject_scroll_event; } inject_scroll_event;
struct { struct {
char *text; // owned, to be freed by free() char *text; // owned, to be freed by SDL_free()
bool paste; bool paste;
} set_clipboard; } set_clipboard;
struct { struct {

View File

@@ -2,27 +2,26 @@
#include <assert.h> #include <assert.h>
#include "config.h"
#include "util/lock.h"
#include "util/log.h" #include "util/log.h"
bool bool
controller_init(struct controller *controller, socket_t control_socket) { controller_init(struct controller *controller, socket_t control_socket) {
cbuf_init(&controller->queue); cbuf_init(&controller->queue);
bool ok = receiver_init(&controller->receiver, control_socket); if (!receiver_init(&controller->receiver, control_socket)) {
if (!ok) {
return false; return false;
} }
ok = sc_mutex_init(&controller->mutex); if (!(controller->mutex = SDL_CreateMutex())) {
if (!ok) {
receiver_destroy(&controller->receiver); receiver_destroy(&controller->receiver);
return false; return false;
} }
ok = sc_cond_init(&controller->msg_cond); if (!(controller->msg_cond = SDL_CreateCond())) {
if (!ok) {
receiver_destroy(&controller->receiver); receiver_destroy(&controller->receiver);
sc_mutex_destroy(&controller->mutex); SDL_DestroyMutex(controller->mutex);
return false; return false;
} }
@@ -34,8 +33,8 @@ controller_init(struct controller *controller, socket_t control_socket) {
void void
controller_destroy(struct controller *controller) { controller_destroy(struct controller *controller) {
sc_cond_destroy(&controller->msg_cond); SDL_DestroyCond(controller->msg_cond);
sc_mutex_destroy(&controller->mutex); SDL_DestroyMutex(controller->mutex);
struct control_msg msg; struct control_msg msg;
while (cbuf_take(&controller->queue, &msg)) { while (cbuf_take(&controller->queue, &msg)) {
@@ -48,13 +47,13 @@ controller_destroy(struct controller *controller) {
bool bool
controller_push_msg(struct controller *controller, controller_push_msg(struct controller *controller,
const struct control_msg *msg) { const struct control_msg *msg) {
sc_mutex_lock(&controller->mutex); mutex_lock(controller->mutex);
bool was_empty = cbuf_is_empty(&controller->queue); bool was_empty = cbuf_is_empty(&controller->queue);
bool res = cbuf_push(&controller->queue, *msg); bool res = cbuf_push(&controller->queue, *msg);
if (was_empty) { if (was_empty) {
sc_cond_signal(&controller->msg_cond); cond_signal(controller->msg_cond);
} }
sc_mutex_unlock(&controller->mutex); mutex_unlock(controller->mutex);
return res; return res;
} }
@@ -62,12 +61,12 @@ static bool
process_msg(struct controller *controller, process_msg(struct controller *controller,
const struct control_msg *msg) { const struct control_msg *msg) {
static unsigned char serialized_msg[CONTROL_MSG_MAX_SIZE]; static unsigned char serialized_msg[CONTROL_MSG_MAX_SIZE];
size_t length = control_msg_serialize(msg, serialized_msg); int length = control_msg_serialize(msg, serialized_msg);
if (!length) { if (!length) {
return false; return false;
} }
int w = net_send_all(controller->control_socket, serialized_msg, length); int w = net_send_all(controller->control_socket, serialized_msg, length);
return (size_t) w == length; return w == length;
} }
static int static int
@@ -75,20 +74,20 @@ run_controller(void *data) {
struct controller *controller = data; struct controller *controller = data;
for (;;) { for (;;) {
sc_mutex_lock(&controller->mutex); mutex_lock(controller->mutex);
while (!controller->stopped && cbuf_is_empty(&controller->queue)) { while (!controller->stopped && cbuf_is_empty(&controller->queue)) {
sc_cond_wait(&controller->msg_cond, &controller->mutex); cond_wait(controller->msg_cond, controller->mutex);
} }
if (controller->stopped) { if (controller->stopped) {
// stop immediately, do not process further msgs // stop immediately, do not process further msgs
sc_mutex_unlock(&controller->mutex); mutex_unlock(controller->mutex);
break; break;
} }
struct control_msg msg; struct control_msg msg;
bool non_empty = cbuf_take(&controller->queue, &msg); bool non_empty = cbuf_take(&controller->queue, &msg);
assert(non_empty); assert(non_empty);
(void) non_empty; (void) non_empty;
sc_mutex_unlock(&controller->mutex); mutex_unlock(controller->mutex);
bool ok = process_msg(controller, &msg); bool ok = process_msg(controller, &msg);
control_msg_destroy(&msg); control_msg_destroy(&msg);
@@ -104,16 +103,16 @@ bool
controller_start(struct controller *controller) { controller_start(struct controller *controller) {
LOGD("Starting controller thread"); LOGD("Starting controller thread");
bool ok = sc_thread_create(&controller->thread, run_controller, controller->thread = SDL_CreateThread(run_controller, "controller",
"controller", controller); controller);
if (!ok) { if (!controller->thread) {
LOGC("Could not start controller thread"); LOGC("Could not start controller thread");
return false; return false;
} }
if (!receiver_start(&controller->receiver)) { if (!receiver_start(&controller->receiver)) {
controller_stop(controller); controller_stop(controller);
sc_thread_join(&controller->thread, NULL); SDL_WaitThread(controller->thread, NULL);
return false; return false;
} }
@@ -122,14 +121,14 @@ controller_start(struct controller *controller) {
void void
controller_stop(struct controller *controller) { controller_stop(struct controller *controller) {
sc_mutex_lock(&controller->mutex); mutex_lock(controller->mutex);
controller->stopped = true; controller->stopped = true;
sc_cond_signal(&controller->msg_cond); cond_signal(controller->msg_cond);
sc_mutex_unlock(&controller->mutex); mutex_unlock(controller->mutex);
} }
void void
controller_join(struct controller *controller) { controller_join(struct controller *controller) {
sc_thread_join(&controller->thread, NULL); SDL_WaitThread(controller->thread, NULL);
receiver_join(&controller->receiver); receiver_join(&controller->receiver);
} }

View File

@@ -1,23 +1,23 @@
#ifndef CONTROLLER_H #ifndef CONTROLLER_H
#define CONTROLLER_H #define CONTROLLER_H
#include "common.h"
#include <stdbool.h> #include <stdbool.h>
#include <SDL2/SDL_mutex.h>
#include <SDL2/SDL_thread.h>
#include "config.h"
#include "control_msg.h" #include "control_msg.h"
#include "receiver.h" #include "receiver.h"
#include "util/cbuf.h" #include "util/cbuf.h"
#include "util/net.h" #include "util/net.h"
#include "util/thread.h"
struct control_msg_queue CBUF(struct control_msg, 64); struct control_msg_queue CBUF(struct control_msg, 64);
struct controller { struct controller {
socket_t control_socket; socket_t control_socket;
sc_thread thread; SDL_Thread *thread;
sc_mutex mutex; SDL_mutex *mutex;
sc_cond msg_cond; SDL_cond *msg_cond;
bool stopped; bool stopped;
struct control_msg_queue queue; struct control_msg_queue queue;
struct receiver receiver; struct receiver receiver;

View File

@@ -1,24 +0,0 @@
#ifndef SC_COORDS
#define SC_COORDS
#include <stdint.h>
struct size {
uint16_t width;
uint16_t height;
};
struct point {
int32_t x;
int32_t y;
};
struct position {
// The video screen size may be different from the real device screen size,
// so store to which size the absolute position apply, to scale it
// accordingly.
struct size screen_size;
struct point point;
};
#endif

View File

@@ -1,43 +1,42 @@
#include "decoder.h" #include "decoder.h"
#include <libavformat/avformat.h> #include <libavformat/avformat.h>
#include <libavutil/time.h>
#include <SDL2/SDL_events.h>
#include <SDL2/SDL_mutex.h>
#include <SDL2/SDL_thread.h>
#include <unistd.h>
#include "config.h"
#include "compat.h"
#include "events.h" #include "events.h"
#include "recorder.h"
#include "video_buffer.h" #include "video_buffer.h"
#include "trait/frame_sink.h" #include "util/buffer_util.h"
#include "util/log.h" #include "util/log.h"
/** Downcast packet_sink to decoder */ // set the decoded frame as ready for rendering, and notify
#define DOWNCAST(SINK) container_of(SINK, struct decoder, packet_sink)
static void static void
decoder_close_first_sinks(struct decoder *decoder, unsigned count) { push_frame(struct decoder *decoder) {
while (count) { bool previous_frame_skipped;
struct sc_frame_sink *sink = decoder->sinks[--count]; video_buffer_offer_decoded_frame(decoder->video_buffer,
sink->ops->close(sink); &previous_frame_skipped);
if (previous_frame_skipped) {
// the previous EVENT_NEW_FRAME will consume this frame
return;
} }
static SDL_Event new_frame_event = {
.type = EVENT_NEW_FRAME,
};
SDL_PushEvent(&new_frame_event);
} }
static inline void void
decoder_close_sinks(struct decoder *decoder) { decoder_init(struct decoder *decoder, struct video_buffer *vb) {
decoder_close_first_sinks(decoder, decoder->sink_count); decoder->video_buffer = vb;
} }
static bool bool
decoder_open_sinks(struct decoder *decoder) {
for (unsigned i = 0; i < decoder->sink_count; ++i) {
struct sc_frame_sink *sink = decoder->sinks[i];
if (!sink->ops->open(sink)) {
LOGE("Could not open frame sink %d", i);
decoder_close_first_sinks(decoder, i);
return false;
}
}
return true;
}
static bool
decoder_open(struct decoder *decoder, const AVCodec *codec) { decoder_open(struct decoder *decoder, const AVCodec *codec) {
decoder->codec_ctx = avcodec_alloc_context3(codec); decoder->codec_ctx = avcodec_alloc_context3(codec);
if (!decoder->codec_ctx) { if (!decoder->codec_ctx) {
@@ -51,105 +50,52 @@ decoder_open(struct decoder *decoder, const AVCodec *codec) {
return false; return false;
} }
decoder->frame = av_frame_alloc();
if (!decoder->frame) {
LOGE("Could not create decoder frame");
avcodec_close(decoder->codec_ctx);
avcodec_free_context(&decoder->codec_ctx);
return false;
}
if (!decoder_open_sinks(decoder)) {
LOGE("Could not open decoder sinks");
av_frame_free(&decoder->frame);
avcodec_close(decoder->codec_ctx);
avcodec_free_context(&decoder->codec_ctx);
}
return true; return true;
} }
static void void
decoder_close(struct decoder *decoder) { decoder_close(struct decoder *decoder) {
decoder_close_sinks(decoder);
av_frame_free(&decoder->frame);
avcodec_close(decoder->codec_ctx); avcodec_close(decoder->codec_ctx);
avcodec_free_context(&decoder->codec_ctx); avcodec_free_context(&decoder->codec_ctx);
} }
static bool bool
push_frame_to_sinks(struct decoder *decoder, const AVFrame *frame) {
for (unsigned i = 0; i < decoder->sink_count; ++i) {
struct sc_frame_sink *sink = decoder->sinks[i];
if (!sink->ops->push(sink, frame)) {
LOGE("Could not send frame to sink %d", i);
return false;
}
}
return true;
}
static bool
decoder_push(struct decoder *decoder, const AVPacket *packet) { decoder_push(struct decoder *decoder, const AVPacket *packet) {
bool is_config = packet->pts == AV_NOPTS_VALUE; // the new decoding/encoding API has been introduced by:
if (is_config) { // <http://git.videolan.org/?p=ffmpeg.git;a=commitdiff;h=7fc329e2dd6226dfecaa4a1d7adf353bf2773726>
// nothing to do #ifdef SCRCPY_LAVF_HAS_NEW_ENCODING_DECODING_API
return true;
}
int ret; int ret;
if ((ret = avcodec_send_packet(decoder->codec_ctx, packet)) < 0) { if ((ret = avcodec_send_packet(decoder->codec_ctx, packet)) < 0) {
LOGE("Could not send video packet: %d", ret); LOGE("Could not send video packet: %d", ret);
return false; return false;
} }
ret = avcodec_receive_frame(decoder->codec_ctx, decoder->frame); ret = avcodec_receive_frame(decoder->codec_ctx,
decoder->video_buffer->decoding_frame);
if (!ret) { if (!ret) {
// a frame was received // a frame was received
bool ok = push_frame_to_sinks(decoder, decoder->frame); push_frame(decoder);
// A frame lost should not make the whole pipeline fail. The error, if
// any, is already logged.
(void) ok;
} else if (ret != AVERROR(EAGAIN)) { } else if (ret != AVERROR(EAGAIN)) {
LOGE("Could not receive video frame: %d", ret); LOGE("Could not receive video frame: %d", ret);
return false; return false;
} }
#else
int got_picture;
int len = avcodec_decode_video2(decoder->codec_ctx,
decoder->video_buffer->decoding_frame,
&got_picture,
packet);
if (len < 0) {
LOGE("Could not decode video packet: %d", len);
return false;
}
if (got_picture) {
push_frame(decoder);
}
#endif
return true; return true;
} }
static bool
decoder_packet_sink_open(struct sc_packet_sink *sink, const AVCodec *codec) {
struct decoder *decoder = DOWNCAST(sink);
return decoder_open(decoder, codec);
}
static void
decoder_packet_sink_close(struct sc_packet_sink *sink) {
struct decoder *decoder = DOWNCAST(sink);
decoder_close(decoder);
}
static bool
decoder_packet_sink_push(struct sc_packet_sink *sink, const AVPacket *packet) {
struct decoder *decoder = DOWNCAST(sink);
return decoder_push(decoder, packet);
}
void void
decoder_init(struct decoder *decoder) { decoder_interrupt(struct decoder *decoder) {
static const struct sc_packet_sink_ops ops = { video_buffer_interrupt(decoder->video_buffer);
.open = decoder_packet_sink_open,
.close = decoder_packet_sink_close,
.push = decoder_packet_sink_push,
};
decoder->packet_sink.ops = &ops;
}
void
decoder_add_sink(struct decoder *decoder, struct sc_frame_sink *sink) {
assert(decoder->sink_count < DECODER_MAX_SINKS);
assert(sink);
assert(sink->ops);
decoder->sinks[decoder->sink_count++] = sink;
} }

View File

@@ -1,29 +1,31 @@
#ifndef DECODER_H #ifndef DECODER_H
#define DECODER_H #define DECODER_H
#include "common.h"
#include "trait/packet_sink.h"
#include <stdbool.h> #include <stdbool.h>
#include <libavformat/avformat.h> #include <libavformat/avformat.h>
#define DECODER_MAX_SINKS 1 #include "config.h"
struct video_buffer;
struct decoder { struct decoder {
struct sc_packet_sink packet_sink; // packet sink trait struct video_buffer *video_buffer;
struct sc_frame_sink *sinks[DECODER_MAX_SINKS];
unsigned sink_count;
AVCodecContext *codec_ctx; AVCodecContext *codec_ctx;
AVFrame *frame;
}; };
void void
decoder_init(struct decoder *decoder); decoder_init(struct decoder *decoder, struct video_buffer *vb);
bool
decoder_open(struct decoder *decoder, const AVCodec *codec);
void void
decoder_add_sink(struct decoder *decoder, struct sc_frame_sink *sink); decoder_close(struct decoder *decoder);
bool
decoder_push(struct decoder *decoder, const AVPacket *packet);
void
decoder_interrupt(struct decoder *decoder);
#endif #endif

View File

@@ -1,5 +1,6 @@
#include "device.h" #include "device.h"
#include "config.h"
#include "util/log.h" #include "util/log.h"
bool bool

View File

@@ -1,11 +1,10 @@
#ifndef DEVICE_H #ifndef DEVICE_H
#define DEVICE_H #define DEVICE_H
#include "common.h"
#include <stdbool.h> #include <stdbool.h>
#include "coords.h" #include "config.h"
#include "common.h"
#include "util/net.h" #include "util/net.h"
#define DEVICE_NAME_FIELD_LENGTH 64 #define DEVICE_NAME_FIELD_LENGTH 64

View File

@@ -1,8 +1,8 @@
#include "device_msg.h" #include "device_msg.h"
#include <stdlib.h>
#include <string.h> #include <string.h>
#include "config.h"
#include "util/buffer_util.h" #include "util/buffer_util.h"
#include "util/log.h" #include "util/log.h"
@@ -21,7 +21,7 @@ device_msg_deserialize(const unsigned char *buf, size_t len,
if (clipboard_len > len - 5) { if (clipboard_len > len - 5) {
return 0; // not available return 0; // not available
} }
char *text = malloc(clipboard_len + 1); char *text = SDL_malloc(clipboard_len + 1);
if (!text) { if (!text) {
LOGW("Could not allocate text for clipboard"); LOGW("Could not allocate text for clipboard");
return -1; return -1;
@@ -43,6 +43,6 @@ device_msg_deserialize(const unsigned char *buf, size_t len,
void void
device_msg_destroy(struct device_msg *msg) { device_msg_destroy(struct device_msg *msg) {
if (msg->type == DEVICE_MSG_TYPE_CLIPBOARD) { if (msg->type == DEVICE_MSG_TYPE_CLIPBOARD) {
free(msg->clipboard.text); SDL_free(msg->clipboard.text);
} }
} }

View File

@@ -1,12 +1,12 @@
#ifndef DEVICEMSG_H #ifndef DEVICEMSG_H
#define DEVICEMSG_H #define DEVICEMSG_H
#include "common.h"
#include <stdbool.h> #include <stdbool.h>
#include <stdint.h> #include <stdint.h>
#include <unistd.h> #include <unistd.h>
#include "config.h"
#define DEVICE_MSG_MAX_SIZE (1 << 18) // 256k #define DEVICE_MSG_MAX_SIZE (1 << 18) // 256k
// type: 1 byte; length: 4 bytes // type: 1 byte; length: 4 bytes
#define DEVICE_MSG_TEXT_MAX_LENGTH (DEVICE_MSG_MAX_SIZE - 5) #define DEVICE_MSG_TEXT_MAX_LENGTH (DEVICE_MSG_MAX_SIZE - 5)
@@ -19,7 +19,7 @@ struct device_msg {
enum device_msg_type type; enum device_msg_type type;
union { union {
struct { struct {
char *text; // owned, to be freed by free() char *text; // owned, to be freed by SDL_free()
} clipboard; } clipboard;
}; };
}; };

View File

@@ -1,5 +1,7 @@
#include "event_converter.h" #include "event_converter.h"
#include "config.h"
#define MAP(FROM, TO) case FROM: *to = TO; return true #define MAP(FROM, TO) case FROM: *to = TO; return true
#define FAIL default: return false #define FAIL default: return false

View File

@@ -1,11 +1,10 @@
#ifndef CONVERT_H #ifndef CONVERT_H
#define CONVERT_H #define CONVERT_H
#include "common.h"
#include <stdbool.h> #include <stdbool.h>
#include <SDL2/SDL_events.h> #include <SDL2/SDL_events.h>
#include "config.h"
#include "control_msg.h" #include "control_msg.h"
bool bool

View File

@@ -1,2 +1,3 @@
#define EVENT_NEW_FRAME SDL_USEREVENT #define EVENT_NEW_SESSION SDL_USEREVENT
#define EVENT_STREAM_STOPPED (SDL_USEREVENT + 1) #define EVENT_NEW_FRAME (SDL_USEREVENT + 1)
#define EVENT_STREAM_STOPPED (SDL_USEREVENT + 2)

View File

@@ -3,14 +3,16 @@
#include <assert.h> #include <assert.h>
#include <string.h> #include <string.h>
#include "adb.h" #include "config.h"
#include "command.h"
#include "util/lock.h"
#include "util/log.h" #include "util/log.h"
#define DEFAULT_PUSH_TARGET "/sdcard/" #define DEFAULT_PUSH_TARGET "/sdcard/"
static void static void
file_handler_request_destroy(struct file_handler_request *req) { file_handler_request_destroy(struct file_handler_request *req) {
free(req->file); SDL_free(req->file);
} }
bool bool
@@ -19,23 +21,21 @@ file_handler_init(struct file_handler *file_handler, const char *serial,
cbuf_init(&file_handler->queue); cbuf_init(&file_handler->queue);
bool ok = sc_mutex_init(&file_handler->mutex); if (!(file_handler->mutex = SDL_CreateMutex())) {
if (!ok) {
return false; return false;
} }
ok = sc_cond_init(&file_handler->event_cond); if (!(file_handler->event_cond = SDL_CreateCond())) {
if (!ok) { SDL_DestroyMutex(file_handler->mutex);
sc_mutex_destroy(&file_handler->mutex);
return false; return false;
} }
if (serial) { if (serial) {
file_handler->serial = strdup(serial); file_handler->serial = SDL_strdup(serial);
if (!file_handler->serial) { if (!file_handler->serial) {
LOGW("Could not strdup serial"); LOGW("Could not strdup serial");
sc_cond_destroy(&file_handler->event_cond); SDL_DestroyCond(file_handler->event_cond);
sc_mutex_destroy(&file_handler->mutex); SDL_DestroyMutex(file_handler->mutex);
return false; return false;
} }
} else { } else {
@@ -55,9 +55,9 @@ file_handler_init(struct file_handler *file_handler, const char *serial,
void void
file_handler_destroy(struct file_handler *file_handler) { file_handler_destroy(struct file_handler *file_handler) {
sc_cond_destroy(&file_handler->event_cond); SDL_DestroyCond(file_handler->event_cond);
sc_mutex_destroy(&file_handler->mutex); SDL_DestroyMutex(file_handler->mutex);
free(file_handler->serial); SDL_free(file_handler->serial);
struct file_handler_request req; struct file_handler_request req;
while (cbuf_take(&file_handler->queue, &req)) { while (cbuf_take(&file_handler->queue, &req)) {
@@ -93,13 +93,13 @@ file_handler_request(struct file_handler *file_handler,
.file = file, .file = file,
}; };
sc_mutex_lock(&file_handler->mutex); mutex_lock(file_handler->mutex);
bool was_empty = cbuf_is_empty(&file_handler->queue); bool was_empty = cbuf_is_empty(&file_handler->queue);
bool res = cbuf_push(&file_handler->queue, req); bool res = cbuf_push(&file_handler->queue, req);
if (was_empty) { if (was_empty) {
sc_cond_signal(&file_handler->event_cond); cond_signal(file_handler->event_cond);
} }
sc_mutex_unlock(&file_handler->mutex); mutex_unlock(file_handler->mutex);
return res; return res;
} }
@@ -108,14 +108,14 @@ run_file_handler(void *data) {
struct file_handler *file_handler = data; struct file_handler *file_handler = data;
for (;;) { for (;;) {
sc_mutex_lock(&file_handler->mutex); mutex_lock(file_handler->mutex);
file_handler->current_process = PROCESS_NONE; file_handler->current_process = PROCESS_NONE;
while (!file_handler->stopped && cbuf_is_empty(&file_handler->queue)) { while (!file_handler->stopped && cbuf_is_empty(&file_handler->queue)) {
sc_cond_wait(&file_handler->event_cond, &file_handler->mutex); cond_wait(file_handler->event_cond, file_handler->mutex);
} }
if (file_handler->stopped) { if (file_handler->stopped) {
// stop immediately, do not process further events // stop immediately, do not process further events
sc_mutex_unlock(&file_handler->mutex); mutex_unlock(file_handler->mutex);
break; break;
} }
struct file_handler_request req; struct file_handler_request req;
@@ -133,16 +133,16 @@ run_file_handler(void *data) {
file_handler->push_target); file_handler->push_target);
} }
file_handler->current_process = process; file_handler->current_process = process;
sc_mutex_unlock(&file_handler->mutex); mutex_unlock(file_handler->mutex);
if (req.action == ACTION_INSTALL_APK) { if (req.action == ACTION_INSTALL_APK) {
if (process_check_success(process, "adb install", false)) { if (process_check_success(process, "adb install")) {
LOGI("%s successfully installed", req.file); LOGI("%s successfully installed", req.file);
} else { } else {
LOGE("Failed to install %s", req.file); LOGE("Failed to install %s", req.file);
} }
} else { } else {
if (process_check_success(process, "adb push", false)) { if (process_check_success(process, "adb push")) {
LOGI("%s successfully pushed to %s", req.file, LOGI("%s successfully pushed to %s", req.file,
file_handler->push_target); file_handler->push_target);
} else { } else {
@@ -151,14 +151,6 @@ run_file_handler(void *data) {
} }
} }
sc_mutex_lock(&file_handler->mutex);
// Close the process (it is necessary already terminated)
// Execute this call with mutex locked to avoid race conditions with
// file_handler_stop()
process_close(file_handler->current_process);
file_handler->current_process = PROCESS_NONE;
sc_mutex_unlock(&file_handler->mutex);
file_handler_request_destroy(&req); file_handler_request_destroy(&req);
} }
return 0; return 0;
@@ -168,9 +160,9 @@ bool
file_handler_start(struct file_handler *file_handler) { file_handler_start(struct file_handler *file_handler) {
LOGD("Starting file_handler thread"); LOGD("Starting file_handler thread");
bool ok = sc_thread_create(&file_handler->thread, run_file_handler, file_handler->thread = SDL_CreateThread(run_file_handler, "file_handler",
"file_handler", file_handler); file_handler);
if (!ok) { if (!file_handler->thread) {
LOGC("Could not start file_handler thread"); LOGC("Could not start file_handler thread");
return false; return false;
} }
@@ -180,18 +172,20 @@ file_handler_start(struct file_handler *file_handler) {
void void
file_handler_stop(struct file_handler *file_handler) { file_handler_stop(struct file_handler *file_handler) {
sc_mutex_lock(&file_handler->mutex); mutex_lock(file_handler->mutex);
file_handler->stopped = true; file_handler->stopped = true;
sc_cond_signal(&file_handler->event_cond); cond_signal(file_handler->event_cond);
if (file_handler->current_process != PROCESS_NONE) { if (file_handler->current_process != PROCESS_NONE) {
if (!process_terminate(file_handler->current_process)) { if (!cmd_terminate(file_handler->current_process)) {
LOGW("Could not terminate push/install process"); LOGW("Could not terminate install process");
} }
cmd_simple_wait(file_handler->current_process, NULL);
file_handler->current_process = PROCESS_NONE;
} }
sc_mutex_unlock(&file_handler->mutex); mutex_unlock(file_handler->mutex);
} }
void void
file_handler_join(struct file_handler *file_handler) { file_handler_join(struct file_handler *file_handler) {
sc_thread_join(&file_handler->thread, NULL); SDL_WaitThread(file_handler->thread, NULL);
} }

View File

@@ -1,13 +1,13 @@
#ifndef FILE_HANDLER_H #ifndef FILE_HANDLER_H
#define FILE_HANDLER_H #define FILE_HANDLER_H
#include "common.h"
#include <stdbool.h> #include <stdbool.h>
#include <SDL2/SDL_mutex.h>
#include <SDL2/SDL_thread.h>
#include "adb.h" #include "config.h"
#include "command.h"
#include "util/cbuf.h" #include "util/cbuf.h"
#include "util/thread.h"
typedef enum { typedef enum {
ACTION_INSTALL_APK, ACTION_INSTALL_APK,
@@ -24,9 +24,9 @@ struct file_handler_request_queue CBUF(struct file_handler_request, 16);
struct file_handler { struct file_handler {
char *serial; char *serial;
const char *push_target; const char *push_target;
sc_thread thread; SDL_Thread *thread;
sc_mutex mutex; SDL_mutex *mutex;
sc_cond event_cond; SDL_cond *event_cond;
bool stopped; bool stopped;
bool initialized; bool initialized;
process_t current_process; process_t current_process;
@@ -49,7 +49,7 @@ file_handler_stop(struct file_handler *file_handler);
void void
file_handler_join(struct file_handler *file_handler); file_handler_join(struct file_handler *file_handler);
// take ownership of file, and will free() it // take ownership of file, and will SDL_free() it
bool bool
file_handler_request(struct file_handler *file_handler, file_handler_request(struct file_handler *file_handler,
file_handler_action_t action, file_handler_action_t action,

View File

@@ -3,24 +3,26 @@
#include <assert.h> #include <assert.h>
#include <SDL2/SDL_timer.h> #include <SDL2/SDL_timer.h>
#include "config.h"
#include "util/lock.h"
#include "util/log.h" #include "util/log.h"
#define FPS_COUNTER_INTERVAL_MS 1000 #define FPS_COUNTER_INTERVAL_MS 1000
bool bool
fps_counter_init(struct fps_counter *counter) { fps_counter_init(struct fps_counter *counter) {
bool ok = sc_mutex_init(&counter->mutex); counter->mutex = SDL_CreateMutex();
if (!ok) { if (!counter->mutex) {
return false; return false;
} }
ok = sc_cond_init(&counter->state_cond); counter->state_cond = SDL_CreateCond();
if (!ok) { if (!counter->state_cond) {
sc_mutex_destroy(&counter->mutex); SDL_DestroyMutex(counter->mutex);
return false; return false;
} }
counter->thread_started = false; counter->thread = NULL;
atomic_init(&counter->started, 0); atomic_init(&counter->started, 0);
// no need to initialize the other fields, they are unused until started // no need to initialize the other fields, they are unused until started
@@ -29,8 +31,8 @@ fps_counter_init(struct fps_counter *counter) {
void void
fps_counter_destroy(struct fps_counter *counter) { fps_counter_destroy(struct fps_counter *counter) {
sc_cond_destroy(&counter->state_cond); SDL_DestroyCond(counter->state_cond);
sc_mutex_destroy(&counter->mutex); SDL_DestroyMutex(counter->mutex);
} }
static inline bool static inline bool
@@ -76,10 +78,10 @@ static int
run_fps_counter(void *data) { run_fps_counter(void *data) {
struct fps_counter *counter = data; struct fps_counter *counter = data;
sc_mutex_lock(&counter->mutex); mutex_lock(counter->mutex);
while (!counter->interrupted) { while (!counter->interrupted) {
while (!counter->interrupted && !is_started(counter)) { while (!counter->interrupted && !is_started(counter)) {
sc_cond_wait(&counter->state_cond, &counter->mutex); cond_wait(counter->state_cond, counter->mutex);
} }
while (!counter->interrupted && is_started(counter)) { while (!counter->interrupted && is_started(counter)) {
uint32_t now = SDL_GetTicks(); uint32_t now = SDL_GetTicks();
@@ -89,35 +91,32 @@ run_fps_counter(void *data) {
uint32_t remaining = counter->next_timestamp - now; uint32_t remaining = counter->next_timestamp - now;
// ignore the reason (timeout or signaled), we just loop anyway // ignore the reason (timeout or signaled), we just loop anyway
sc_cond_timedwait(&counter->state_cond, &counter->mutex, remaining); cond_wait_timeout(counter->state_cond, counter->mutex, remaining);
} }
} }
sc_mutex_unlock(&counter->mutex); mutex_unlock(counter->mutex);
return 0; return 0;
} }
bool bool
fps_counter_start(struct fps_counter *counter) { fps_counter_start(struct fps_counter *counter) {
sc_mutex_lock(&counter->mutex); mutex_lock(counter->mutex);
counter->next_timestamp = SDL_GetTicks() + FPS_COUNTER_INTERVAL_MS; counter->next_timestamp = SDL_GetTicks() + FPS_COUNTER_INTERVAL_MS;
counter->nr_rendered = 0; counter->nr_rendered = 0;
counter->nr_skipped = 0; counter->nr_skipped = 0;
sc_mutex_unlock(&counter->mutex); mutex_unlock(counter->mutex);
set_started(counter, true); set_started(counter, true);
sc_cond_signal(&counter->state_cond); cond_signal(counter->state_cond);
// counter->thread_started and counter->thread are always accessed from the // counter->thread is always accessed from the same thread, no need to lock
// same thread, no need to lock if (!counter->thread) {
if (!counter->thread_started) { counter->thread =
bool ok = sc_thread_create(&counter->thread, run_fps_counter, SDL_CreateThread(run_fps_counter, "fps counter", counter);
"fps counter", counter); if (!counter->thread) {
if (!ok) {
LOGE("Could not start FPS counter thread"); LOGE("Could not start FPS counter thread");
return false; return false;
} }
counter->thread_started = true;
} }
return true; return true;
@@ -126,7 +125,7 @@ fps_counter_start(struct fps_counter *counter) {
void void
fps_counter_stop(struct fps_counter *counter) { fps_counter_stop(struct fps_counter *counter) {
set_started(counter, false); set_started(counter, false);
sc_cond_signal(&counter->state_cond); cond_signal(counter->state_cond);
} }
bool bool
@@ -136,21 +135,21 @@ fps_counter_is_started(struct fps_counter *counter) {
void void
fps_counter_interrupt(struct fps_counter *counter) { fps_counter_interrupt(struct fps_counter *counter) {
if (!counter->thread_started) { if (!counter->thread) {
return; return;
} }
sc_mutex_lock(&counter->mutex); mutex_lock(counter->mutex);
counter->interrupted = true; counter->interrupted = true;
sc_mutex_unlock(&counter->mutex); mutex_unlock(counter->mutex);
// wake up blocking wait // wake up blocking wait
sc_cond_signal(&counter->state_cond); cond_signal(counter->state_cond);
} }
void void
fps_counter_join(struct fps_counter *counter) { fps_counter_join(struct fps_counter *counter) {
if (counter->thread_started) { if (counter->thread) {
sc_thread_join(&counter->thread, NULL); SDL_WaitThread(counter->thread, NULL);
} }
} }
@@ -160,11 +159,11 @@ fps_counter_add_rendered_frame(struct fps_counter *counter) {
return; return;
} }
sc_mutex_lock(&counter->mutex); mutex_lock(counter->mutex);
uint32_t now = SDL_GetTicks(); uint32_t now = SDL_GetTicks();
check_interval_expired(counter, now); check_interval_expired(counter, now);
++counter->nr_rendered; ++counter->nr_rendered;
sc_mutex_unlock(&counter->mutex); mutex_unlock(counter->mutex);
} }
void void
@@ -173,9 +172,9 @@ fps_counter_add_skipped_frame(struct fps_counter *counter) {
return; return;
} }
sc_mutex_lock(&counter->mutex); mutex_lock(counter->mutex);
uint32_t now = SDL_GetTicks(); uint32_t now = SDL_GetTicks();
check_interval_expired(counter, now); check_interval_expired(counter, now);
++counter->nr_skipped; ++counter->nr_skipped;
sc_mutex_unlock(&counter->mutex); mutex_unlock(counter->mutex);
} }

View File

@@ -1,20 +1,18 @@
#ifndef FPSCOUNTER_H #ifndef FPSCOUNTER_H
#define FPSCOUNTER_H #define FPSCOUNTER_H
#include "common.h"
#include <stdatomic.h> #include <stdatomic.h>
#include <stdbool.h> #include <stdbool.h>
#include <stdint.h> #include <stdint.h>
#include <SDL2/SDL_mutex.h>
#include <SDL2/SDL_thread.h>
#include "util/thread.h" #include "config.h"
struct fps_counter { struct fps_counter {
sc_thread thread; SDL_Thread *thread;
sc_mutex mutex; SDL_mutex *mutex;
sc_cond state_cond; SDL_cond *state_cond;
bool thread_started;
// atomic so that we can check without locking the mutex // atomic so that we can check without locking the mutex
// if the FPS counter is disabled, we don't want to lock unnecessarily // if the FPS counter is disabled, we don't want to lock unnecessarily

View File

@@ -3,7 +3,9 @@
#include <assert.h> #include <assert.h>
#include <SDL2/SDL_keycode.h> #include <SDL2/SDL_keycode.h>
#include "config.h"
#include "event_converter.h" #include "event_converter.h"
#include "util/lock.h"
#include "util/log.h" #include "util/log.h"
static const int ACTION_DOWN = 1; static const int ACTION_DOWN = 1;
@@ -189,20 +191,13 @@ set_device_clipboard(struct controller *controller, bool paste) {
return; return;
} }
char *text_dup = strdup(text);
SDL_free(text);
if (!text_dup) {
LOGW("Could not strdup input text");
return;
}
struct control_msg msg; struct control_msg msg;
msg.type = CONTROL_MSG_TYPE_SET_CLIPBOARD; msg.type = CONTROL_MSG_TYPE_SET_CLIPBOARD;
msg.set_clipboard.text = text_dup; msg.set_clipboard.text = text;
msg.set_clipboard.paste = paste; msg.set_clipboard.paste = paste;
if (!controller_push_msg(controller, &msg)) { if (!controller_push_msg(controller, &msg)) {
free(text_dup); SDL_free(text);
LOGW("Could not request 'set device clipboard'"); LOGW("Could not request 'set device clipboard'");
} }
} }
@@ -248,18 +243,11 @@ clipboard_paste(struct controller *controller) {
return; return;
} }
char *text_dup = strdup(text);
SDL_free(text);
if (!text_dup) {
LOGW("Could not strdup input text");
return;
}
struct control_msg msg; struct control_msg msg;
msg.type = CONTROL_MSG_TYPE_INJECT_TEXT; msg.type = CONTROL_MSG_TYPE_INJECT_TEXT;
msg.inject_text.text = text_dup; msg.inject_text.text = text;
if (!controller_push_msg(controller, &msg)) { if (!controller_push_msg(controller, &msg)) {
free(text_dup); SDL_free(text);
LOGW("Could not request 'paste clipboard'"); LOGW("Could not request 'paste clipboard'");
} }
} }
@@ -286,7 +274,7 @@ rotate_client_right(struct screen *screen) {
screen_set_rotation(screen, new_rotation); screen_set_rotation(screen, new_rotation);
} }
static void void
input_manager_process_text_input(struct input_manager *im, input_manager_process_text_input(struct input_manager *im,
const SDL_TextInputEvent *event) { const SDL_TextInputEvent *event) {
if (is_shortcut_mod(im, SDL_GetModState())) { if (is_shortcut_mod(im, SDL_GetModState())) {
@@ -304,13 +292,13 @@ input_manager_process_text_input(struct input_manager *im,
struct control_msg msg; struct control_msg msg;
msg.type = CONTROL_MSG_TYPE_INJECT_TEXT; msg.type = CONTROL_MSG_TYPE_INJECT_TEXT;
msg.inject_text.text = strdup(event->text); msg.inject_text.text = SDL_strdup(event->text);
if (!msg.inject_text.text) { if (!msg.inject_text.text) {
LOGW("Could not strdup input text"); LOGW("Could not strdup input text");
return; return;
} }
if (!controller_push_msg(im->controller, &msg)) { if (!controller_push_msg(im->controller, &msg)) {
free(msg.inject_text.text); SDL_free(msg.inject_text.text);
LOGW("Could not request 'inject text'"); LOGW("Could not request 'inject text'");
} }
} }
@@ -366,7 +354,7 @@ convert_input_key(const SDL_KeyboardEvent *from, struct control_msg *to,
return true; return true;
} }
static void void
input_manager_process_key(struct input_manager *im, input_manager_process_key(struct input_manager *im,
const SDL_KeyboardEvent *event) { const SDL_KeyboardEvent *event) {
// control: indicates the state of the command-line option --no-control // control: indicates the state of the command-line option --no-control
@@ -480,7 +468,9 @@ input_manager_process_key(struct input_manager *im,
return; return;
case SDLK_i: case SDLK_i:
if (!shift && !repeat && down) { if (!shift && !repeat && down) {
switch_fps_counter_state(im->fps_counter); struct fps_counter *fps_counter =
im->video_buffer->fps_counter;
switch_fps_counter_state(fps_counter);
} }
return; return;
case SDLK_n: case SDLK_n:
@@ -549,7 +539,7 @@ convert_mouse_motion(const SDL_MouseMotionEvent *from, struct screen *screen,
return true; return true;
} }
static void void
input_manager_process_mouse_motion(struct input_manager *im, input_manager_process_mouse_motion(struct input_manager *im,
const SDL_MouseMotionEvent *event) { const SDL_MouseMotionEvent *event) {
if (!event->state) { if (!event->state) {
@@ -603,7 +593,7 @@ convert_touch(const SDL_TouchFingerEvent *from, struct screen *screen,
return true; return true;
} }
static void void
input_manager_process_touch(struct input_manager *im, input_manager_process_touch(struct input_manager *im,
const SDL_TouchFingerEvent *event) { const SDL_TouchFingerEvent *event) {
struct control_msg msg; struct control_msg msg;
@@ -635,7 +625,7 @@ convert_mouse_button(const SDL_MouseButtonEvent *from, struct screen *screen,
return true; return true;
} }
static void void
input_manager_process_mouse_button(struct input_manager *im, input_manager_process_mouse_button(struct input_manager *im,
const SDL_MouseButtonEvent *event) { const SDL_MouseButtonEvent *event) {
bool control = im->control; bool control = im->control;
@@ -734,7 +724,7 @@ convert_mouse_wheel(const SDL_MouseWheelEvent *from, struct screen *screen,
return true; return true;
} }
static void void
input_manager_process_mouse_wheel(struct input_manager *im, input_manager_process_mouse_wheel(struct input_manager *im,
const SDL_MouseWheelEvent *event) { const SDL_MouseWheelEvent *event) {
struct control_msg msg; struct control_msg msg;
@@ -744,46 +734,3 @@ input_manager_process_mouse_wheel(struct input_manager *im,
} }
} }
} }
bool
input_manager_handle_event(struct input_manager *im, SDL_Event *event) {
switch (event->type) {
case SDL_TEXTINPUT:
if (!im->control) {
return true;
}
input_manager_process_text_input(im, &event->text);
return true;
case SDL_KEYDOWN:
case SDL_KEYUP:
// some key events do not interact with the device, so process the
// event even if control is disabled
input_manager_process_key(im, &event->key);
return true;
case SDL_MOUSEMOTION:
if (!im->control) {
break;
}
input_manager_process_mouse_motion(im, &event->motion);
return true;
case SDL_MOUSEWHEEL:
if (!im->control) {
break;
}
input_manager_process_mouse_wheel(im, &event->wheel);
return true;
case SDL_MOUSEBUTTONDOWN:
case SDL_MOUSEBUTTONUP:
// some mouse events do not interact with the device, so process
// the event even if control is disabled
input_manager_process_mouse_button(im, &event->button);
return true;
case SDL_FINGERMOTION:
case SDL_FINGERDOWN:
case SDL_FINGERUP:
input_manager_process_touch(im, &event->tfinger);
return true;
}
return false;
}

View File

@@ -1,20 +1,21 @@
#ifndef INPUTMANAGER_H #ifndef INPUTMANAGER_H
#define INPUTMANAGER_H #define INPUTMANAGER_H
#include "common.h"
#include <stdbool.h> #include <stdbool.h>
#include <SDL2/SDL.h> #include <SDL2/SDL.h>
#include "config.h"
#include "common.h"
#include "controller.h" #include "controller.h"
#include "fps_counter.h" #include "fps_counter.h"
#include "scrcpy.h" #include "scrcpy.h"
#include "screen.h" #include "screen.h"
#include "video_buffer.h"
struct input_manager { struct input_manager {
struct controller *controller; struct controller *controller;
struct fps_counter *fps_counter; struct video_buffer *video_buffer;
struct screen *screen; struct screen *screen;
// SDL reports repeated events as a boolean, but Android expects the actual // SDL reports repeated events as a boolean, but Android expects the actual
@@ -39,7 +40,28 @@ void
input_manager_init(struct input_manager *im, input_manager_init(struct input_manager *im,
const struct scrcpy_options *options); const struct scrcpy_options *options);
bool void
input_manager_handle_event(struct input_manager *im, SDL_Event *event); input_manager_process_text_input(struct input_manager *im,
const SDL_TextInputEvent *event);
void
input_manager_process_key(struct input_manager *im,
const SDL_KeyboardEvent *event);
void
input_manager_process_mouse_motion(struct input_manager *im,
const SDL_MouseMotionEvent *event);
void
input_manager_process_touch(struct input_manager *im,
const SDL_TouchFingerEvent *event);
void
input_manager_process_mouse_button(struct input_manager *im,
const SDL_MouseButtonEvent *event);
void
input_manager_process_mouse_wheel(struct input_manager *im,
const SDL_MouseWheelEvent *event);
#endif #endif

View File

@@ -1,7 +1,5 @@
#include "scrcpy.h" #include "scrcpy.h"
#include "common.h"
#include <assert.h> #include <assert.h>
#include <stdbool.h> #include <stdbool.h>
#include <unistd.h> #include <unistd.h>
@@ -9,7 +7,9 @@
#define SDL_MAIN_HANDLED // avoid link error on Linux Windows Subsystem #define SDL_MAIN_HANDLED // avoid link error on Linux Windows Subsystem
#include <SDL2/SDL.h> #include <SDL2/SDL.h>
#include "config.h"
#include "cli.h" #include "cli.h"
#include "compat.h"
#include "util/log.h" #include "util/log.h"
static void static void

View File

@@ -1,11 +1,11 @@
#ifndef SC_OPENGL_H #ifndef SC_OPENGL_H
#define SC_OPENGL_H #define SC_OPENGL_H
#include "common.h"
#include <stdbool.h> #include <stdbool.h>
#include <SDL2/SDL_opengl.h> #include <SDL2/SDL_opengl.h>
#include "config.h"
struct sc_opengl { struct sc_opengl {
const char *version; const char *version;
bool is_opengles; bool is_opengles;

View File

@@ -3,13 +3,14 @@
#include <assert.h> #include <assert.h>
#include <SDL2/SDL_clipboard.h> #include <SDL2/SDL_clipboard.h>
#include "config.h"
#include "device_msg.h" #include "device_msg.h"
#include "util/lock.h"
#include "util/log.h" #include "util/log.h"
bool bool
receiver_init(struct receiver *receiver, socket_t control_socket) { receiver_init(struct receiver *receiver, socket_t control_socket) {
bool ok = sc_mutex_init(&receiver->mutex); if (!(receiver->mutex = SDL_CreateMutex())) {
if (!ok) {
return false; return false;
} }
receiver->control_socket = control_socket; receiver->control_socket = control_socket;
@@ -18,7 +19,7 @@ receiver_init(struct receiver *receiver, socket_t control_socket) {
void void
receiver_destroy(struct receiver *receiver) { receiver_destroy(struct receiver *receiver) {
sc_mutex_destroy(&receiver->mutex); SDL_DestroyMutex(receiver->mutex);
} }
static void static void
@@ -101,9 +102,8 @@ bool
receiver_start(struct receiver *receiver) { receiver_start(struct receiver *receiver) {
LOGD("Starting receiver thread"); LOGD("Starting receiver thread");
bool ok = sc_thread_create(&receiver->thread, run_receiver, "receiver", receiver->thread = SDL_CreateThread(run_receiver, "receiver", receiver);
receiver); if (!receiver->thread) {
if (!ok) {
LOGC("Could not start receiver thread"); LOGC("Could not start receiver thread");
return false; return false;
} }
@@ -113,5 +113,5 @@ receiver_start(struct receiver *receiver) {
void void
receiver_join(struct receiver *receiver) { receiver_join(struct receiver *receiver) {
sc_thread_join(&receiver->thread, NULL); SDL_WaitThread(receiver->thread, NULL);
} }

View File

@@ -1,19 +1,19 @@
#ifndef RECEIVER_H #ifndef RECEIVER_H
#define RECEIVER_H #define RECEIVER_H
#include "common.h"
#include <stdbool.h> #include <stdbool.h>
#include <SDL2/SDL_mutex.h>
#include <SDL2/SDL_thread.h>
#include "config.h"
#include "util/net.h" #include "util/net.h"
#include "util/thread.h"
// receive events from the device // receive events from the device
// managed by the controller // managed by the controller
struct receiver { struct receiver {
socket_t control_socket; socket_t control_socket;
sc_thread thread; SDL_Thread *thread;
sc_mutex mutex; SDL_mutex *mutex;
}; };
bool bool

View File

@@ -3,11 +3,11 @@
#include <assert.h> #include <assert.h>
#include <libavutil/time.h> #include <libavutil/time.h>
#include "config.h"
#include "compat.h"
#include "util/lock.h"
#include "util/log.h" #include "util/log.h"
/** Downcast packet_sink to recorder */
#define DOWNCAST(SINK) container_of(SINK, struct recorder, packet_sink)
static const AVRational SCRCPY_TIME_BASE = {1, 1000000}; // timestamps in us static const AVRational SCRCPY_TIME_BASE = {1, 1000000}; // timestamps in us
static const AVOutputFormat * static const AVOutputFormat *
@@ -29,7 +29,7 @@ find_muxer(const char *name) {
static struct record_packet * static struct record_packet *
record_packet_new(const AVPacket *packet) { record_packet_new(const AVPacket *packet) {
struct record_packet *rec = malloc(sizeof(*rec)); struct record_packet *rec = SDL_malloc(sizeof(*rec));
if (!rec) { if (!rec) {
return NULL; return NULL;
} }
@@ -39,7 +39,7 @@ record_packet_new(const AVPacket *packet) {
av_init_packet(&rec->packet); av_init_packet(&rec->packet);
if (av_packet_ref(&rec->packet, packet)) { if (av_packet_ref(&rec->packet, packet)) {
free(rec); SDL_free(rec);
return NULL; return NULL;
} }
return rec; return rec;
@@ -48,7 +48,7 @@ record_packet_new(const AVPacket *packet) {
static void static void
record_packet_delete(struct record_packet *rec) { record_packet_delete(struct record_packet *rec) {
av_packet_unref(&rec->packet); av_packet_unref(&rec->packet);
free(rec); SDL_free(rec);
} }
static void static void
@@ -60,6 +60,50 @@ recorder_queue_clear(struct recorder_queue *queue) {
} }
} }
bool
recorder_init(struct recorder *recorder,
const char *filename,
enum sc_record_format format,
struct size declared_frame_size) {
recorder->filename = SDL_strdup(filename);
if (!recorder->filename) {
LOGE("Could not strdup filename");
return false;
}
recorder->mutex = SDL_CreateMutex();
if (!recorder->mutex) {
LOGC("Could not create mutex");
SDL_free(recorder->filename);
return false;
}
recorder->queue_cond = SDL_CreateCond();
if (!recorder->queue_cond) {
LOGC("Could not create cond");
SDL_DestroyMutex(recorder->mutex);
SDL_free(recorder->filename);
return false;
}
queue_init(&recorder->queue);
recorder->stopped = false;
recorder->failed = false;
recorder->format = format;
recorder->declared_frame_size = declared_frame_size;
recorder->header_written = false;
recorder->previous = NULL;
return true;
}
void
recorder_destroy(struct recorder *recorder) {
SDL_DestroyCond(recorder->queue_cond);
SDL_DestroyMutex(recorder->mutex);
SDL_free(recorder->filename);
}
static const char * static const char *
recorder_get_format_name(enum sc_record_format format) { recorder_get_format_name(enum sc_record_format format) {
switch (format) { switch (format) {
@@ -69,154 +113,7 @@ recorder_get_format_name(enum sc_record_format format) {
} }
} }
static bool bool
recorder_write_header(struct recorder *recorder, const AVPacket *packet) {
AVStream *ostream = recorder->ctx->streams[0];
uint8_t *extradata = av_malloc(packet->size * sizeof(uint8_t));
if (!extradata) {
LOGC("Could not allocate extradata");
return false;
}
// copy the first packet to the extra data
memcpy(extradata, packet->data, packet->size);
ostream->codecpar->extradata = extradata;
ostream->codecpar->extradata_size = packet->size;
int ret = avformat_write_header(recorder->ctx, NULL);
if (ret < 0) {
LOGE("Failed to write header to %s", recorder->filename);
return false;
}
return true;
}
static void
recorder_rescale_packet(struct recorder *recorder, AVPacket *packet) {
AVStream *ostream = recorder->ctx->streams[0];
av_packet_rescale_ts(packet, SCRCPY_TIME_BASE, ostream->time_base);
}
static bool
recorder_write(struct recorder *recorder, AVPacket *packet) {
if (!recorder->header_written) {
if (packet->pts != AV_NOPTS_VALUE) {
LOGE("The first packet is not a config packet");
return false;
}
bool ok = recorder_write_header(recorder, packet);
if (!ok) {
return false;
}
recorder->header_written = true;
return true;
}
if (packet->pts == AV_NOPTS_VALUE) {
// ignore config packets
return true;
}
recorder_rescale_packet(recorder, packet);
return av_write_frame(recorder->ctx, packet) >= 0;
}
static int
run_recorder(void *data) {
struct recorder *recorder = data;
for (;;) {
sc_mutex_lock(&recorder->mutex);
while (!recorder->stopped && queue_is_empty(&recorder->queue)) {
sc_cond_wait(&recorder->queue_cond, &recorder->mutex);
}
// if stopped is set, continue to process the remaining events (to
// finish the recording) before actually stopping
if (recorder->stopped && queue_is_empty(&recorder->queue)) {
sc_mutex_unlock(&recorder->mutex);
struct record_packet *last = recorder->previous;
if (last) {
// assign an arbitrary duration to the last packet
last->packet.duration = 100000;
bool ok = recorder_write(recorder, &last->packet);
if (!ok) {
// failing to write the last frame is not very serious, no
// future frame may depend on it, so the resulting file
// will still be valid
LOGW("Could not record last packet");
}
record_packet_delete(last);
}
break;
}
struct record_packet *rec;
queue_take(&recorder->queue, next, &rec);
sc_mutex_unlock(&recorder->mutex);
// recorder->previous is only written from this thread, no need to lock
struct record_packet *previous = recorder->previous;
recorder->previous = rec;
if (!previous) {
// we just received the first packet
continue;
}
// config packets have no PTS, we must ignore them
if (rec->packet.pts != AV_NOPTS_VALUE
&& previous->packet.pts != AV_NOPTS_VALUE) {
// we now know the duration of the previous packet
previous->packet.duration = rec->packet.pts - previous->packet.pts;
}
bool ok = recorder_write(recorder, &previous->packet);
record_packet_delete(previous);
if (!ok) {
LOGE("Could not record packet");
sc_mutex_lock(&recorder->mutex);
recorder->failed = true;
// discard pending packets
recorder_queue_clear(&recorder->queue);
sc_mutex_unlock(&recorder->mutex);
break;
}
}
if (!recorder->failed) {
if (recorder->header_written) {
int ret = av_write_trailer(recorder->ctx);
if (ret < 0) {
LOGE("Failed to write trailer to %s", recorder->filename);
recorder->failed = true;
}
} else {
// the recorded file is empty
recorder->failed = true;
}
}
if (recorder->failed) {
LOGE("Recording failed to %s", recorder->filename);
} else {
const char *format_name = recorder_get_format_name(recorder->format);
LOGI("Recording complete to %s file: %s", format_name, recorder->filename);
}
LOGD("Recorder thread ended");
return 0;
}
static bool
recorder_open(struct recorder *recorder, const AVCodec *input_codec) { recorder_open(struct recorder *recorder, const AVCodec *input_codec) {
const char *format_name = recorder_get_format_name(recorder->format); const char *format_name = recorder_get_format_name(recorder->format);
assert(format_name); assert(format_name);
@@ -247,11 +144,19 @@ recorder_open(struct recorder *recorder, const AVCodec *input_codec) {
return false; return false;
} }
#ifdef SCRCPY_LAVF_HAS_NEW_CODEC_PARAMS_API
ostream->codecpar->codec_type = AVMEDIA_TYPE_VIDEO; ostream->codecpar->codec_type = AVMEDIA_TYPE_VIDEO;
ostream->codecpar->codec_id = input_codec->id; ostream->codecpar->codec_id = input_codec->id;
ostream->codecpar->format = AV_PIX_FMT_YUV420P; ostream->codecpar->format = AV_PIX_FMT_YUV420P;
ostream->codecpar->width = recorder->declared_frame_size.width; ostream->codecpar->width = recorder->declared_frame_size.width;
ostream->codecpar->height = recorder->declared_frame_size.height; ostream->codecpar->height = recorder->declared_frame_size.height;
#else
ostream->codec->codec_type = AVMEDIA_TYPE_VIDEO;
ostream->codec->codec_id = input_codec->id;
ostream->codec->pix_fmt = AV_PIX_FMT_YUV420P;
ostream->codec->width = recorder->declared_frame_size.width;
ostream->codec->height = recorder->declared_frame_size.height;
#endif
int ret = avio_open(&recorder->ctx->pb, recorder->filename, int ret = avio_open(&recorder->ctx->pb, recorder->filename,
AVIO_FLAG_WRITE); AVIO_FLAG_WRITE);
@@ -262,124 +167,214 @@ recorder_open(struct recorder *recorder, const AVCodec *input_codec) {
return false; return false;
} }
LOGD("Starting recorder thread");
bool ok = sc_thread_create(&recorder->thread, run_recorder, "recorder",
recorder);
if (!ok) {
LOGC("Could not start recorder thread");
avformat_free_context(recorder->ctx);
return false;
}
LOGI("Recording started to %s file: %s", format_name, recorder->filename); LOGI("Recording started to %s file: %s", format_name, recorder->filename);
return true; return true;
} }
static void void
recorder_close(struct recorder *recorder) { recorder_close(struct recorder *recorder) {
sc_mutex_lock(&recorder->mutex); if (recorder->header_written) {
recorder->stopped = true; int ret = av_write_trailer(recorder->ctx);
sc_cond_signal(&recorder->queue_cond); if (ret < 0) {
sc_mutex_unlock(&recorder->mutex); LOGE("Failed to write trailer to %s", recorder->filename);
recorder->failed = true;
sc_thread_join(&recorder->thread, NULL); }
} else {
// the recorded file is empty
recorder->failed = true;
}
avio_close(recorder->ctx->pb); avio_close(recorder->ctx->pb);
avformat_free_context(recorder->ctx); avformat_free_context(recorder->ctx);
if (recorder->failed) {
LOGE("Recording failed to %s", recorder->filename);
} else {
const char *format_name = recorder_get_format_name(recorder->format);
LOGI("Recording complete to %s file: %s", format_name, recorder->filename);
}
} }
static bool static bool
recorder_write_header(struct recorder *recorder, const AVPacket *packet) {
AVStream *ostream = recorder->ctx->streams[0];
uint8_t *extradata = av_malloc(packet->size * sizeof(uint8_t));
if (!extradata) {
LOGC("Could not allocate extradata");
return false;
}
// copy the first packet to the extra data
memcpy(extradata, packet->data, packet->size);
#ifdef SCRCPY_LAVF_HAS_NEW_CODEC_PARAMS_API
ostream->codecpar->extradata = extradata;
ostream->codecpar->extradata_size = packet->size;
#else
ostream->codec->extradata = extradata;
ostream->codec->extradata_size = packet->size;
#endif
int ret = avformat_write_header(recorder->ctx, NULL);
if (ret < 0) {
LOGE("Failed to write header to %s", recorder->filename);
return false;
}
return true;
}
static void
recorder_rescale_packet(struct recorder *recorder, AVPacket *packet) {
AVStream *ostream = recorder->ctx->streams[0];
av_packet_rescale_ts(packet, SCRCPY_TIME_BASE, ostream->time_base);
}
bool
recorder_write(struct recorder *recorder, AVPacket *packet) {
if (!recorder->header_written) {
if (packet->pts != AV_NOPTS_VALUE) {
LOGE("The first packet is not a config packet");
return false;
}
bool ok = recorder_write_header(recorder, packet);
if (!ok) {
return false;
}
recorder->header_written = true;
return true;
}
if (packet->pts == AV_NOPTS_VALUE) {
// ignore config packets
return true;
}
recorder_rescale_packet(recorder, packet);
return av_write_frame(recorder->ctx, packet) >= 0;
}
static int
run_recorder(void *data) {
struct recorder *recorder = data;
for (;;) {
mutex_lock(recorder->mutex);
while (!recorder->stopped && queue_is_empty(&recorder->queue)) {
cond_wait(recorder->queue_cond, recorder->mutex);
}
// if stopped is set, continue to process the remaining events (to
// finish the recording) before actually stopping
if (recorder->stopped && queue_is_empty(&recorder->queue)) {
mutex_unlock(recorder->mutex);
struct record_packet *last = recorder->previous;
if (last) {
// assign an arbitrary duration to the last packet
last->packet.duration = 100000;
bool ok = recorder_write(recorder, &last->packet);
if (!ok) {
// failing to write the last frame is not very serious, no
// future frame may depend on it, so the resulting file
// will still be valid
LOGW("Could not record last packet");
}
record_packet_delete(last);
}
break;
}
struct record_packet *rec;
queue_take(&recorder->queue, next, &rec);
mutex_unlock(recorder->mutex);
// recorder->previous is only written from this thread, no need to lock
struct record_packet *previous = recorder->previous;
recorder->previous = rec;
if (!previous) {
// we just received the first packet
continue;
}
// config packets have no PTS, we must ignore them
if (rec->packet.pts != AV_NOPTS_VALUE
&& previous->packet.pts != AV_NOPTS_VALUE) {
// we now know the duration of the previous packet
previous->packet.duration = rec->packet.pts - previous->packet.pts;
}
bool ok = recorder_write(recorder, &previous->packet);
record_packet_delete(previous);
if (!ok) {
LOGE("Could not record packet");
mutex_lock(recorder->mutex);
recorder->failed = true;
// discard pending packets
recorder_queue_clear(&recorder->queue);
mutex_unlock(recorder->mutex);
break;
}
}
LOGD("Recorder thread ended");
return 0;
}
bool
recorder_start(struct recorder *recorder) {
LOGD("Starting recorder thread");
recorder->thread = SDL_CreateThread(run_recorder, "recorder", recorder);
if (!recorder->thread) {
LOGC("Could not start recorder thread");
return false;
}
return true;
}
void
recorder_stop(struct recorder *recorder) {
mutex_lock(recorder->mutex);
recorder->stopped = true;
cond_signal(recorder->queue_cond);
mutex_unlock(recorder->mutex);
}
void
recorder_join(struct recorder *recorder) {
SDL_WaitThread(recorder->thread, NULL);
}
bool
recorder_push(struct recorder *recorder, const AVPacket *packet) { recorder_push(struct recorder *recorder, const AVPacket *packet) {
sc_mutex_lock(&recorder->mutex); mutex_lock(recorder->mutex);
assert(!recorder->stopped); assert(!recorder->stopped);
if (recorder->failed) { if (recorder->failed) {
// reject any new packet (this will stop the stream) // reject any new packet (this will stop the stream)
sc_mutex_unlock(&recorder->mutex); mutex_unlock(recorder->mutex);
return false; return false;
} }
struct record_packet *rec = record_packet_new(packet); struct record_packet *rec = record_packet_new(packet);
if (!rec) { if (!rec) {
LOGC("Could not allocate record packet"); LOGC("Could not allocate record packet");
sc_mutex_unlock(&recorder->mutex); mutex_unlock(recorder->mutex);
return false; return false;
} }
queue_push(&recorder->queue, next, rec); queue_push(&recorder->queue, next, rec);
sc_cond_signal(&recorder->queue_cond); cond_signal(recorder->queue_cond);
sc_mutex_unlock(&recorder->mutex); mutex_unlock(recorder->mutex);
return true; return true;
} }
static bool
recorder_packet_sink_open(struct sc_packet_sink *sink, const AVCodec *codec) {
struct recorder *recorder = DOWNCAST(sink);
return recorder_open(recorder, codec);
}
static void
recorder_packet_sink_close(struct sc_packet_sink *sink) {
struct recorder *recorder = DOWNCAST(sink);
recorder_close(recorder);
}
static bool
recorder_packet_sink_push(struct sc_packet_sink *sink, const AVPacket *packet) {
struct recorder *recorder = DOWNCAST(sink);
return recorder_push(recorder, packet);
}
bool
recorder_init(struct recorder *recorder,
const char *filename,
enum sc_record_format format,
struct size declared_frame_size) {
recorder->filename = strdup(filename);
if (!recorder->filename) {
LOGE("Could not strdup filename");
return false;
}
bool ok = sc_mutex_init(&recorder->mutex);
if (!ok) {
LOGC("Could not create mutex");
free(recorder->filename);
return false;
}
ok = sc_cond_init(&recorder->queue_cond);
if (!ok) {
LOGC("Could not create cond");
sc_mutex_destroy(&recorder->mutex);
free(recorder->filename);
return false;
}
queue_init(&recorder->queue);
recorder->stopped = false;
recorder->failed = false;
recorder->format = format;
recorder->declared_frame_size = declared_frame_size;
recorder->header_written = false;
recorder->previous = NULL;
static const struct sc_packet_sink_ops ops = {
.open = recorder_packet_sink_open,
.close = recorder_packet_sink_close,
.push = recorder_packet_sink_push,
};
recorder->packet_sink.ops = &ops;
return true;
}
void
recorder_destroy(struct recorder *recorder) {
sc_cond_destroy(&recorder->queue_cond);
sc_mutex_destroy(&recorder->mutex);
free(recorder->filename);
}

View File

@@ -1,16 +1,15 @@
#ifndef RECORDER_H #ifndef RECORDER_H
#define RECORDER_H #define RECORDER_H
#include "common.h"
#include <stdbool.h> #include <stdbool.h>
#include <libavformat/avformat.h> #include <libavformat/avformat.h>
#include <SDL2/SDL_mutex.h>
#include <SDL2/SDL_thread.h>
#include "coords.h" #include "config.h"
#include "common.h"
#include "scrcpy.h" #include "scrcpy.h"
#include "trait/packet_sink.h"
#include "util/queue.h" #include "util/queue.h"
#include "util/thread.h"
struct record_packet { struct record_packet {
AVPacket packet; AVPacket packet;
@@ -20,18 +19,16 @@ struct record_packet {
struct recorder_queue QUEUE(struct record_packet); struct recorder_queue QUEUE(struct record_packet);
struct recorder { struct recorder {
struct sc_packet_sink packet_sink; // packet sink trait
char *filename; char *filename;
enum sc_record_format format; enum sc_record_format format;
AVFormatContext *ctx; AVFormatContext *ctx;
struct size declared_frame_size; struct size declared_frame_size;
bool header_written; bool header_written;
sc_thread thread; SDL_Thread *thread;
sc_mutex mutex; SDL_mutex *mutex;
sc_cond queue_cond; SDL_cond *queue_cond;
bool stopped; // set on recorder_close() bool stopped; // set on recorder_stop() by the stream reader
bool failed; // set on packet write failure bool failed; // set on packet write failure
struct recorder_queue queue; struct recorder_queue queue;
@@ -49,4 +46,22 @@ recorder_init(struct recorder *recorder, const char *filename,
void void
recorder_destroy(struct recorder *recorder); recorder_destroy(struct recorder *recorder);
bool
recorder_open(struct recorder *recorder, const AVCodec *input_codec);
void
recorder_close(struct recorder *recorder);
bool
recorder_start(struct recorder *recorder);
void
recorder_stop(struct recorder *recorder);
void
recorder_join(struct recorder *recorder);
bool
recorder_push(struct recorder *recorder, const AVPacket *packet);
#endif #endif

View File

@@ -13,6 +13,10 @@
# include <windows.h> # include <windows.h>
#endif #endif
#include "config.h"
#include "command.h"
#include "common.h"
#include "compat.h"
#include "controller.h" #include "controller.h"
#include "decoder.h" #include "decoder.h"
#include "device.h" #include "device.h"
@@ -25,12 +29,15 @@
#include "server.h" #include "server.h"
#include "stream.h" #include "stream.h"
#include "tiny_xpm.h" #include "tiny_xpm.h"
#include "video_buffer.h"
#include "util/lock.h"
#include "util/log.h" #include "util/log.h"
#include "util/net.h" #include "util/net.h"
static struct server server; static struct server server = SERVER_INITIALIZER;
static struct screen screen; static struct screen screen = SCREEN_INITIALIZER;
static struct fps_counter fps_counter; static struct fps_counter fps_counter;
static struct video_buffer video_buffer;
static struct stream stream; static struct stream stream;
static struct decoder decoder; static struct decoder decoder;
static struct recorder recorder; static struct recorder recorder;
@@ -39,7 +46,7 @@ static struct file_handler file_handler;
static struct input_manager input_manager = { static struct input_manager input_manager = {
.controller = &controller, .controller = &controller,
.fps_counter = &fps_counter, .video_buffer = &video_buffer,
.screen = &screen, .screen = &screen,
.repeat = 0, .repeat = 0,
@@ -171,37 +178,68 @@ handle_event(SDL_Event *event, const struct scrcpy_options *options) {
case SDL_QUIT: case SDL_QUIT:
LOGD("User requested to quit"); LOGD("User requested to quit");
return EVENT_RESULT_STOPPED_BY_USER; return EVENT_RESULT_STOPPED_BY_USER;
case EVENT_NEW_FRAME:
if (!screen.has_frame) {
screen.has_frame = true;
// this is the very first frame, show the window
screen_show_window(&screen);
}
if (!screen_update_frame(&screen, &video_buffer)) {
return EVENT_RESULT_CONTINUE;
}
break;
case SDL_WINDOWEVENT:
screen_handle_window_event(&screen, &event->window);
break;
case SDL_TEXTINPUT:
if (!options->control) {
break;
}
input_manager_process_text_input(&input_manager, &event->text);
break;
case SDL_KEYDOWN:
case SDL_KEYUP:
// some key events do not interact with the device, so process the
// event even if control is disabled
input_manager_process_key(&input_manager, &event->key);
break;
case SDL_MOUSEMOTION:
if (!options->control) {
break;
}
input_manager_process_mouse_motion(&input_manager, &event->motion);
break;
case SDL_MOUSEWHEEL:
if (!options->control) {
break;
}
input_manager_process_mouse_wheel(&input_manager, &event->wheel);
break;
case SDL_MOUSEBUTTONDOWN:
case SDL_MOUSEBUTTONUP:
// some mouse events do not interact with the device, so process
// the event even if control is disabled
input_manager_process_mouse_button(&input_manager, &event->button);
break;
case SDL_FINGERMOTION:
case SDL_FINGERDOWN:
case SDL_FINGERUP:
input_manager_process_touch(&input_manager, &event->tfinger);
break;
case SDL_DROPFILE: { case SDL_DROPFILE: {
if (!options->control) { if (!options->control) {
break; break;
} }
char *file = strdup(event->drop.file);
SDL_free(event->drop.file);
if (!file) {
LOGW("Could not strdup drop filename\n");
break;
}
file_handler_action_t action; file_handler_action_t action;
if (is_apk(file)) { if (is_apk(event->drop.file)) {
action = ACTION_INSTALL_APK; action = ACTION_INSTALL_APK;
} else { } else {
action = ACTION_PUSH_FILE; action = ACTION_PUSH_FILE;
} }
file_handler_request(&file_handler, action, file); file_handler_request(&file_handler, action, event->drop.file);
break; break;
} }
} }
bool consumed = screen_handle_event(&screen, event);
if (consumed) {
goto end;
}
consumed = input_manager_handle_event(&input_manager, event);
(void) consumed;
end:
return EVENT_RESULT_CONTINUE; return EVENT_RESULT_CONTINUE;
} }
@@ -252,7 +290,7 @@ av_log_callback(void *avcl, int level, const char *fmt, va_list vl) {
if (priority == 0) { if (priority == 0) {
return; return;
} }
char *local_fmt = malloc(strlen(fmt) + 10); char *local_fmt = SDL_malloc(strlen(fmt) + 10);
if (!local_fmt) { if (!local_fmt) {
LOGC("Could not allocate string"); LOGC("Could not allocate string");
return; return;
@@ -261,26 +299,11 @@ av_log_callback(void *avcl, int level, const char *fmt, va_list vl) {
strcpy(local_fmt, "[FFmpeg] "); strcpy(local_fmt, "[FFmpeg] ");
strcpy(local_fmt + 9, fmt); strcpy(local_fmt + 9, fmt);
SDL_LogMessageV(SDL_LOG_CATEGORY_VIDEO, priority, local_fmt, vl); SDL_LogMessageV(SDL_LOG_CATEGORY_VIDEO, priority, local_fmt, vl);
free(local_fmt); SDL_free(local_fmt);
} }
bool bool
scrcpy(const struct scrcpy_options *options) { scrcpy(const struct scrcpy_options *options) {
if (!server_init(&server)) {
return false;
}
bool ret = false;
bool server_started = false;
bool fps_counter_initialized = false;
bool file_handler_initialized = false;
bool recorder_initialized = false;
bool stream_started = false;
bool controller_initialized = false;
bool controller_started = false;
bool screen_initialized = false;
bool record = !!options->record_filename; bool record = !!options->record_filename;
struct server_params params = { struct server_params params = {
.log_level = options->log_level, .log_level = options->log_level,
@@ -297,13 +320,20 @@ scrcpy(const struct scrcpy_options *options) {
.codec_options = options->codec_options, .codec_options = options->codec_options,
.encoder_name = options->encoder_name, .encoder_name = options->encoder_name,
.force_adb_forward = options->force_adb_forward, .force_adb_forward = options->force_adb_forward,
.power_off_on_close = options->power_off_on_close,
}; };
if (!server_start(&server, options->serial, &params)) { if (!server_start(&server, options->serial, &params)) {
goto end; return false;
} }
server_started = true; bool ret = false;
bool fps_counter_initialized = false;
bool video_buffer_initialized = false;
bool file_handler_initialized = false;
bool recorder_initialized = false;
bool stream_started = false;
bool controller_initialized = false;
bool controller_started = false;
if (!sdl_init_and_configure(options->display, options->render_driver, if (!sdl_init_and_configure(options->display, options->render_driver,
options->disable_screensaver)) { options->disable_screensaver)) {
@@ -331,6 +361,12 @@ scrcpy(const struct scrcpy_options *options) {
} }
fps_counter_initialized = true; fps_counter_initialized = true;
if (!video_buffer_init(&video_buffer, &fps_counter,
options->render_expired_frames)) {
goto end;
}
video_buffer_initialized = true;
if (options->control) { if (options->control) {
if (!file_handler_init(&file_handler, server.serial, if (!file_handler_init(&file_handler, server.serial,
options->push_target)) { options->push_target)) {
@@ -339,7 +375,7 @@ scrcpy(const struct scrcpy_options *options) {
file_handler_initialized = true; file_handler_initialized = true;
} }
decoder_init(&decoder); decoder_init(&decoder, &video_buffer);
dec = &decoder; dec = &decoder;
} }
@@ -357,15 +393,14 @@ scrcpy(const struct scrcpy_options *options) {
av_log_set_callback(av_log_callback); av_log_set_callback(av_log_callback);
stream_init(&stream, server.video_socket); stream_init(&stream, server.video_socket, dec, rec);
if (dec) { // now we consumed the header values, the socket receives the video stream
stream_add_sink(&stream, &dec->packet_sink); // start the stream
} if (!stream_start(&stream)) {
goto end;
if (rec) {
stream_add_sink(&stream, &rec->packet_sink);
} }
stream_started = true;
if (options->display) { if (options->display) {
if (options->control) { if (options->control) {
@@ -383,25 +418,14 @@ scrcpy(const struct scrcpy_options *options) {
const char *window_title = const char *window_title =
options->window_title ? options->window_title : device_name; options->window_title ? options->window_title : device_name;
struct screen_params screen_params = { if (!screen_init_rendering(&screen, window_title, frame_size,
.window_title = window_title, options->always_on_top, options->window_x,
.frame_size = frame_size, options->window_y, options->window_width,
.always_on_top = options->always_on_top, options->window_height,
.window_x = options->window_x, options->window_borderless,
.window_y = options->window_y, options->rotation, options->mipmaps)) {
.window_width = options->window_width,
.window_height = options->window_height,
.window_borderless = options->window_borderless,
.rotation = options->rotation,
.mipmaps = options->mipmaps,
};
if (!screen_init(&screen, &fps_counter, &screen_params)) {
goto end; goto end;
} }
screen_initialized = true;
decoder_add_sink(&decoder, &screen.frame_sink);
if (options->turn_screen_off) { if (options->turn_screen_off) {
struct control_msg msg; struct control_msg msg;
@@ -418,21 +442,19 @@ scrcpy(const struct scrcpy_options *options) {
} }
} }
// now we consumed the header values, the socket receives the video stream
// start the stream
if (!stream_start(&stream)) {
goto end;
}
stream_started = true;
input_manager_init(&input_manager, options); input_manager_init(&input_manager, options);
ret = event_loop(options); ret = event_loop(options);
LOGD("quit..."); LOGD("quit...");
screen_destroy(&screen);
end: end:
// The stream is not stopped explicitly, because it will stop by itself on // stop stream and controller so that they don't continue once their socket
// end-of-stream // is shutdown
if (stream_started) {
stream_stop(&stream);
}
if (controller_started) { if (controller_started) {
controller_stop(&controller); controller_stop(&controller);
} }
@@ -443,23 +465,14 @@ end:
fps_counter_interrupt(&fps_counter); fps_counter_interrupt(&fps_counter);
} }
if (server_started) { // shutdown the sockets and kill the server
// shutdown the sockets and kill the server server_stop(&server);
server_stop(&server);
}
// now that the sockets are shutdown, the stream and controller are // now that the sockets are shutdown, the stream and controller are
// interrupted, we can join them // interrupted, we can join them
if (stream_started) { if (stream_started) {
stream_join(&stream); stream_join(&stream);
} }
// Destroy the screen only after the stream is guaranteed to be finished,
// because otherwise the screen could receive new frames after destruction
if (screen_initialized) {
screen_destroy(&screen);
}
if (controller_started) { if (controller_started) {
controller_join(&controller); controller_join(&controller);
} }
@@ -476,6 +489,10 @@ end:
file_handler_destroy(&file_handler); file_handler_destroy(&file_handler);
} }
if (video_buffer_initialized) {
video_buffer_destroy(&video_buffer);
}
if (fps_counter_initialized) { if (fps_counter_initialized) {
fps_counter_join(&fps_counter); fps_counter_join(&fps_counter);
fps_counter_destroy(&fps_counter); fps_counter_destroy(&fps_counter);

View File

@@ -1,12 +1,12 @@
#ifndef SCRCPY_H #ifndef SCRCPY_H
#define SCRCPY_H #define SCRCPY_H
#include "common.h"
#include <stdbool.h> #include <stdbool.h>
#include <stddef.h> #include <stddef.h>
#include <stdint.h> #include <stdint.h>
#include "config.h"
enum sc_log_level { enum sc_log_level {
SC_LOG_LEVEL_DEBUG, SC_LOG_LEVEL_DEBUG,
SC_LOG_LEVEL_INFO, SC_LOG_LEVEL_INFO,
@@ -65,13 +65,14 @@ struct scrcpy_options {
int16_t window_y; // SC_WINDOW_POSITION_UNDEFINED for "auto" int16_t window_y; // SC_WINDOW_POSITION_UNDEFINED for "auto"
uint16_t window_width; uint16_t window_width;
uint16_t window_height; uint16_t window_height;
uint32_t display_id; uint16_t display_id;
bool show_touches; bool show_touches;
bool fullscreen; bool fullscreen;
bool always_on_top; bool always_on_top;
bool control; bool control;
bool display; bool display;
bool turn_screen_off; bool turn_screen_off;
bool render_expired_frames;
bool prefer_text; bool prefer_text;
bool window_borderless; bool window_borderless;
bool mipmaps; bool mipmaps;
@@ -81,7 +82,6 @@ struct scrcpy_options {
bool forward_key_repeat; bool forward_key_repeat;
bool forward_all_clicks; bool forward_all_clicks;
bool legacy_paste; bool legacy_paste;
bool power_off_on_close;
}; };
#define SCRCPY_OPTIONS_DEFAULT { \ #define SCRCPY_OPTIONS_DEFAULT { \
@@ -103,10 +103,10 @@ struct scrcpy_options {
.data = {SC_MOD_LALT, SC_MOD_LSUPER}, \ .data = {SC_MOD_LALT, SC_MOD_LSUPER}, \
.count = 2, \ .count = 2, \
}, \ }, \
.max_size = 0, \ .max_size = DEFAULT_MAX_SIZE, \
.bit_rate = DEFAULT_BIT_RATE, \ .bit_rate = DEFAULT_BIT_RATE, \
.max_fps = 0, \ .max_fps = 0, \
.lock_video_orientation = -1, \ .lock_video_orientation = DEFAULT_LOCK_VIDEO_ORIENTATION, \
.rotation = 0, \ .rotation = 0, \
.window_x = SC_WINDOW_POSITION_UNDEFINED, \ .window_x = SC_WINDOW_POSITION_UNDEFINED, \
.window_y = SC_WINDOW_POSITION_UNDEFINED, \ .window_y = SC_WINDOW_POSITION_UNDEFINED, \
@@ -119,6 +119,7 @@ struct scrcpy_options {
.control = true, \ .control = true, \
.display = true, \ .display = true, \
.turn_screen_off = false, \ .turn_screen_off = false, \
.render_expired_frames = false, \
.prefer_text = false, \ .prefer_text = false, \
.window_borderless = false, \ .window_borderless = false, \
.mipmaps = true, \ .mipmaps = true, \
@@ -128,7 +129,6 @@ struct scrcpy_options {
.forward_key_repeat = true, \ .forward_key_repeat = true, \
.forward_all_clicks = false, \ .forward_all_clicks = false, \
.legacy_paste = false, \ .legacy_paste = false, \
.power_off_on_close = false, \
} }
bool bool

View File

@@ -4,17 +4,18 @@
#include <string.h> #include <string.h>
#include <SDL2/SDL.h> #include <SDL2/SDL.h>
#include "events.h" #include "config.h"
#include "common.h"
#include "compat.h"
#include "icon.xpm" #include "icon.xpm"
#include "scrcpy.h" #include "scrcpy.h"
#include "tiny_xpm.h" #include "tiny_xpm.h"
#include "video_buffer.h" #include "video_buffer.h"
#include "util/lock.h"
#include "util/log.h" #include "util/log.h"
#define DISPLAY_MARGINS 96 #define DISPLAY_MARGINS 96
#define DOWNCAST(SINK) container_of(SINK, struct screen, frame_sink)
static inline struct size static inline struct size
get_rotated_size(struct size size, int rotation) { get_rotated_size(struct size size, int rotation) {
struct size rotated_size; struct size rotated_size;
@@ -193,6 +194,11 @@ screen_update_content_rect(struct screen *screen) {
} }
} }
void
screen_init(struct screen *screen) {
*screen = (struct screen) SCREEN_INITIALIZER;
}
static inline SDL_Texture * static inline SDL_Texture *
create_texture(struct screen *screen) { create_texture(struct screen *screen) {
SDL_Renderer *renderer = screen->renderer; SDL_Renderer *renderer = screen->renderer;
@@ -220,87 +226,27 @@ create_texture(struct screen *screen) {
return texture; return texture;
} }
static bool
screen_frame_sink_open(struct sc_frame_sink *sink) {
struct screen *screen = DOWNCAST(sink);
(void) screen;
#ifndef NDEBUG
screen->open = true;
#endif
// nothing to do, the screen is already open on the main thread
return true;
}
static void
screen_frame_sink_close(struct sc_frame_sink *sink) {
struct screen *screen = DOWNCAST(sink);
(void) screen;
#ifndef NDEBUG
screen->open = false;
#endif
// nothing to do, the screen lifecycle is not managed by the frame producer
}
static bool
screen_frame_sink_push(struct sc_frame_sink *sink, const AVFrame *frame) {
struct screen *screen = DOWNCAST(sink);
bool previous_frame_skipped;
bool ok = video_buffer_push(&screen->vb, frame, &previous_frame_skipped);
if (!ok) {
return false;
}
if (previous_frame_skipped) {
fps_counter_add_skipped_frame(screen->fps_counter);
// The EVENT_NEW_FRAME triggered for the previous frame will consume
// this new frame instead
} else {
static SDL_Event new_frame_event = {
.type = EVENT_NEW_FRAME,
};
// Post the event on the UI thread
SDL_PushEvent(&new_frame_event);
}
return true;
}
bool bool
screen_init(struct screen *screen, struct fps_counter *fps_counter, screen_init_rendering(struct screen *screen, const char *window_title,
const struct screen_params *params) { struct size frame_size, bool always_on_top,
screen->fps_counter = fps_counter; int16_t window_x, int16_t window_y, uint16_t window_width,
uint16_t window_height, bool window_borderless,
screen->resize_pending = false; uint8_t rotation, bool mipmaps) {
screen->has_frame = false; screen->frame_size = frame_size;
screen->fullscreen = false; screen->rotation = rotation;
screen->maximized = false; if (rotation) {
LOGI("Initial display rotation set to %u", rotation);
bool ok = video_buffer_init(&screen->vb);
if (!ok) {
LOGE("Could not initialize video buffer");
return false;
} }
struct size content_size = get_rotated_size(frame_size, screen->rotation);
screen->frame_size = params->frame_size;
screen->rotation = params->rotation;
if (screen->rotation) {
LOGI("Initial display rotation set to %u", screen->rotation);
}
struct size content_size =
get_rotated_size(screen->frame_size, screen->rotation);
screen->content_size = content_size; screen->content_size = content_size;
struct size window_size = get_initial_optimal_size(content_size, struct size window_size =
params->window_width, get_initial_optimal_size(content_size, window_width, window_height);
params->window_height); uint32_t window_flags = SDL_WINDOW_HIDDEN | SDL_WINDOW_RESIZABLE;
uint32_t window_flags = SDL_WINDOW_HIDDEN #ifdef HIDPI_SUPPORT
| SDL_WINDOW_RESIZABLE window_flags |= SDL_WINDOW_ALLOW_HIGHDPI;
| SDL_WINDOW_ALLOW_HIGHDPI; #endif
if (params->always_on_top) { if (always_on_top) {
#ifdef SCRCPY_SDL_HAS_WINDOW_ALWAYS_ON_TOP #ifdef SCRCPY_SDL_HAS_WINDOW_ALWAYS_ON_TOP
window_flags |= SDL_WINDOW_ALWAYS_ON_TOP; window_flags |= SDL_WINDOW_ALWAYS_ON_TOP;
#else #else
@@ -308,15 +254,15 @@ screen_init(struct screen *screen, struct fps_counter *fps_counter,
"(compile with SDL >= 2.0.5 to enable it)"); "(compile with SDL >= 2.0.5 to enable it)");
#endif #endif
} }
if (params->window_borderless) { if (window_borderless) {
window_flags |= SDL_WINDOW_BORDERLESS; window_flags |= SDL_WINDOW_BORDERLESS;
} }
int x = params->window_x != SC_WINDOW_POSITION_UNDEFINED int x = window_x != SC_WINDOW_POSITION_UNDEFINED
? params->window_x : (int) SDL_WINDOWPOS_UNDEFINED; ? window_x : (int) SDL_WINDOWPOS_UNDEFINED;
int y = params->window_y != SC_WINDOW_POSITION_UNDEFINED int y = window_y != SC_WINDOW_POSITION_UNDEFINED
? params->window_y : (int) SDL_WINDOWPOS_UNDEFINED; ? window_y : (int) SDL_WINDOWPOS_UNDEFINED;
screen->window = SDL_CreateWindow(params->window_title, x, y, screen->window = SDL_CreateWindow(window_title, x, y,
window_size.width, window_size.height, window_size.width, window_size.height,
window_flags); window_flags);
if (!screen->window) { if (!screen->window) {
@@ -328,8 +274,7 @@ screen_init(struct screen *screen, struct fps_counter *fps_counter,
SDL_RENDERER_ACCELERATED); SDL_RENDERER_ACCELERATED);
if (!screen->renderer) { if (!screen->renderer) {
LOGC("Could not create renderer: %s", SDL_GetError()); LOGC("Could not create renderer: %s", SDL_GetError());
SDL_DestroyWindow(screen->window); screen_destroy(screen);
video_buffer_destroy(&screen->vb);
return false; return false;
} }
@@ -338,17 +283,15 @@ screen_init(struct screen *screen, struct fps_counter *fps_counter,
const char *renderer_name = r ? NULL : renderer_info.name; const char *renderer_name = r ? NULL : renderer_info.name;
LOGI("Renderer: %s", renderer_name ? renderer_name : "(unknown)"); LOGI("Renderer: %s", renderer_name ? renderer_name : "(unknown)");
screen->mipmaps = false;
// starts with "opengl" // starts with "opengl"
bool use_opengl = renderer_name && !strncmp(renderer_name, "opengl", 6); screen->use_opengl = renderer_name && !strncmp(renderer_name, "opengl", 6);
if (use_opengl) { if (screen->use_opengl) {
struct sc_opengl *gl = &screen->gl; struct sc_opengl *gl = &screen->gl;
sc_opengl_init(gl); sc_opengl_init(gl);
LOGI("OpenGL version: %s", gl->version); LOGI("OpenGL version: %s", gl->version);
if (params->mipmaps) { if (mipmaps) {
bool supports_mipmaps = bool supports_mipmaps =
sc_opengl_version_at_least(gl, 3, 0, /* OpenGL 3.0+ */ sc_opengl_version_at_least(gl, 3, 0, /* OpenGL 3.0+ */
2, 0 /* OpenGL ES 2.0+ */); 2, 0 /* OpenGL ES 2.0+ */);
@@ -362,7 +305,7 @@ screen_init(struct screen *screen, struct fps_counter *fps_counter,
} else { } else {
LOGI("Trilinear filtering disabled"); LOGI("Trilinear filtering disabled");
} }
} else if (params->mipmaps) { } else {
LOGD("Trilinear filtering disabled (not an OpenGL renderer)"); LOGD("Trilinear filtering disabled (not an OpenGL renderer)");
} }
@@ -374,24 +317,12 @@ screen_init(struct screen *screen, struct fps_counter *fps_counter,
LOGW("Could not load icon"); LOGW("Could not load icon");
} }
LOGI("Initial texture: %" PRIu16 "x%" PRIu16, params->frame_size.width, LOGI("Initial texture: %" PRIu16 "x%" PRIu16, frame_size.width,
params->frame_size.height); frame_size.height);
screen->texture = create_texture(screen); screen->texture = create_texture(screen);
if (!screen->texture) { if (!screen->texture) {
LOGC("Could not create texture: %s", SDL_GetError()); LOGC("Could not create texture: %s", SDL_GetError());
SDL_DestroyRenderer(screen->renderer); screen_destroy(screen);
SDL_DestroyWindow(screen->window);
video_buffer_destroy(&screen->vb);
return false;
}
screen->frame = av_frame_alloc();
if (!screen->frame) {
LOGC("Could not create screen frame");
SDL_DestroyTexture(screen->texture);
SDL_DestroyRenderer(screen->renderer);
SDL_DestroyWindow(screen->window);
video_buffer_destroy(&screen->vb);
return false; return false;
} }
@@ -402,18 +333,6 @@ screen_init(struct screen *screen, struct fps_counter *fps_counter,
screen_update_content_rect(screen); screen_update_content_rect(screen);
static const struct sc_frame_sink_ops ops = {
.open = screen_frame_sink_open,
.close = screen_frame_sink_close,
.push = screen_frame_sink_push,
};
screen->frame_sink.ops = &ops;
#ifndef NDEBUG
screen->open = false;
#endif
return true; return true;
} }
@@ -424,14 +343,15 @@ screen_show_window(struct screen *screen) {
void void
screen_destroy(struct screen *screen) { screen_destroy(struct screen *screen) {
#ifndef NDEBUG if (screen->texture) {
assert(!screen->open); SDL_DestroyTexture(screen->texture);
#endif }
av_frame_free(&screen->frame); if (screen->renderer) {
SDL_DestroyTexture(screen->texture); SDL_DestroyRenderer(screen->renderer);
SDL_DestroyRenderer(screen->renderer); }
SDL_DestroyWindow(screen->window); if (screen->window) {
video_buffer_destroy(&screen->vb); SDL_DestroyWindow(screen->window);
}
} }
static void static void
@@ -528,25 +448,24 @@ update_texture(struct screen *screen, const AVFrame *frame) {
frame->data[2], frame->linesize[2]); frame->data[2], frame->linesize[2]);
if (screen->mipmaps) { if (screen->mipmaps) {
assert(screen->use_opengl);
SDL_GL_BindTexture(screen->texture, NULL, NULL); SDL_GL_BindTexture(screen->texture, NULL, NULL);
screen->gl.GenerateMipmap(GL_TEXTURE_2D); screen->gl.GenerateMipmap(GL_TEXTURE_2D);
SDL_GL_UnbindTexture(screen->texture); SDL_GL_UnbindTexture(screen->texture);
} }
} }
static bool bool
screen_update_frame(struct screen *screen) { screen_update_frame(struct screen *screen, struct video_buffer *vb) {
av_frame_unref(screen->frame); mutex_lock(vb->mutex);
video_buffer_consume(&screen->vb, screen->frame); const AVFrame *frame = video_buffer_consume_rendered_frame(vb);
AVFrame *frame = screen->frame;
fps_counter_add_rendered_frame(screen->fps_counter);
struct size new_frame_size = {frame->width, frame->height}; struct size new_frame_size = {frame->width, frame->height};
if (!prepare_for_frame(screen, new_frame_size)) { if (!prepare_for_frame(screen, new_frame_size)) {
mutex_unlock(vb->mutex);
return false; return false;
} }
update_texture(screen, frame); update_texture(screen, frame);
mutex_unlock(vb->mutex);
screen_render(screen, false); screen_render(screen, false);
return true; return true;
@@ -633,52 +552,31 @@ screen_resize_to_pixel_perfect(struct screen *screen) {
content_size.height); content_size.height);
} }
bool void
screen_handle_event(struct screen *screen, SDL_Event *event) { screen_handle_window_event(struct screen *screen,
switch (event->type) { const SDL_WindowEvent *event) {
case EVENT_NEW_FRAME: switch (event->event) {
if (!screen->has_frame) { case SDL_WINDOWEVENT_EXPOSED:
screen->has_frame = true; screen_render(screen, true);
// this is the very first frame, show the window break;
screen_show_window(screen); case SDL_WINDOWEVENT_SIZE_CHANGED:
screen_render(screen, true);
break;
case SDL_WINDOWEVENT_MAXIMIZED:
screen->maximized = true;
break;
case SDL_WINDOWEVENT_RESTORED:
if (screen->fullscreen) {
// On Windows, in maximized+fullscreen, disabling fullscreen
// mode unexpectedly triggers the "restored" then "maximized"
// events, leaving the window in a weird state (maximized
// according to the events, but not maximized visually).
break;
} }
bool ok = screen_update_frame(screen); screen->maximized = false;
if (!ok) { apply_pending_resize(screen);
LOGW("Frame update failed\n"); break;
}
return true;
case SDL_WINDOWEVENT:
if (!screen->has_frame) {
// Do nothing
return true;
}
switch (event->window.event) {
case SDL_WINDOWEVENT_EXPOSED:
screen_render(screen, true);
break;
case SDL_WINDOWEVENT_SIZE_CHANGED:
screen_render(screen, true);
break;
case SDL_WINDOWEVENT_MAXIMIZED:
screen->maximized = true;
break;
case SDL_WINDOWEVENT_RESTORED:
if (screen->fullscreen) {
// On Windows, in maximized+fullscreen, disabling
// fullscreen mode unexpectedly triggers the "restored"
// then "maximized" events, leaving the window in a
// weird state (maximized according to the events, but
// not maximized visually).
break;
}
screen->maximized = false;
apply_pending_resize(screen);
break;
}
return true;
} }
return false;
} }
struct point struct point

View File

@@ -1,32 +1,21 @@
#ifndef SCREEN_H #ifndef SCREEN_H
#define SCREEN_H #define SCREEN_H
#include "common.h"
#include <stdbool.h> #include <stdbool.h>
#include <SDL2/SDL.h> #include <SDL2/SDL.h>
#include <libavformat/avformat.h> #include <libavformat/avformat.h>
#include "coords.h" #include "config.h"
#include "common.h"
#include "opengl.h" #include "opengl.h"
#include "trait/frame_sink.h"
#include "video_buffer.h"
struct video_buffer; struct video_buffer;
struct screen { struct screen {
struct sc_frame_sink frame_sink; // frame sink trait
#ifndef NDEBUG
bool open; // track the open/close state to assert correct behavior
#endif
struct video_buffer vb;
struct fps_counter *fps_counter;
SDL_Window *window; SDL_Window *window;
SDL_Renderer *renderer; SDL_Renderer *renderer;
SDL_Texture *texture; SDL_Texture *texture;
bool use_opengl;
struct sc_opengl gl; struct sc_opengl gl;
struct size frame_size; struct size frame_size;
struct size content_size; // rotated frame_size struct size content_size; // rotated frame_size
@@ -43,31 +32,55 @@ struct screen {
bool has_frame; bool has_frame;
bool fullscreen; bool fullscreen;
bool maximized; bool maximized;
bool mipmaps; bool no_window;
AVFrame *frame;
};
struct screen_params {
const char *window_title;
struct size frame_size;
bool always_on_top;
int16_t window_x;
int16_t window_y;
uint16_t window_width; // accepts SC_WINDOW_POSITION_UNDEFINED
uint16_t window_height; // accepts SC_WINDOW_POSITION_UNDEFINED
bool window_borderless;
uint8_t rotation;
bool mipmaps; bool mipmaps;
}; };
#define SCREEN_INITIALIZER { \
.window = NULL, \
.renderer = NULL, \
.texture = NULL, \
.use_opengl = false, \
.gl = {0}, \
.frame_size = { \
.width = 0, \
.height = 0, \
}, \
.content_size = { \
.width = 0, \
.height = 0, \
}, \
.resize_pending = false, \
.windowed_content_size = { \
.width = 0, \
.height = 0, \
}, \
.rotation = 0, \
.rect = { \
.x = 0, \
.y = 0, \
.w = 0, \
.h = 0, \
}, \
.has_frame = false, \
.fullscreen = false, \
.maximized = false, \
.no_window = false, \
.mipmaps = false, \
}
// initialize default values
void
screen_init(struct screen *screen);
// initialize screen, create window, renderer and texture (window is hidden) // initialize screen, create window, renderer and texture (window is hidden)
// window_x and window_y accept SC_WINDOW_POSITION_UNDEFINED
bool bool
screen_init(struct screen *screen, struct fps_counter *fps_counter, screen_init_rendering(struct screen *screen, const char *window_title,
const struct screen_params *params); struct size frame_size, bool always_on_top,
int16_t window_x, int16_t window_y, uint16_t window_width,
uint16_t window_height, bool window_borderless,
uint8_t rotation, bool mipmaps);
// show the window // show the window
void void
@@ -77,6 +90,10 @@ screen_show_window(struct screen *screen);
void void
screen_destroy(struct screen *screen); screen_destroy(struct screen *screen);
// resize if necessary and write the rendered frame into the texture
bool
screen_update_frame(struct screen *screen, struct video_buffer *vb);
// render the texture to the renderer // render the texture to the renderer
// //
// Set the update_content_rect flag if the window or content size may have // Set the update_content_rect flag if the window or content size may have
@@ -100,9 +117,9 @@ screen_resize_to_pixel_perfect(struct screen *screen);
void void
screen_set_rotation(struct screen *screen, unsigned rotation); screen_set_rotation(struct screen *screen, unsigned rotation);
// react to SDL events // react to window events
bool void
screen_handle_event(struct screen *screen, SDL_Event *event); screen_handle_window_event(struct screen *screen, const SDL_WindowEvent *event);
// convert point from window coordinates to frame coordinates // convert point from window coordinates to frame coordinates
// x and y are expressed in pixels // x and y are expressed in pixels

View File

@@ -5,10 +5,12 @@
#include <inttypes.h> #include <inttypes.h>
#include <libgen.h> #include <libgen.h>
#include <stdio.h> #include <stdio.h>
#include <SDL2/SDL_thread.h>
#include <SDL2/SDL_timer.h> #include <SDL2/SDL_timer.h>
#include <SDL2/SDL_platform.h> #include <SDL2/SDL_platform.h>
#include "adb.h" #include "config.h"
#include "command.h"
#include "util/log.h" #include "util/log.h"
#include "util/net.h" #include "util/net.h"
#include "util/str_util.h" #include "util/str_util.h"
@@ -31,7 +33,7 @@ get_server_path(void) {
#ifdef __WINDOWS__ #ifdef __WINDOWS__
char *server_path = utf8_from_wide_char(server_path_env); char *server_path = utf8_from_wide_char(server_path_env);
#else #else
char *server_path = strdup(server_path_env); char *server_path = SDL_strdup(server_path_env);
#endif #endif
if (!server_path) { if (!server_path) {
LOGE("Could not allocate memory"); LOGE("Could not allocate memory");
@@ -43,7 +45,7 @@ get_server_path(void) {
#ifndef PORTABLE #ifndef PORTABLE
LOGD("Using server: " DEFAULT_SERVER_PATH); LOGD("Using server: " DEFAULT_SERVER_PATH);
char *server_path = strdup(DEFAULT_SERVER_PATH); char *server_path = SDL_strdup(DEFAULT_SERVER_PATH);
if (!server_path) { if (!server_path) {
LOGE("Could not allocate memory"); LOGE("Could not allocate memory");
return NULL; return NULL;
@@ -65,11 +67,11 @@ get_server_path(void) {
// sizeof(SERVER_FILENAME) gives statically the size including the null byte // sizeof(SERVER_FILENAME) gives statically the size including the null byte
size_t len = dirlen + 1 + sizeof(SERVER_FILENAME); size_t len = dirlen + 1 + sizeof(SERVER_FILENAME);
char *server_path = malloc(len); char *server_path = SDL_malloc(len);
if (!server_path) { if (!server_path) {
LOGE("Could not alloc server path string, " LOGE("Could not alloc server path string, "
"using " SERVER_FILENAME " from current directory"); "using " SERVER_FILENAME " from current directory");
free(executable_path); SDL_free(executable_path);
return SERVER_FILENAME; return SERVER_FILENAME;
} }
@@ -78,7 +80,7 @@ get_server_path(void) {
memcpy(&server_path[dirlen + 1], SERVER_FILENAME, sizeof(SERVER_FILENAME)); memcpy(&server_path[dirlen + 1], SERVER_FILENAME, sizeof(SERVER_FILENAME));
// the final null byte has been copied with SERVER_FILENAME // the final null byte has been copied with SERVER_FILENAME
free(executable_path); SDL_free(executable_path);
LOGD("Using server (portable): %s", server_path); LOGD("Using server (portable): %s", server_path);
return server_path; return server_path;
@@ -93,36 +95,36 @@ push_server(const char *serial) {
} }
if (!is_regular_file(server_path)) { if (!is_regular_file(server_path)) {
LOGE("'%s' does not exist or is not a regular file\n", server_path); LOGE("'%s' does not exist or is not a regular file\n", server_path);
free(server_path); SDL_free(server_path);
return false; return false;
} }
process_t process = adb_push(serial, server_path, DEVICE_SERVER_PATH); process_t process = adb_push(serial, server_path, DEVICE_SERVER_PATH);
free(server_path); SDL_free(server_path);
return process_check_success(process, "adb push", true); return process_check_success(process, "adb push");
} }
static bool static bool
enable_tunnel_reverse(const char *serial, uint16_t local_port) { enable_tunnel_reverse(const char *serial, uint16_t local_port) {
process_t process = adb_reverse(serial, SOCKET_NAME, local_port); process_t process = adb_reverse(serial, SOCKET_NAME, local_port);
return process_check_success(process, "adb reverse", true); return process_check_success(process, "adb reverse");
} }
static bool static bool
disable_tunnel_reverse(const char *serial) { disable_tunnel_reverse(const char *serial) {
process_t process = adb_reverse_remove(serial, SOCKET_NAME); process_t process = adb_reverse_remove(serial, SOCKET_NAME);
return process_check_success(process, "adb reverse --remove", true); return process_check_success(process, "adb reverse --remove");
} }
static bool static bool
enable_tunnel_forward(const char *serial, uint16_t local_port) { enable_tunnel_forward(const char *serial, uint16_t local_port) {
process_t process = adb_forward(serial, local_port, SOCKET_NAME); process_t process = adb_forward(serial, local_port, SOCKET_NAME);
return process_check_success(process, "adb forward", true); return process_check_success(process, "adb forward");
} }
static bool static bool
disable_tunnel_forward(const char *serial, uint16_t local_port) { disable_tunnel_forward(const char *serial, uint16_t local_port) {
process_t process = adb_forward_remove(serial, local_port); process_t process = adb_forward_remove(serial, local_port);
return process_check_success(process, "adb forward --remove", true); return process_check_success(process, "adb forward --remove");
} }
static bool static bool
@@ -255,12 +257,12 @@ execute_server(struct server *server, const struct server_params *params) {
char bit_rate_string[11]; char bit_rate_string[11];
char max_fps_string[6]; char max_fps_string[6];
char lock_video_orientation_string[5]; char lock_video_orientation_string[5];
char display_id_string[11]; char display_id_string[6];
sprintf(max_size_string, "%"PRIu16, params->max_size); sprintf(max_size_string, "%"PRIu16, params->max_size);
sprintf(bit_rate_string, "%"PRIu32, params->bit_rate); sprintf(bit_rate_string, "%"PRIu32, params->bit_rate);
sprintf(max_fps_string, "%"PRIu16, params->max_fps); sprintf(max_fps_string, "%"PRIu16, params->max_fps);
sprintf(lock_video_orientation_string, "%"PRIi8, params->lock_video_orientation); sprintf(lock_video_orientation_string, "%"PRIi8, params->lock_video_orientation);
sprintf(display_id_string, "%"PRIu32, params->display_id); sprintf(display_id_string, "%"PRIu16, params->display_id);
const char *const cmd[] = { const char *const cmd[] = {
"shell", "shell",
"CLASSPATH=" DEVICE_SERVER_PATH, "CLASSPATH=" DEVICE_SERVER_PATH,
@@ -293,7 +295,6 @@ execute_server(struct server *server, const struct server_params *params) {
params->stay_awake ? "true" : "false", params->stay_awake ? "true" : "false",
params->codec_options ? params->codec_options : "-", params->codec_options ? params->codec_options : "-",
params->encoder_name ? params->encoder_name : "-", params->encoder_name ? params->encoder_name : "-",
params->power_off_on_close ? "true" : "false",
}; };
#ifdef SERVER_DEBUGGER #ifdef SERVER_DEBUGGER
LOGI("Server debugger waiting for a client on device port " LOGI("Server debugger waiting for a client on device port "
@@ -352,48 +353,15 @@ close_socket(socket_t socket) {
} }
} }
bool void
server_init(struct server *server) { server_init(struct server *server) {
server->serial = NULL; *server = (struct server) SERVER_INITIALIZER;
server->process = PROCESS_NONE;
atomic_flag_clear_explicit(&server->server_socket_closed,
memory_order_relaxed);
bool ok = sc_mutex_init(&server->mutex);
if (!ok) {
return false;
}
ok = sc_cond_init(&server->process_terminated_cond);
if (!ok) {
sc_mutex_destroy(&server->mutex);
return false;
}
server->process_terminated = false;
server->server_socket = INVALID_SOCKET;
server->video_socket = INVALID_SOCKET;
server->control_socket = INVALID_SOCKET;
server->local_port = 0;
server->tunnel_enabled = false;
server->tunnel_forward = false;
return true;
} }
static int static int
run_wait_server(void *data) { run_wait_server(void *data) {
struct server *server = data; struct server *server = data;
process_wait(server->process, false); // ignore exit code cmd_simple_wait(server->process, NULL); // ignore exit code
sc_mutex_lock(&server->mutex);
server->process_terminated = true;
sc_cond_signal(&server->process_terminated_cond);
sc_mutex_unlock(&server->mutex);
// no need for synchronization, server_socket is initialized before this // no need for synchronization, server_socket is initialized before this
// thread was created // thread was created
if (server->server_socket != INVALID_SOCKET if (server->server_socket != INVALID_SOCKET
@@ -409,8 +377,10 @@ run_wait_server(void *data) {
bool bool
server_start(struct server *server, const char *serial, server_start(struct server *server, const char *serial,
const struct server_params *params) { const struct server_params *params) {
server->port_range = params->port_range;
if (serial) { if (serial) {
server->serial = strdup(serial); server->serial = SDL_strdup(serial);
if (!server->serial) { if (!server->serial) {
return false; return false;
} }
@@ -437,11 +407,11 @@ server_start(struct server *server, const char *serial,
// things simple and multiplatform, just spawn a new thread waiting for the // things simple and multiplatform, just spawn a new thread waiting for the
// server process and calling shutdown()/close() on the server socket if // server process and calling shutdown()/close() on the server socket if
// necessary to wake up any accept() blocking call. // necessary to wake up any accept() blocking call.
bool ok = sc_thread_create(&server->wait_server_thread, run_wait_server, server->wait_server_thread =
"wait-server", server); SDL_CreateThread(run_wait_server, "wait-server", server);
if (!ok) { if (!server->wait_server_thread) {
process_terminate(server->process); cmd_terminate(server->process);
process_wait(server->process, true); // ignore exit code cmd_simple_wait(server->process, NULL); // ignore exit code
goto error2; goto error2;
} }
@@ -460,7 +430,7 @@ error2:
} }
disable_tunnel(server); disable_tunnel(server);
error1: error1:
free(server->serial); SDL_free(server->serial);
return false; return false;
} }
@@ -523,39 +493,17 @@ server_stop(struct server *server) {
assert(server->process != PROCESS_NONE); assert(server->process != PROCESS_NONE);
cmd_terminate(server->process);
if (server->tunnel_enabled) { if (server->tunnel_enabled) {
// ignore failure // ignore failure
disable_tunnel(server); disable_tunnel(server);
} }
// Give some delay for the server to terminate properly SDL_WaitThread(server->wait_server_thread, NULL);
sc_mutex_lock(&server->mutex);
bool signaled = false;
if (!server->process_terminated) {
#define WATCHDOG_DELAY_MS 1000
signaled = sc_cond_timedwait(&server->process_terminated_cond,
&server->mutex,
WATCHDOG_DELAY_MS);
}
sc_mutex_unlock(&server->mutex);
// After this delay, kill the server if it's not dead already.
// On some devices, closing the sockets is not sufficient to wake up the
// blocking calls while the device is asleep.
if (!signaled) {
// The process is terminated, but not reaped (closed) yet, so its PID
// is still valid.
LOGW("Killing the server...");
process_terminate(server->process);
}
sc_thread_join(&server->wait_server_thread, NULL);
process_close(server->process);
} }
void void
server_destroy(struct server *server) { server_destroy(struct server *server) {
free(server->serial); SDL_free(server->serial);
sc_cond_destroy(&server->process_terminated_cond);
sc_mutex_destroy(&server->mutex);
} }

View File

@@ -1,36 +1,49 @@
#ifndef SERVER_H #ifndef SERVER_H
#define SERVER_H #define SERVER_H
#include "common.h"
#include <stdatomic.h> #include <stdatomic.h>
#include <stdbool.h> #include <stdbool.h>
#include <stdint.h> #include <stdint.h>
#include <SDL2/SDL_thread.h>
#include "adb.h" #include "config.h"
#include "command.h"
#include "common.h"
#include "scrcpy.h" #include "scrcpy.h"
#include "util/log.h" #include "util/log.h"
#include "util/net.h" #include "util/net.h"
#include "util/thread.h"
struct server { struct server {
char *serial; char *serial;
process_t process; process_t process;
sc_thread wait_server_thread; SDL_Thread *wait_server_thread;
atomic_flag server_socket_closed; atomic_flag server_socket_closed;
sc_mutex mutex;
sc_cond process_terminated_cond;
bool process_terminated;
socket_t server_socket; // only used if !tunnel_forward socket_t server_socket; // only used if !tunnel_forward
socket_t video_socket; socket_t video_socket;
socket_t control_socket; socket_t control_socket;
struct sc_port_range port_range;
uint16_t local_port; // selected from port_range uint16_t local_port; // selected from port_range
bool tunnel_enabled; bool tunnel_enabled;
bool tunnel_forward; // use "adb forward" instead of "adb reverse" bool tunnel_forward; // use "adb forward" instead of "adb reverse"
}; };
#define SERVER_INITIALIZER { \
.serial = NULL, \
.process = PROCESS_NONE, \
.wait_server_thread = NULL, \
.server_socket_closed = ATOMIC_FLAG_INIT, \
.server_socket = INVALID_SOCKET, \
.video_socket = INVALID_SOCKET, \
.control_socket = INVALID_SOCKET, \
.port_range = { \
.first = 0, \
.last = 0, \
}, \
.local_port = 0, \
.tunnel_enabled = false, \
.tunnel_forward = false, \
}
struct server_params { struct server_params {
enum sc_log_level log_level; enum sc_log_level log_level;
const char *crop; const char *crop;
@@ -42,15 +55,14 @@ struct server_params {
uint16_t max_fps; uint16_t max_fps;
int8_t lock_video_orientation; int8_t lock_video_orientation;
bool control; bool control;
uint32_t display_id; uint16_t display_id;
bool show_touches; bool show_touches;
bool stay_awake; bool stay_awake;
bool force_adb_forward; bool force_adb_forward;
bool power_off_on_close;
}; };
// init default values // init default values
bool void
server_init(struct server *server); server_init(struct server *server);
// push, enable tunnel et start the server // push, enable tunnel et start the server

View File

@@ -4,8 +4,12 @@
#include <libavformat/avformat.h> #include <libavformat/avformat.h>
#include <libavutil/time.h> #include <libavutil/time.h>
#include <SDL2/SDL_events.h> #include <SDL2/SDL_events.h>
#include <SDL2/SDL_mutex.h>
#include <SDL2/SDL_thread.h>
#include <unistd.h> #include <unistd.h>
#include "config.h"
#include "compat.h"
#include "decoder.h" #include "decoder.h"
#include "events.h" #include "events.h"
#include "recorder.h" #include "recorder.h"
@@ -66,11 +70,25 @@ notify_stopped(void) {
} }
static bool static bool
push_packet_to_sinks(struct stream *stream, const AVPacket *packet) { process_config_packet(struct stream *stream, AVPacket *packet) {
for (unsigned i = 0; i < stream->sink_count; ++i) { if (stream->recorder && !recorder_push(stream->recorder, packet)) {
struct sc_packet_sink *sink = stream->sinks[i]; LOGE("Could not send config packet to recorder");
if (!sink->ops->push(sink, packet)) { return false;
LOGE("Could not send config packet to sink %d", i); }
return true;
}
static bool
process_frame(struct stream *stream, AVPacket *packet) {
if (stream->decoder && !decoder_push(stream->decoder, packet)) {
return false;
}
if (stream->recorder) {
packet->dts = packet->pts;
if (!recorder_push(stream->recorder, packet)) {
LOGE("Could not send packet to recorder");
return false; return false;
} }
} }
@@ -97,11 +115,9 @@ stream_parse(struct stream *stream, AVPacket *packet) {
packet->flags |= AV_PKT_FLAG_KEY; packet->flags |= AV_PKT_FLAG_KEY;
} }
packet->dts = packet->pts; bool ok = process_frame(stream, packet);
bool ok = push_packet_to_sinks(stream, packet);
if (!ok) { if (!ok) {
LOGE("Could not process packet"); LOGE("Could not process frame");
return false; return false;
} }
@@ -144,7 +160,7 @@ stream_push_packet(struct stream *stream, AVPacket *packet) {
if (is_config) { if (is_config) {
// config packet // config packet
bool ok = push_packet_to_sinks(stream, packet); bool ok = process_config_packet(stream, packet);
if (!ok) { if (!ok) {
return false; return false;
} }
@@ -165,33 +181,6 @@ stream_push_packet(struct stream *stream, AVPacket *packet) {
return true; return true;
} }
static void
stream_close_first_sinks(struct stream *stream, unsigned count) {
while (count) {
struct sc_packet_sink *sink = stream->sinks[--count];
sink->ops->close(sink);
}
}
static inline void
stream_close_sinks(struct stream *stream) {
stream_close_first_sinks(stream, stream->sink_count);
}
static bool
stream_open_sinks(struct stream *stream, const AVCodec *codec) {
for (unsigned i = 0; i < stream->sink_count; ++i) {
struct sc_packet_sink *sink = stream->sinks[i];
if (!sink->ops->open(sink, codec)) {
LOGE("Could not open packet sink %d", i);
stream_close_first_sinks(stream, i);
return false;
}
}
return true;
}
static int static int
run_stream(void *data) { run_stream(void *data) {
struct stream *stream = data; struct stream *stream = data;
@@ -208,15 +197,27 @@ run_stream(void *data) {
goto end; goto end;
} }
if (!stream_open_sinks(stream, codec)) { if (stream->decoder && !decoder_open(stream->decoder, codec)) {
LOGE("Could not open stream sinks"); LOGE("Could not open decoder");
goto finally_free_codec_ctx; goto finally_free_codec_ctx;
} }
if (stream->recorder) {
if (!recorder_open(stream->recorder, codec)) {
LOGE("Could not open recorder");
goto finally_close_decoder;
}
if (!recorder_start(stream->recorder)) {
LOGE("Could not start recorder");
goto finally_close_recorder;
}
}
stream->parser = av_parser_init(AV_CODEC_ID_H264); stream->parser = av_parser_init(AV_CODEC_ID_H264);
if (!stream->parser) { if (!stream->parser) {
LOGE("Could not initialize parser"); LOGE("Could not initialize parser");
goto finally_close_sinks; goto finally_stop_and_join_recorder;
} }
// We must only pass complete frames to av_parser_parse2()! // We must only pass complete frames to av_parser_parse2()!
@@ -246,8 +247,20 @@ run_stream(void *data) {
} }
av_parser_close(stream->parser); av_parser_close(stream->parser);
finally_close_sinks: finally_stop_and_join_recorder:
stream_close_sinks(stream); if (stream->recorder) {
recorder_stop(stream->recorder);
LOGI("Finishing recording...");
recorder_join(stream->recorder);
}
finally_close_recorder:
if (stream->recorder) {
recorder_close(stream->recorder);
}
finally_close_decoder:
if (stream->decoder) {
decoder_close(stream->decoder);
}
finally_free_codec_ctx: finally_free_codec_ctx:
avcodec_free_context(&stream->codec_ctx); avcodec_free_context(&stream->codec_ctx);
end: end:
@@ -256,26 +269,20 @@ end:
} }
void void
stream_init(struct stream *stream, socket_t socket) { stream_init(struct stream *stream, socket_t socket,
struct decoder *decoder, struct recorder *recorder) {
stream->socket = socket; stream->socket = socket;
stream->decoder = decoder,
stream->recorder = recorder;
stream->has_pending = false; stream->has_pending = false;
stream->sink_count = 0;
}
void
stream_add_sink(struct stream *stream, struct sc_packet_sink *sink) {
assert(stream->sink_count < STREAM_MAX_SINKS);
assert(sink);
assert(sink->ops);
stream->sinks[stream->sink_count++] = sink;
} }
bool bool
stream_start(struct stream *stream) { stream_start(struct stream *stream) {
LOGD("Starting stream thread"); LOGD("Starting stream thread");
bool ok = sc_thread_create(&stream->thread, run_stream, "stream", stream); stream->thread = SDL_CreateThread(run_stream, "stream", stream);
if (!ok) { if (!stream->thread) {
LOGC("Could not start stream thread"); LOGC("Could not start stream thread");
return false; return false;
} }
@@ -283,6 +290,13 @@ stream_start(struct stream *stream) {
} }
void void
stream_join(struct stream *stream) { stream_stop(struct stream *stream) {
sc_thread_join(&stream->thread, NULL); if (stream->decoder) {
decoder_interrupt(stream->decoder);
}
}
void
stream_join(struct stream *stream) {
SDL_WaitThread(stream->thread, NULL);
} }

View File

@@ -1,26 +1,22 @@
#ifndef STREAM_H #ifndef STREAM_H
#define STREAM_H #define STREAM_H
#include "common.h"
#include <stdbool.h> #include <stdbool.h>
#include <stdint.h> #include <stdint.h>
#include <libavformat/avformat.h> #include <libavformat/avformat.h>
#include <SDL2/SDL_atomic.h> #include <SDL2/SDL_atomic.h>
#include <SDL2/SDL_thread.h>
#include "trait/packet_sink.h" #include "config.h"
#include "util/net.h" #include "util/net.h"
#include "util/thread.h"
#define STREAM_MAX_SINKS 2 struct video_buffer;
struct stream { struct stream {
socket_t socket; socket_t socket;
sc_thread thread; SDL_Thread *thread;
struct decoder *decoder;
struct sc_packet_sink *sinks[STREAM_MAX_SINKS]; struct recorder *recorder;
unsigned sink_count;
AVCodecContext *codec_ctx; AVCodecContext *codec_ctx;
AVCodecParserContext *parser; AVCodecParserContext *parser;
// successive packets may need to be concatenated, until a non-config // successive packets may need to be concatenated, until a non-config
@@ -30,14 +26,15 @@ struct stream {
}; };
void void
stream_init(struct stream *stream, socket_t socket); stream_init(struct stream *stream, socket_t socket,
struct decoder *decoder, struct recorder *recorder);
void
stream_add_sink(struct stream *stream, struct sc_packet_sink *sink);
bool bool
stream_start(struct stream *stream); stream_start(struct stream *stream);
void
stream_stop(struct stream *stream);
void void
stream_join(struct stream *stream); stream_join(struct stream *stream);

View File

@@ -1,4 +1,17 @@
#include "util/process.h" // for portability (kill, readlink, strdup, strtok_r)
#define _POSIX_C_SOURCE 200809L
#define _BSD_SOURCE
// modern glibc will complain without this
#define _DEFAULT_SOURCE
#ifdef __APPLE__
# define _DARWIN_C_SOURCE // for strdup(), strtok_r(), memset_pattern4()
#endif
#include "command.h"
#include "config.h"
#include <errno.h> #include <errno.h>
#include <fcntl.h> #include <fcntl.h>
@@ -8,13 +21,14 @@
#include <string.h> #include <string.h>
#include <sys/stat.h> #include <sys/stat.h>
#include <sys/types.h> #include <sys/types.h>
#include <sys/stat.h>
#include <sys/wait.h> #include <sys/wait.h>
#include <unistd.h> #include <unistd.h>
#include "util/log.h" #include "util/log.h"
bool bool
search_executable(const char *file) { cmd_search(const char *file) {
char *path = getenv("PATH"); char *path = getenv("PATH");
if (!path) if (!path)
return false; return false;
@@ -50,7 +64,7 @@ search_executable(const char *file) {
} }
enum process_result enum process_result
process_execute(const char *const argv[], pid_t *pid) { cmd_execute(const char *const argv[], pid_t *pid) {
int fd[2]; int fd[2];
if (pipe(fd) == -1) { if (pipe(fd) == -1) {
@@ -112,37 +126,29 @@ end:
} }
bool bool
process_terminate(pid_t pid) { cmd_terminate(pid_t pid) {
if (pid <= 0) { if (pid <= 0) {
LOGC("Requested to kill %d, this is an error. Please report the bug.\n", LOGC("Requested to kill %d, this is an error. Please report the bug.\n",
(int) pid); (int) pid);
abort(); abort();
} }
return kill(pid, SIGKILL) != -1; return kill(pid, SIGTERM) != -1;
} }
exit_code_t bool
process_wait(pid_t pid, bool close) { cmd_simple_wait(pid_t pid, int *exit_code) {
int status;
int code; int code;
int options = WEXITED; if (waitpid(pid, &status, 0) == -1 || !WIFEXITED(status)) {
if (!close) {
options |= WNOWAIT;
}
siginfo_t info;
int r = waitid(P_PID, pid, &info, options);
if (r == -1 || info.si_code != CLD_EXITED) {
// could not wait, or exited unexpectedly, probably by a signal // could not wait, or exited unexpectedly, probably by a signal
code = NO_EXIT_CODE; code = -1;
} else { } else {
code = info.si_status; code = WEXITSTATUS(status);
} }
return code; if (exit_code) {
} *exit_code = code;
}
void return !code;
process_close(pid_t pid) {
process_wait(pid, true); // ignore exit code
} }
char * char *
@@ -156,7 +162,7 @@ get_executable_path(void) {
return NULL; return NULL;
} }
buf[len] = '\0'; buf[len] = '\0';
return strdup(buf); return SDL_strdup(buf);
#else #else
// in practice, we only need this feature for portable builds, only used on // in practice, we only need this feature for portable builds, only used on
// Windows, so we don't care implementing it for every platform // Windows, so we don't care implementing it for every platform

View File

@@ -1,8 +1,8 @@
#include "util/process.h" #include "command.h"
#include <assert.h>
#include <sys/stat.h> #include <sys/stat.h>
#include "config.h"
#include "util/log.h" #include "util/log.h"
#include "util/str_util.h" #include "util/str_util.h"
@@ -21,7 +21,7 @@ build_cmd(char *cmd, size_t len, const char *const argv[]) {
} }
enum process_result enum process_result
process_execute(const char *const argv[], HANDLE *handle) { cmd_execute(const char *const argv[], HANDLE *handle) {
STARTUPINFOW si; STARTUPINFOW si;
PROCESS_INFORMATION pi; PROCESS_INFORMATION pi;
memset(&si, 0, sizeof(si)); memset(&si, 0, sizeof(si));
@@ -39,9 +39,14 @@ process_execute(const char *const argv[], HANDLE *handle) {
return PROCESS_ERROR_GENERIC; return PROCESS_ERROR_GENERIC;
} }
if (!CreateProcessW(NULL, wide, NULL, NULL, FALSE, 0, NULL, NULL, &si, #ifdef WINDOWS_NOCONSOLE
int flags = CREATE_NO_WINDOW;
#else
int flags = 0;
#endif
if (!CreateProcessW(NULL, wide, NULL, NULL, FALSE, flags, NULL, NULL, &si,
&pi)) { &pi)) {
free(wide); SDL_free(wide);
*handle = NULL; *handle = NULL;
if (GetLastError() == ERROR_FILE_NOT_FOUND) { if (GetLastError() == ERROR_FILE_NOT_FOUND) {
return PROCESS_ERROR_MISSING_BINARY; return PROCESS_ERROR_MISSING_BINARY;
@@ -49,35 +54,28 @@ process_execute(const char *const argv[], HANDLE *handle) {
return PROCESS_ERROR_GENERIC; return PROCESS_ERROR_GENERIC;
} }
free(wide); SDL_free(wide);
*handle = pi.hProcess; *handle = pi.hProcess;
return PROCESS_SUCCESS; return PROCESS_SUCCESS;
} }
bool bool
process_terminate(HANDLE handle) { cmd_terminate(HANDLE handle) {
return TerminateProcess(handle, 1); return TerminateProcess(handle, 1) && CloseHandle(handle);
} }
exit_code_t bool
process_wait(HANDLE handle, bool close) { cmd_simple_wait(HANDLE handle, DWORD *exit_code) {
DWORD code; DWORD code;
if (WaitForSingleObject(handle, INFINITE) != WAIT_OBJECT_0 if (WaitForSingleObject(handle, INFINITE) != WAIT_OBJECT_0
|| !GetExitCodeProcess(handle, &code)) { || !GetExitCodeProcess(handle, &code)) {
// could not wait or retrieve the exit code // could not wait or retrieve the exit code
code = NO_EXIT_CODE; // max value, it's unsigned code = -1; // max value, it's unsigned
} }
if (close) { if (exit_code) {
CloseHandle(handle); *exit_code = code;
} }
return code; return !code;
}
void
process_close(HANDLE handle) {
bool closed = CloseHandle(handle);
assert(closed);
(void) closed;
} }
char * char *
@@ -105,7 +103,7 @@ is_regular_file(const char *path) {
struct _stat path_stat; struct _stat path_stat;
int r = _wstat(wide_path, &path_stat); int r = _wstat(wide_path, &path_stat);
free(wide_path); SDL_free(wide_path);
if (r) { if (r) {
perror("stat"); perror("stat");

View File

@@ -6,6 +6,7 @@
#include <stdio.h> #include <stdio.h>
#include <stdlib.h> #include <stdlib.h>
#include "config.h"
#include "util/log.h" #include "util/log.h"
struct index { struct index {

View File

@@ -1,10 +1,10 @@
#ifndef TINYXPM_H #ifndef TINYXPM_H
#define TINYXPM_H #define TINYXPM_H
#include "common.h"
#include <SDL2/SDL.h> #include <SDL2/SDL.h>
#include "config.h"
SDL_Surface * SDL_Surface *
read_xpm(char *xpm[]); read_xpm(char *xpm[]);

View File

@@ -1,24 +0,0 @@
#ifndef SC_FRAME_SINK
#define SC_FRAME_SINK
#include "common.h"
#include <assert.h>
#include <stdbool.h>
/**
* Frame sink trait.
*
* Component able to receive AVFrames should implement this trait.
*/
struct sc_frame_sink {
const struct sc_frame_sink_ops *ops;
};
struct sc_frame_sink_ops {
bool (*open)(struct sc_frame_sink *sink);
void (*close)(struct sc_frame_sink *sink);
bool (*push)(struct sc_frame_sink *sink, const AVFrame *frame);
};
#endif

View File

@@ -1,25 +0,0 @@
#ifndef SC_PACKET_SINK
#define SC_PACKET_SINK
#include "common.h"
#include <assert.h>
#include <stdbool.h>
#include <libavcodec/avcodec.h>
/**
* Packet sink trait.
*
* Component able to receive AVPackets should implement this trait.
*/
struct sc_packet_sink {
const struct sc_packet_sink_ops *ops;
};
struct sc_packet_sink_ops {
bool (*open)(struct sc_packet_sink *sink, const AVCodec *codec);
void (*close)(struct sc_packet_sink *sink);
bool (*push)(struct sc_packet_sink *sink, const AVPacket *packet);
};
#endif

View File

@@ -1,11 +1,11 @@
#ifndef BUFFER_UTIL_H #ifndef BUFFER_UTIL_H
#define BUFFER_UTIL_H #define BUFFER_UTIL_H
#include "common.h"
#include <stdbool.h> #include <stdbool.h>
#include <stdint.h> #include <stdint.h>
#include "config.h"
static inline void static inline void
buffer_write16be(uint8_t *buf, uint16_t value) { buffer_write16be(uint8_t *buf, uint16_t value) {
buf[0] = value >> 8; buf[0] = value >> 8;
@@ -33,7 +33,7 @@ buffer_read16be(const uint8_t *buf) {
static inline uint32_t static inline uint32_t
buffer_read32be(const uint8_t *buf) { buffer_read32be(const uint8_t *buf) {
return ((uint32_t) buf[0] << 24) | (buf[1] << 16) | (buf[2] << 8) | buf[3]; return (buf[0] << 24) | (buf[1] << 16) | (buf[2] << 8) | buf[3];
} }
static inline uint64_t static inline uint64_t

View File

@@ -2,11 +2,11 @@
#ifndef CBUF_H #ifndef CBUF_H
#define CBUF_H #define CBUF_H
#include "common.h"
#include <stdbool.h> #include <stdbool.h>
#include <unistd.h> #include <unistd.h>
#include "config.h"
// To define a circular buffer type of 20 ints: // To define a circular buffer type of 20 ints:
// struct cbuf_int CBUF(int, 20); // struct cbuf_int CBUF(int, 20);
// //

74
app/src/util/lock.h Normal file
View File

@@ -0,0 +1,74 @@
#ifndef LOCK_H
#define LOCK_H
#include <stdint.h>
#include <SDL2/SDL_mutex.h>
#include "config.h"
#include "log.h"
static inline void
mutex_lock(SDL_mutex *mutex) {
int r = SDL_LockMutex(mutex);
#ifndef NDEBUG
if (r) {
LOGC("Could not lock mutex: %s", SDL_GetError());
abort();
}
#else
(void) r;
#endif
}
static inline void
mutex_unlock(SDL_mutex *mutex) {
int r = SDL_UnlockMutex(mutex);
#ifndef NDEBUG
if (r) {
LOGC("Could not unlock mutex: %s", SDL_GetError());
abort();
}
#else
(void) r;
#endif
}
static inline void
cond_wait(SDL_cond *cond, SDL_mutex *mutex) {
int r = SDL_CondWait(cond, mutex);
#ifndef NDEBUG
if (r) {
LOGC("Could not wait on condition: %s", SDL_GetError());
abort();
}
#else
(void) r;
#endif
}
static inline int
cond_wait_timeout(SDL_cond *cond, SDL_mutex *mutex, uint32_t ms) {
int r = SDL_CondWaitTimeout(cond, mutex, ms);
#ifndef NDEBUG
if (r < 0) {
LOGC("Could not wait on condition with timeout: %s", SDL_GetError());
abort();
}
#endif
return r;
}
static inline void
cond_signal(SDL_cond *cond) {
int r = SDL_CondSignal(cond);
#ifndef NDEBUG
if (r) {
LOGC("Could not signal a condition: %s", SDL_GetError());
abort();
}
#else
(void) r;
#endif
}
#endif

View File

@@ -3,6 +3,7 @@
#include <stdio.h> #include <stdio.h>
#include <SDL2/SDL_platform.h> #include <SDL2/SDL_platform.h>
#include "config.h"
#include "log.h" #include "log.h"
#ifdef __WINDOWS__ #ifdef __WINDOWS__

View File

@@ -1,8 +1,6 @@
#ifndef NET_H #ifndef NET_H
#define NET_H #define NET_H
#include "common.h"
#include <stdbool.h> #include <stdbool.h>
#include <stdint.h> #include <stdint.h>
#include <SDL2/SDL_platform.h> #include <SDL2/SDL_platform.h>
@@ -19,6 +17,8 @@
typedef int socket_t; typedef int socket_t;
#endif #endif
#include "config.h"
bool bool
net_init(void); net_init(void);

View File

@@ -1,21 +0,0 @@
#include "process.h"
#include "log.h"
bool
process_check_success(process_t proc, const char *name, bool close) {
if (proc == PROCESS_NONE) {
LOGE("Could not execute \"%s\"", name);
return false;
}
exit_code_t exit_code = process_wait(proc, close);
if (exit_code) {
if (exit_code != NO_EXIT_CODE) {
LOGE("\"%s\" returned with value %" PRIexitcode, name, exit_code);
} else {
LOGE("\"%s\" exited unexpectedly", name);
}
return false;
}
return true;
}

View File

@@ -2,12 +2,12 @@
#ifndef QUEUE_H #ifndef QUEUE_H
#define QUEUE_H #define QUEUE_H
#include "common.h"
#include <assert.h> #include <assert.h>
#include <stdbool.h> #include <stdbool.h>
#include <stddef.h> #include <stddef.h>
#include "config.h"
// To define a queue type of "struct foo": // To define a queue type of "struct foo":
// struct queue_foo QUEUE(struct foo); // struct queue_foo QUEUE(struct foo);
#define QUEUE(TYPE) { \ #define QUEUE(TYPE) { \

View File

@@ -10,6 +10,10 @@
# include <tchar.h> # include <tchar.h>
#endif #endif
#include <SDL2/SDL_stdinc.h>
#include "config.h"
size_t size_t
xstrncpy(char *dest, const char *src, size_t n) { xstrncpy(char *dest, const char *src, size_t n) {
size_t i; size_t i;
@@ -47,7 +51,7 @@ truncated:
char * char *
strquote(const char *src) { strquote(const char *src) {
size_t len = strlen(src); size_t len = strlen(src);
char *quoted = malloc(len + 3); char *quoted = SDL_malloc(len + 3);
if (!quoted) { if (!quoted) {
return NULL; return NULL;
} }
@@ -165,7 +169,7 @@ utf8_to_wide_char(const char *utf8) {
return NULL; return NULL;
} }
wchar_t *wide = malloc(len * sizeof(wchar_t)); wchar_t *wide = SDL_malloc(len * sizeof(wchar_t));
if (!wide) { if (!wide) {
return NULL; return NULL;
} }
@@ -181,7 +185,7 @@ utf8_from_wide_char(const wchar_t *ws) {
return NULL; return NULL;
} }
char *utf8 = malloc(len); char *utf8 = SDL_malloc(len);
if (!utf8) { if (!utf8) {
return NULL; return NULL;
} }

View File

@@ -1,11 +1,11 @@
#ifndef STRUTIL_H #ifndef STRUTIL_H
#define STRUTIL_H #define STRUTIL_H
#include "common.h"
#include <stdbool.h> #include <stdbool.h>
#include <stddef.h> #include <stddef.h>
#include "config.h"
// like strncpy, except: // like strncpy, except:
// - it copies at most n-1 chars // - it copies at most n-1 chars
// - the dest string is nul-terminated // - the dest string is nul-terminated

View File

@@ -1,160 +0,0 @@
#include "thread.h"
#include <assert.h>
#include <SDL2/SDL_thread.h>
#include "log.h"
bool
sc_thread_create(sc_thread *thread, sc_thread_fn fn, const char *name,
void *userdata) {
SDL_Thread *sdl_thread = SDL_CreateThread(fn, name, userdata);
if (!sdl_thread) {
return false;
}
thread->thread = sdl_thread;
return true;
}
void
sc_thread_join(sc_thread *thread, int *status) {
SDL_WaitThread(thread->thread, status);
}
bool
sc_mutex_init(sc_mutex *mutex) {
SDL_mutex *sdl_mutex = SDL_CreateMutex();
if (!sdl_mutex) {
return false;
}
mutex->mutex = sdl_mutex;
#ifndef NDEBUG
mutex->locker = 0;
#endif
return true;
}
void
sc_mutex_destroy(sc_mutex *mutex) {
SDL_DestroyMutex(mutex->mutex);
}
void
sc_mutex_lock(sc_mutex *mutex) {
// SDL mutexes are recursive, but we don't want to use recursive mutexes
assert(!sc_mutex_held(mutex));
int r = SDL_LockMutex(mutex->mutex);
#ifndef NDEBUG
if (r) {
LOGC("Could not lock mutex: %s", SDL_GetError());
abort();
}
mutex->locker = sc_thread_get_id();
#else
(void) r;
#endif
}
void
sc_mutex_unlock(sc_mutex *mutex) {
#ifndef NDEBUG
assert(sc_mutex_held(mutex));
mutex->locker = 0;
#endif
int r = SDL_UnlockMutex(mutex->mutex);
#ifndef NDEBUG
if (r) {
LOGC("Could not lock mutex: %s", SDL_GetError());
abort();
}
#else
(void) r;
#endif
}
sc_thread_id
sc_thread_get_id(void) {
return SDL_ThreadID();
}
#ifndef NDEBUG
bool
sc_mutex_held(struct sc_mutex *mutex) {
return mutex->locker == sc_thread_get_id();
}
#endif
bool
sc_cond_init(sc_cond *cond) {
SDL_cond *sdl_cond = SDL_CreateCond();
if (!sdl_cond) {
return false;
}
cond->cond = sdl_cond;
return true;
}
void
sc_cond_destroy(sc_cond *cond) {
SDL_DestroyCond(cond->cond);
}
void
sc_cond_wait(sc_cond *cond, sc_mutex *mutex) {
int r = SDL_CondWait(cond->cond, mutex->mutex);
#ifndef NDEBUG
if (r) {
LOGC("Could not wait on condition: %s", SDL_GetError());
abort();
}
mutex->locker = sc_thread_get_id();
#else
(void) r;
#endif
}
bool
sc_cond_timedwait(sc_cond *cond, sc_mutex *mutex, uint32_t ms) {
int r = SDL_CondWaitTimeout(cond->cond, mutex->mutex, ms);
#ifndef NDEBUG
if (r < 0) {
LOGC("Could not wait on condition with timeout: %s", SDL_GetError());
abort();
}
mutex->locker = sc_thread_get_id();
#endif
assert(r == 0 || r == SDL_MUTEX_TIMEDOUT);
return r == 0;
}
void
sc_cond_signal(sc_cond *cond) {
int r = SDL_CondSignal(cond->cond);
#ifndef NDEBUG
if (r) {
LOGC("Could not signal a condition: %s", SDL_GetError());
abort();
}
#else
(void) r;
#endif
}
void
sc_cond_broadcast(sc_cond *cond) {
int r = SDL_CondBroadcast(cond->cond);
#ifndef NDEBUG
if (r) {
LOGC("Could not broadcast a condition: %s", SDL_GetError());
abort();
}
#else
(void) r;
#endif
}

View File

@@ -1,81 +0,0 @@
#ifndef SC_THREAD_H
#define SC_THREAD_H
#include "common.h"
#include <stdbool.h>
#include <stdint.h>
/* Forward declarations */
typedef struct SDL_Thread SDL_Thread;
typedef struct SDL_mutex SDL_mutex;
typedef struct SDL_cond SDL_cond;
typedef int sc_thread_fn(void *);
typedef unsigned int sc_thread_id;
typedef struct sc_thread {
SDL_Thread *thread;
} sc_thread;
typedef struct sc_mutex {
SDL_mutex *mutex;
#ifndef NDEBUG
sc_thread_id locker;
#endif
} sc_mutex;
typedef struct sc_cond {
SDL_cond *cond;
} sc_cond;
bool
sc_thread_create(sc_thread *thread, sc_thread_fn fn, const char *name,
void *userdata);
void
sc_thread_join(sc_thread *thread, int *status);
bool
sc_mutex_init(sc_mutex *mutex);
void
sc_mutex_destroy(sc_mutex *mutex);
void
sc_mutex_lock(sc_mutex *mutex);
void
sc_mutex_unlock(sc_mutex *mutex);
sc_thread_id
sc_thread_get_id(void);
#ifndef NDEBUG
bool
sc_mutex_held(struct sc_mutex *mutex);
# define sc_mutex_assert(mutex) assert(sc_mutex_held(mutex))
#else
# define sc_mutex_assert(mutex)
#endif
bool
sc_cond_init(sc_cond *cond);
void
sc_cond_destroy(sc_cond *cond);
void
sc_cond_wait(sc_cond *cond, sc_mutex *mutex);
// return true on signaled, false on timeout
bool
sc_cond_timedwait(sc_cond *cond, sc_mutex *mutex, uint32_t ms);
void
sc_cond_signal(sc_cond *cond);
void
sc_cond_broadcast(sc_cond *cond);
#endif

View File

@@ -1,88 +1,113 @@
#include "video_buffer.h" #include "video_buffer.h"
#include <assert.h> #include <assert.h>
#include <SDL2/SDL_mutex.h>
#include <libavutil/avutil.h> #include <libavutil/avutil.h>
#include <libavformat/avformat.h> #include <libavformat/avformat.h>
#include "config.h"
#include "util/lock.h"
#include "util/log.h" #include "util/log.h"
bool bool
video_buffer_init(struct video_buffer *vb) { video_buffer_init(struct video_buffer *vb, struct fps_counter *fps_counter,
vb->pending_frame = av_frame_alloc(); bool render_expired_frames) {
if (!vb->pending_frame) { vb->fps_counter = fps_counter;
return false;
if (!(vb->decoding_frame = av_frame_alloc())) {
goto error_0;
} }
vb->tmp_frame = av_frame_alloc(); if (!(vb->rendering_frame = av_frame_alloc())) {
if (!vb->tmp_frame) { goto error_1;
av_frame_free(&vb->pending_frame);
return false;
} }
bool ok = sc_mutex_init(&vb->mutex); if (!(vb->mutex = SDL_CreateMutex())) {
if (!ok) { goto error_2;
av_frame_free(&vb->pending_frame);
av_frame_free(&vb->tmp_frame);
return false;
} }
// there is initially no frame, so consider it has already been consumed vb->render_expired_frames = render_expired_frames;
vb->pending_frame_consumed = true; if (render_expired_frames) {
if (!(vb->rendering_frame_consumed_cond = SDL_CreateCond())) {
SDL_DestroyMutex(vb->mutex);
goto error_2;
}
// interrupted is not used if expired frames are not rendered
// since offering a frame will never block
vb->interrupted = false;
}
// there is initially no rendering frame, so consider it has already been
// consumed
vb->rendering_frame_consumed = true;
return true; return true;
error_2:
av_frame_free(&vb->rendering_frame);
error_1:
av_frame_free(&vb->decoding_frame);
error_0:
return false;
} }
void void
video_buffer_destroy(struct video_buffer *vb) { video_buffer_destroy(struct video_buffer *vb) {
sc_mutex_destroy(&vb->mutex); if (vb->render_expired_frames) {
av_frame_free(&vb->pending_frame); SDL_DestroyCond(vb->rendering_frame_consumed_cond);
av_frame_free(&vb->tmp_frame); }
SDL_DestroyMutex(vb->mutex);
av_frame_free(&vb->rendering_frame);
av_frame_free(&vb->decoding_frame);
} }
static inline void static void
swap_frames(AVFrame **lhs, AVFrame **rhs) { video_buffer_swap_frames(struct video_buffer *vb) {
AVFrame *tmp = *lhs; AVFrame *tmp = vb->decoding_frame;
*lhs = *rhs; vb->decoding_frame = vb->rendering_frame;
*rhs = tmp; vb->rendering_frame = tmp;
}
bool
video_buffer_push(struct video_buffer *vb, const AVFrame *frame,
bool *previous_frame_skipped) {
sc_mutex_lock(&vb->mutex);
// Use a temporary frame to preserve pending_frame in case of error.
// tmp_frame is an empty frame, no need to call av_frame_unref() beforehand.
int r = av_frame_ref(vb->tmp_frame, frame);
if (r) {
LOGE("Could not ref frame: %d", r);
return false;
}
// Now that av_frame_ref() succeeded, we can replace the previous
// pending_frame
swap_frames(&vb->pending_frame, &vb->tmp_frame);
av_frame_unref(vb->tmp_frame);
if (previous_frame_skipped) {
*previous_frame_skipped = !vb->pending_frame_consumed;
}
vb->pending_frame_consumed = false;
sc_mutex_unlock(&vb->mutex);
return true;
} }
void void
video_buffer_consume(struct video_buffer *vb, AVFrame *dst) { video_buffer_offer_decoded_frame(struct video_buffer *vb,
sc_mutex_lock(&vb->mutex); bool *previous_frame_skipped) {
assert(!vb->pending_frame_consumed); mutex_lock(vb->mutex);
vb->pending_frame_consumed = true; if (vb->render_expired_frames) {
// wait for the current (expired) frame to be consumed
while (!vb->rendering_frame_consumed && !vb->interrupted) {
cond_wait(vb->rendering_frame_consumed_cond, vb->mutex);
}
} else if (!vb->rendering_frame_consumed) {
fps_counter_add_skipped_frame(vb->fps_counter);
}
av_frame_move_ref(dst, vb->pending_frame); video_buffer_swap_frames(vb);
// av_frame_move_ref() resets its source frame, so no need to call
// av_frame_unref()
sc_mutex_unlock(&vb->mutex); *previous_frame_skipped = !vb->rendering_frame_consumed;
vb->rendering_frame_consumed = false;
mutex_unlock(vb->mutex);
}
const AVFrame *
video_buffer_consume_rendered_frame(struct video_buffer *vb) {
assert(!vb->rendering_frame_consumed);
vb->rendering_frame_consumed = true;
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);
}
return vb->rendering_frame;
}
void
video_buffer_interrupt(struct video_buffer *vb) {
if (vb->render_expired_frames) {
mutex_lock(vb->mutex);
vb->interrupted = true;
mutex_unlock(vb->mutex);
// wake up blocking wait
cond_signal(vb->rendering_frame_consumed_cond);
}
} }

View File

@@ -1,50 +1,49 @@
#ifndef VIDEO_BUFFER_H #ifndef VIDEO_BUFFER_H
#define VIDEO_BUFFER_H #define VIDEO_BUFFER_H
#include "common.h"
#include <stdbool.h> #include <stdbool.h>
#include <SDL2/SDL_mutex.h>
#include "config.h"
#include "fps_counter.h" #include "fps_counter.h"
#include "util/thread.h"
// forward declarations // forward declarations
typedef struct AVFrame AVFrame; typedef struct AVFrame AVFrame;
/**
* A video buffer holds 1 pending frame, which is the last frame received from
* the producer (typically, the decoder).
*
* If a pending frame has not been consumed when the producer pushes a new
* frame, then it is lost. The intent is to always provide access to the very
* last frame to minimize latency.
*
* The producer and the consumer typically do not live in the same thread.
* That's the reason why the callback on_frame_available() does not provide the
* frame as parameter: the consumer might post an event to its own thread to
* retrieve the pending frame from there, and that frame may have changed since
* the callback if producer pushed a new one in between.
*/
struct video_buffer { struct video_buffer {
AVFrame *pending_frame; AVFrame *decoding_frame;
AVFrame *tmp_frame; // To preserve the pending frame on error AVFrame *rendering_frame;
SDL_mutex *mutex;
sc_mutex mutex; bool render_expired_frames;
bool interrupted;
bool pending_frame_consumed; SDL_cond *rendering_frame_consumed_cond;
bool rendering_frame_consumed;
struct fps_counter *fps_counter;
}; };
bool bool
video_buffer_init(struct video_buffer *vb); video_buffer_init(struct video_buffer *vb, struct fps_counter *fps_counter,
bool render_expired_frames);
void void
video_buffer_destroy(struct video_buffer *vb); video_buffer_destroy(struct video_buffer *vb);
bool // set the decoded frame as ready for rendering
video_buffer_push(struct video_buffer *vb, const AVFrame *frame, bool *skipped); // this function locks frames->mutex during its execution
// the output flag is set to report whether the previous frame has been skipped
void void
video_buffer_consume(struct video_buffer *vb, AVFrame *dst); video_buffer_offer_decoded_frame(struct video_buffer *vb,
bool *previous_frame_skipped);
// mark the rendering frame as consumed and return it
// MUST be called with frames->mutex locked!!!
// the caller is expected to render the returned frame to some texture before
// unlocking frames->mutex
const AVFrame *
video_buffer_consume_rendered_frame(struct video_buffer *vb);
// wake up and avoid any blocking call
void
video_buffer_interrupt(struct video_buffer *vb);
#endif #endif

View File

@@ -1,5 +1,3 @@
#include "common.h"
#include <assert.h> #include <assert.h>
#include "util/buffer_util.h" #include "util/buffer_util.h"

View File

@@ -1,5 +1,3 @@
#include "common.h"
#include <assert.h> #include <assert.h>
#include <string.h> #include <string.h>

View File

@@ -1,9 +1,8 @@
#include "common.h"
#include <assert.h> #include <assert.h>
#include <string.h> #include <string.h>
#include "cli.h" #include "cli.h"
#include "common.h"
#include "scrcpy.h" #include "scrcpy.h"
static void test_flag_version(void) { static void test_flag_version(void) {
@@ -58,6 +57,7 @@ static void test_options(void) {
"--push-target", "/sdcard/Movies", "--push-target", "/sdcard/Movies",
"--record", "file", "--record", "file",
"--record-format", "mkv", "--record-format", "mkv",
"--render-expired-frames",
"--serial", "0123456789abcdef", "--serial", "0123456789abcdef",
"--show-touches", "--show-touches",
"--turn-screen-off", "--turn-screen-off",
@@ -86,6 +86,7 @@ static void test_options(void) {
assert(!strcmp(opts->push_target, "/sdcard/Movies")); assert(!strcmp(opts->push_target, "/sdcard/Movies"));
assert(!strcmp(opts->record_filename, "file")); assert(!strcmp(opts->record_filename, "file"));
assert(opts->record_format == SC_RECORD_FORMAT_MKV); assert(opts->record_format == SC_RECORD_FORMAT_MKV);
assert(opts->render_expired_frames);
assert(!strcmp(opts->serial, "0123456789abcdef")); assert(!strcmp(opts->serial, "0123456789abcdef"));
assert(opts->show_touches); assert(opts->show_touches);
assert(opts->turn_screen_off); assert(opts->turn_screen_off);

View File

@@ -1,5 +1,3 @@
#include "common.h"
#include <assert.h> #include <assert.h>
#include <string.h> #include <string.h>
@@ -17,7 +15,7 @@ static void test_serialize_inject_keycode(void) {
}; };
unsigned char buf[CONTROL_MSG_MAX_SIZE]; unsigned char buf[CONTROL_MSG_MAX_SIZE];
size_t size = control_msg_serialize(&msg, buf); int size = control_msg_serialize(&msg, buf);
assert(size == 14); assert(size == 14);
const unsigned char expected[] = { const unsigned char expected[] = {
@@ -39,7 +37,7 @@ static void test_serialize_inject_text(void) {
}; };
unsigned char buf[CONTROL_MSG_MAX_SIZE]; unsigned char buf[CONTROL_MSG_MAX_SIZE];
size_t size = control_msg_serialize(&msg, buf); int size = control_msg_serialize(&msg, buf);
assert(size == 18); assert(size == 18);
const unsigned char expected[] = { const unsigned char expected[] = {
@@ -59,7 +57,7 @@ static void test_serialize_inject_text_long(void) {
msg.inject_text.text = text; msg.inject_text.text = text;
unsigned char buf[CONTROL_MSG_MAX_SIZE]; unsigned char buf[CONTROL_MSG_MAX_SIZE];
size_t size = control_msg_serialize(&msg, buf); int size = control_msg_serialize(&msg, buf);
assert(size == 5 + CONTROL_MSG_INJECT_TEXT_MAX_LENGTH); assert(size == 5 + CONTROL_MSG_INJECT_TEXT_MAX_LENGTH);
unsigned char expected[5 + CONTROL_MSG_INJECT_TEXT_MAX_LENGTH]; unsigned char expected[5 + CONTROL_MSG_INJECT_TEXT_MAX_LENGTH];
@@ -95,7 +93,7 @@ static void test_serialize_inject_touch_event(void) {
}; };
unsigned char buf[CONTROL_MSG_MAX_SIZE]; unsigned char buf[CONTROL_MSG_MAX_SIZE];
size_t size = control_msg_serialize(&msg, buf); int size = control_msg_serialize(&msg, buf);
assert(size == 28); assert(size == 28);
const unsigned char expected[] = { const unsigned char expected[] = {
@@ -130,7 +128,7 @@ static void test_serialize_inject_scroll_event(void) {
}; };
unsigned char buf[CONTROL_MSG_MAX_SIZE]; unsigned char buf[CONTROL_MSG_MAX_SIZE];
size_t size = control_msg_serialize(&msg, buf); int size = control_msg_serialize(&msg, buf);
assert(size == 21); assert(size == 21);
const unsigned char expected[] = { const unsigned char expected[] = {
@@ -149,7 +147,7 @@ static void test_serialize_back_or_screen_on(void) {
}; };
unsigned char buf[CONTROL_MSG_MAX_SIZE]; unsigned char buf[CONTROL_MSG_MAX_SIZE];
size_t size = control_msg_serialize(&msg, buf); int size = control_msg_serialize(&msg, buf);
assert(size == 1); assert(size == 1);
const unsigned char expected[] = { const unsigned char expected[] = {
@@ -164,7 +162,7 @@ static void test_serialize_expand_notification_panel(void) {
}; };
unsigned char buf[CONTROL_MSG_MAX_SIZE]; unsigned char buf[CONTROL_MSG_MAX_SIZE];
size_t size = control_msg_serialize(&msg, buf); int size = control_msg_serialize(&msg, buf);
assert(size == 1); assert(size == 1);
const unsigned char expected[] = { const unsigned char expected[] = {
@@ -179,7 +177,7 @@ static void test_serialize_collapse_notification_panel(void) {
}; };
unsigned char buf[CONTROL_MSG_MAX_SIZE]; unsigned char buf[CONTROL_MSG_MAX_SIZE];
size_t size = control_msg_serialize(&msg, buf); int size = control_msg_serialize(&msg, buf);
assert(size == 1); assert(size == 1);
const unsigned char expected[] = { const unsigned char expected[] = {
@@ -194,7 +192,7 @@ static void test_serialize_get_clipboard(void) {
}; };
unsigned char buf[CONTROL_MSG_MAX_SIZE]; unsigned char buf[CONTROL_MSG_MAX_SIZE];
size_t size = control_msg_serialize(&msg, buf); int size = control_msg_serialize(&msg, buf);
assert(size == 1); assert(size == 1);
const unsigned char expected[] = { const unsigned char expected[] = {
@@ -213,7 +211,7 @@ static void test_serialize_set_clipboard(void) {
}; };
unsigned char buf[CONTROL_MSG_MAX_SIZE]; unsigned char buf[CONTROL_MSG_MAX_SIZE];
size_t size = control_msg_serialize(&msg, buf); int size = control_msg_serialize(&msg, buf);
assert(size == 19); assert(size == 19);
const unsigned char expected[] = { const unsigned char expected[] = {
@@ -234,7 +232,7 @@ static void test_serialize_set_screen_power_mode(void) {
}; };
unsigned char buf[CONTROL_MSG_MAX_SIZE]; unsigned char buf[CONTROL_MSG_MAX_SIZE];
size_t size = control_msg_serialize(&msg, buf); int size = control_msg_serialize(&msg, buf);
assert(size == 2); assert(size == 2);
const unsigned char expected[] = { const unsigned char expected[] = {
@@ -250,7 +248,7 @@ static void test_serialize_rotate_device(void) {
}; };
unsigned char buf[CONTROL_MSG_MAX_SIZE]; unsigned char buf[CONTROL_MSG_MAX_SIZE];
size_t size = control_msg_serialize(&msg, buf); int size = control_msg_serialize(&msg, buf);
assert(size == 1); assert(size == 1);
const unsigned char expected[] = { const unsigned char expected[] = {

View File

@@ -1,5 +1,3 @@
#include "common.h"
#include <assert.h> #include <assert.h>
#include <string.h> #include <string.h>

View File

@@ -1,5 +1,3 @@
#include "common.h"
#include <assert.h> #include <assert.h>
#include "util/queue.h" #include "util/queue.h"

View File

@@ -1,9 +1,8 @@
#include "common.h"
#include <assert.h> #include <assert.h>
#include <limits.h> #include <limits.h>
#include <stdio.h> #include <stdio.h>
#include <string.h> #include <string.h>
#include <SDL2/SDL.h>
#include "util/str_util.h" #include "util/str_util.h"
@@ -137,7 +136,7 @@ static void test_strquote(void) {
// add '"' at the beginning and the end // add '"' at the beginning and the end
assert(!strcmp("\"abcde\"", out)); assert(!strcmp("\"abcde\"", out));
free(out); SDL_free(out);
} }
static void test_utf8_truncate(void) { static void test_utf8_truncate(void) {

View File

@@ -2,11 +2,11 @@
[binaries] [binaries]
name = 'mingw' name = 'mingw'
c = 'i686-w64-mingw32-gcc' c = '/usr/bin/i686-w64-mingw32-gcc'
cpp = 'i686-w64-mingw32-g++' cpp = '/usr/bin/i686-w64-mingw32-g++'
ar = 'i686-w64-mingw32-ar' ar = '/usr/bin/i686-w64-mingw32-ar'
strip = 'i686-w64-mingw32-strip' strip = '/usr/bin/i686-w64-mingw32-strip'
pkgconfig = 'i686-w64-mingw32-pkg-config' pkgconfig = '/usr/bin/i686-w64-mingw32-pkg-config'
[host_machine] [host_machine]
system = 'windows' system = 'windows'
@@ -17,4 +17,4 @@ endian = 'little'
[properties] [properties]
prebuilt_ffmpeg_shared = 'ffmpeg-4.3.1-win32-shared' prebuilt_ffmpeg_shared = 'ffmpeg-4.3.1-win32-shared'
prebuilt_ffmpeg_dev = 'ffmpeg-4.3.1-win32-dev' prebuilt_ffmpeg_dev = 'ffmpeg-4.3.1-win32-dev'
prebuilt_sdl2 = 'SDL2-2.0.14/i686-w64-mingw32' prebuilt_sdl2 = 'SDL2-2.0.12/i686-w64-mingw32'

View File

@@ -2,11 +2,11 @@
[binaries] [binaries]
name = 'mingw' name = 'mingw'
c = 'x86_64-w64-mingw32-gcc' c = '/usr/bin/x86_64-w64-mingw32-gcc'
cpp = 'x86_64-w64-mingw32-g++' cpp = '/usr/bin/x86_64-w64-mingw32-g++'
ar = 'x86_64-w64-mingw32-ar' ar = '/usr/bin/x86_64-w64-mingw32-ar'
strip = 'x86_64-w64-mingw32-strip' strip = '/usr/bin/x86_64-w64-mingw32-strip'
pkgconfig = 'x86_64-w64-mingw32-pkg-config' pkgconfig = '/usr/bin/x86_64-w64-mingw32-pkg-config'
[host_machine] [host_machine]
system = 'windows' system = 'windows'
@@ -17,4 +17,4 @@ endian = 'little'
[properties] [properties]
prebuilt_ffmpeg_shared = 'ffmpeg-4.3.1-win64-shared' prebuilt_ffmpeg_shared = 'ffmpeg-4.3.1-win64-shared'
prebuilt_ffmpeg_dev = 'ffmpeg-4.3.1-win64-dev' prebuilt_ffmpeg_dev = 'ffmpeg-4.3.1-win64-dev'
prebuilt_sdl2 = 'SDL2-2.0.14/x86_64-w64-mingw32' prebuilt_sdl2 = 'SDL2-2.0.12/x86_64-w64-mingw32'

View File

@@ -1,7 +0,0 @@
strCommand = "cmd /c scrcpy.exe"
For Each Arg In WScript.Arguments
strCommand = strCommand & " """ & replace(Arg, """", """""""""") & """"
Next
CreateObject("Wscript.Shell").Run strCommand, 0, false

View File

@@ -1,10 +1,9 @@
project('scrcpy', 'c', project('scrcpy', 'c',
version: '1.17', version: '1.16',
meson_version: '>= 0.48', meson_version: '>= 0.48',
default_options: [ default_options: [
'c_std=c11', 'c_std=c11',
'warning_level=2', 'warning_level=2',
'b_ndebug=if-release',
]) ])
if get_option('compile_app') if get_option('compile_app')

View File

@@ -1,7 +1,9 @@
option('compile_app', type: 'boolean', value: true, description: 'Build the client') option('compile_app', type: 'boolean', value: true, description: 'Build the client')
option('compile_server', type: 'boolean', value: true, description: 'Build the server') option('compile_server', type: 'boolean', value: true, description: 'Build the server')
option('crossbuild_windows', type: 'boolean', value: false, description: 'Build for Windows from Linux') 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('prebuilt_server', type: 'string', description: 'Path of the prebuilt server')
option('portable', type: 'boolean', value: false, description: 'Use scrcpy-server from the same directory as the scrcpy executable') option('portable', type: 'boolean', value: false, description: 'Use scrcpy-server from the same directory as the scrcpy executable')
option('hidpi_support', type: 'boolean', value: true, description: 'Enable High DPI support')
option('server_debugger', type: 'boolean', value: false, description: 'Run a server debugger and wait for a client to be attached') option('server_debugger', type: 'boolean', value: false, description: 'Run a server debugger and wait for a client to be attached')
option('server_debugger_method', type: 'combo', choices: ['old', 'new'], value: 'new', description: 'Select the debugger method (Android < 9: "old", Android >= 9: "new")') option('server_debugger_method', type: 'combo', choices: ['old', 'new'], value: 'new', description: 'Select the debugger method (Android < 9: "old", Android >= 9: "new")')

View File

@@ -30,9 +30,9 @@ prepare-ffmpeg-dev-win64:
ffmpeg-4.3.1-win64-dev ffmpeg-4.3.1-win64-dev
prepare-sdl2: prepare-sdl2:
@./prepare-dep https://libsdl.org/release/SDL2-devel-2.0.14-mingw.tar.gz \ @./prepare-dep https://libsdl.org/release/SDL2-devel-2.0.12-mingw.tar.gz \
405eaff3eb18f2e08fe669ef9e63bc9a8710b7d343756f238619761e9b60407d \ e614a60f797e35ef9f3f96aef3dc6a1d786de3cc7ca6216f97e435c0b6aafc46 \
SDL2-2.0.14 SDL2-2.0.12
prepare-adb: prepare-adb:
@./prepare-dep https://dl.google.com/android/repository/platform-tools_r30.0.5-windows.zip \ @./prepare-dep https://dl.google.com/android/repository/platform-tools_r30.0.5-windows.zip \

View File

@@ -1,2 +1,44 @@
#!/bin/bash #!/bin/bash
make -f release.mk set -e
# test locally
TESTDIR=build_test
rm -rf "$TESTDIR"
# run client tests with ASAN enabled
meson "$TESTDIR" -Db_sanitize=address
ninja -C"$TESTDIR" test
# test server
GRADLE=${GRADLE:-./gradlew}
$GRADLE -p server check
BUILDDIR=build_release
rm -rf "$BUILDDIR"
meson "$BUILDDIR" --buildtype release --strip -Db_lto=true
cd "$BUILDDIR"
ninja
cd -
# build Windows releases
make -f Makefile.CrossWindows
# the generated server must be the same everywhere
cmp "$BUILDDIR/server/scrcpy-server" dist/scrcpy-win32/scrcpy-server
cmp "$BUILDDIR/server/scrcpy-server" dist/scrcpy-win64/scrcpy-server
# get version name
TAG=$(git describe --tags --always)
# create release directory
mkdir -p "release-$TAG"
cp "$BUILDDIR/server/scrcpy-server" "release-$TAG/scrcpy-server-$TAG"
cp "dist/scrcpy-win32-$TAG.zip" "release-$TAG/"
cp "dist/scrcpy-win64-$TAG.zip" "release-$TAG/"
# generate checksums
cd "release-$TAG"
sha256sum "scrcpy-server-$TAG" \
"scrcpy-win32-$TAG.zip" \
"scrcpy-win64-$TAG.zip" > SHA256SUMS.txt
echo "Release generated in release-$TAG/"

View File

@@ -6,8 +6,8 @@ android {
applicationId "com.genymobile.scrcpy" applicationId "com.genymobile.scrcpy"
minSdkVersion 21 minSdkVersion 21
targetSdkVersion 30 targetSdkVersion 30
versionCode 20 versionCode 19
versionName "1.17" versionName "1.16"
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
} }
buildTypes { buildTypes {

View File

@@ -12,7 +12,7 @@
set -e set -e
SCRCPY_DEBUG=false SCRCPY_DEBUG=false
SCRCPY_VERSION_NAME=1.17 SCRCPY_VERSION_NAME=1.16
PLATFORM=${ANDROID_PLATFORM:-30} PLATFORM=${ANDROID_PLATFORM:-30}
BUILD_TOOLS=${ANDROID_BUILD_TOOLS:-30.0.0} BUILD_TOOLS=${ANDROID_BUILD_TOOLS:-30.0.0}

View File

@@ -19,21 +19,19 @@ public final class CleanUp {
// not instantiable // not instantiable
} }
public static void configure(boolean disableShowTouches, int restoreStayOn, boolean restoreNormalPowerMode, boolean powerOffScreen, int displayId) public static void configure(boolean disableShowTouches, int restoreStayOn, boolean restoreNormalPowerMode) throws IOException {
throws IOException { boolean needProcess = disableShowTouches || restoreStayOn != -1 || restoreNormalPowerMode;
boolean needProcess = disableShowTouches || restoreStayOn != -1 || restoreNormalPowerMode || powerOffScreen;
if (needProcess) { if (needProcess) {
startProcess(disableShowTouches, restoreStayOn, restoreNormalPowerMode, powerOffScreen, displayId); startProcess(disableShowTouches, restoreStayOn, restoreNormalPowerMode);
} else { } else {
// There is no additional clean up to do when scrcpy dies // There is no additional clean up to do when scrcpy dies
unlinkSelf(); unlinkSelf();
} }
} }
private static void startProcess(boolean disableShowTouches, int restoreStayOn, boolean restoreNormalPowerMode, boolean powerOffScreen, private static void startProcess(boolean disableShowTouches, int restoreStayOn, boolean restoreNormalPowerMode) throws IOException {
int displayId) throws IOException {
String[] cmd = {"app_process", "/", CleanUp.class.getName(), String.valueOf(disableShowTouches), String.valueOf( String[] cmd = {"app_process", "/", CleanUp.class.getName(), String.valueOf(disableShowTouches), String.valueOf(
restoreStayOn), String.valueOf(restoreNormalPowerMode), String.valueOf(powerOffScreen), String.valueOf(displayId)}; restoreStayOn), String.valueOf(restoreNormalPowerMode)};
ProcessBuilder builder = new ProcessBuilder(cmd); ProcessBuilder builder = new ProcessBuilder(cmd);
builder.environment().put("CLASSPATH", SERVER_PATH); builder.environment().put("CLASSPATH", SERVER_PATH);
@@ -63,8 +61,6 @@ public final class CleanUp {
boolean disableShowTouches = Boolean.parseBoolean(args[0]); boolean disableShowTouches = Boolean.parseBoolean(args[0]);
int restoreStayOn = Integer.parseInt(args[1]); int restoreStayOn = Integer.parseInt(args[1]);
boolean restoreNormalPowerMode = Boolean.parseBoolean(args[2]); boolean restoreNormalPowerMode = Boolean.parseBoolean(args[2]);
boolean powerOffScreen = Boolean.parseBoolean(args[3]);
int displayId = Integer.parseInt(args[4]);
if (disableShowTouches || restoreStayOn != -1) { if (disableShowTouches || restoreStayOn != -1) {
ServiceManager serviceManager = new ServiceManager(); ServiceManager serviceManager = new ServiceManager();
@@ -80,12 +76,9 @@ public final class CleanUp {
} }
} }
if (Device.isScreenOn()) { if (restoreNormalPowerMode) {
if (powerOffScreen) { Ln.i("Restoring normal power mode");
Ln.i("Power off screen"); if (Device.isScreenOn()) {
Device.powerOffScreen(displayId);
} else if (restoreNormalPowerMode) {
Ln.i("Restoring normal power mode");
Device.setScreenPowerMode(Device.POWER_MODE_NORMAL); Device.setScreenPowerMode(Device.POWER_MODE_NORMAL);
} }
} }

View File

@@ -14,7 +14,7 @@ import java.util.concurrent.TimeUnit;
public class Controller { public class Controller {
private static final int DEFAULT_DEVICE_ID = 0; private static final int DEVICE_ID_VIRTUAL = -1;
private static final ScheduledExecutorService EXECUTOR = Executors.newSingleThreadScheduledExecutor(); private static final ScheduledExecutorService EXECUTOR = Executors.newSingleThreadScheduledExecutor();
@@ -45,7 +45,7 @@ public class Controller {
MotionEvent.PointerCoords coords = new MotionEvent.PointerCoords(); MotionEvent.PointerCoords coords = new MotionEvent.PointerCoords();
coords.orientation = 0; coords.orientation = 0;
coords.size = 0; coords.size = 1;
pointerProperties[i] = props; pointerProperties[i] = props;
pointerCoords[i] = coords; pointerCoords[i] = coords;
@@ -208,13 +208,9 @@ public class Controller {
// Right-click and middle-click only work if the source is a mouse // Right-click and middle-click only work if the source is a mouse
boolean nonPrimaryButtonPressed = (buttons & ~MotionEvent.BUTTON_PRIMARY) != 0; boolean nonPrimaryButtonPressed = (buttons & ~MotionEvent.BUTTON_PRIMARY) != 0;
int source = nonPrimaryButtonPressed ? InputDevice.SOURCE_MOUSE : InputDevice.SOURCE_TOUCHSCREEN; int source = nonPrimaryButtonPressed ? InputDevice.SOURCE_MOUSE : InputDevice.SOURCE_TOUCHSCREEN;
if (source != InputDevice.SOURCE_MOUSE) {
// Buttons must not be set for touch events
buttons = 0;
}
MotionEvent event = MotionEvent MotionEvent event = MotionEvent
.obtain(lastTouchDown, now, action, pointerCount, pointerProperties, pointerCoords, 0, buttons, 1f, 1f, DEFAULT_DEVICE_ID, 0, source, .obtain(lastTouchDown, now, action, pointerCount, pointerProperties, pointerCoords, 0, buttons, 1f, 1f, DEVICE_ID_VIRTUAL, 0, source,
0); 0);
return device.injectEvent(event); return device.injectEvent(event);
} }
@@ -237,7 +233,7 @@ public class Controller {
coords.setAxisValue(MotionEvent.AXIS_VSCROLL, vScroll); coords.setAxisValue(MotionEvent.AXIS_VSCROLL, vScroll);
MotionEvent event = MotionEvent MotionEvent event = MotionEvent
.obtain(lastTouchDown, now, MotionEvent.ACTION_SCROLL, 1, pointerProperties, pointerCoords, 0, 0, 1f, 1f, DEFAULT_DEVICE_ID, 0, .obtain(lastTouchDown, now, MotionEvent.ACTION_SCROLL, 1, pointerProperties, pointerCoords, 0, 0, 1f, 1f, DEVICE_ID_VIRTUAL, 0,
InputDevice.SOURCE_TOUCHSCREEN, 0); InputDevice.SOURCE_TOUCHSCREEN, 0);
return device.injectEvent(event); return device.injectEvent(event);
} }

View File

@@ -153,17 +153,13 @@ public final class Device {
return Build.MODEL; return Build.MODEL;
} }
public static boolean supportsInputEvents(int displayId) {
return displayId == 0 || Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q;
}
public boolean supportsInputEvents() { public boolean supportsInputEvents() {
return supportsInputEvents; return supportsInputEvents;
} }
public static boolean injectEvent(InputEvent inputEvent, int mode, int displayId) { public boolean injectEvent(InputEvent inputEvent, int mode) {
if (!supportsInputEvents(displayId)) { if (!supportsInputEvents()) {
return false; throw new AssertionError("Could not inject input event if !supportsInputEvents()");
} }
if (displayId != 0 && !InputManager.setDisplayId(inputEvent, displayId)) { if (displayId != 0 && !InputManager.setDisplayId(inputEvent, displayId)) {
@@ -173,29 +169,10 @@ public final class Device {
return SERVICE_MANAGER.getInputManager().injectInputEvent(inputEvent, mode); return SERVICE_MANAGER.getInputManager().injectInputEvent(inputEvent, mode);
} }
public boolean injectEvent(InputEvent inputEvent, int mode) {
if (!supportsInputEvents()) {
throw new AssertionError("Could not inject input event if !supportsInputEvents()");
}
return injectEvent(inputEvent, mode, displayId);
}
public static boolean injectEventOnDisplay(InputEvent event, int displayId) {
return injectEvent(event, InputManager.INJECT_INPUT_EVENT_MODE_ASYNC, displayId);
}
public boolean injectEvent(InputEvent event) { public boolean injectEvent(InputEvent event) {
return injectEvent(event, InputManager.INJECT_INPUT_EVENT_MODE_ASYNC); return injectEvent(event, InputManager.INJECT_INPUT_EVENT_MODE_ASYNC);
} }
public static boolean injectKeyEvent(int action, int keyCode, int repeat, int metaState, int displayId) {
long now = SystemClock.uptimeMillis();
KeyEvent event = new KeyEvent(now, now, action, keyCode, repeat, metaState, KeyCharacterMap.VIRTUAL_KEYBOARD, 0, 0,
InputDevice.SOURCE_KEYBOARD);
return injectEventOnDisplay(event, displayId);
}
public boolean injectKeyEvent(int action, int keyCode, int repeat, int metaState) { public boolean injectKeyEvent(int action, int keyCode, int repeat, int metaState) {
long now = SystemClock.uptimeMillis(); long now = SystemClock.uptimeMillis();
KeyEvent event = new KeyEvent(now, now, action, keyCode, repeat, metaState, KeyCharacterMap.VIRTUAL_KEYBOARD, 0, 0, KeyEvent event = new KeyEvent(now, now, action, keyCode, repeat, metaState, KeyCharacterMap.VIRTUAL_KEYBOARD, 0, 0,
@@ -203,10 +180,6 @@ public final class Device {
return injectEvent(event); return injectEvent(event);
} }
public static boolean injectKeycode(int keyCode, int displayId) {
return injectKeyEvent(KeyEvent.ACTION_DOWN, keyCode, 0, 0, displayId) && injectKeyEvent(KeyEvent.ACTION_UP, keyCode, 0, 0, displayId);
}
public boolean injectKeycode(int keyCode) { public boolean injectKeycode(int keyCode) {
return injectKeyEvent(KeyEvent.ACTION_DOWN, keyCode, 0, 0) && injectKeyEvent(KeyEvent.ACTION_UP, keyCode, 0, 0); return injectKeyEvent(KeyEvent.ACTION_DOWN, keyCode, 0, 0) && injectKeyEvent(KeyEvent.ACTION_UP, keyCode, 0, 0);
} }
@@ -276,13 +249,6 @@ public final class Device {
return SurfaceControl.setDisplayPowerMode(d, mode); return SurfaceControl.setDisplayPowerMode(d, mode);
} }
public static boolean powerOffScreen(int displayId) {
if (!isScreenOn()) {
return true;
}
return injectKeycode(KeyEvent.KEYCODE_POWER, displayId);
}
/** /**
* Disable auto-rotation (if enabled), set the screen rotation and re-enable auto-rotation (if it was enabled). * Disable auto-rotation (if enabled), set the screen rotation and re-enable auto-rotation (if it was enabled).
*/ */

View File

@@ -17,7 +17,6 @@ public class Options {
private boolean stayAwake; private boolean stayAwake;
private String codecOptions; private String codecOptions;
private String encoderName; private String encoderName;
private boolean powerOffScreenOnClose;
public Ln.Level getLogLevel() { public Ln.Level getLogLevel() {
return logLevel; return logLevel;
@@ -130,12 +129,4 @@ public class Options {
public void setEncoderName(String encoderName) { public void setEncoderName(String encoderName) {
this.encoderName = encoderName; this.encoderName = encoderName;
} }
public void setPowerOffScreenOnClose(boolean powerOffScreenOnClose) {
this.powerOffScreenOnClose = powerOffScreenOnClose;
}
public boolean getPowerOffScreenOnClose() {
return this.powerOffScreenOnClose;
}
} }

View File

@@ -50,7 +50,7 @@ public final class Server {
} }
} }
CleanUp.configure(mustDisableShowTouchesOnCleanUp, restoreStayOn, true, options.getPowerOffScreenOnClose(), options.getDisplayId()); CleanUp.configure(mustDisableShowTouchesOnCleanUp, restoreStayOn, true);
boolean tunnelForward = options.isTunnelForward(); boolean tunnelForward = options.isTunnelForward();
@@ -58,14 +58,12 @@ public final class Server {
ScreenEncoder screenEncoder = new ScreenEncoder(options.getSendFrameMeta(), options.getBitRate(), options.getMaxFps(), codecOptions, ScreenEncoder screenEncoder = new ScreenEncoder(options.getSendFrameMeta(), options.getBitRate(), options.getMaxFps(), codecOptions,
options.getEncoderName()); options.getEncoderName());
Thread controllerThread = null;
Thread deviceMessageSenderThread = null;
if (options.getControl()) { if (options.getControl()) {
final Controller controller = new Controller(device, connection); final Controller controller = new Controller(device, connection);
// asynchronous // asynchronous
controllerThread = startController(controller); startController(controller);
deviceMessageSenderThread = startDeviceMessageSender(controller.getSender()); startDeviceMessageSender(controller.getSender());
device.setClipboardListener(new Device.ClipboardListener() { device.setClipboardListener(new Device.ClipboardListener() {
@Override @Override
@@ -81,19 +79,12 @@ public final class Server {
} catch (IOException e) { } catch (IOException e) {
// this is expected on close // this is expected on close
Ln.d("Screen streaming stopped"); Ln.d("Screen streaming stopped");
} finally {
if (controllerThread != null) {
controllerThread.interrupt();
}
if (deviceMessageSenderThread != null) {
deviceMessageSenderThread.interrupt();
}
} }
} }
} }
private static Thread startController(final Controller controller) { private static void startController(final Controller controller) {
Thread thread = new Thread(new Runnable() { new Thread(new Runnable() {
@Override @Override
public void run() { public void run() {
try { try {
@@ -103,13 +94,11 @@ public final class Server {
Ln.d("Controller stopped"); Ln.d("Controller stopped");
} }
} }
}); }).start();
thread.start();
return thread;
} }
private static Thread startDeviceMessageSender(final DeviceMessageSender sender) { private static void startDeviceMessageSender(final DeviceMessageSender sender) {
Thread thread = new Thread(new Runnable() { new Thread(new Runnable() {
@Override @Override
public void run() { public void run() {
try { try {
@@ -119,9 +108,7 @@ public final class Server {
Ln.d("Device message sender stopped"); Ln.d("Device message sender stopped");
} }
} }
}); }).start();
thread.start();
return thread;
} }
private static Options createOptions(String... args) { private static Options createOptions(String... args) {
@@ -135,7 +122,7 @@ public final class Server {
"The server version (" + BuildConfig.VERSION_NAME + ") does not match the client " + "(" + clientVersion + ")"); "The server version (" + BuildConfig.VERSION_NAME + ") does not match the client " + "(" + clientVersion + ")");
} }
final int expectedParameters = 16; final int expectedParameters = 15;
if (args.length != expectedParameters) { if (args.length != expectedParameters) {
throw new IllegalArgumentException("Expecting " + expectedParameters + " parameters"); throw new IllegalArgumentException("Expecting " + expectedParameters + " parameters");
} }
@@ -185,9 +172,6 @@ public final class Server {
String encoderName = "-".equals(args[14]) ? null : args[14]; String encoderName = "-".equals(args[14]) ? null : args[14];
options.setEncoderName(encoderName); options.setEncoderName(encoderName);
boolean powerOffScreenOnClose = Boolean.parseBoolean(args[15]);
options.setPowerOffScreenOnClose(powerOffScreenOnClose);
return options; return options;
} }
@@ -233,7 +217,7 @@ public final class Server {
if (encoders != null && encoders.length > 0) { if (encoders != null && encoders.length > 0) {
Ln.e("Try to use one of the available encoders:"); Ln.e("Try to use one of the available encoders:");
for (MediaCodecInfo encoder : encoders) { for (MediaCodecInfo encoder : encoders) {
Ln.e(" scrcpy --encoder '" + encoder.getName() + "'"); Ln.e(" scrcpy --encoder-name '" + encoder.getName() + "'");
} }
} }
} }