Compare commits

..

1 Commits

Author SHA1 Message Date
e_vigurskiy
429153abfb Add display id parameter
Add --display command line parameter to specify a display id.
2020-03-28 23:56:37 +01:00
43 changed files with 294 additions and 991 deletions

View File

@@ -1,25 +0,0 @@
---
name: Bug report
about: Create a report to help us improve
title: ''
labels: ''
assignees: ''
---
- [ ] I have read the [FAQ](https://github.com/Genymobile/scrcpy/blob/master/FAQ.md).
- [ ] I have searched in existing [issues](https://github.com/Genymobile/scrcpy/issues).
**Environment**
- OS: [e.g. Debian, Windows, macOS...]
- scrcpy version: [e.g. 1.12.1]
- installation method: [e.g. manual build, apt, snap, brew, Windows release...]
- device model:
- Android version: [e.g. 10]
**Describe the bug**
A clear and concise description of what the bug is.
On errors, please provide the output of the console (and `adb logcat` if relevant).
Format them between code blocks (delimited by ```).
Please do not post screenshots of your terminal, just post the content as text instead.

View File

@@ -1,22 +0,0 @@
---
name: Feature request
about: Suggest an idea for this project
title: ''
labels: ''
assignees: ''
---
- [ ] I have checked that a similar [feature request](https://github.com/Genymobile/scrcpy/issues?q=is%3Aopen+is%3Aissue+label%3A%22feature+request%22) does not already exist.
**Is your feature request related to a problem? Please describe.**
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
**Describe the solution you'd like**
A clear and concise description of what you want to happen.
**Describe alternatives you've considered**
A clear and concise description of any alternative solutions or features you've considered.
**Additional context**
Add any other context or screenshots about the feature request here.

1
.gitignore vendored
View File

@@ -2,4 +2,3 @@ build/
/dist/ /dist/
.idea/ .idea/
.gradle/ .gradle/
/x/

View File

@@ -8,22 +8,6 @@ case, use the [prebuilt server] (so you will not need Java or the Android SDK).
[prebuilt server]: #prebuilt-server [prebuilt server]: #prebuilt-server
## Branches
### `master`
The `master` branch concerns the latest release, and is the home page of the
project on Github.
### `dev`
`dev` is the current development branch. Every commit present in `dev` will be
in the next release.
If you want to contribute code, please base your commits on the latest `dev`
branch.
## Requirements ## Requirements
@@ -263,6 +247,3 @@ meson x --buildtype release --strip -Db_lto=true \
ninja -Cx ninja -Cx
sudo ninja -Cx install sudo ninja -Cx install
``` ```
The server only works with a matching client version (this server works with the
`master` branch).

167
FAQ.md
View File

@@ -3,102 +3,19 @@
Here are the common reported problems and their status. Here are the common reported problems and their status.
## `adb` issues ### On Windows, my device is not detected
`scrcpy` execute `adb` commands to initialize the connection with the device. If The most common is your device not being detected by `adb`, or is unauthorized.
`adb` fails, then scrcpy will not work. Check everything is ok by calling:
In that case, it will print this error: adb devices
> ERROR: "adb push" returned with value 1 Windows may need some [drivers] to detect your device.
This is typically not a bug in _scrcpy_, but a problem in your environment.
To find out the cause, execute:
```bash
adb devices
```
### `adb` not found
You need `adb` accessible from your `PATH`.
On Windows, the current directory is in your `PATH`, and `adb.exe` is included
in the release, so it should work out-of-the-box.
### Device unauthorized
Check [stackoverflow][device-unauthorized].
[device-unauthorized]: https://stackoverflow.com/questions/23081263/adb-android-device-unauthorized
### Device not detected
If your device is not detected, you may need some [drivers] (on Windows).
[drivers]: https://developer.android.com/studio/run/oem-usb.html [drivers]: https://developer.android.com/studio/run/oem-usb.html
### Several devices connected ### I can only mirror, I cannot interact with the device
If several devices are connected, you will encounter this error:
> adb: error: failed to get feature set: more than one device/emulator
the identifier of the device you want to mirror must be provided:
```bash
scrcpy -s 01234567890abcdef
```
Note that if your device is connected over TCP/IP, you'll get this message:
> adb: error: more than one device/emulator
> ERROR: "adb reverse" returned with value 1
> WARN: 'adb reverse' failed, fallback to 'adb forward'
This is expected (due to a bug on old Android versions, see [#5]), but in that
case, scrcpy fallbacks to a different method, which should work.
[#5]: https://github.com/Genymobile/scrcpy/issues/5
### Conflicts between adb versions
> adb server version (41) doesn't match this client (39); killing...
This error occurs when you use several `adb` versions simultaneously. You must
find the program using a different `adb` version, and use the same `adb` version
everywhere.
You could overwrite the `adb` binary in the other program, or ask _scrcpy_ to
use a specific `adb` binary, by setting the `ADB` environment variable:
```bash
set ADB=/path/to/your/adb
scrcpy
```
### Device disconnected
If _scrcpy_ stops itself with the warning "Device disconnected", then the
`adb` connection has been closed.
Try with another USB cable or plug it into another USB port. See [#281] and
[#283].
[#281]: https://github.com/Genymobile/scrcpy/issues/281
[#283]: https://github.com/Genymobile/scrcpy/issues/283
## Control issues
### Mouse and keyboard do not work
On some devices, you may need to enable an option to allow [simulating input]. On some devices, you may need to enable an option to allow [simulating input].
In developer options, enable: In developer options, enable:
@@ -112,43 +29,22 @@ In developer options, enable:
### Mouse clicks at wrong location ### Mouse clicks at wrong location
On MacOS, with HiDPI support and multiple screens, input location are wrongly On MacOS, with HiDPI support and multiple screens, input location are wrongly
scaled. See [#15]. scaled. See [issue 15].
[#15]: https://github.com/Genymobile/scrcpy/issues/15 [issue 15]: https://github.com/Genymobile/scrcpy/issues/15
Open _scrcpy_ directly on the monitor you use it. A workaround is to build with HiDPI support disabled:
```bash
### Special characters do not work meson x --buildtype release -Dhidpi_support=false
Injecting text input is [limited to ASCII characters][text-input]. A trick
allows to also inject some [accented characters][accented-characters], but
that's all. See [#37].
[text-input]: https://github.com/Genymobile/scrcpy/issues?q=is%3Aopen+is%3Aissue+label%3Aunicode
[accented-characters]: https://blog.rom1v.com/2018/03/introducing-scrcpy/#handle-accented-characters
[#37]: https://github.com/Genymobile/scrcpy/issues/37
## Client issues
### The quality is low
If the definition of your client window is smaller than that of your device
screen, then you might get poor quality, especially visible on text (see [#40]).
[#40]: https://github.com/Genymobile/scrcpy/issues/40
To improve downscaling quality, trilinear filtering is enabled automatically
if the renderer is OpenGL and if it supports mipmapping.
On Windows, you might want to force OpenGL:
```
scrcpy --render-driver=opengl
``` ```
You may also need to configure the [scaling behavior]: However, the video will be displayed at lower resolution.
### The quality is low on HiDPI display
On Windows, you may need to configure the [scaling behavior].
> `scrcpy.exe` > Properties > Compatibility > Change high DPI settings > > `scrcpy.exe` > Properties > Compatibility > Change high DPI settings >
> Override high DPI scaling behavior > Scaling performed by: _Application_. > Override high DPI scaling behavior > Scaling performed by: _Application_.
@@ -156,7 +52,6 @@ You may also need to configure the [scaling behavior]:
[scaling behavior]: https://github.com/Genymobile/scrcpy/issues/40#issuecomment-424466723 [scaling behavior]: https://github.com/Genymobile/scrcpy/issues/40#issuecomment-424466723
### KWin compositor crashes ### KWin compositor crashes
On Plasma Desktop, compositor is disabled while _scrcpy_ is running. On Plasma Desktop, compositor is disabled while _scrcpy_ is running.
@@ -166,29 +61,19 @@ As a workaround, [disable "Block compositing"][kwin].
[kwin]: https://github.com/Genymobile/scrcpy/issues/114#issuecomment-378778613 [kwin]: https://github.com/Genymobile/scrcpy/issues/114#issuecomment-378778613
## Crashes ### I get an error "Could not open video stream"
### Exception
There may be many reasons. One common cause is that the hardware encoder of your There may be many reasons. One common cause is that the hardware encoder of your
device is not able to encode at the given definition: device is not able to encode at the given definition:
> ``` ```
> ERROR: Exception on thread Thread[main,5,main] ERROR: Exception on thread Thread[main,5,main]
> android.media.MediaCodec$CodecException: Error 0xfffffc0e android.media.MediaCodec$CodecException: Error 0xfffffc0e
> ... ...
> Exit due to uncaughtException in main thread: Exit due to uncaughtException in main thread:
> ERROR: Could not open video stream ERROR: Could not open video stream
> INFO: Initial texture: 1080x2336 INFO: Initial texture: 1080x2336
> ``` ```
or
> ```
> ERROR: Exception on thread Thread[main,5,main]
> java.lang.IllegalStateException
> at android.media.MediaCodec.native_dequeueOutputBuffer(Native Method)
> ```
Just try with a lower definition: Just try with a lower definition:

View File

@@ -66,12 +66,15 @@ hard).
### Windows ### Windows
For Windows, for simplicity, a prebuilt archive with all the dependencies For Windows, for simplicity, prebuilt archives with all the dependencies
(including `adb`) is available: (including `adb`) are available:
- [`scrcpy-win32-v1.12.1.zip`][direct-win32]
_(SHA-256: 0f4b3b063536b50a2df05dc42c760f9cc0093a9a26dbdf02d8232c74dab43480)_
- [`scrcpy-win64-v1.12.1.zip`][direct-win64] - [`scrcpy-win64-v1.12.1.zip`][direct-win64]
_(SHA-256: 57d34b6d16cfd9fe169bc37c4df58ebd256d05c1ea3febc63d9cb0a027ab47c9)_ _(SHA-256: 57d34b6d16cfd9fe169bc37c4df58ebd256d05c1ea3febc63d9cb0a027ab47c9)_
[direct-win32]: https://github.com/Genymobile/scrcpy/releases/download/v1.12.1/scrcpy-win32-v1.12.1.zip
[direct-win64]: https://github.com/Genymobile/scrcpy/releases/download/v1.12.1/scrcpy-win64-v1.12.1.zip [direct-win64]: https://github.com/Genymobile/scrcpy/releases/download/v1.12.1/scrcpy-win64-v1.12.1.zip
It is also available in [Chocolatey]: It is also available in [Chocolatey]:
@@ -80,18 +83,14 @@ It is also available in [Chocolatey]:
```bash ```bash
choco install scrcpy choco install scrcpy
choco install adb # if you don't have it yet
``` ```
And in [Scoop]: You need `adb`, accessible from your `PATH`. If you don't have it yet:
```bash ```bash
scoop install scrcpy choco install adb
scoop install adb # if you don't have it yet
``` ```
[Scoop]: https://scoop.sh
You can also [build the app manually][BUILD]. You can also [build the app manually][BUILD].
@@ -341,33 +340,6 @@ scrcpy -f # short version
Fullscreen can then be toggled dynamically with `Ctrl`+`f`. Fullscreen can then be toggled dynamically with `Ctrl`+`f`.
#### Rotation
The window may be rotated:
```bash
scrcpy --rotation 1
```
Possibles values are:
- `0`: no rotation
- `1`: 90 degrees counterclockwise
- `2`: 180 degrees
- `3`: 90 degrees clockwise
The rotation can also be changed dynamically with `Ctrl`+`←` _(left)_ and
`Ctrl`+`→` _(right)_.
Note that _scrcpy_ manages 3 different rotations:
- `Ctrl`+`r` requests the device to switch between portrait and landscape (the
current running app may refuse, if it does support the requested
orientation).
- `--lock-video-orientation` changes the mirroring orientation (the orientation
of the video sent from the device to the computer). This affects the
recording.
- `--rotation` (or `Ctrl`+`←`/`Ctrl`+`→`) rotates only the window content. This
affects only the display, not the recording.
### Other mirroring options ### Other mirroring options
@@ -519,8 +491,6 @@ Also see [issue #14].
| Action | Shortcut | Shortcut (macOS) | Action | Shortcut | Shortcut (macOS)
| -------------------------------------- |:----------------------------- |:----------------------------- | -------------------------------------- |:----------------------------- |:-----------------------------
| Switch fullscreen mode | `Ctrl`+`f` | `Cmd`+`f` | Switch fullscreen mode | `Ctrl`+`f` | `Cmd`+`f`
| Rotate display left | `Ctrl`+`←` _(left)_ | `Cmd`+`←` _(left)_
| Rotate display right | `Ctrl`+`→` _(right)_ | `Cmd`+`→` _(right)_
| Resize window to 1:1 (pixel-perfect) | `Ctrl`+`g` | `Cmd`+`g` | Resize window to 1:1 (pixel-perfect) | `Ctrl`+`g` | `Cmd`+`g`
| Resize window to remove black borders | `Ctrl`+`x` \| _Double-click¹_ | `Cmd`+`x` \| _Double-click¹_ | Resize window to remove black borders | `Ctrl`+`x` \| _Double-click¹_ | `Cmd`+`x` \| _Double-click¹_
| Click on `HOME` | `Ctrl`+`h` \| _Middle-click_ | `Ctrl`+`h` \| _Middle-click_ | Click on `HOME` | `Ctrl`+`h` \| _Middle-click_ | `Ctrl`+`h` \| _Middle-click_

View File

@@ -11,7 +11,6 @@ src = [
'src/file_handler.c', 'src/file_handler.c',
'src/fps_counter.c', 'src/fps_counter.c',
'src/input_manager.c', 'src/input_manager.c',
'src/opengl.c',
'src/receiver.c', 'src/receiver.c',
'src/recorder.c', 'src/recorder.c',
'src/scrcpy.c', 'src/scrcpy.c',

View File

@@ -52,7 +52,7 @@ Print this help.
.TP .TP
.BI "\-\-lock\-video\-orientation " value .BI "\-\-lock\-video\-orientation " value
Lock video orientation to \fIvalue\fR. Possible values are -1 (unlocked), 0, 1, 2 and 3. Natural device orientation is 0, and each increment adds a 90 degrees otation counterclockwise. Lock video orientation to \fIvalue\fR. Values are integers in the range [-1..3]. Natural device orientation is 0 and each increment adds 90 degrees counterclockwise.
Default is -1 (unlocked). Default is -1 (unlocked).
@@ -74,10 +74,6 @@ Disable device control (mirror the device in read\-only).
.B \-N, \-\-no\-display .B \-N, \-\-no\-display
Do not display device (only when screen recording is enabled). Do not display device (only when screen recording is enabled).
.TP
.B \-\-no\-mipmaps
If the renderer is OpenGL 3.0+ or OpenGL ES 2.0+, then mipmaps are automatically generated to improve downscaling quality. This option disables the generation of mipmaps.
.TP .TP
.BI "\-p, \-\-port " port[:port] .BI "\-p, \-\-port " port[:port]
Set the TCP port (range) used by the client to listen. Set the TCP port (range) used by the client to listen.
@@ -110,22 +106,10 @@ option if set, or by the file extension (.mp4 or .mkv).
.BI "\-\-record\-format " format .BI "\-\-record\-format " format
Force recording format (either mp4 or mkv). Force recording format (either mp4 or mkv).
.TP
.BI "\-\-render\-driver " name
Request SDL to use the given render driver (this is just a hint).
Supported names are currently "direct3d", "opengl", "opengles2", "opengles", "metal" and "software".
.UR https://wiki.libsdl.org/SDL_HINT_RENDER_DRIVER
.UE
.TP .TP
.B \-\-render\-expired\-frames .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. 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
.BI "\-\-rotation " value
Set the initial display rotation. Possibles values are 0, 1, 2 and 3. Each increment adds a 90 degrees rotation counterclockwise.
.TP .TP
.BI "\-s, \-\-serial " number .BI "\-s, \-\-serial " number
The device serial number. Mandatory only if several devices are connected to adb. The device serial number. Mandatory only if several devices are connected to adb.
@@ -180,23 +164,15 @@ Default is 0 (automatic).\n
.TP .TP
.B Ctrl+f .B Ctrl+f
Switch fullscreen mode switch fullscreen mode
.TP
.B Ctrl+Left
Rotate display left
.TP
.B Ctrl+Right
Rotate display right
.TP .TP
.B Ctrl+g .B Ctrl+g
Resize window to 1:1 (pixel\-perfect) resize window to 1:1 (pixel\-perfect)
.TP .TP
.B Ctrl+x, Double\-click on black borders .B Ctrl+x, Double\-click on black borders
Resize window to remove black borders resize window to remove black borders
.TP .TP
.B Ctrl+h, Home, Middle\-click .B Ctrl+h, Home, Middle\-click
@@ -228,43 +204,43 @@ Click on POWER (turn screen on/off)
.TP .TP
.B Right\-click (when screen is off) .B Right\-click (when screen is off)
Turn screen on turn screen on
.TP .TP
.B Ctrl+o .B Ctrl+o
Turn device screen off (keep mirroring) turn device screen off (keep mirroring)
.TP .TP
.B Ctrl+r .B Ctrl+r
Rotate device screen rotate device screen
.TP .TP
.B Ctrl+n .B Ctrl+n
Expand notification panel expand notification panel
.TP .TP
.B Ctrl+Shift+n .B Ctrl+Shift+n
Collapse notification panel collapse notification panel
.TP .TP
.B Ctrl+c .B Ctrl+c
Copy device clipboard to computer copy device clipboard to computer
.TP .TP
.B Ctrl+v .B Ctrl+v
Paste computer clipboard to device paste computer clipboard to device
.TP .TP
.B Ctrl+Shift+v .B Ctrl+Shift+v
Copy computer clipboard to device copy computer clipboard to device
.TP .TP
.B Ctrl+i .B Ctrl+i
Enable/disable FPS counter (print frames/second in logs) enable/disable FPS counter (print frames/second in logs)
.TP .TP
.B Drag & drop APK file .B Drag & drop APK file
Install APK from computer install APK from computer
.SH Environment variables .SH Environment variables

View File

@@ -52,10 +52,9 @@ scrcpy_print_usage(const char *arg0) {
" Print this help.\n" " Print this help.\n"
"\n" "\n"
" --lock-video-orientation value\n" " --lock-video-orientation value\n"
" Lock video orientation to value.\n" " Lock video orientation to value. Values are integers in the\n"
" Possible values are -1 (unlocked), 0, 1, 2 and 3.\n" " range [-1..3]. Natural device orientation is 0 and each\n"
" Natural device orientation is 0, and each increment adds a\n" " increment adds 90 degrees counterclockwise.\n"
" 90 degrees rotation counterclockwise.\n"
" Default is %d%s.\n" " Default is %d%s.\n"
"\n" "\n"
" --max-fps value\n" " --max-fps value\n"
@@ -75,11 +74,6 @@ scrcpy_print_usage(const char *arg0) {
" Do not display device (only when screen recording is\n" " Do not display device (only when screen recording is\n"
" enabled).\n" " enabled).\n"
"\n" "\n"
" --no-mipmaps\n"
" If the renderer is OpenGL 3.0+ or OpenGL ES 2.0+, then\n"
" mipmaps are automatically generated to improve downscaling\n"
" quality. This option disables the generation of mipmaps.\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 %d:%d.\n" " Default is %d:%d.\n"
@@ -104,24 +98,12 @@ scrcpy_print_usage(const char *arg0) {
" --record-format format\n" " --record-format format\n"
" Force recording format (either mp4 or mkv).\n" " Force recording format (either mp4 or mkv).\n"
"\n" "\n"
" --render-driver name\n"
" Request SDL to use the given render driver (this is just a\n"
" hint).\n"
" Supported names are currently \"direct3d\", \"opengl\",\n"
" \"opengles2\", \"opengles\", \"metal\" and \"software\".\n"
" <https://wiki.libsdl.org/SDL_HINT_RENDER_DRIVER>\n"
"\n"
" --render-expired-frames\n" " --render-expired-frames\n"
" By default, to minimize latency, scrcpy always renders the\n" " By default, to minimize latency, scrcpy always renders the\n"
" last available decoded frame, and drops any previous ones.\n" " last available decoded frame, and drops any previous ones.\n"
" This flag forces to render all frames, at a cost of a\n" " This flag forces to render all frames, at a cost of a\n"
" possible increased latency.\n" " possible increased latency.\n"
"\n" "\n"
" --rotation value\n"
" Set the initial display rotation.\n"
" Possibles values are 0, 1, 2 and 3. Each increment adds a 90\n"
" degrees rotation counterclockwise.\n"
"\n"
" -s, --serial serial\n" " -s, --serial serial\n"
" The device serial number. Mandatory only if several devices\n" " The device serial number. Mandatory only if several devices\n"
" are connected to adb.\n" " are connected to adb.\n"
@@ -161,74 +143,68 @@ scrcpy_print_usage(const char *arg0) {
"Shortcuts:\n" "Shortcuts:\n"
"\n" "\n"
" " CTRL_OR_CMD "+f\n" " " CTRL_OR_CMD "+f\n"
" Switch fullscreen mode\n" " switch fullscreen mode\n"
"\n"
" " CTRL_OR_CMD "+Left\n"
" Rotate display left\n"
"\n"
" " CTRL_OR_CMD "+Right\n"
" Rotate display right\n"
"\n" "\n"
" " CTRL_OR_CMD "+g\n" " " CTRL_OR_CMD "+g\n"
" Resize window to 1:1 (pixel-perfect)\n" " resize window to 1:1 (pixel-perfect)\n"
"\n" "\n"
" " CTRL_OR_CMD "+x\n" " " CTRL_OR_CMD "+x\n"
" Double-click on black borders\n" " Double-click on black borders\n"
" Resize window to remove black borders\n" " resize window to remove black borders\n"
"\n" "\n"
" Ctrl+h\n" " Ctrl+h\n"
" Middle-click\n" " Middle-click\n"
" Click on HOME\n" " click on HOME\n"
"\n" "\n"
" " CTRL_OR_CMD "+b\n" " " CTRL_OR_CMD "+b\n"
" " CTRL_OR_CMD "+Backspace\n" " " CTRL_OR_CMD "+Backspace\n"
" Right-click (when screen is on)\n" " Right-click (when screen is on)\n"
" Click on BACK\n" " click on BACK\n"
"\n" "\n"
" " CTRL_OR_CMD "+s\n" " " CTRL_OR_CMD "+s\n"
" Click on APP_SWITCH\n" " click on APP_SWITCH\n"
"\n" "\n"
" Ctrl+m\n" " Ctrl+m\n"
" Click on MENU\n" " click on MENU\n"
"\n" "\n"
" " CTRL_OR_CMD "+Up\n" " " CTRL_OR_CMD "+Up\n"
" Click on VOLUME_UP\n" " click on VOLUME_UP\n"
"\n" "\n"
" " CTRL_OR_CMD "+Down\n" " " CTRL_OR_CMD "+Down\n"
" Click on VOLUME_DOWN\n" " click on VOLUME_DOWN\n"
"\n" "\n"
" " CTRL_OR_CMD "+p\n" " " CTRL_OR_CMD "+p\n"
" Click on POWER (turn screen on/off)\n" " click on POWER (turn screen on/off)\n"
"\n" "\n"
" Right-click (when screen is off)\n" " Right-click (when screen is off)\n"
" Power on\n" " power on\n"
"\n" "\n"
" " CTRL_OR_CMD "+o\n" " " CTRL_OR_CMD "+o\n"
" Turn device screen off (keep mirroring)\n" " turn device screen off (keep mirroring)\n"
"\n" "\n"
" " CTRL_OR_CMD "+r\n" " " CTRL_OR_CMD "+r\n"
" Rotate device screen\n" " rotate device screen\n"
"\n" "\n"
" " CTRL_OR_CMD "+n\n" " " CTRL_OR_CMD "+n\n"
" Expand notification panel\n" " expand notification panel\n"
"\n" "\n"
" " CTRL_OR_CMD "+Shift+n\n" " " CTRL_OR_CMD "+Shift+n\n"
" Collapse notification panel\n" " collapse notification panel\n"
"\n" "\n"
" " CTRL_OR_CMD "+c\n" " " CTRL_OR_CMD "+c\n"
" Copy device clipboard to computer\n" " copy device clipboard to computer\n"
"\n" "\n"
" " CTRL_OR_CMD "+v\n" " " CTRL_OR_CMD "+v\n"
" Paste computer clipboard to device\n" " paste computer clipboard to device\n"
"\n" "\n"
" " CTRL_OR_CMD "+Shift+v\n" " " CTRL_OR_CMD "+Shift+v\n"
" Copy computer clipboard to device\n" " copy computer clipboard to device\n"
"\n" "\n"
" " CTRL_OR_CMD "+i\n" " " CTRL_OR_CMD "+i\n"
" Enable/disable FPS counter (print frames/second in logs)\n" " enable/disable FPS counter (print frames/second in logs)\n"
"\n" "\n"
" Drag & drop APK file\n" " Drag & drop APK file\n"
" Install APK from computer\n" " install APK from computer\n"
"\n", "\n",
arg0, arg0,
DEFAULT_BIT_RATE, DEFAULT_BIT_RATE,
@@ -334,18 +310,6 @@ parse_lock_video_orientation(const char *s, int8_t *lock_video_orientation) {
return true; return true;
} }
static bool
parse_rotation(const char *s, uint8_t *rotation) {
long value;
bool ok = parse_integer_arg(s, &value, false, 0, 3, "rotation");
if (!ok) {
return false;
}
*rotation = (uint8_t) value;
return true;
}
static bool static bool
parse_window_position(const char *s, int16_t *position) { parse_window_position(const char *s, int16_t *position) {
// special value for "auto" // special value for "auto"
@@ -465,9 +429,6 @@ guess_record_format(const char *filename) {
#define OPT_MAX_FPS 1012 #define OPT_MAX_FPS 1012
#define OPT_LOCK_VIDEO_ORIENTATION 1013 #define OPT_LOCK_VIDEO_ORIENTATION 1013
#define OPT_DISPLAY_ID 1014 #define OPT_DISPLAY_ID 1014
#define OPT_ROTATION 1015
#define OPT_RENDER_DRIVER 1016
#define OPT_NO_MIPMAPS 1017
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[]) {
@@ -484,15 +445,12 @@ scrcpy_parse_args(struct scrcpy_cli_args *args, int argc, char *argv[]) {
{"max-size", required_argument, NULL, 'm'}, {"max-size", required_argument, NULL, 'm'},
{"no-control", no_argument, NULL, 'n'}, {"no-control", no_argument, NULL, 'n'},
{"no-display", no_argument, NULL, 'N'}, {"no-display", no_argument, NULL, 'N'},
{"no-mipmaps", no_argument, NULL, OPT_NO_MIPMAPS},
{"port", required_argument, NULL, 'p'}, {"port", required_argument, NULL, 'p'},
{"push-target", required_argument, NULL, OPT_PUSH_TARGET}, {"push-target", required_argument, NULL, OPT_PUSH_TARGET},
{"record", required_argument, NULL, 'r'}, {"record", required_argument, NULL, 'r'},
{"record-format", required_argument, NULL, OPT_RECORD_FORMAT}, {"record-format", required_argument, NULL, OPT_RECORD_FORMAT},
{"render-driver", required_argument, NULL, OPT_RENDER_DRIVER},
{"render-expired-frames", no_argument, NULL, {"render-expired-frames", no_argument, NULL,
OPT_RENDER_EXPIRED_FRAMES}, OPT_RENDER_EXPIRED_FRAMES},
{"rotation", required_argument, NULL, OPT_ROTATION},
{"serial", required_argument, NULL, 's'}, {"serial", required_argument, NULL, 's'},
{"show-touches", no_argument, NULL, 't'}, {"show-touches", no_argument, NULL, 't'},
{"turn-screen-off", no_argument, NULL, 'S'}, {"turn-screen-off", no_argument, NULL, 'S'},
@@ -628,17 +586,6 @@ scrcpy_parse_args(struct scrcpy_cli_args *args, int argc, char *argv[]) {
case OPT_PREFER_TEXT: case OPT_PREFER_TEXT:
opts->prefer_text = true; opts->prefer_text = true;
break; break;
case OPT_ROTATION:
if (!parse_rotation(optarg, &opts->rotation)) {
return false;
}
break;
case OPT_RENDER_DRIVER:
opts->render_driver = optarg;
break;
case OPT_NO_MIPMAPS:
opts->mipmaps = false;
break;
default: default:
// getopt prints the error message on stderr // getopt prints the error message on stderr
return false; return false;
@@ -650,6 +597,11 @@ scrcpy_parse_args(struct scrcpy_cli_args *args, int argc, char *argv[]) {
return false; return false;
} }
if (!opts->display && opts->fullscreen) {
LOGE("-f/--fullscreen-window is incompatible with -N/--no-display");
return false;
}
int index = optind; int index = optind;
if (index < argc) { if (index < argc) {
LOGE("Unexpected additional argument: %s", argv[index]); LOGE("Unexpected additional argument: %s", argv[index]);

View File

@@ -45,9 +45,8 @@ control_msg_serialize(const struct control_msg *msg, unsigned char *buf) {
buffer_write32be(&buf[6], msg->inject_keycode.metastate); buffer_write32be(&buf[6], msg->inject_keycode.metastate);
return 10; return 10;
case CONTROL_MSG_TYPE_INJECT_TEXT: { case CONTROL_MSG_TYPE_INJECT_TEXT: {
size_t len = size_t len = write_string(msg->inject_text.text,
write_string(msg->inject_text.text, CONTROL_MSG_TEXT_MAX_LENGTH, &buf[1]);
CONTROL_MSG_INJECT_TEXT_MAX_LENGTH, &buf[1]);
return 1 + len; return 1 + len;
} }
case CONTROL_MSG_TYPE_INJECT_TOUCH_EVENT: case CONTROL_MSG_TYPE_INJECT_TOUCH_EVENT:

View File

@@ -10,7 +10,7 @@
#include "android/keycodes.h" #include "android/keycodes.h"
#include "common.h" #include "common.h"
#define CONTROL_MSG_INJECT_TEXT_MAX_LENGTH 300 #define CONTROL_MSG_TEXT_MAX_LENGTH 300
#define CONTROL_MSG_CLIPBOARD_TEXT_MAX_LENGTH 4093 #define CONTROL_MSG_CLIPBOARD_TEXT_MAX_LENGTH 4093
#define CONTROL_MSG_SERIALIZED_MAX_SIZE \ #define CONTROL_MSG_SERIALIZED_MAX_SIZE \
(3 + CONTROL_MSG_CLIPBOARD_TEXT_MAX_LENGTH) (3 + CONTROL_MSG_CLIPBOARD_TEXT_MAX_LENGTH)

View File

@@ -23,7 +23,7 @@ fps_counter_init(struct fps_counter *counter) {
} }
counter->thread = NULL; counter->thread = NULL;
atomic_init(&counter->started, 0); SDL_AtomicSet(&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
return true; return true;
@@ -35,16 +35,6 @@ fps_counter_destroy(struct fps_counter *counter) {
SDL_DestroyMutex(counter->mutex); SDL_DestroyMutex(counter->mutex);
} }
static inline bool
is_started(struct fps_counter *counter) {
return atomic_load_explicit(&counter->started, memory_order_acquire);
}
static inline void
set_started(struct fps_counter *counter, bool started) {
atomic_store_explicit(&counter->started, started, memory_order_release);
}
// must be called with mutex locked // must be called with mutex locked
static void static void
display_fps(struct fps_counter *counter) { display_fps(struct fps_counter *counter) {
@@ -80,10 +70,10 @@ run_fps_counter(void *data) {
mutex_lock(counter->mutex); mutex_lock(counter->mutex);
while (!counter->interrupted) { while (!counter->interrupted) {
while (!counter->interrupted && !is_started(counter)) { while (!counter->interrupted && !SDL_AtomicGet(&counter->started)) {
cond_wait(counter->state_cond, counter->mutex); cond_wait(counter->state_cond, counter->mutex);
} }
while (!counter->interrupted && is_started(counter)) { while (!counter->interrupted && SDL_AtomicGet(&counter->started)) {
uint32_t now = SDL_GetTicks(); uint32_t now = SDL_GetTicks();
check_interval_expired(counter, now); check_interval_expired(counter, now);
@@ -106,7 +96,7 @@ fps_counter_start(struct fps_counter *counter) {
counter->nr_skipped = 0; counter->nr_skipped = 0;
mutex_unlock(counter->mutex); mutex_unlock(counter->mutex);
set_started(counter, true); SDL_AtomicSet(&counter->started, 1);
cond_signal(counter->state_cond); cond_signal(counter->state_cond);
// counter->thread is always accessed from the same thread, no need to lock // counter->thread is always accessed from the same thread, no need to lock
@@ -124,13 +114,13 @@ 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); SDL_AtomicSet(&counter->started, 0);
cond_signal(counter->state_cond); cond_signal(counter->state_cond);
} }
bool bool
fps_counter_is_started(struct fps_counter *counter) { fps_counter_is_started(struct fps_counter *counter) {
return is_started(counter); return SDL_AtomicGet(&counter->started);
} }
void void
@@ -155,7 +145,7 @@ fps_counter_join(struct fps_counter *counter) {
void void
fps_counter_add_rendered_frame(struct fps_counter *counter) { fps_counter_add_rendered_frame(struct fps_counter *counter) {
if (!is_started(counter)) { if (!SDL_AtomicGet(&counter->started)) {
return; return;
} }
@@ -168,7 +158,7 @@ fps_counter_add_rendered_frame(struct fps_counter *counter) {
void void
fps_counter_add_skipped_frame(struct fps_counter *counter) { fps_counter_add_skipped_frame(struct fps_counter *counter) {
if (!is_started(counter)) { if (!SDL_AtomicGet(&counter->started)) {
return; return;
} }

View File

@@ -1,9 +1,9 @@
#ifndef FPSCOUNTER_H #ifndef FPSCOUNTER_H
#define FPSCOUNTER_H #define FPSCOUNTER_H
#include <stdatomic.h>
#include <stdbool.h> #include <stdbool.h>
#include <stdint.h> #include <stdint.h>
#include <SDL2/SDL_atomic.h>
#include <SDL2/SDL_mutex.h> #include <SDL2/SDL_mutex.h>
#include <SDL2/SDL_thread.h> #include <SDL2/SDL_thread.h>
@@ -16,7 +16,7 @@ struct fps_counter {
// 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
atomic_bool started; SDL_atomic_t started;
// the following fields are protected by the mutex // the following fields are protected by the mutex
bool interrupted; bool interrupted;

View File

@@ -7,6 +7,33 @@
#include "util/lock.h" #include "util/lock.h"
#include "util/log.h" #include "util/log.h"
// Convert window coordinates (as provided by SDL_GetMouseState() to renderer
// coordinates (as provided in SDL mouse events)
//
// See my question:
// <https://stackoverflow.com/questions/49111054/how-to-get-mouse-position-on-mouse-wheel-event>
static void
convert_to_renderer_coordinates(SDL_Renderer *renderer, int *x, int *y) {
SDL_Rect viewport;
float scale_x, scale_y;
SDL_RenderGetViewport(renderer, &viewport);
SDL_RenderGetScale(renderer, &scale_x, &scale_y);
*x = (int) (*x / scale_x) - viewport.x;
*y = (int) (*y / scale_y) - viewport.y;
}
static struct point
get_mouse_point(struct screen *screen) {
int x;
int y;
SDL_GetMouseState(&x, &y);
convert_to_renderer_coordinates(screen->renderer, &x, &y);
return (struct point) {
.x = x,
.y = y,
};
}
static const int ACTION_DOWN = 1; static const int ACTION_DOWN = 1;
static const int ACTION_UP = 1 << 1; static const int ACTION_UP = 1 << 1;
@@ -194,18 +221,6 @@ rotate_device(struct controller *controller) {
} }
} }
static void
rotate_client_left(struct screen *screen) {
unsigned new_rotation = (screen->rotation + 1) % 4;
screen_set_rotation(screen, new_rotation);
}
static void
rotate_client_right(struct screen *screen) {
unsigned new_rotation = (screen->rotation + 3) % 4;
screen_set_rotation(screen, new_rotation);
}
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) {
@@ -336,16 +351,6 @@ input_manager_process_key(struct input_manager *im,
action_volume_up(controller, action); action_volume_up(controller, action);
} }
return; return;
case SDLK_LEFT:
if (cmd && !shift && down) {
rotate_client_left(im->screen);
}
return;
case SDLK_RIGHT:
if (cmd && !shift && down) {
rotate_client_right(im->screen);
}
return;
case SDLK_c: case SDLK_c:
if (control && cmd && !shift && !repeat && down) { if (control && cmd && !shift && !repeat && down) {
request_device_clipboard(controller); request_device_clipboard(controller);
@@ -422,8 +427,8 @@ convert_mouse_motion(const SDL_MouseMotionEvent *from, struct screen *screen,
to->inject_touch_event.action = AMOTION_EVENT_ACTION_MOVE; to->inject_touch_event.action = AMOTION_EVENT_ACTION_MOVE;
to->inject_touch_event.pointer_id = POINTER_ID_MOUSE; to->inject_touch_event.pointer_id = POINTER_ID_MOUSE;
to->inject_touch_event.position.screen_size = screen->frame_size; to->inject_touch_event.position.screen_size = screen->frame_size;
to->inject_touch_event.position.point = to->inject_touch_event.position.point.x = from->x;
screen_convert_to_frame_coords(screen, from->x, from->y); to->inject_touch_event.position.point.y = from->y;
to->inject_touch_event.pressure = 1.f; to->inject_touch_event.pressure = 1.f;
to->inject_touch_event.buttons = convert_mouse_buttons(from->state); to->inject_touch_event.buttons = convert_mouse_buttons(from->state);
@@ -458,19 +463,13 @@ convert_touch(const SDL_TouchFingerEvent *from, struct screen *screen,
return false; return false;
} }
struct size frame_size = screen->frame_size;
to->inject_touch_event.pointer_id = from->fingerId; to->inject_touch_event.pointer_id = from->fingerId;
to->inject_touch_event.position.screen_size = screen->frame_size; to->inject_touch_event.position.screen_size = frame_size;
int ww;
int wh;
SDL_GL_GetDrawableSize(screen->window, &ww, &wh);
// SDL touch event coordinates are normalized in the range [0; 1] // SDL touch event coordinates are normalized in the range [0; 1]
int32_t x = from->x * ww; to->inject_touch_event.position.point.x = from->x * frame_size.width;
int32_t y = from->y * wh; to->inject_touch_event.position.point.y = from->y * frame_size.height;
to->inject_touch_event.position.point =
screen_convert_to_frame_coords(screen, x, y);
to->inject_touch_event.pressure = from->pressure; to->inject_touch_event.pressure = from->pressure;
to->inject_touch_event.buttons = 0; to->inject_touch_event.buttons = 0;
return true; return true;
@@ -487,6 +486,13 @@ input_manager_process_touch(struct input_manager *im,
} }
} }
static bool
is_outside_device_screen(struct input_manager *im, int x, int y)
{
return x < 0 || x >= im->screen->frame_size.width ||
y < 0 || y >= im->screen->frame_size.height;
}
static bool static bool
convert_mouse_button(const SDL_MouseButtonEvent *from, struct screen *screen, convert_mouse_button(const SDL_MouseButtonEvent *from, struct screen *screen,
struct control_msg *to) { struct control_msg *to) {
@@ -498,8 +504,8 @@ convert_mouse_button(const SDL_MouseButtonEvent *from, struct screen *screen,
to->inject_touch_event.pointer_id = POINTER_ID_MOUSE; to->inject_touch_event.pointer_id = POINTER_ID_MOUSE;
to->inject_touch_event.position.screen_size = screen->frame_size; to->inject_touch_event.position.screen_size = screen->frame_size;
to->inject_touch_event.position.point = to->inject_touch_event.position.point.x = from->x;
screen_convert_to_frame_coords(screen, from->x, from->y); to->inject_touch_event.position.point.y = from->y;
to->inject_touch_event.pressure = 1.f; to->inject_touch_event.pressure = 1.f;
to->inject_touch_event.buttons = to->inject_touch_event.buttons =
convert_mouse_buttons(SDL_BUTTON(from->button)); convert_mouse_buttons(SDL_BUTTON(from->button));
@@ -524,14 +530,10 @@ input_manager_process_mouse_button(struct input_manager *im,
action_home(im->controller, ACTION_DOWN | ACTION_UP); action_home(im->controller, ACTION_DOWN | ACTION_UP);
return; return;
} }
// double-click on black borders resize to fit the device screen // double-click on black borders resize to fit the device screen
if (event->button == SDL_BUTTON_LEFT && event->clicks == 2) { if (event->button == SDL_BUTTON_LEFT && event->clicks == 2) {
int x = event->x; bool outside =
int y = event->y; is_outside_device_screen(im, event->x, event->y);
SDL_Rect *r = &im->screen->rect;
bool outside = x < r->x || x >= r->x + r->w
|| y < r->y || y >= r->y + r->h;
if (outside) { if (outside) {
screen_resize_to_fit(im->screen); screen_resize_to_fit(im->screen);
return; return;
@@ -555,15 +557,9 @@ input_manager_process_mouse_button(struct input_manager *im,
static bool static bool
convert_mouse_wheel(const SDL_MouseWheelEvent *from, struct screen *screen, convert_mouse_wheel(const SDL_MouseWheelEvent *from, struct screen *screen,
struct control_msg *to) { struct control_msg *to) {
// mouse_x and mouse_y are expressed in pixels relative to the window
int mouse_x;
int mouse_y;
SDL_GetMouseState(&mouse_x, &mouse_y);
struct position position = { struct position position = {
.screen_size = screen->frame_size, .screen_size = screen->frame_size,
.point = screen_convert_to_frame_coords(screen, mouse_x, mouse_y), .point = get_mouse_point(screen),
}; };
to->type = CONTROL_MSG_TYPE_INJECT_SCROLL_EVENT; to->type = CONTROL_MSG_TYPE_INJECT_SCROLL_EVENT;

View File

@@ -1,56 +0,0 @@
#include "opengl.h"
#include <assert.h>
#include <stdio.h>
#include "SDL2/SDL.h"
void
sc_opengl_init(struct sc_opengl *gl) {
gl->GetString = SDL_GL_GetProcAddress("glGetString");
assert(gl->GetString);
gl->TexParameterf = SDL_GL_GetProcAddress("glTexParameterf");
assert(gl->TexParameterf);
gl->TexParameteri = SDL_GL_GetProcAddress("glTexParameteri");
assert(gl->TexParameteri);
// optional
gl->GenerateMipmap = SDL_GL_GetProcAddress("glGenerateMipmap");
const char *version = (const char *) gl->GetString(GL_VERSION);
assert(version);
gl->version = version;
#define OPENGL_ES_PREFIX "OpenGL ES "
/* starts with "OpenGL ES " */
gl->is_opengles = !strncmp(gl->version, OPENGL_ES_PREFIX,
sizeof(OPENGL_ES_PREFIX) - 1);
if (gl->is_opengles) {
/* skip the prefix */
version += sizeof(PREFIX) - 1;
}
int r = sscanf(version, "%d.%d", &gl->version_major, &gl->version_minor);
if (r != 2) {
// failed to parse the version
gl->version_major = 0;
gl->version_minor = 0;
}
}
bool
sc_opengl_version_at_least(struct sc_opengl *gl,
int minver_major, int minver_minor,
int minver_es_major, int minver_es_minor)
{
if (gl->is_opengles) {
return gl->version_major > minver_es_major
|| (gl->version_major == minver_es_major
&& gl->version_minor >= minver_es_minor);
}
return gl->version_major > minver_major
|| (gl->version_major == minver_major
&& gl->version_minor >= minver_minor);
}

View File

@@ -1,36 +0,0 @@
#ifndef SC_OPENGL_H
#define SC_OPENGL_H
#include <stdbool.h>
#include <SDL2/SDL_opengl.h>
#include "config.h"
struct sc_opengl {
const char *version;
bool is_opengles;
int version_major;
int version_minor;
const GLubyte *
(*GetString)(GLenum name);
void
(*TexParameterf)(GLenum target, GLenum pname, GLfloat param);
void
(*TexParameteri)(GLenum target, GLenum pname, GLint param);
void
(*GenerateMipmap)(GLenum target);
};
void
sc_opengl_init(struct sc_opengl *gl);
bool
sc_opengl_version_at_least(struct sc_opengl *gl,
int minver_major, int minver_minor,
int minver_es_major, int minver_es_minor);
#endif

View File

@@ -47,7 +47,7 @@ static struct input_manager input_manager = {
// init SDL and set appropriate hints // init SDL and set appropriate hints
static bool static bool
sdl_init_and_configure(bool display, const char *render_driver) { sdl_init_and_configure(bool display) {
uint32_t flags = display ? SDL_INIT_VIDEO : SDL_INIT_EVENTS; uint32_t flags = display ? SDL_INIT_VIDEO : SDL_INIT_EVENTS;
if (SDL_Init(flags)) { if (SDL_Init(flags)) {
LOGC("Could not initialize SDL: %s", SDL_GetError()); LOGC("Could not initialize SDL: %s", SDL_GetError());
@@ -60,10 +60,6 @@ sdl_init_and_configure(bool display, const char *render_driver) {
return true; return true;
} }
if (render_driver && !SDL_SetHint(SDL_HINT_RENDER_DRIVER, render_driver)) {
LOGW("Could not set render driver");
}
// Linear filtering // Linear filtering
if (!SDL_SetHint(SDL_HINT_RENDER_SCALE_QUALITY, "1")) { if (!SDL_SetHint(SDL_HINT_RENDER_SCALE_QUALITY, "1")) {
LOGW("Could not enable linear filtering"); LOGW("Could not enable linear filtering");
@@ -314,7 +310,7 @@ scrcpy(const struct scrcpy_options *options) {
bool controller_initialized = false; bool controller_initialized = false;
bool controller_started = false; bool controller_started = false;
if (!sdl_init_and_configure(options->display, options->render_driver)) { if (!sdl_init_and_configure(options->display)) {
goto end; goto end;
} }
@@ -400,8 +396,7 @@ scrcpy(const struct scrcpy_options *options) {
options->always_on_top, options->window_x, options->always_on_top, options->window_x,
options->window_y, options->window_width, options->window_y, options->window_width,
options->window_height, options->window_height,
options->window_borderless, options->window_borderless)) {
options->rotation, options-> mipmaps)) {
goto end; goto end;
} }

View File

@@ -15,14 +15,12 @@ struct scrcpy_options {
const char *record_filename; const char *record_filename;
const char *window_title; const char *window_title;
const char *push_target; const char *push_target;
const char *render_driver;
enum recorder_format record_format; enum recorder_format record_format;
struct port_range port_range; struct port_range port_range;
uint16_t max_size; uint16_t max_size;
uint32_t bit_rate; uint32_t bit_rate;
uint16_t max_fps; uint16_t max_fps;
int8_t lock_video_orientation; int8_t lock_video_orientation;
uint8_t rotation;
int16_t window_x; // WINDOW_POSITION_UNDEFINED for "auto" int16_t window_x; // WINDOW_POSITION_UNDEFINED for "auto"
int16_t window_y; // WINDOW_POSITION_UNDEFINED for "auto" int16_t window_y; // WINDOW_POSITION_UNDEFINED for "auto"
uint16_t window_width; uint16_t window_width;
@@ -37,7 +35,6 @@ struct scrcpy_options {
bool render_expired_frames; bool render_expired_frames;
bool prefer_text; bool prefer_text;
bool window_borderless; bool window_borderless;
bool mipmaps;
}; };
#define SCRCPY_OPTIONS_DEFAULT { \ #define SCRCPY_OPTIONS_DEFAULT { \
@@ -46,7 +43,6 @@ struct scrcpy_options {
.record_filename = NULL, \ .record_filename = NULL, \
.window_title = NULL, \ .window_title = NULL, \
.push_target = NULL, \ .push_target = NULL, \
.render_driver = NULL, \
.record_format = RECORDER_FORMAT_AUTO, \ .record_format = RECORDER_FORMAT_AUTO, \
.port_range = { \ .port_range = { \
.first = DEFAULT_LOCAL_PORT_RANGE_FIRST, \ .first = DEFAULT_LOCAL_PORT_RANGE_FIRST, \
@@ -56,7 +52,6 @@ struct scrcpy_options {
.bit_rate = DEFAULT_BIT_RATE, \ .bit_rate = DEFAULT_BIT_RATE, \
.max_fps = 0, \ .max_fps = 0, \
.lock_video_orientation = DEFAULT_LOCK_VIDEO_ORIENTATION, \ .lock_video_orientation = DEFAULT_LOCK_VIDEO_ORIENTATION, \
.rotation = 0, \
.window_x = WINDOW_POSITION_UNDEFINED, \ .window_x = WINDOW_POSITION_UNDEFINED, \
.window_y = WINDOW_POSITION_UNDEFINED, \ .window_y = WINDOW_POSITION_UNDEFINED, \
.window_width = 0, \ .window_width = 0, \
@@ -71,7 +66,6 @@ struct scrcpy_options {
.render_expired_frames = false, \ .render_expired_frames = false, \
.prefer_text = false, \ .prefer_text = false, \
.window_borderless = false, \ .window_borderless = false, \
.mipmaps = true, \
} }
bool bool

View File

@@ -15,19 +15,6 @@
#define DISPLAY_MARGINS 96 #define DISPLAY_MARGINS 96
static inline struct size
get_rotated_size(struct size size, int rotation) {
struct size rotated_size;
if (rotation & 1) {
rotated_size.width = size.height;
rotated_size.height = size.width;
} else {
rotated_size.width = size.width;
rotated_size.height = size.height;
}
return rotated_size;
}
// get the window size in a struct size // get the window size in a struct size
static struct size static struct size
get_window_size(SDL_Window *window) { get_window_size(SDL_Window *window) {
@@ -87,177 +74,102 @@ get_preferred_display_bounds(struct size *bounds) {
return true; return true;
} }
// Indicate if the width should be kept on computing the optimal rectangle size
// to display the content in the window.
// Return true if the width should be kept, false if the height should be kept.
static bool
should_keep_width(struct size window_size, struct size content_size) {
// 32 bits because we need to multiply two 16 bits values
uint32_t ww = window_size.width;
uint32_t wh = window_size.height;
uint32_t cw = content_size.width;
uint32_t ch = content_size.height;
// To avoid keeping alternatively width and height on successive resizes
// due to rounding to integer, always prefer height (arbitrarily) if in the
// error range.
// err = ceil of content aspect ratio
unsigned err = (cw + ch - 1) / ch;
return cw * wh > ch * (ww + err);
}
// return the optimal size of the window, with the following constraints: // return the optimal size of the window, with the following constraints:
// - it attempts to keep at least one dimension of the current_size (i.e. it // - it attempts to keep at least one dimension of the current_size (i.e. it
// crops the black borders) // crops the black borders)
// - it keeps the aspect ratio // - it keeps the aspect ratio
// - it scales down to make it fit in the display_size // - it scales down to make it fit in the display_size
static struct size static struct size
get_optimal_size(struct size current_size, struct size content_size) { get_optimal_size(struct size current_size, struct size frame_size) {
if (content_size.width == 0 || content_size.height == 0) { if (frame_size.width == 0 || frame_size.height == 0) {
// avoid division by 0 // avoid division by 0
return current_size; return current_size;
} }
struct size window_size;
struct size display_size; struct size display_size;
// 32 bits because we need to multiply two 16 bits values
uint32_t w;
uint32_t h;
if (!get_preferred_display_bounds(&display_size)) { if (!get_preferred_display_bounds(&display_size)) {
// could not get display bounds, do not constraint the size // could not get display bounds, do not constraint the size
window_size.width = current_size.width; w = current_size.width;
window_size.height = current_size.height; h = current_size.height;
} else { } else {
window_size.width = MIN(current_size.width, display_size.width); w = MIN(current_size.width, display_size.width);
window_size.height = MIN(current_size.height, display_size.height); h = MIN(current_size.height, display_size.height);
} }
bool keep_width = should_keep_width(window_size, content_size); bool keep_width = frame_size.width * h > frame_size.height * w;
if (keep_width) { if (keep_width) {
// remove black borders on top and bottom // remove black borders on top and bottom
window_size.height = content_size.height * window_size.width h = frame_size.height * w / frame_size.width;
/ content_size.width;
} else { } else {
// remove black borders on left and right (or none at all if it already // remove black borders on left and right (or none at all if it already
// fits) // fits)
window_size.width = content_size.width * window_size.height w = frame_size.width * h / frame_size.height;
/ content_size.height;
} }
// width and height must fit into 16 bits // w and h must fit into 16 bits
assert(window_size.width < 0x10000 && window_size.height < 0x10000); assert(w < 0x10000 && h < 0x10000);
return window_size; return (struct size) {w, h};
} }
// same as get_optimal_size(), but read the current size from the window // same as get_optimal_size(), but read the current size from the window
static inline struct size static inline struct size
get_optimal_window_size(const struct screen *screen, struct size content_size) { get_optimal_window_size(const struct screen *screen, struct size frame_size) {
struct size windowed_size = get_windowed_window_size(screen); struct size windowed_size = get_windowed_window_size(screen);
return get_optimal_size(windowed_size, content_size); return get_optimal_size(windowed_size, frame_size);
} }
// initially, there is no current size, so use the frame size as current size // initially, there is no current size, so use the frame size as current size
// req_width and req_height, if not 0, are the sizes requested by the user // req_width and req_height, if not 0, are the sizes requested by the user
static inline struct size static inline struct size
get_initial_optimal_size(struct size content_size, uint16_t req_width, get_initial_optimal_size(struct size frame_size, uint16_t req_width,
uint16_t req_height) { uint16_t req_height) {
struct size window_size; struct size window_size;
if (!req_width && !req_height) { if (!req_width && !req_height) {
window_size = get_optimal_size(content_size, content_size); window_size = get_optimal_size(frame_size, frame_size);
} else { } else {
if (req_width) { if (req_width) {
window_size.width = req_width; window_size.width = req_width;
} else { } else {
// compute from the requested height // compute from the requested height
window_size.width = (uint32_t) req_height * content_size.width window_size.width = (uint32_t) req_height * frame_size.width
/ content_size.height; / frame_size.height;
} }
if (req_height) { if (req_height) {
window_size.height = req_height; window_size.height = req_height;
} else { } else {
// compute from the requested width // compute from the requested width
window_size.height = (uint32_t) req_width * content_size.height window_size.height = (uint32_t) req_width * frame_size.height
/ content_size.width; / frame_size.width;
} }
} }
return window_size; return window_size;
} }
static void
update_content_rect(struct screen *screen) {
int w;
int h;
SDL_GL_GetDrawableSize(screen->window, &w, &h);
struct size window_size = {w, h};
uint16_t ww = window_size.width;
uint16_t wh = window_size.height;
uint16_t cw = screen->content_size.width;
uint16_t ch = screen->content_size.height;
SDL_Rect *rect = &screen->rect;
bool keep_width = should_keep_width(window_size, screen->content_size);
if (keep_width) {
rect->x = 0;
rect->w = ww;
rect->h = ww * ch / cw;
rect->y = (wh - rect->h) / 2;
} else {
rect->y = 0;
rect->h = wh;
rect->w = wh * cw / ch;
rect->x = (ww - rect->w) / 2;
}
}
void void
screen_init(struct screen *screen) { screen_init(struct screen *screen) {
*screen = (struct screen) SCREEN_INITIALIZER; *screen = (struct screen) SCREEN_INITIALIZER;
} }
static inline SDL_Texture * static inline SDL_Texture *
create_texture(struct screen *screen) { create_texture(SDL_Renderer *renderer, struct size frame_size) {
SDL_Renderer *renderer = screen->renderer; return SDL_CreateTexture(renderer, SDL_PIXELFORMAT_YV12,
struct size size = screen->frame_size;
SDL_Texture *texture = SDL_CreateTexture(renderer, SDL_PIXELFORMAT_YV12,
SDL_TEXTUREACCESS_STREAMING, SDL_TEXTUREACCESS_STREAMING,
size.width, size.height); frame_size.width, frame_size.height);
if (!texture) {
return NULL;
}
if (screen->mipmaps) {
struct sc_opengl *gl = &screen->gl;
SDL_GL_BindTexture(texture, NULL, NULL);
// Enable trilinear filtering for downscaling
gl->TexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER,
GL_LINEAR_MIPMAP_LINEAR);
gl->TexParameterf(GL_TEXTURE_2D, GL_TEXTURE_LOD_BIAS, -.5f);
SDL_GL_UnbindTexture(texture);
}
return texture;
} }
bool bool
screen_init_rendering(struct screen *screen, const char *window_title, screen_init_rendering(struct screen *screen, const char *window_title,
struct size frame_size, bool always_on_top, struct size frame_size, bool always_on_top,
int16_t window_x, int16_t window_y, uint16_t window_width, int16_t window_x, int16_t window_y, uint16_t window_width,
uint16_t window_height, bool window_borderless, uint16_t window_height, bool window_borderless) {
uint8_t rotation, bool mipmaps) {
screen->frame_size = frame_size; screen->frame_size = frame_size;
screen->rotation = rotation;
if (rotation) {
LOGI("Initial display rotation set to %u", rotation);
}
struct size content_size = get_rotated_size(frame_size, screen->rotation);
screen->content_size = content_size;
struct size window_size = struct size window_size =
get_initial_optimal_size(content_size, window_width, window_height); get_initial_optimal_size(frame_size, window_width, window_height);
uint32_t window_flags = SDL_WINDOW_HIDDEN | SDL_WINDOW_RESIZABLE; uint32_t window_flags = SDL_WINDOW_HIDDEN | SDL_WINDOW_RESIZABLE;
#ifdef HIDPI_SUPPORT #ifdef HIDPI_SUPPORT
window_flags |= SDL_WINDOW_ALLOW_HIGHDPI; window_flags |= SDL_WINDOW_ALLOW_HIGHDPI;
@@ -294,35 +206,11 @@ screen_init_rendering(struct screen *screen, const char *window_title,
return false; return false;
} }
SDL_RendererInfo renderer_info; if (SDL_RenderSetLogicalSize(screen->renderer, frame_size.width,
int r = SDL_GetRendererInfo(screen->renderer, &renderer_info); frame_size.height)) {
const char *renderer_name = r ? NULL : renderer_info.name; LOGE("Could not set renderer logical size: %s", SDL_GetError());
LOGI("Renderer: %s", renderer_name ? renderer_name : "(unknown)"); screen_destroy(screen);
return false;
// stats with "opengl"
screen->use_opengl = renderer_name && !strncmp(renderer_name, "opengl", 6);
if (screen->use_opengl) {
struct sc_opengl *gl = &screen->gl;
sc_opengl_init(gl);
LOGI("OpenGL version: %s", gl->version);
if (mipmaps) {
bool supports_mipmaps =
sc_opengl_version_at_least(gl, 3, 0, /* OpenGL 3.0+ */
2, 0 /* OpenGL ES 2.0+ */);
if (supports_mipmaps) {
LOGI("Trilinear filtering enabled");
screen->mipmaps = true;
} else {
LOGW("Trilinear filtering disabled "
"(OpenGL 3.0+ or ES 2.0+ required)");
}
} else {
LOGI("Trilinear filtering disabled");
}
} else {
LOGW("Trilinear filtering disabled (not an OpenGL renderer)");
} }
SDL_Surface *icon = read_xpm(icon_xpm); SDL_Surface *icon = read_xpm(icon_xpm);
@@ -335,15 +223,13 @@ screen_init_rendering(struct screen *screen, const char *window_title,
LOGI("Initial texture: %" PRIu16 "x%" PRIu16, frame_size.width, LOGI("Initial texture: %" PRIu16 "x%" PRIu16, frame_size.width,
frame_size.height); frame_size.height);
screen->texture = create_texture(screen); screen->texture = create_texture(screen->renderer, frame_size);
if (!screen->texture) { if (!screen->texture) {
LOGC("Could not create texture: %s", SDL_GetError()); LOGC("Could not create texture: %s", SDL_GetError());
screen_destroy(screen); screen_destroy(screen);
return false; return false;
} }
update_content_rect(screen);
screen->windowed_window_size = window_size; screen->windowed_window_size = window_size;
return true; return true;
@@ -367,63 +253,35 @@ screen_destroy(struct screen *screen) {
} }
} }
void
screen_set_rotation(struct screen *screen, unsigned rotation) {
assert(rotation < 4);
if (rotation == screen->rotation) {
return;
}
struct size old_content_size = screen->content_size;
struct size new_content_size =
get_rotated_size(screen->frame_size, rotation);
struct size windowed_size = get_windowed_window_size(screen);
struct size target_size = {
.width = (uint32_t) windowed_size.width * new_content_size.width
/ old_content_size.width,
.height = (uint32_t) windowed_size.height * new_content_size.height
/ old_content_size.height,
};
target_size = get_optimal_size(target_size, new_content_size);
set_window_size(screen, target_size);
screen->content_size = new_content_size;
screen->rotation = rotation;
LOGI("Display rotation set to %u", rotation);
update_content_rect(screen);
screen_render(screen);
}
// recreate the texture and resize the window if the frame size has changed // recreate the texture and resize the window if the frame size has changed
static bool static bool
prepare_for_frame(struct screen *screen, struct size new_frame_size) { prepare_for_frame(struct screen *screen, struct size new_frame_size) {
if (screen->frame_size.width != new_frame_size.width if (screen->frame_size.width != new_frame_size.width
|| screen->frame_size.height != new_frame_size.height) { || screen->frame_size.height != new_frame_size.height) {
if (SDL_RenderSetLogicalSize(screen->renderer, new_frame_size.width,
new_frame_size.height)) {
LOGE("Could not set renderer logical size: %s", SDL_GetError());
return false;
}
// frame dimension changed, destroy texture // frame dimension changed, destroy texture
SDL_DestroyTexture(screen->texture); SDL_DestroyTexture(screen->texture);
struct size new_content_size =
get_rotated_size(new_frame_size, screen->rotation);
struct size content_size = screen->content_size;
struct size windowed_size = get_windowed_window_size(screen); struct size windowed_size = get_windowed_window_size(screen);
struct size target_size = { struct size target_size = {
(uint32_t) windowed_size.width * new_content_size.width (uint32_t) windowed_size.width * new_frame_size.width
/ content_size.width, / screen->frame_size.width,
(uint32_t) windowed_size.height * new_content_size.height (uint32_t) windowed_size.height * new_frame_size.height
/ content_size.height, / screen->frame_size.height,
}; };
target_size = get_optimal_size(target_size, new_content_size); target_size = get_optimal_size(target_size, new_frame_size);
set_window_size(screen, target_size); set_window_size(screen, target_size);
screen->frame_size = new_frame_size; screen->frame_size = new_frame_size;
screen->content_size = new_content_size;
update_content_rect(screen);
LOGI("New texture: %" PRIu16 "x%" PRIu16, LOGI("New texture: %" PRIu16 "x%" PRIu16,
screen->frame_size.width, screen->frame_size.height); screen->frame_size.width, screen->frame_size.height);
screen->texture = create_texture(screen); screen->texture = create_texture(screen->renderer, new_frame_size);
if (!screen->texture) { if (!screen->texture) {
LOGC("Could not create texture: %s", SDL_GetError()); LOGC("Could not create texture: %s", SDL_GetError());
return false; return false;
@@ -440,13 +298,6 @@ update_texture(struct screen *screen, const AVFrame *frame) {
frame->data[0], frame->linesize[0], frame->data[0], frame->linesize[0],
frame->data[1], frame->linesize[1], frame->data[1], frame->linesize[1],
frame->data[2], frame->linesize[2]); frame->data[2], frame->linesize[2]);
if (screen->mipmaps) {
assert(screen->use_opengl);
SDL_GL_BindTexture(screen->texture, NULL, NULL);
screen->gl.GenerateMipmap(GL_TEXTURE_2D);
SDL_GL_UnbindTexture(screen->texture);
}
} }
bool bool
@@ -465,39 +316,10 @@ screen_update_frame(struct screen *screen, struct video_buffer *vb) {
return true; return true;
} }
void
screen_window_resized(struct screen *screen) {
update_content_rect(screen);
screen_render(screen);
}
void void
screen_render(struct screen *screen) { screen_render(struct screen *screen) {
SDL_RenderClear(screen->renderer); SDL_RenderClear(screen->renderer);
if (screen->rotation == 0) { SDL_RenderCopy(screen->renderer, screen->texture, NULL, NULL);
SDL_RenderCopy(screen->renderer, screen->texture, NULL, &screen->rect);
} else {
// rotation in RenderCopyEx() is clockwise, while screen->rotation is
// counterclockwise (to be consistent with --lock-video-orientation)
int cw_rotation = (4 - screen->rotation) % 4;
double angle = 90 * cw_rotation;
SDL_Rect *dstrect = NULL;
SDL_Rect rect;
if (screen->rotation & 1) {
rect.x = screen->rect.x + (screen->rect.w - screen->rect.h) / 2;
rect.y = screen->rect.y + (screen->rect.h - screen->rect.w) / 2;
rect.w = screen->rect.h;
rect.h = screen->rect.w;
dstrect = &rect;
} else {
assert(screen->rotation == 2);
dstrect = &screen->rect;
}
SDL_RenderCopyEx(screen->renderer, screen->texture, NULL, dstrect,
angle, NULL, 0);
}
SDL_RenderPresent(screen->renderer); SDL_RenderPresent(screen->renderer);
} }
@@ -528,10 +350,9 @@ screen_resize_to_fit(struct screen *screen) {
} }
struct size optimal_size = struct size optimal_size =
get_optimal_window_size(screen, screen->content_size); get_optimal_window_size(screen, screen->frame_size);
SDL_SetWindowSize(screen->window, optimal_size.width, optimal_size.height); SDL_SetWindowSize(screen->window, optimal_size.width, optimal_size.height);
LOGD("Resized to optimal size: %ux%u", optimal_size.width, LOGD("Resized to optimal size");
optimal_size.height);
} }
void void
@@ -545,10 +366,9 @@ screen_resize_to_pixel_perfect(struct screen *screen) {
screen->maximized = false; screen->maximized = false;
} }
struct size content_size = screen->content_size; SDL_SetWindowSize(screen->window, screen->frame_size.width,
SDL_SetWindowSize(screen->window, content_size.width, content_size.height); screen->frame_size.height);
LOGD("Resized to pixel-perfect: %ux%u", content_size.width, LOGD("Resized to pixel-perfect");
content_size.height);
} }
void void
@@ -571,7 +391,7 @@ screen_handle_window_event(struct screen *screen,
// window is maximized or fullscreen is enabled. // window is maximized or fullscreen is enabled.
screen->windowed_window_size = get_window_size(screen->window); screen->windowed_window_size = get_window_size(screen->window);
} }
screen_window_resized(screen); screen_render(screen);
break; break;
case SDL_WINDOWEVENT_MAXIMIZED: case SDL_WINDOWEVENT_MAXIMIZED:
// The backup size must be non-nul. // The backup size must be non-nul.
@@ -592,44 +412,3 @@ screen_handle_window_event(struct screen *screen,
break; break;
} }
} }
struct point
screen_convert_to_frame_coords(struct screen *screen, int32_t x, int32_t y) {
unsigned rotation = screen->rotation;
assert(rotation < 4);
int32_t w = screen->content_size.width;
int32_t h = screen->content_size.height;
// take the HiDPI scaling (dw/ww and dh/wh) into account
int ww, wh, dw, dh;
SDL_GetWindowSize(screen->window, &ww, &wh);
SDL_GL_GetDrawableSize(screen->window, &dw, &dh);
// scale (64 bits for intermediate multiplications)
x = (int64_t) (x - screen->rect.x) * w * dw / (screen->rect.w * ww);
y = (int64_t) (y - screen->rect.y) * h * dh / (screen->rect.h * wh);
// rotate
struct point result;
switch (rotation) {
case 0:
result.x = x;
result.y = y;
break;
case 1:
result.x = h - y;
result.y = x;
break;
case 2:
result.x = w - x;
result.y = h - y;
break;
default:
assert(rotation == 3);
result.x = y;
result.y = w - x;
break;
}
return result;
}

View File

@@ -7,7 +7,6 @@
#include "config.h" #include "config.h"
#include "common.h" #include "common.h"
#include "opengl.h"
#define WINDOW_POSITION_UNDEFINED (-0x8000) #define WINDOW_POSITION_UNDEFINED (-0x8000)
@@ -17,40 +16,26 @@ struct screen {
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 size frame_size; struct size frame_size;
struct size content_size; // rotated frame_size
// The window size the last time it was not maximized or fullscreen. // The window size the last time it was not maximized or fullscreen.
struct size windowed_window_size; struct size windowed_window_size;
// Since we receive the event SIZE_CHANGED before MAXIMIZED, we must be // Since we receive the event SIZE_CHANGED before MAXIMIZED, we must be
// able to revert the size to its non-maximized value. // able to revert the size to its non-maximized value.
struct size windowed_window_size_backup; struct size windowed_window_size_backup;
// client rotation: 0, 1, 2 or 3 (x90 degrees counterclockwise)
unsigned rotation;
// rectangle of the content (excluding black borders)
struct SDL_Rect rect;
bool has_frame; bool has_frame;
bool fullscreen; bool fullscreen;
bool maximized; bool maximized;
bool no_window; bool no_window;
bool mipmaps;
}; };
#define SCREEN_INITIALIZER { \ #define SCREEN_INITIALIZER { \
.window = NULL, \ .window = NULL, \
.renderer = NULL, \ .renderer = NULL, \
.texture = NULL, \ .texture = NULL, \
.use_opengl = false, \
.gl = {0}, \
.frame_size = { \ .frame_size = { \
.width = 0, \ .width = 0, \
.height = 0, \ .height = 0, \
}, \ }, \
.content_size = { \
.width = 0, \
.height = 0, \
}, \
.windowed_window_size = { \ .windowed_window_size = { \
.width = 0, \ .width = 0, \
.height = 0, \ .height = 0, \
@@ -59,18 +44,10 @@ struct screen {
.width = 0, \ .width = 0, \
.height = 0, \ .height = 0, \
}, \ }, \
.rotation = 0, \
.rect = { \
.x = 0, \
.y = 0, \
.w = 0, \
.h = 0, \
}, \
.has_frame = false, \ .has_frame = false, \
.fullscreen = false, \ .fullscreen = false, \
.maximized = false, \ .maximized = false, \
.no_window = false, \ .no_window = false, \
.mipmaps = false, \
} }
// initialize default values // initialize default values
@@ -83,8 +60,7 @@ bool
screen_init_rendering(struct screen *screen, const char *window_title, screen_init_rendering(struct screen *screen, const char *window_title,
struct size frame_size, bool always_on_top, struct size frame_size, bool always_on_top,
int16_t window_x, int16_t window_y, uint16_t window_width, int16_t window_x, int16_t window_y, uint16_t window_width,
uint16_t window_height, bool window_borderless, uint16_t window_height, bool window_borderless);
uint8_t rotation, bool mipmaps);
// show the window // show the window
void void
@@ -98,10 +74,6 @@ screen_destroy(struct screen *screen);
bool bool
screen_update_frame(struct screen *screen, struct video_buffer *vb); screen_update_frame(struct screen *screen, struct video_buffer *vb);
// update content after window resizing
void
screen_window_resized(struct screen *screen);
// render the texture to the renderer // render the texture to the renderer
void void
screen_render(struct screen *screen); screen_render(struct screen *screen);
@@ -118,17 +90,8 @@ screen_resize_to_fit(struct screen *screen);
void void
screen_resize_to_pixel_perfect(struct screen *screen); screen_resize_to_pixel_perfect(struct screen *screen);
// set the display rotation (0, 1, 2 or 3, x90 degrees counterclockwise)
void
screen_set_rotation(struct screen *screen, unsigned rotation);
// react to window events // react to window events
void void
screen_handle_window_event(struct screen *screen, const SDL_WindowEvent *event); screen_handle_window_event(struct screen *screen, const SDL_WindowEvent *event);
// convert point from window coordinates to frame coordinates
// x and y are expressed in pixels
struct point
screen_convert_to_frame_coords(struct screen *screen, int32_t x, int32_t y);
#endif #endif

View File

@@ -5,7 +5,6 @@
#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>
@@ -172,7 +171,7 @@ enable_tunnel_reverse_any_port(struct server *server,
// check before incrementing to avoid overflow on port 65535 // check before incrementing to avoid overflow on port 65535
if (port < port_range.last) { if (port < port_range.last) {
LOGW("Could not listen on port %" PRIu16", retrying on %" PRIu16, LOGW("Could not listen on port %" PRIu16", retrying on %" PRIu16,
port, (uint16_t) (port + 1)); port, port + 1);
port++; port++;
continue; continue;
} }
@@ -318,12 +317,14 @@ connect_to_server(uint16_t port, uint32_t attempts, uint32_t delay) {
} }
static void static void
close_socket(socket_t socket) { close_socket(socket_t *socket) {
assert(socket != INVALID_SOCKET); assert(*socket != INVALID_SOCKET);
net_shutdown(socket, SHUT_RDWR); net_shutdown(*socket, SHUT_RDWR);
if (!net_close(socket)) { if (!net_close(*socket)) {
LOGW("Could not close socket"); LOGW("Could not close socket");
return;
} }
*socket = INVALID_SOCKET;
} }
void void
@@ -331,22 +332,6 @@ server_init(struct server *server) {
*server = (struct server) SERVER_INITIALIZER; *server = (struct server) SERVER_INITIALIZER;
} }
static int
run_wait_server(void *data) {
struct server *server = data;
cmd_simple_wait(server->process, NULL); // ignore exit code
// no need for synchronization, server_socket is initialized before this
// thread was created
if (server->server_socket != INVALID_SOCKET
&& !atomic_flag_test_and_set(&server->server_socket_closed)) {
// On Linux, accept() is unblocked by shutdown(), but on Windows, it is
// unblocked by closesocket(). Therefore, call both (close_socket()).
close_socket(server->server_socket);
}
LOGD("Server terminated");
return 0;
}
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) {
@@ -360,50 +345,30 @@ server_start(struct server *server, const char *serial,
} }
if (!push_server(serial)) { if (!push_server(serial)) {
goto error1; SDL_free(server->serial);
return false;
} }
if (!enable_tunnel_any_port(server, params->port_range)) { if (!enable_tunnel_any_port(server, params->port_range)) {
goto error1; SDL_free(server->serial);
return false;
} }
// server will connect to our server socket // server will connect to our server socket
server->process = execute_server(server, params); server->process = execute_server(server, params);
if (server->process == PROCESS_NONE) {
goto error2;
}
// If the server process dies before connecting to the server socket, then if (server->process == PROCESS_NONE) {
// the client will be stuck forever on accept(). To avoid the problem, we if (!server->tunnel_forward) {
// must be able to wake up the accept() call when the server dies. To keep close_socket(&server->server_socket);
// things simple and multiplatform, just spawn a new thread waiting for the }
// server process and calling shutdown()/close() on the server socket if disable_tunnel(server);
// necessary to wake up any accept() blocking call. SDL_free(server->serial);
server->wait_server_thread = return false;
SDL_CreateThread(run_wait_server, "wait-server", server);
if (!server->wait_server_thread) {
cmd_terminate(server->process);
cmd_simple_wait(server->process, NULL); // ignore exit code
goto error2;
} }
server->tunnel_enabled = true; server->tunnel_enabled = true;
return true; return true;
error2:
if (!server->tunnel_forward) {
bool was_closed =
atomic_flag_test_and_set(&server->server_socket_closed);
// the thread is not started, the flag could not be already set
assert(!was_closed);
(void) was_closed;
close_socket(server->server_socket);
}
disable_tunnel(server);
error1:
SDL_free(server->serial);
return false;
} }
bool bool
@@ -421,11 +386,7 @@ server_connect_to(struct server *server) {
} }
// we don't need the server socket anymore // we don't need the server socket anymore
if (!atomic_flag_test_and_set(&server->server_socket_closed)) { close_socket(&server->server_socket);
// close it from here
close_socket(server->server_socket);
// otherwise, it is closed by run_wait_server()
}
} else { } else {
uint32_t attempts = 100; uint32_t attempts = 100;
uint32_t delay = 100; // ms uint32_t delay = 100; // ms
@@ -452,27 +413,29 @@ server_connect_to(struct server *server) {
void void
server_stop(struct server *server) { server_stop(struct server *server) {
if (server->server_socket != INVALID_SOCKET if (server->server_socket != INVALID_SOCKET) {
&& !atomic_flag_test_and_set(&server->server_socket_closed)) { close_socket(&server->server_socket);
close_socket(server->server_socket);
} }
if (server->video_socket != INVALID_SOCKET) { if (server->video_socket != INVALID_SOCKET) {
close_socket(server->video_socket); close_socket(&server->video_socket);
} }
if (server->control_socket != INVALID_SOCKET) { if (server->control_socket != INVALID_SOCKET) {
close_socket(server->control_socket); close_socket(&server->control_socket);
} }
assert(server->process != PROCESS_NONE); assert(server->process != PROCESS_NONE);
cmd_terminate(server->process); if (!cmd_terminate(server->process)) {
LOGW("Could not terminate server");
}
cmd_simple_wait(server->process, NULL); // ignore exit code
LOGD("Server terminated");
if (server->tunnel_enabled) { if (server->tunnel_enabled) {
// ignore failure // ignore failure
disable_tunnel(server); disable_tunnel(server);
} }
SDL_WaitThread(server->wait_server_thread, NULL);
} }
void void

View File

@@ -1,10 +1,8 @@
#ifndef SERVER_H #ifndef SERVER_H
#define SERVER_H #define SERVER_H
#include <stdatomic.h>
#include <stdbool.h> #include <stdbool.h>
#include <stdint.h> #include <stdint.h>
#include <SDL2/SDL_thread.h>
#include "config.h" #include "config.h"
#include "command.h" #include "command.h"
@@ -14,8 +12,6 @@
struct server { struct server {
char *serial; char *serial;
process_t process; process_t process;
SDL_Thread *wait_server_thread;
atomic_flag server_socket_closed;
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;
@@ -28,8 +24,6 @@ struct server {
#define SERVER_INITIALIZER { \ #define SERVER_INITIALIZER { \
.serial = NULL, \ .serial = NULL, \
.process = PROCESS_NONE, \ .process = PROCESS_NONE, \
.wait_server_thread = NULL, \
.server_socket_closed = ATOMIC_FLAG_INIT, \
.server_socket = INVALID_SOCKET, \ .server_socket = INVALID_SOCKET, \
.video_socket = INVALID_SOCKET, \ .video_socket = INVALID_SOCKET, \
.control_socket = INVALID_SOCKET, \ .control_socket = INVALID_SOCKET, \

View File

@@ -14,7 +14,6 @@
#include <limits.h> #include <limits.h>
#include <signal.h> #include <signal.h>
#include <stdlib.h> #include <stdlib.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/stat.h>

View File

@@ -49,20 +49,20 @@ static void test_serialize_inject_text(void) {
static void test_serialize_inject_text_long(void) { static void test_serialize_inject_text_long(void) {
struct control_msg msg; struct control_msg msg;
msg.type = CONTROL_MSG_TYPE_INJECT_TEXT; msg.type = CONTROL_MSG_TYPE_INJECT_TEXT;
char text[CONTROL_MSG_INJECT_TEXT_MAX_LENGTH + 1]; char text[CONTROL_MSG_TEXT_MAX_LENGTH + 1];
memset(text, 'a', sizeof(text)); memset(text, 'a', sizeof(text));
text[CONTROL_MSG_INJECT_TEXT_MAX_LENGTH] = '\0'; text[CONTROL_MSG_TEXT_MAX_LENGTH] = '\0';
msg.inject_text.text = text; msg.inject_text.text = text;
unsigned char buf[CONTROL_MSG_SERIALIZED_MAX_SIZE]; unsigned char buf[CONTROL_MSG_SERIALIZED_MAX_SIZE];
int size = control_msg_serialize(&msg, buf); int size = control_msg_serialize(&msg, buf);
assert(size == 3 + CONTROL_MSG_INJECT_TEXT_MAX_LENGTH); assert(size == 3 + CONTROL_MSG_TEXT_MAX_LENGTH);
unsigned char expected[3 + CONTROL_MSG_INJECT_TEXT_MAX_LENGTH]; unsigned char expected[3 + CONTROL_MSG_TEXT_MAX_LENGTH];
expected[0] = CONTROL_MSG_TYPE_INJECT_TEXT; expected[0] = CONTROL_MSG_TYPE_INJECT_TEXT;
expected[1] = 0x01; expected[1] = 0x01;
expected[2] = 0x2c; // text length (16 bits) expected[2] = 0x2c; // text length (16 bits)
memset(&expected[3], 'a', CONTROL_MSG_INJECT_TEXT_MAX_LENGTH); memset(&expected[3], 'a', CONTROL_MSG_TEXT_MAX_LENGTH);
assert(!memcmp(buf, expected, sizeof(expected))); assert(!memcmp(buf, expected, sizeof(expected)));
} }

View File

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

Binary file not shown.

View File

@@ -1,5 +1,5 @@
distributionBase=GRADLE_USER_HOME distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-6.3-bin.zip distributionUrl=https\://services.gradle.org/distributions/gradle-5.5.1-all.zip
zipStoreBase=GRADLE_USER_HOME zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists zipStorePath=wrapper/dists

33
gradlew vendored
View File

@@ -125,8 +125,8 @@ if $darwin; then
GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
fi fi
# For Cygwin or MSYS, switch paths to Windows format before running java # For Cygwin, switch paths to Windows format before running java
if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then if $cygwin ; then
APP_HOME=`cygpath --path --mixed "$APP_HOME"` APP_HOME=`cygpath --path --mixed "$APP_HOME"`
CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
JAVACMD=`cygpath --unix "$JAVACMD"` JAVACMD=`cygpath --unix "$JAVACMD"`
@@ -154,19 +154,19 @@ if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then
else else
eval `echo args$i`="\"$arg\"" eval `echo args$i`="\"$arg\""
fi fi
i=`expr $i + 1` i=$((i+1))
done done
case $i in case $i in
0) set -- ;; (0) set -- ;;
1) set -- "$args0" ;; (1) set -- "$args0" ;;
2) set -- "$args0" "$args1" ;; (2) set -- "$args0" "$args1" ;;
3) set -- "$args0" "$args1" "$args2" ;; (3) set -- "$args0" "$args1" "$args2" ;;
4) set -- "$args0" "$args1" "$args2" "$args3" ;; (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
esac esac
fi fi
@@ -175,9 +175,14 @@ save () {
for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
echo " " echo " "
} }
APP_ARGS=`save "$@"` APP_ARGS=$(save "$@")
# Collect all arguments for the java command, following the shell quoting and substitution rules # Collect all arguments for the java command, following the shell quoting and substitution rules
eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong
if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then
cd "$(dirname "$0")"
fi
exec "$JAVACMD" "$@" exec "$JAVACMD" "$@"

3
gradlew.bat vendored
View File

@@ -29,9 +29,6 @@ if "%DIRNAME%" == "" set DIRNAME=.
set APP_BASE_NAME=%~n0 set APP_BASE_NAME=%~n0
set APP_HOME=%DIRNAME% set APP_HOME=%DIRNAME%
@rem Resolve any "." and ".." in APP_HOME to make it shorter.
for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"

View File

@@ -1,6 +1,6 @@
project('scrcpy', 'c', project('scrcpy', 'c',
version: '1.12.1', version: '1.12.1',
meson_version: '>= 0.48', meson_version: '>= 0.37',
default_options: [ default_options: [
'c_std=c11', 'c_std=c11',
'warning_level=2', 'warning_level=2',

View File

@@ -3,9 +3,7 @@
prebuilt_server = get_option('prebuilt_server') prebuilt_server = get_option('prebuilt_server')
if prebuilt_server == '' if prebuilt_server == ''
custom_target('scrcpy-server', custom_target('scrcpy-server',
# gradle is responsible for tracking source changes build_always: true, # gradle is responsible for tracking source changes
build_by_default: true,
build_always_stale: true,
output: 'scrcpy-server', output: 'scrcpy-server',
command: [find_program('./scripts/build-wrapper.sh'), meson.current_source_dir(), '@OUTPUT@', get_option('buildtype')], command: [find_program('./scripts/build-wrapper.sh'), meson.current_source_dir(), '@OUTPUT@', get_option('buildtype')],
console: true, console: true,

View File

@@ -13,8 +13,8 @@ public class ControlMessageReader {
static final int INJECT_SCROLL_EVENT_PAYLOAD_LENGTH = 20; static final int INJECT_SCROLL_EVENT_PAYLOAD_LENGTH = 20;
static final int SET_SCREEN_POWER_MODE_PAYLOAD_LENGTH = 1; static final int SET_SCREEN_POWER_MODE_PAYLOAD_LENGTH = 1;
public static final int TEXT_MAX_LENGTH = 300;
public static final int CLIPBOARD_TEXT_MAX_LENGTH = 4093; public static final int CLIPBOARD_TEXT_MAX_LENGTH = 4093;
public static final int INJECT_TEXT_MAX_LENGTH = 300;
private static final int RAW_BUFFER_SIZE = 1024; private static final int RAW_BUFFER_SIZE = 1024;
private final byte[] rawBuffer = new byte[RAW_BUFFER_SIZE]; private final byte[] rawBuffer = new byte[RAW_BUFFER_SIZE];

View File

@@ -36,7 +36,10 @@ public final class Device {
*/ */
private final int layerStack; private final int layerStack;
private final boolean supportsInputEvents; /**
* The FLAG_PRESENTATION from the DisplayInfo
*/
private final boolean isPresentationDisplay;
public Device(Options options) { public Device(Options options) {
displayId = options.getDisplayId(); displayId = options.getDisplayId();
@@ -50,6 +53,7 @@ public final class Device {
screenInfo = ScreenInfo.computeScreenInfo(displayInfo, options.getCrop(), options.getMaxSize(), options.getLockedVideoOrientation()); screenInfo = ScreenInfo.computeScreenInfo(displayInfo, options.getCrop(), options.getMaxSize(), options.getLockedVideoOrientation());
layerStack = displayInfo.getLayerStack(); layerStack = displayInfo.getLayerStack();
isPresentationDisplay = (displayInfoFlags & DisplayInfo.FLAG_PRESENTATION) != 0;
registerRotationWatcher(new IRotationWatcher.Stub() { registerRotationWatcher(new IRotationWatcher.Stub() {
@Override @Override
@@ -63,16 +67,14 @@ public final class Device {
} }
} }
} }
}, displayId); });
if ((displayInfoFlags & DisplayInfo.FLAG_SUPPORTS_PROTECTED_BUFFERS) == 0) { if ((displayInfoFlags & DisplayInfo.FLAG_SUPPORTS_PROTECTED_BUFFERS) == 0) {
Ln.w("Display doesn't have FLAG_SUPPORTS_PROTECTED_BUFFERS flag, mirroring can be restricted"); Ln.w("Display doesn't have FLAG_SUPPORTS_PROTECTED_BUFFERS flag, mirroring can be restricted");
} }
// main display or any display on Android >= Q if (!supportsInputEvents()) {
supportsInputEvents = displayId == 0 || Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q; Ln.w("Input events are not supported for displays with FLAG_PRESENTATION enabled for devices with API lower than 29");
if (!supportsInputEvents) {
Ln.w("Input events are not supported for secondary displays before Android 10");
} }
} }
@@ -114,7 +116,10 @@ public final class Device {
} }
public boolean supportsInputEvents() { public boolean supportsInputEvents() {
return supportsInputEvents; if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
return true;
}
return !isPresentationDisplay;
} }
public boolean injectInputEvent(InputEvent inputEvent, int mode) { public boolean injectInputEvent(InputEvent inputEvent, int mode) {
@@ -133,8 +138,8 @@ public final class Device {
return serviceManager.getPowerManager().isScreenOn(); return serviceManager.getPowerManager().isScreenOn();
} }
public void registerRotationWatcher(IRotationWatcher rotationWatcher, int displayId) { public void registerRotationWatcher(IRotationWatcher rotationWatcher) {
serviceManager.getWindowManager().registerRotationWatcher(rotationWatcher, displayId); serviceManager.getWindowManager().registerRotationWatcher(rotationWatcher);
} }
public synchronized void setRotationListener(RotationListener rotationListener) { public synchronized void setRotationListener(RotationListener rotationListener) {
@@ -158,11 +163,9 @@ public final class Device {
} }
public void setClipboardText(String text) { public void setClipboardText(String text) {
boolean ok = serviceManager.getClipboardManager().setText(text); serviceManager.getClipboardManager().setText(text);
if (ok) {
Ln.i("Device clipboard set"); Ln.i("Device clipboard set");
} }
}
/** /**
* @param mode one of the {@code SCREEN_POWER_MODE_*} constants * @param mode one of the {@code SCREEN_POWER_MODE_*} constants
@@ -173,11 +176,9 @@ public final class Device {
Ln.e("Could not get built-in display"); Ln.e("Could not get built-in display");
return; return;
} }
boolean ok = SurfaceControl.setDisplayPowerMode(d, mode); SurfaceControl.setDisplayPowerMode(d, mode);
if (ok) {
Ln.i("Device screen turned " + (mode == Device.POWER_MODE_OFF ? "off" : "on")); Ln.i("Device screen turned " + (mode == Device.POWER_MODE_OFF ? "off" : "on"));
} }
}
/** /**
* 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

@@ -8,6 +8,7 @@ public final class DisplayInfo {
private final int flags; private final int flags;
public static final int FLAG_SUPPORTS_PROTECTED_BUFFERS = 0x00000001; public static final int FLAG_SUPPORTS_PROTECTED_BUFFERS = 0x00000001;
public static final int FLAG_PRESENTATION = 0x00000008;
public DisplayInfo(int displayId, Size size, int rotation, int layerStack, int flags) { public DisplayInfo(int displayId, Size size, int rotation, int layerStack, int flags) {
this.displayId = displayId; this.displayId = displayId;

View File

@@ -27,13 +27,21 @@ public class ScreenEncoder implements Device.RotationListener {
private int bitRate; private int bitRate;
private int maxFps; private int maxFps;
private int lockedVideoOrientation;
private int iFrameInterval;
private boolean sendFrameMeta; private boolean sendFrameMeta;
private long ptsOrigin; private long ptsOrigin;
public ScreenEncoder(boolean sendFrameMeta, int bitRate, int maxFps) { public ScreenEncoder(boolean sendFrameMeta, int bitRate, int maxFps, int lockedVideoOrientation, int iFrameInterval) {
this.sendFrameMeta = sendFrameMeta; this.sendFrameMeta = sendFrameMeta;
this.bitRate = bitRate; this.bitRate = bitRate;
this.maxFps = maxFps; this.maxFps = maxFps;
this.lockedVideoOrientation = lockedVideoOrientation;
this.iFrameInterval = iFrameInterval;
}
public ScreenEncoder(boolean sendFrameMeta, int bitRate, int maxFps, int lockedVideoOrientation) {
this(sendFrameMeta, bitRate, maxFps, lockedVideoOrientation, DEFAULT_I_FRAME_INTERVAL);
} }
@Override @Override
@@ -49,7 +57,7 @@ public class ScreenEncoder implements Device.RotationListener {
Workarounds.prepareMainLooper(); Workarounds.prepareMainLooper();
Workarounds.fillAppInfo(); Workarounds.fillAppInfo();
MediaFormat format = createFormat(bitRate, maxFps, DEFAULT_I_FRAME_INTERVAL); MediaFormat format = createFormat(bitRate, maxFps, iFrameInterval);
device.setRotationListener(this); device.setRotationListener(this);
boolean alive; boolean alive;
try { try {
@@ -136,12 +144,12 @@ public class ScreenEncoder implements Device.RotationListener {
} }
private static MediaCodec createCodec() throws IOException { private static MediaCodec createCodec() throws IOException {
return MediaCodec.createEncoderByType(MediaFormat.MIMETYPE_VIDEO_AVC); return MediaCodec.createEncoderByType("video/avc");
} }
private static MediaFormat createFormat(int bitRate, int maxFps, int iFrameInterval) { private static MediaFormat createFormat(int bitRate, int maxFps, int iFrameInterval) {
MediaFormat format = new MediaFormat(); MediaFormat format = new MediaFormat();
format.setString(MediaFormat.KEY_MIME, MediaFormat.MIMETYPE_VIDEO_AVC); format.setString(MediaFormat.KEY_MIME, "video/avc");
format.setInteger(MediaFormat.KEY_BIT_RATE, bitRate); format.setInteger(MediaFormat.KEY_BIT_RATE, bitRate);
// must be present to configure the encoder, but does not impact the actual frame rate, which is variable // must be present to configure the encoder, but does not impact the actual frame rate, which is variable
format.setInteger(MediaFormat.KEY_FRAME_RATE, 60); format.setInteger(MediaFormat.KEY_FRAME_RATE, 60);

View File

@@ -20,7 +20,8 @@ public final class Server {
final Device device = new Device(options); final Device device = new Device(options);
boolean tunnelForward = options.isTunnelForward(); boolean tunnelForward = options.isTunnelForward();
try (DesktopConnection connection = DesktopConnection.open(device, tunnelForward)) { try (DesktopConnection connection = DesktopConnection.open(device, tunnelForward)) {
ScreenEncoder screenEncoder = new ScreenEncoder(options.getSendFrameMeta(), options.getBitRate(), options.getMaxFps()); ScreenEncoder screenEncoder = new ScreenEncoder(options.getSendFrameMeta(), options.getBitRate(), options.getMaxFps(),
options.getLockedVideoOrientation());
if (options.getControl()) { if (options.getControl()) {
Controller controller = new Controller(device, connection); Controller controller = new Controller(device, connection);

View File

@@ -28,7 +28,7 @@ public final class Workarounds {
Looper.prepareMainLooper(); Looper.prepareMainLooper();
} }
@SuppressLint("PrivateApi,DiscouragedPrivateApi") @SuppressLint("PrivateApi")
public static void fillAppInfo() { public static void fillAppInfo() {
try { try {
// ActivityThread activityThread = new ActivityThread(); // ActivityThread activityThread = new ActivityThread();

View File

@@ -74,15 +74,13 @@ public class ClipboardManager {
} }
} }
public boolean setText(CharSequence text) { public void setText(CharSequence text) {
try { try {
Method method = getSetPrimaryClipMethod(); Method method = getSetPrimaryClipMethod();
ClipData clipData = ClipData.newPlainText(null, text); ClipData clipData = ClipData.newPlainText(null, text);
setPrimaryClip(method, manager, clipData); setPrimaryClip(method, manager, clipData);
return true;
} catch (InvocationTargetException | IllegalAccessException | NoSuchMethodException e) { } catch (InvocationTargetException | IllegalAccessException | NoSuchMethodException e) {
Ln.e("Could not invoke method", e); Ln.e("Could not invoke method", e);
return false;
} }
} }
} }

View File

@@ -53,7 +53,8 @@ public final class InputManager {
method.invoke(inputEvent, displayId); method.invoke(inputEvent, displayId);
return true; return true;
} catch (InvocationTargetException | IllegalAccessException | NoSuchMethodException e) { } catch (InvocationTargetException | IllegalAccessException | NoSuchMethodException e) {
Ln.e("Cannot associate a display id to the input event", e); // just a warning, it might happen on old devices
Ln.w("Cannot associate a display id to the input event");
return false; return false;
} }
} }

View File

@@ -6,7 +6,7 @@ import android.os.IInterface;
import java.lang.reflect.Method; import java.lang.reflect.Method;
@SuppressLint("PrivateApi,DiscouragedPrivateApi") @SuppressLint("PrivateApi")
public final class ServiceManager { public final class ServiceManager {
private final Method getServiceMethod; private final Method getServiceMethod;

View File

@@ -121,14 +121,12 @@ public final class SurfaceControl {
return setDisplayPowerModeMethod; return setDisplayPowerModeMethod;
} }
public static boolean setDisplayPowerMode(IBinder displayToken, int mode) { public static void setDisplayPowerMode(IBinder displayToken, int mode) {
try { try {
Method method = getSetDisplayPowerModeMethod(); Method method = getSetDisplayPowerModeMethod();
method.invoke(null, displayToken, mode); method.invoke(null, displayToken, mode);
return true;
} catch (InvocationTargetException | IllegalAccessException | NoSuchMethodException e) { } catch (InvocationTargetException | IllegalAccessException | NoSuchMethodException e) {
Ln.e("Could not invoke method", e); Ln.e("Could not invoke method", e);
return false;
} }
} }

View File

@@ -93,13 +93,13 @@ public final class WindowManager {
} }
} }
public void registerRotationWatcher(IRotationWatcher rotationWatcher, int displayId) { public void registerRotationWatcher(IRotationWatcher rotationWatcher) {
try { try {
Class<?> cls = manager.getClass(); Class<?> cls = manager.getClass();
try { try {
// display parameter added since this commit: // display parameter added since this commit:
// https://android.googlesource.com/platform/frameworks/base/+/35fa3c26adcb5f6577849fd0df5228b1f67cf2c6%5E%21/#F1 // https://android.googlesource.com/platform/frameworks/base/+/35fa3c26adcb5f6577849fd0df5228b1f67cf2c6%5E%21/#F1
cls.getMethod("watchRotation", IRotationWatcher.class, int.class).invoke(manager, rotationWatcher, displayId); cls.getMethod("watchRotation", IRotationWatcher.class, int.class).invoke(manager, rotationWatcher, 0);
} catch (NoSuchMethodException e) { } catch (NoSuchMethodException e) {
// old version // old version
cls.getMethod("watchRotation", IRotationWatcher.class).invoke(manager, rotationWatcher); cls.getMethod("watchRotation", IRotationWatcher.class).invoke(manager, rotationWatcher);

View File

@@ -66,7 +66,7 @@ public class ControlMessageReaderTest {
ByteArrayOutputStream bos = new ByteArrayOutputStream(); ByteArrayOutputStream bos = new ByteArrayOutputStream();
DataOutputStream dos = new DataOutputStream(bos); DataOutputStream dos = new DataOutputStream(bos);
dos.writeByte(ControlMessage.TYPE_INJECT_TEXT); dos.writeByte(ControlMessage.TYPE_INJECT_TEXT);
byte[] text = new byte[ControlMessageReader.INJECT_TEXT_MAX_LENGTH]; byte[] text = new byte[ControlMessageReader.TEXT_MAX_LENGTH];
Arrays.fill(text, (byte) 'a'); Arrays.fill(text, (byte) 'a');
dos.writeShort(text.length); dos.writeShort(text.length);
dos.write(text); dos.write(text);