Compare commits

...

34 Commits

Author SHA1 Message Date
0e272561d6 Disable input events when necessary
Disable input events on secondary displays before Android 10, even if
FLAG_PRESENTATION is not set.

Ref #1288 <https://github.com/Genymobile/scrcpy/issues/1288>
2020-04-15 21:11:00 +02:00
cc22f4622a Mention mipmapping in FAQ 2020-04-15 17:39:51 +02:00
11a61b2cb3 Add option --no-mipmaps
Add an option to disable trilinear filtering even if mipmapping is
available.
2020-04-15 17:39:51 +02:00
bea7658807 Enable trilinear filtering for OpenGL
Improve downscaling quality if mipmapping is available.

Suggested-by: Giumo Clanjor (哆啦比猫/兰威举) <cjxgm2@gmail.com>

Fixes #40 <https://github.com/Genymobile/scrcpy/issues/40>
Ref: <https://github.com/Genymobile/scrcpy/issues/40#issuecomment-591917787>
2020-04-15 17:39:51 +02:00
8a9b20b27e Add --render-driver command-line option
Add an option to set a render driver hint (SDL_HINT_RENDER_DRIVER).
2020-04-15 17:39:51 +02:00
d62eb2b11c Fix typo in README 2020-04-15 09:57:59 +02:00
eb8f7a1f28 Require Meson 0.48 to get rid of warnings
Debian buster (stable) provides Meson 0.49, which is also available in
stretch (oldstable) backports. It's time to abandon Meson 0.37.

Ref: 20b3f101a4
2020-04-13 22:47:03 +02:00
270d0bf639 Rename max length constant for text injection
To avoid confusion with the max text size for clipboard, rename the
constant limiting the text injection length.
2020-04-13 19:38:43 +02:00
95fa1a69e4 Workaround compiler warning
Some compilers warns on uninitialized value in impossible case:

    warning: variable 'result' is used uninitialized whenever switch
    default is taken [-Wsometimes-uninitialized]
2020-04-13 16:33:21 +02:00
ea46d3ab68 Add missing include string.h
Include <string.h> for strdup() and strtok_r().
2020-04-13 16:33:21 +02:00
7eb16ce364 Fix log format warning
The expression port + 1 is promoted to int, but printed as uint16_t.
2020-04-13 16:33:19 +02:00
927d655ff6 Simplify ScreenEncoder
Do not handle iFrameInterval field and parameter, it is never used
dynamically.
2020-04-12 02:09:28 +02:00
ee2894779a Remove unused lockedVideoOrientation field
During PR #1151, this field has been moved to ScreenInfo, but has not
been removed from ScreenEncoder.
2020-04-12 02:08:16 +02:00
1c6207f8ce Merge branch 'master' into dev 2020-04-12 00:31:57 +02:00
ab52b36895 Reorder options in alphabetical order 2020-04-11 14:31:14 +02:00
9f4735ede3 Fix double click on rotated display
A double-click outside the device content (in the black borders) resizes
so that black borders are removed. But the display rotation was not
taken into account to detect the content.

Use the content size instead of the frame size to fix the issue.

Ref: <https://github.com/Genymobile/scrcpy/issues/898#issuecomment-610993695>
2020-04-08 16:37:33 +02:00
6295c1a110 Remap event positions on rotated display
If the display is rotated, the position of clicks must be adapted.
2020-04-08 14:27:25 +02:00
f3fba3c4b9 Store rotated content size
This avoids to compute it every time from the frame size.
2020-04-08 14:12:54 +02:00
c1ebea26e6 Register rotation watcher on selected display
PR #1275 <https://github.com/Genymobile/scrcpy/pull/1275>

Signed-off-by: Kostiantyn Luzan <vblack2006@gmail.com>
Signed-off-by: Romain Vimont <rom@rom1v.com>
2020-04-08 12:09:24 +02:00
f07d21f050 Suppress DiscouragedPrivateApi lint warning 2020-04-08 12:09:24 +02:00
a8fd4aec9a Remove --fullscreen validation
Many options are meaningless if --no-display is set.

