Compare commits
1 Commits
logical_si
...
pr1238
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
429153abfb |
25
.github/ISSUE_TEMPLATE/bug_report.md
vendored
25
.github/ISSUE_TEMPLATE/bug_report.md
vendored
@@ -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.
|
|
||||||
22
.github/ISSUE_TEMPLATE/feature_request.md
vendored
22
.github/ISSUE_TEMPLATE/feature_request.md
vendored
@@ -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
1
.gitignore
vendored
@@ -2,4 +2,3 @@ build/
|
|||||||
/dist/
|
/dist/
|
||||||
.idea/
|
.idea/
|
||||||
.gradle/
|
.gradle/
|
||||||
/x/
|
|
||||||
|
|||||||
19
BUILD.md
19
BUILD.md
@@ -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
167
FAQ.md
@@ -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:
|
||||||
|
|
||||||
|
|||||||
44
README.md
44
README.md
@@ -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_
|
||||||
|
|||||||
@@ -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',
|
||||||
|
|||||||
52
app/scrcpy.1
52
app/scrcpy.1
@@ -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
|
||||||
|
|||||||
104
app/src/cli.c
104
app/src/cli.c
@@ -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]);
|
||||||
|
|||||||
@@ -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:
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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);
|
|
||||||
}
|
|
||||||
@@ -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
|
|
||||||
@@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
329
app/src/screen.c
329
app/src/screen.c
@@ -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 = ▭
|
|
||||||
} 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;
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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, \
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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)));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
BIN
gradle/wrapper/gradle-wrapper.jar
vendored
BIN
gradle/wrapper/gradle-wrapper.jar
vendored
Binary file not shown.
2
gradle/wrapper/gradle-wrapper.properties
vendored
2
gradle/wrapper/gradle-wrapper.properties
vendored
@@ -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
33
gradlew
vendored
@@ -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
3
gradlew.bat
vendored
@@ -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"
|
||||||
|
|
||||||
|
|||||||
@@ -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',
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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];
|
||||||
|
|||||||
@@ -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).
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
@@ -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();
|
||||||
|
|||||||
@@ -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;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|
||||||
|
|||||||
@@ -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;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
Reference in New Issue
Block a user