We don't want to validate all possible combinations, so don't make an
exception for --fullscreen.
2020-04-08 12:09:24 +02:00
cbde7b964a Improve documentation for consistency
Make --lock-video-orientation documentation consistent with that of
--rotation.
2020-04-08 12:09:24 +02:00
28c71c528f Add --rotation command-line option
In addition to Ctrl+Left and Ctrl+Right shortcuts, add a command-line
parameter to set the initial rotation.
2020-04-08 12:09:22 +02:00
d48b375a1d Add shortcuts to rotate display
Add Ctrl+Left and Ctrl+Right shortcuts to rotate the display (the
content of the scrcpy window).

Contrary to --lock-video-orientation, the rotation has no impact on
recording, and can be changed dynamically (and immediately).

Fixes #218 <https://github.com/Genymobile/scrcpy/issues/218>
2020-04-08 12:02:26 +02:00
fd63e7eb5a Format shortcut documentation
For consistency, start the descriptions with a capital letter.
2020-04-08 12:02:15 +02:00
cdd8edbbb6 Add a note about prebuilt server in BUILD.md
Mention that it works with a matching client version.
2020-04-07 23:06:33 +02:00
9b9e717c41 Explain master and dev branches in BUILD
People may not guess that `master` is not the development branch.
2020-04-07 10:43:20 +02:00
15e4da08a3 Improve "low quality" section in FAQ 2020-04-07 10:37:19 +02:00
2afbfc2c75 Add Android device and version in issue template 2020-04-03 21:29:09 +02:00
2cf022491f Add issue templates
Closes #1157 <https://github.com/Genymobile/scrcpy/issues/1157>
2020-04-03 18:51:18 +02:00
7bb91638ad Improve FAQ 2020-03-19 19:22:58 +01:00
bc7508427b Add scoop instructions for Windows
PR #1202 <https://github.com/Genymobile/scrcpy/pull/1202>

Signed-off-by: Romain Vimont <rom@rom1v.com>
2020-03-14 17:11:49 +01:00
24ade6ad77 Simplify Chocolatey documentation 2020-03-14 17:07:20 +01:00
c396758b4e Remove link to Windows 32 bits release
Binaries created with MinGW (even a simple Hello World) are detected as
malware by some anti-virus. For some reason, only the 32 bits version of
scrcpy is impacted.

Since users should use the 64 bits version by default anyway, remove the
link to the 32 bits version from the main page.

The 32 bits release is still available in the "releases" tab.

See <https://github.com/Genymobile/scrcpy/issues/1102>
2020-03-03 21:39:27 +01:00
31 changed files with 754 additions and 166 deletions

25
.github/ISSUE_TEMPLATE/bug_report.md vendored Normal file
View File

@ -0,0 +1,25 @@
---
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

@ -0,0 +1,22 @@
---
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.

View File

@ -8,6 +8,22 @@ case, use the [prebuilt server] (so you will not need Java or the Android SDK).
[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
@ -247,3 +263,6 @@ meson x --buildtype release --strip -Db_lto=true \
ninja -Cx
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,19 +3,102 @@
Here are the common reported problems and their status.
### On Windows, my device is not detected
## `adb` issues
The most common is your device not being detected by `adb`, or is unauthorized.
Check everything is ok by calling:
`scrcpy` execute `adb` commands to initialize the connection with the device. If
`adb` fails, then scrcpy will not work.
adb devices
In that case, it will print this error:
Windows may need some [drivers] to detect your device.
> ERROR: "adb push" returned with value 1
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
### I can only mirror, I cannot interact with the device
### Several devices connected
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].
In developer options, enable:
@ -29,22 +112,43 @@ In developer options, enable:
### Mouse clicks at wrong location
On MacOS, with HiDPI support and multiple screens, input location are wrongly
scaled. See [issue 15].
scaled. See [#15].
[issue 15]: https://github.com/Genymobile/scrcpy/issues/15
[#15]: https://github.com/Genymobile/scrcpy/issues/15
A workaround is to build with HiDPI support disabled:
Open _scrcpy_ directly on the monitor you use it.
```bash
meson x --buildtype release -Dhidpi_support=false
### Special characters do not work
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
```
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].
You may also need to configure the [scaling behavior]:
> `scrcpy.exe` > Properties > Compatibility > Change high DPI settings >
> Override high DPI scaling behavior > Scaling performed by: _Application_.
@ -52,6 +156,7 @@ On Windows, you may need to configure the [scaling behavior].
[scaling behavior]: https://github.com/Genymobile/scrcpy/issues/40#issuecomment-424466723
### KWin compositor crashes
On Plasma Desktop, compositor is disabled while _scrcpy_ is running.
@ -61,19 +166,29 @@ As a workaround, [disable "Block compositing"][kwin].
[kwin]: https://github.com/Genymobile/scrcpy/issues/114#issuecomment-378778613
### I get an error "Could not open video stream"
## Crashes
### Exception
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:
```
ERROR: Exception on thread Thread[main,5,main]
android.media.MediaCodec$CodecException: Error 0xfffffc0e
...
Exit due to uncaughtException in main thread:
ERROR: Could not open video stream
INFO: Initial texture: 1080x2336
```
> ```
> ERROR: Exception on thread Thread[main,5,main]
> android.media.MediaCodec$CodecException: Error 0xfffffc0e
> ...
> Exit due to uncaughtException in main thread:
> ERROR: Could not open video stream
> 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:

View File

@ -66,15 +66,12 @@ hard).
### Windows
For Windows, for simplicity, prebuilt archives with all the dependencies
(including `adb`) are available:
For Windows, for simplicity, a prebuilt archive with all the dependencies
(including `adb`) is available:
- [`scrcpy-win32-v1.12.1.zip`][direct-win32]
_(SHA-256: 0f4b3b063536b50a2df05dc42c760f9cc0093a9a26dbdf02d8232c74dab43480)_
- [`scrcpy-win64-v1.12.1.zip`][direct-win64]
_(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
It is also available in [Chocolatey]:
@ -83,14 +80,18 @@ It is also available in [Chocolatey]:
```bash
choco install scrcpy
choco install adb # if you don't have it yet
```
You need `adb`, accessible from your `PATH`. If you don't have it yet:
And in [Scoop]:
```bash
choco install adb
scoop install scrcpy
scoop install adb # if you don't have it yet
```
[Scoop]: https://scoop.sh
You can also [build the app manually][BUILD].
@ -340,6 +341,33 @@ scrcpy -f # short version
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
@ -491,6 +519,8 @@ Also see [issue #14].
| Action | Shortcut | Shortcut (macOS)
| -------------------------------------- |:----------------------------- |:-----------------------------
| 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 remove black borders | `Ctrl`+`x` \| _Double-click¹_ | `Cmd`+`x` \| _Double-click¹_
| Click on `HOME` | `Ctrl`+`h` \| _Middle-click_ | `Ctrl`+`h` \| _Middle-click_

View File

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

View File

@ -52,7 +52,7 @@ Print this help.
.TP
.BI "\-\-lock\-video\-orientation " value
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.
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.
Default is -1 (unlocked).
@ -74,6 +74,10 @@ Disable device control (mirror the device in read\-only).
.B \-N, \-\-no\-display
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
.BI "\-p, \-\-port " port[:port]
Set the TCP port (range) used by the client to listen.
@ -106,10 +110,22 @@ option if set, or by the file extension (.mp4 or .mkv).
.BI "\-\-record\-format " format
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
.B \-\-render\-expired\-frames
By default, to minimize latency, scrcpy always renders the last available decoded frame, and drops any previous ones. This flag forces to render all frames, at a cost of a possible increased latency.
.TP
.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
.BI "\-s, \-\-serial " number
The device serial number. Mandatory only if several devices are connected to adb.
@ -164,15 +180,23 @@ Default is 0 (automatic).\n
.TP
.B Ctrl+f
switch fullscreen mode
Switch fullscreen mode
.TP
.B Ctrl+Left
Rotate display left
.TP
.B Ctrl+Right
Rotate display right
.TP
.B Ctrl+g
resize window to 1:1 (pixel\-perfect)
Resize window to 1:1 (pixel\-perfect)
.TP
.B Ctrl+x, Double\-click on black borders
resize window to remove black borders
Resize window to remove black borders
.TP
.B Ctrl+h, Home, Middle\-click
@ -204,43 +228,43 @@ Click on POWER (turn screen on/off)
.TP
.B Right\-click (when screen is off)
turn screen on
Turn screen on
.TP
.B Ctrl+o
turn device screen off (keep mirroring)
Turn device screen off (keep mirroring)
.TP
.B Ctrl+r
rotate device screen
Rotate device screen
.TP
.B Ctrl+n
expand notification panel
Expand notification panel
.TP
.B Ctrl+Shift+n
collapse notification panel
Collapse notification panel
.TP
.B Ctrl+c
copy device clipboard to computer
Copy device clipboard to computer
.TP
.B Ctrl+v
paste computer clipboard to device
Paste computer clipboard to device
.TP
.B Ctrl+Shift+v
copy computer clipboard to device
Copy computer clipboard to device
.TP
.B Ctrl+i
enable/disable FPS counter (print frames/second in logs)
Enable/disable FPS counter (print frames/second in logs)
.TP
.B Drag & drop APK file
install APK from computer
Install APK from computer
.SH Environment variables

View File

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

View File

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

View File

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

View File

@ -221,6 +221,18 @@ 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
input_manager_process_text_input(struct input_manager *im,
const SDL_TextInputEvent *event) {
@ -351,6 +363,16 @@ input_manager_process_key(struct input_manager *im,
action_volume_up(controller, action);
}
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:
if (control && cmd && !shift && !repeat && down) {
request_device_clipboard(controller);
@ -420,6 +442,36 @@ input_manager_process_key(struct input_manager *im,
}
}
static struct point
rotate_position(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;
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;
}
static bool
convert_mouse_motion(const SDL_MouseMotionEvent *from, struct screen *screen,
struct control_msg *to) {
@ -427,8 +479,8 @@ convert_mouse_motion(const SDL_MouseMotionEvent *from, struct screen *screen,
to->inject_touch_event.action = AMOTION_EVENT_ACTION_MOVE;
to->inject_touch_event.pointer_id = POINTER_ID_MOUSE;
to->inject_touch_event.position.screen_size = screen->frame_size;
to->inject_touch_event.position.point.x = from->x;
to->inject_touch_event.position.point.y = from->y;
to->inject_touch_event.position.point =
rotate_position(screen, from->x, from->y);
to->inject_touch_event.pressure = 1.f;
to->inject_touch_event.buttons = convert_mouse_buttons(from->state);
@ -468,8 +520,9 @@ convert_touch(const SDL_TouchFingerEvent *from, struct screen *screen,
to->inject_touch_event.pointer_id = from->fingerId;
to->inject_touch_event.position.screen_size = frame_size;
// SDL touch event coordinates are normalized in the range [0; 1]
to->inject_touch_event.position.point.x = from->x * frame_size.width;
to->inject_touch_event.position.point.y = from->y * frame_size.height;
float x = from->x * frame_size.width;
float y = from->y * frame_size.height;
to->inject_touch_event.position.point = rotate_position(screen, x, y);
to->inject_touch_event.pressure = from->pressure;
to->inject_touch_event.buttons = 0;
return true;
@ -489,8 +542,8 @@ 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;
return x < 0 || x >= im->screen->content_size.width ||
y < 0 || y >= im->screen->content_size.height;
}
static bool
@ -504,8 +557,8 @@ convert_mouse_button(const SDL_MouseButtonEvent *from, struct screen *screen,
to->inject_touch_event.pointer_id = POINTER_ID_MOUSE;
to->inject_touch_event.position.screen_size = screen->frame_size;
to->inject_touch_event.position.point.x = from->x;
to->inject_touch_event.position.point.y = from->y;
to->inject_touch_event.position.point =
rotate_position(screen, from->x, from->y);
to->inject_touch_event.pressure = 1.f;
to->inject_touch_event.buttons =
convert_mouse_buttons(SDL_BUTTON(from->button));

56
app/src/opengl.c Normal file
View File

@ -0,0 +1,56 @@
#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);
}

36
app/src/opengl.h Normal file
View File

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

View File

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

View File

@ -15,6 +15,19 @@
#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
static struct size
get_window_size(SDL_Window *window) {
@ -80,8 +93,8 @@ get_preferred_display_bounds(struct size *bounds) {
// - it keeps the aspect ratio
// - it scales down to make it fit in the display_size
static struct size
get_optimal_size(struct size current_size, struct size frame_size) {
if (frame_size.width == 0 || frame_size.height == 0) {
get_optimal_size(struct size current_size, struct size content_size) {
if (content_size.width == 0 || content_size.height == 0) {
// avoid division by 0
return current_size;
}
@ -100,14 +113,14 @@ get_optimal_size(struct size current_size, struct size frame_size) {
h = MIN(current_size.height, display_size.height);
}
bool keep_width = frame_size.width * h > frame_size.height * w;
bool keep_width = content_size.width * h > content_size.height * w;
if (keep_width) {
// remove black borders on top and bottom
h = frame_size.height * w / frame_size.width;
h = content_size.height * w / content_size.width;
} else {
// remove black borders on left and right (or none at all if it already
// fits)
w = frame_size.width * h / frame_size.height;
w = content_size.width * h / content_size.height;
}
// w and h must fit into 16 bits
@ -117,33 +130,33 @@ get_optimal_size(struct size current_size, struct size frame_size) {
// same as get_optimal_size(), but read the current size from the window
static inline struct size
get_optimal_window_size(const struct screen *screen, struct size frame_size) {
get_optimal_window_size(const struct screen *screen, struct size content_size) {
struct size windowed_size = get_windowed_window_size(screen);
return get_optimal_size(windowed_size, frame_size);
return get_optimal_size(windowed_size, content_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
static inline struct size
get_initial_optimal_size(struct size frame_size, uint16_t req_width,
get_initial_optimal_size(struct size content_size, uint16_t req_width,
uint16_t req_height) {
struct size window_size;
if (!req_width && !req_height) {
window_size = get_optimal_size(frame_size, frame_size);
window_size = get_optimal_size(content_size, content_size);
} else {
if (req_width) {
window_size.width = req_width;
} else {
// compute from the requested height
window_size.width = (uint32_t) req_height * frame_size.width
/ frame_size.height;
window_size.width = (uint32_t) req_height * content_size.width
/ content_size.height;
}
if (req_height) {
window_size.height = req_height;
} else {
// compute from the requested width
window_size.height = (uint32_t) req_width * frame_size.height
/ frame_size.width;
window_size.height = (uint32_t) req_width * content_size.height
/ content_size.width;
}
}
return window_size;
@ -155,21 +168,48 @@ screen_init(struct screen *screen) {
}
static inline SDL_Texture *
create_texture(SDL_Renderer *renderer, struct size frame_size) {
return SDL_CreateTexture(renderer, SDL_PIXELFORMAT_YV12,
SDL_TEXTUREACCESS_STREAMING,
frame_size.width, frame_size.height);
create_texture(struct screen *screen) {
SDL_Renderer *renderer = screen->renderer;
struct size size = screen->frame_size;
SDL_Texture *texture = SDL_CreateTexture(renderer, SDL_PIXELFORMAT_YV12,
SDL_TEXTUREACCESS_STREAMING,
size.width, 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
screen_init_rendering(struct screen *screen, const char *window_title,
struct size frame_size, bool always_on_top,
int16_t window_x, int16_t window_y, uint16_t window_width,
uint16_t window_height, bool window_borderless) {
uint16_t window_height, bool window_borderless,
uint8_t rotation, bool mipmaps) {
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 =
get_initial_optimal_size(frame_size, window_width, window_height);
get_initial_optimal_size(content_size, window_width, window_height);
uint32_t window_flags = SDL_WINDOW_HIDDEN | SDL_WINDOW_RESIZABLE;
#ifdef HIDPI_SUPPORT
window_flags |= SDL_WINDOW_ALLOW_HIGHDPI;
@ -206,13 +246,44 @@ screen_init_rendering(struct screen *screen, const char *window_title,
return false;
}
if (SDL_RenderSetLogicalSize(screen->renderer, frame_size.width,
frame_size.height)) {
SDL_RendererInfo renderer_info;
int r = SDL_GetRendererInfo(screen->renderer, &renderer_info);
const char *renderer_name = r ? NULL : renderer_info.name;
LOGI("Renderer: %s", renderer_name ? renderer_name : "(unknown)");
if (SDL_RenderSetLogicalSize(screen->renderer, content_size.width,
content_size.height)) {
LOGE("Could not set renderer logical size: %s", SDL_GetError());
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);
if (icon) {
SDL_SetWindowIcon(screen->window, icon);
@ -223,7 +294,7 @@ screen_init_rendering(struct screen *screen, const char *window_title,
LOGI("Initial texture: %" PRIu16 "x%" PRIu16, frame_size.width,
frame_size.height);
screen->texture = create_texture(screen->renderer, frame_size);
screen->texture = create_texture(screen);
if (!screen->texture) {
LOGC("Could not create texture: %s", SDL_GetError());
screen_destroy(screen);
@ -253,13 +324,51 @@ 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);
if (SDL_RenderSetLogicalSize(screen->renderer,
new_content_size.width,
new_content_size.height)) {
LOGE("Could not set renderer logical size: %s", SDL_GetError());
return;
}
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);
screen_render(screen);
}
// recreate the texture and resize the window if the frame size has changed
static bool
prepare_for_frame(struct screen *screen, struct size new_frame_size) {
if (screen->frame_size.width != new_frame_size.width
|| screen->frame_size.height != new_frame_size.height) {
if (SDL_RenderSetLogicalSize(screen->renderer, new_frame_size.width,
new_frame_size.height)) {
struct size new_content_size =
get_rotated_size(new_frame_size, screen->rotation);
if (SDL_RenderSetLogicalSize(screen->renderer,
new_content_size.width,
new_content_size.height)) {
LOGE("Could not set renderer logical size: %s", SDL_GetError());
return false;
}
@ -267,21 +376,23 @@ prepare_for_frame(struct screen *screen, struct size new_frame_size) {
// frame dimension changed, destroy texture
SDL_DestroyTexture(screen->texture);
struct size content_size = screen->content_size;
struct size windowed_size = get_windowed_window_size(screen);
struct size target_size = {
(uint32_t) windowed_size.width * new_frame_size.width
/ screen->frame_size.width,
(uint32_t) windowed_size.height * new_frame_size.height
/ screen->frame_size.height,
(uint32_t) windowed_size.width * new_content_size.width
/ content_size.width,
(uint32_t) windowed_size.height * new_content_size.height
/ content_size.height,
};
target_size = get_optimal_size(target_size, new_frame_size);
target_size = get_optimal_size(target_size, new_content_size);
set_window_size(screen, target_size);
screen->frame_size = new_frame_size;
screen->content_size = new_content_size;
LOGI("New texture: %" PRIu16 "x%" PRIu16,
screen->frame_size.width, screen->frame_size.height);
screen->texture = create_texture(screen->renderer, new_frame_size);
screen->texture = create_texture(screen);
if (!screen->texture) {
LOGC("Could not create texture: %s", SDL_GetError());
return false;
@ -298,6 +409,13 @@ update_texture(struct screen *screen, const AVFrame *frame) {
frame->data[0], frame->linesize[0],
frame->data[1], frame->linesize[1],
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
@ -319,7 +437,28 @@ screen_update_frame(struct screen *screen, struct video_buffer *vb) {
void
screen_render(struct screen *screen) {
SDL_RenderClear(screen->renderer);
SDL_RenderCopy(screen->renderer, screen->texture, NULL, NULL);
if (screen->rotation == 0) {
SDL_RenderCopy(screen->renderer, screen->texture, NULL, NULL);
} 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) {
struct size size = screen->content_size;
rect.x = (size.width - size.height) / 2;
rect.y = (size.height - size.width) / 2;
rect.w = size.height;
rect.h = size.width;
dstrect = &rect;
}
SDL_RenderCopyEx(screen->renderer, screen->texture, NULL, dstrect,
angle, NULL, 0);
}
SDL_RenderPresent(screen->renderer);
}
@ -350,7 +489,7 @@ screen_resize_to_fit(struct screen *screen) {
}
struct size optimal_size =
get_optimal_window_size(screen, screen->frame_size);
get_optimal_window_size(screen, screen->content_size);
SDL_SetWindowSize(screen->window, optimal_size.width, optimal_size.height);
LOGD("Resized to optimal size");
}
@ -366,8 +505,8 @@ screen_resize_to_pixel_perfect(struct screen *screen) {
screen->maximized = false;
}
SDL_SetWindowSize(screen->window, screen->frame_size.width,
screen->frame_size.height);
struct size content_size = screen->content_size;
SDL_SetWindowSize(screen->window, content_size.width, content_size.height);
LOGD("Resized to pixel-perfect");
}

View File

@ -7,6 +7,7 @@
#include "config.h"
#include "common.h"
#include "opengl.h"
#define WINDOW_POSITION_UNDEFINED (-0x8000)
@ -16,24 +17,36 @@ struct screen {
SDL_Window *window;
SDL_Renderer *renderer;
SDL_Texture *texture;
bool use_opengl;
struct sc_opengl gl;
struct size frame_size;
struct size content_size; // rotated frame_size
// The window size the last time it was not maximized or fullscreen.
struct size windowed_window_size;
// Since we receive the event SIZE_CHANGED before MAXIMIZED, we must be
// able to revert the size to its non-maximized value.
struct size windowed_window_size_backup;
// client rotation: 0, 1, 2 or 3 (x90 degrees counterclockwise)
unsigned rotation;
bool has_frame;
bool fullscreen;
bool maximized;
bool no_window;
bool mipmaps;
};
#define SCREEN_INITIALIZER { \
.window = NULL, \
.renderer = NULL, \
.texture = NULL, \
.use_opengl = false, \
.gl = {0}, \
.frame_size = { \
.width = 0, \
.width = 0, \
.height = 0, \
}, \
.content_size = { \
.width = 0, \
.height = 0, \
}, \
.windowed_window_size = { \
@ -44,10 +57,12 @@ struct screen {
.width = 0, \
.height = 0, \
}, \
.rotation = 0, \
.has_frame = false, \
.fullscreen = false, \
.maximized = false, \
.no_window = false, \
.mipmaps = false, \
}
// initialize default values
@ -60,7 +75,8 @@ bool
screen_init_rendering(struct screen *screen, const char *window_title,
struct size frame_size, bool always_on_top,
int16_t window_x, int16_t window_y, uint16_t window_width,
uint16_t window_height, bool window_borderless);
uint16_t window_height, bool window_borderless,
uint8_t rotation, bool mipmaps);
// show the window
void
@ -90,6 +106,10 @@ screen_resize_to_fit(struct screen *screen);
void
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
void
screen_handle_window_event(struct screen *screen, const SDL_WindowEvent *event);

View File

@ -172,7 +172,7 @@ enable_tunnel_reverse_any_port(struct server *server,
// check before incrementing to avoid overflow on port 65535
if (port < port_range.last) {
LOGW("Could not listen on port %" PRIu16", retrying on %" PRIu16,
port, port + 1);
port, (uint16_t) (port + 1));
port++;
continue;
}

View File

@ -14,6 +14,7 @@
#include <limits.h>
#include <signal.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/types.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) {
struct control_msg msg;
msg.type = CONTROL_MSG_TYPE_INJECT_TEXT;
char text[CONTROL_MSG_TEXT_MAX_LENGTH + 1];
char text[CONTROL_MSG_INJECT_TEXT_MAX_LENGTH + 1];
memset(text, 'a', sizeof(text));
text[CONTROL_MSG_TEXT_MAX_LENGTH] = '\0';
text[CONTROL_MSG_INJECT_TEXT_MAX_LENGTH] = '\0';
msg.inject_text.text = text;
unsigned char buf[CONTROL_MSG_SERIALIZED_MAX_SIZE];
int size = control_msg_serialize(&msg, buf);
assert(size == 3 + CONTROL_MSG_TEXT_MAX_LENGTH);
assert(size == 3 + CONTROL_MSG_INJECT_TEXT_MAX_LENGTH);
unsigned char expected[3 + CONTROL_MSG_TEXT_MAX_LENGTH];
unsigned char expected[3 + CONTROL_MSG_INJECT_TEXT_MAX_LENGTH];
expected[0] = CONTROL_MSG_TYPE_INJECT_TEXT;
expected[1] = 0x01;
expected[2] = 0x2c; // text length (16 bits)
memset(&expected[3], 'a', CONTROL_MSG_TEXT_MAX_LENGTH);
memset(&expected[3], 'a', CONTROL_MSG_INJECT_TEXT_MAX_LENGTH);
assert(!memcmp(buf, expected, sizeof(expected)));
}

View File

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

View File

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

View File

@ -13,8 +13,8 @@ public class ControlMessageReader {
static final int INJECT_SCROLL_EVENT_PAYLOAD_LENGTH = 20;
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 INJECT_TEXT_MAX_LENGTH = 300;
private static final int RAW_BUFFER_SIZE = 1024;
private final byte[] rawBuffer = new byte[RAW_BUFFER_SIZE];

View File

@ -36,10 +36,7 @@ public final class Device {
*/
private final int layerStack;
/**
* The FLAG_PRESENTATION from the DisplayInfo
*/
private final boolean isPresentationDisplay;
private final boolean supportsInputEvents;
public Device(Options options) {
displayId = options.getDisplayId();
@ -53,7 +50,6 @@ public final class Device {
screenInfo = ScreenInfo.computeScreenInfo(displayInfo, options.getCrop(), options.getMaxSize(), options.getLockedVideoOrientation());
layerStack = displayInfo.getLayerStack();
isPresentationDisplay = (displayInfoFlags & DisplayInfo.FLAG_PRESENTATION) != 0;
registerRotationWatcher(new IRotationWatcher.Stub() {
@Override
@ -67,14 +63,16 @@ public final class Device {
}
}
}
});
}, displayId);
if ((displayInfoFlags & DisplayInfo.FLAG_SUPPORTS_PROTECTED_BUFFERS) == 0) {
Ln.w("Display doesn't have FLAG_SUPPORTS_PROTECTED_BUFFERS flag, mirroring can be restricted");
}
if (!supportsInputEvents()) {
Ln.w("Input events are not supported for displays with FLAG_PRESENTATION enabled for devices with API lower than 29");
// main display or any display on Android >= Q
supportsInputEvents = displayId == 0 || Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q;
if (!supportsInputEvents) {
Ln.w("Input events are not supported for secondary displays before Android 10");
}
}
@ -116,10 +114,7 @@ public final class Device {
}
public boolean supportsInputEvents() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
return true;
}
return !isPresentationDisplay;
return supportsInputEvents;
}
public boolean injectInputEvent(InputEvent inputEvent, int mode) {
@ -138,8 +133,8 @@ public final class Device {
return serviceManager.getPowerManager().isScreenOn();
}
public void registerRotationWatcher(IRotationWatcher rotationWatcher) {
serviceManager.getWindowManager().registerRotationWatcher(rotationWatcher);
public void registerRotationWatcher(IRotationWatcher rotationWatcher, int displayId) {
serviceManager.getWindowManager().registerRotationWatcher(rotationWatcher, displayId);
}
public synchronized void setRotationListener(RotationListener rotationListener) {

View File

@ -8,7 +8,6 @@ public final class DisplayInfo {
private final int flags;
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) {
this.displayId = displayId;

View File

@ -27,21 +27,13 @@ public class ScreenEncoder implements Device.RotationListener {
private int bitRate;
private int maxFps;
private int lockedVideoOrientation;
private int iFrameInterval;
private boolean sendFrameMeta;
private long ptsOrigin;
public ScreenEncoder(boolean sendFrameMeta, int bitRate, int maxFps, int lockedVideoOrientation, int iFrameInterval) {
public ScreenEncoder(boolean sendFrameMeta, int bitRate, int maxFps) {
this.sendFrameMeta = sendFrameMeta;
this.bitRate = bitRate;
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
@ -57,7 +49,7 @@ public class ScreenEncoder implements Device.RotationListener {
Workarounds.prepareMainLooper();
Workarounds.fillAppInfo();
MediaFormat format = createFormat(bitRate, maxFps, iFrameInterval);
MediaFormat format = createFormat(bitRate, maxFps, DEFAULT_I_FRAME_INTERVAL);
device.setRotationListener(this);
boolean alive;
try {

View File

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

View File

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

View File

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

View File

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

View File

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