Compare commits

..

87 Commits

Author SHA1 Message Date
Romain Vimont
5508c635cb Enable mouse focus clickthrough in OTG mode
A single click on the window must both give focus and capture the mouse.

PR #2974 <https://github.com/Genymobile/scrcpy/pull/2974>
2022-01-27 23:36:30 +01:00
Romain Vimont
ea68a003a2 Make HID keyboard and mouse optional in OTG mode
Allow to only enable HID keyboard or HID mouse:

    scrcpy --otg -K   # keyboard only
    scrcpy --otg -M   # mouse only
    scrcpy --otg -KM  # keyboard and mouse
    scrcpy --otg      # keyboard and mouse

PR #2974 <https://github.com/Genymobile/scrcpy/pull/2974>
2022-01-27 23:36:27 +01:00
Romain Vimont
c5be0d6438 Document OTG mode in README
PR #2974 <https://github.com/Genymobile/scrcpy/pull/2974>
2022-01-27 23:36:25 +01:00
Romain Vimont
91418c79ab Add OTG mode
Add an option --otg to run scrcpy with only physical keyboard and mouse
simulation (HID over AOA), without mirroring and without requiring adb.

To avoid adding complexity into the scrcpy initialization and screen
implementation, OTG mode is implemented totally separately, with a
separate window.

PR #2974 <https://github.com/Genymobile/scrcpy/pull/2974>
2022-01-27 23:36:23 +01:00
Romain Vimont
36aaf70279 Move input event helpers
Input events helpers to convert from SDL events to scrcpy events were
implemented in input_manager. To reuse them for OTG mode, move them to
input_events.h.

PR #2974 <https://github.com/Genymobile/scrcpy/pull/2974>
2022-01-27 23:36:21 +01:00
Romain Vimont
1a03206e36 Detect USB device disconnection
The device disconnection is detected when the video socket closes.

In order to introduce an OTG mode (HID events) without mirroring (and
without server), we must be able to detect USB device disconnection.

This feature will only be used in OTG mode.

PR #2974 <https://github.com/Genymobile/scrcpy/pull/2974>
2022-01-27 23:36:18 +01:00
Romain Vimont
37987b822e Make acksync optional for AOA initialization
Acksync is used to delay HID events until some request (in practice,
device clipboard synchronization) is acknowledged by the device.

This mechanism will not be necessary for OTG mode.

PR #2974 <https://github.com/Genymobile/scrcpy/pull/2974>
2022-01-27 23:36:16 +01:00
Romain Vimont
8fc9dca8cb Make serial optional to find USB devices
If no serial is provided, then list all available USB devices (which can
be open and having a serial).

PR #2974 <https://github.com/Genymobile/scrcpy/pull/2974>
2022-01-27 23:36:15 +01:00
Romain Vimont
1c17f57c10 Find a list of devices instead of a single one
Several devices may match the requested serial, but above all, this
paves the way to list all devices (when no serial is provided).

PR #2974 <https://github.com/Genymobile/scrcpy/pull/2974>
2022-01-27 23:36:13 +01:00
Romain Vimont
d8b37fe189 Wrap libusb_device
Introduce a structure to wrap a libusb_device and expose its descriptor
data read during discovery.

PR #2974 <https://github.com/Genymobile/scrcpy/pull/2974>
2022-01-27 23:36:11 +01:00
Romain Vimont
0ee9e2ff51 Expose function to find a USB device
The device was automatically found by sc_usb_connect(). Instead, expose
a function to find a device from a serial, and let the caller connect to
the device found (if any).

This will allow to list all devices first, then select one device to
connect to.

PR #2974 <https://github.com/Genymobile/scrcpy/pull/2974>
2022-01-27 23:36:08 +01:00
Romain Vimont
1ab3692f3d Add util function to read USB descriptor string
Use it from accept_device() to simplify (at the cost an additional
allocation for each serial, but it is not important).

It will also be useful in other functions in further commits.

PR #2974 <https://github.com/Genymobile/scrcpy/pull/2974>
2022-01-27 23:36:06 +01:00
Romain Vimont
bbef426a4b Split USB initialization and connection
This will allow to execute other USB calls (retrieving the device list
for example) before connecting to the selected device.

PR #2974 <https://github.com/Genymobile/scrcpy/pull/2974>
2022-01-27 23:36:04 +01:00
Romain Vimont
2114f48185 Find device with USB context
An explicit context was used everywhere except for listing the devices.

PR #2974 <https://github.com/Genymobile/scrcpy/pull/2974>
2022-01-27 23:36:02 +01:00
Romain Vimont
b779eca8d3 Remove libusb_device field
It is possible to retrieve the device instance from the handle via
libusb_get_device(), so we don't need to reference the device one more
time.

PR #2974 <https://github.com/Genymobile/scrcpy/pull/2974>
2022-01-27 23:36:00 +01:00
Romain Vimont
adda47b0f7 Move sc_usb out of sc_aoa
This will allow to initialize a USB device separately and pass it to
sc_aoa.

PR #2974 <https://github.com/Genymobile/scrcpy/pull/2974>
2022-01-27 23:35:59 +01:00
Romain Vimont
48e3ff284f Make serial mandatory for sc_usb
In practice, it is already mandatory.

PR #2974 <https://github.com/Genymobile/scrcpy/pull/2974>
2022-01-27 23:35:57 +01:00
Romain Vimont
1d6f9952ee Extract USB handling from AOA
The AOA code handled both USB initialization and AOA commands/events.
Extract USB-related code to a separate file and structure.

PR #2974 <https://github.com/Genymobile/scrcpy/pull/2974>
2022-01-27 23:35:55 +01:00
Romain Vimont
d48d191262 Rename HAVE_AOA_HID to HAVE_USB
The condition actually determines whether scrcpy can use libusb or not.

PR #2974 <https://github.com/Genymobile/scrcpy/pull/2974>
2022-01-27 23:35:48 +01:00
Romain Vimont
2762f5d183 Move AOA/HID code to usb/
PR #2974 <https://github.com/Genymobile/scrcpy/pull/2974>
2022-01-27 23:35:34 +01:00
Romain Vimont
c996a6d462 Fix socket close race condition
The server needs to interrupt the sockets on stop, but it must not close
them while other threads may attempt to read from or write to them.

In particular, the video_socket is read by the stream thread, and the
control_socket is written by the controller and read by receiver.

Therefore, close the socket only on sc_server_destroy(), which is called
after all other threads are joined.

Reported by TSAN on close:

    WARNING: ThreadSanitizer: data race (pid=3287612)
      Write of size 8 at 0x7ba000000080 by thread T1:
        #0 close ../../../../src/libsanitizer/tsan/tsan_interceptors_posix.cpp:1690 (libtsan.so.0+0x359d8)
        #1 net_close ../app/src/util/net.c:280 (scrcpy+0x23643)
        #2 run_server ../app/src/server.c:772 (scrcpy+0x20047)
        #3 <null> <null> (libSDL2-2.0.so.0+0x905a0)

      Previous read of size 8 at 0x7ba000000080 by thread T16:
        #0 recv ../../../../src/libsanitizer/sanitizer_common/sanitizer_common_interceptors.inc:6603 (libtsan.so.0+0x4f4a6)
        #1 net_recv_all ../app/src/util/net.c:228 (scrcpy+0x234a9)
        #2 stream_recv_packet ../app/src/stream.c:33 (scrcpy+0x2045c)
        #3 run_stream ../app/src/stream.c:228 (scrcpy+0x21169)
        #4 <null> <null> (libSDL2-2.0.so.0+0x905a0)

Refs ddb9396743
2022-01-27 23:26:46 +01:00
Romain Vimont
8ea6fb1f0f Print version on stdout
Refs b25404ee4b
2022-01-27 21:26:56 +01:00
Romain Vimont
b546c33eff Do not print scrcpy version twice on --version
Refs 6da6d905c2
2022-01-27 21:12:46 +01:00
Romain Vimont
f51c53e913 Mention "screen copy" in README
Help users make sense of the app name :)
2022-01-27 19:05:33 +01:00
Romain Vimont
c0de365f67 Upgrade platform-tools (32.0.0) for Windows
Include the latest version of adb in Windows releases.
2022-01-27 19:02:07 +01:00
Romain Vimont
34e19dcc57 Upgrade SDL (2.0.20) for Windows
Include the latest version of SDL in Windows releases.
2022-01-27 19:02:07 +01:00
Romain Vimont
4817cadd09 Fix code style
Align function parameters.
2022-01-27 19:02:07 +01:00
Romain Vimont
02b5e87802 Slightly reduce lock usage
Locking the frame_buffer mutex to reference the input frame into the
tmp_frame is unnecessary.

This also fixes the missing unlock on error.
2022-01-27 19:01:43 +01:00
Romain Vimont
8e4d3beb01 Fix return value on adb commands error 2022-01-27 16:47:51 +01:00
Romain Vimont
50f4f1639c Add a shorcut "open a terminal here" on Windows
On Windows, the file explorer does not provide any "open a terminal
here" shortcut.

Add a bat file which just runs `cmd` to easily open a terminal directly
in the scrcpy directory (so there is no need to `cd C:\path\…`).

PR #2970 <https://github.com/Genymobile/scrcpy/pull/2970>
2022-01-27 16:47:51 +01:00
Romain Vimont
c8dc1917f4 Do not restore power mode if --no-control
This totally disables deferred cleanup when --no-control is passed.

Refs f289d206ea
2022-01-27 16:47:51 +01:00
Romain Vimont
9d2e00697e Use sc_ prefix for control_msg enums
Refs afa4a1b728
2022-01-27 16:47:51 +01:00
Romain Vimont
2faf9715be Add server option raw_video_stream
For convenience, this new option forces the 3 following options:
 - send_device_meta=false
 - send_frame_meta=false
 - send_dummy_byte=false

This allows to send a raw H.264 stream on the video socket.

Concretely:

    adb push scrcpy-server /data/local/tmp/scrcpy-server.jar
    adb forward tcp:1234 localabstract:scrcpy
    adb shell CLASSPATH=/data/local/tmp/scrcpy-server.jar \
        app_process / com.genymobile.scrcpy.Server 1.21 \
        raw_video_stream=true tunnel_forward=true control=false

As soon as a client connects via TCP to localhost:1234, it will receive
the raw H.264 stream.

Refs #1419 comment <https://github.com/Genymobile/scrcpy/pull/1419#issuecomment-1013964650>
PR #2971 <https://github.com/Genymobile/scrcpy/pull/2971>
2022-01-27 16:47:51 +01:00
Romain Vimont
45a5e560df Add server option send_dummy_byte
If set to false, no dummy byte is written to detect a connection error.

PR #2971 <https://github.com/Genymobile/scrcpy/pull/2971>
2022-01-26 11:30:46 +01:00
Romain Vimont
3ba32c2a0d Add server option send_device_meta
Similar to send_device_frame, this option allows to disable sending the
device name and size on start.

This is only useful when using the scrcpy-server alone to get a raw
H.264 stream, without using the scrcpy client.

PR #2971 <https://github.com/Genymobile/scrcpy/pull/2971>
2022-01-26 11:30:37 +01:00
Romain Vimont
6b21f4ae13 Reorder scrcpy-server options
Move the options unused by the scrcpy client at the end.

These options may be useful to use scrcpy-server directly (to get a raw
H.264 stream for example).

PR #2971 <https://github.com/Genymobile/scrcpy/pull/2971>
2022-01-26 11:30:30 +01:00
Romain Vimont
31a5d0c2bf Move call to send device name and size
This will allow to optionally disable it.

PR #2971 <https://github.com/Genymobile/scrcpy/pull/2971>
2022-01-26 11:30:19 +01:00
Romain Vimont
f289d206ea Disable more actions if --no-control
If control is disabled, then do not enable "show touches" or
automatically power off the device on close.
2022-01-24 21:45:44 +01:00
Romain Vimont
ca516f4318 Refactor if-block in cli
Several tests must be performed if opts->control is false.
2022-01-24 21:44:28 +01:00
Romain Vimont
5d6076bffd Move misplaced break statements
With ifdefs, the resulting code could contain both a return statement
and a break.
2022-01-24 21:38:30 +01:00
Romain Vimont
e0bce1725b Fix header guard prefix 2022-01-24 21:37:40 +01:00
Romain Vimont
a9429efa34 Fix downsize on error before first frame
Retry with a lower definition if MediaCodec fails before the first
frame, not the first packet.

In practice, the first packet is a config packet without any frame, and
MediaCodec might fail just after.

Refs 2eb6fe7d81
Refs #2963 <https://github.com/Genymobile/scrcpy/issues/2963>
2022-01-23 21:46:57 +01:00
Romain Vimont
063d103dd6 Capture mouse on start for --hid-mouse
If relative mode is enabled, capture the mouse immediately.
2022-01-23 21:31:53 +01:00
Romain Vimont
4bf9c057fe Extract relative mode check to an inline function
This will allow to reuse the condition in another function.
2022-01-23 21:31:11 +01:00
Romain Vimont
17c97820b2 Never forward capture keys
In relative mode, Alt and Super are "capture keys". Never forward them
to the input manager, to avoid inconsistencies between UP and DOWN
events.
2022-01-23 21:16:40 +01:00
Romain Vimont
8c7f0ed5ea Fix warning message
Make the message consistent for HID keyboard and HID mouse.
2022-01-23 21:16:35 +01:00
Romain Vimont
ac038f276e Add missing break statement
This was harmless because this is the last "case" of the switch, but for
consistency, add the missing break.
2022-01-23 15:01:11 +01:00
Romain Vimont
1f65b1bf87 Remove inline hint
There is no reason to request inlining here.
2022-01-23 15:01:11 +01:00
Romain Vimont
d41a46dc95 Handle libusb_get_device_descriptor() error
The function libusb_get_device_descriptor() might return an error.
Handle it.
2022-01-23 12:32:37 +01:00
Romain Vimont
308a1f8192 Simplify error handling in sc_aoa_init()
Use goto to avoid many repetitions.
2022-01-23 12:32:37 +01:00
Romain Vimont
241a587e61 Fix missing HID mouse destructor call
The destructor unregisters the HID mouse, so it was not reported as a
leak, but it must still be called.
2022-01-23 12:32:04 +01:00
Romain Vimont
7e35bfe382 Refactor if-blocks
Group all conditions requiring a controller in a single if-block.
2022-01-23 12:16:24 +01:00
Romain Vimont
855819bbd8 Remove redundant control boolean
The controller is NULL if and only if control is disabled, so an
additional control boolean is redundant.
2022-01-23 12:16:24 +01:00
Romain Vimont
557daf280e Pass NULL controller if control is disabled
If --no-control is requested, then the controller instance is not
initialized. However, its reference was still passed to screen and
input_manager.

Instead, pass NULL if no controller is available.
2022-01-23 12:16:24 +01:00
Romain Vimont
0b8e926330 Do not process finger events if no control
If --no-control is passed, then im->mp is NULL, so processing touches
would crash.
2022-01-23 12:16:24 +01:00
Romain Vimont
0ec3361bc9 Fix crash on --no-control
Relative mouse mode assumed that a mouse processor was always available,
but this is not the case if --no-control is passed.
2022-01-23 12:16:07 +01:00
Romain Vimont
81ff7ebd06 Simplify event loop
Merge single event handling with the event loop function.
2022-01-21 21:52:41 +01:00
Romain Vimont
1ffe312369 Handle file drop from input_manager
A file is pushed (or an apk is installed) to the device on file drop.
This behavior is specific to the screen and its input_manager.
2022-01-21 21:52:41 +01:00
Romain Vimont
ebef027c4f Do not return status for event handling
It is never read. Simplify.
2022-01-21 21:52:41 +01:00
Romain Vimont
8e4e7d42f1 Fix leak on file pusher error
If a file_push request fails, the allocated filename must be freed.
2022-01-21 21:52:41 +01:00
Romain Vimont
b066dc0bbf Rename file_handler to sc_file_pusher
Rename handler to pusher ("handler" is too generic), and add sc_ prefix.
2022-01-21 21:52:41 +01:00
Romain Vimont
262506c733 Limit retry-on-error to IllegalStateException
MediaCodec errors always trigger IllegalStateException or a subtype
(like MediaCodec.CodecException).

In practice, this avoids to retry if the error is caused by an
IOException when writing the video packet to the socket.
2022-01-21 21:52:29 +01:00
Romain Vimont
2eb6fe7d81 Downsize on error only before the first frame
The purpose of automatic downscaling on error is to make mirroring work
by just starting scrcpy without an explicit -m value, even if the
encoder could not encode at the screen definition.

It is only useful when we detect an encoding failure before the first
frame. Downsizing later could be surprising, so disable it.

PR #2947 <https://github.com/Genymobile/scrcpy/pull/2947>
2022-01-21 18:44:05 +01:00
Romain Vimont
3a0ba7d0a4 Disable downsizing on error if V4L2 is enabled
V4L2 device is created with the initial device size, it does not support
resizing.

PR #2947 <https://github.com/Genymobile/scrcpy/pull/2947>
2022-01-21 18:44:05 +01:00
Romain Vimont
75c5dc6859 Position and size the window on first frame
The optimal initial size was computed from the expected dimensions, sent
immediately by the server before encoding any video frame.

However, the actual frame size may be different, for example when the
device encoder does not support the requested size.

To always handle this case properly, position and size the window only
once the first frame size is known.

PR #2947 <https://github.com/Genymobile/scrcpy/pull/2947>
2022-01-21 18:43:46 +01:00
Romain Vimont
fa30f9806a Move "show window" call on first frame
Show the window only after the actual frame size is known (and if no
error has occurred).

This will allow to properly position and size the window when the size
of the first frame is different from the size initially announced by the
server.

PR #2947 <https://github.com/Genymobile/scrcpy/pull/2947>
2022-01-21 18:42:39 +01:00
Romain Vimont
4fb61ac83d Fix screen comments
The position fields accept SC_WINDOW_POSITION_UNDEFINED, not the size
fields.

PR #2947 <https://github.com/Genymobile/scrcpy/pull/2947>
2022-01-21 18:42:39 +01:00
Romain Vimont
8fa9e6b01a Mention auto-downsize feature in FAQ
PR #2947 <https://github.com/Genymobile/scrcpy/pull/2947>
2022-01-21 18:42:38 +01:00
Romain Vimont
0ec64baad4 Remove MediaCodec error suggestion fix
Now that scrcpy attempts with a lower definition on any MediaCodec
error (or the user explicitly requests to disable auto-downsizing), the
suggestion is unnecessary.

PR #2947 <https://github.com/Genymobile/scrcpy/pull/2947>
2022-01-21 18:41:26 +01:00
Romain Vimont
15bf27afdd Make auto-downsize on error optional
Add --no-downsize-on-error option to disable attempts to use a lower
definition on MediaCodec error.

PR #2947 <https://github.com/Genymobile/scrcpy/pull/2947>
2022-01-21 18:36:46 +01:00
Romain Vimont
26b4104844 Downsize on error
Some devices are not able to encode at the device screen definition.

Instead of just failing, try with a lower definition on any MediaCodec
error.

PR #2947 <https://github.com/Genymobile/scrcpy/pull/2947>
2022-01-21 18:36:46 +01:00
Romain Vimont
723faa5dee Remember Device parameters
This will allow to reuse them to recreate a ScreenInfo instance in order
to change the maxSize value on MediaCodec error.

PR #2947 <https://github.com/Genymobile/scrcpy/pull/2947>
2022-01-21 18:36:46 +01:00
Romain Vimont
162043911e Compute screen size without DisplayInfo instance
Use the actual rotation and size values directly.

This will allow to automatically change the maxSize value on MediaCodec
error.

PR #2947 <https://github.com/Genymobile/scrcpy/pull/2947>
2022-01-21 18:36:46 +01:00
Romain Vimont
117fe32626 Fix visibility modifier
Refs b7a06278fe
2022-01-21 18:36:44 +01:00
Thomas Rebele
b7a06278fe Fix NoSuchMethodException for injectInputEvent()
Some devices with modified ROMs expose a different signature for
injectInputEvent().

Fixes #2250 <https://github.com/Genymobile/scrcpy/issues/2250>
PR #2946 <https://github.com/Genymobile/scrcpy/pull/2946>

Signed-off-by: Romain Vimont <rom@rom1v.com>
2022-01-18 10:15:55 +01:00
Romain Vimont
b3ff1f6b3b Upgrade FFmpeg (5.0) for Windows 64-bit
Use FFmpeg win64 binaries from gyan.dev (referenced from ffmpeg.org):

 - https://www.gyan.dev/ffmpeg/builds/
 - https://ffmpeg.org/download.html#build-windows

Keep the old FFmpeg prebuilt binaries (4.3.1) for win32 builds.

Fixes #1753 <https://github.com/Genymobile/scrcpy/issues/1753>
Refs #1838 <https://github.com/Genymobile/scrcpy/pull/1838>
Refs #2583 <https://github.com/Genymobile/scrcpy/pull/2583>
PR #2952 <https://github.com/Genymobile/scrcpy/pull/2952>

Co-authored-by: Yu-Chen Lin <npes87184@gmail.com>
Co-authored-by: nkh0472 <nkh0472@hotmail.com>
Signed-off-by: Romain Vimont <rom@rom1v.com>
2022-01-18 10:13:41 +01:00
Romain Vimont
a2495c5ef1 Use symlink to simplify Windows ffmpeg dependency
The FFmpeg dependency is downloaded from two separate zipfiles.

Symlink include/ to expose everything from a single directory, to
simplify the meson script.

PR #2952 <https://github.com/Genymobile/scrcpy/pull/2952>
2022-01-18 10:13:38 +01:00
Romain Vimont
37c7827d46 Simplify ffmpeg dependencies Makefile
The fact that the current prebuilt FFmpeg is split into two separate
zipfiles is an implementation detail.

Use a single Makefile recipe for both files.

PR #2952 <https://github.com/Genymobile/scrcpy/pull/2952>
2022-01-18 10:13:18 +01:00
Bernhard Rosenkränzer
5e8fa56e7a Fix build with ffmpeg 5.0
PR #2948 <https://github.com/Genymobile/scrcpy/pull/2948>

Signed-off-by: Bernhard Rosenkränzer <bero@lindev.ch>
Signed-off-by: Romain Vimont <rom@rom1v.com>
2022-01-16 17:37:28 +01:00
Romain Vimont
60bf133ac2 Add final modifier to ScreenEncoder fields
These fields are only set from the constructor.
2022-01-15 23:25:55 +01:00
Romain Vimont
1c71bd16be Use constant string for known booleans
Boolean options explicitly passed to the server are statically known.
2022-01-15 23:25:43 +01:00
Romain Vimont
afa4a1b728 Use sc_ prefix for control_msg 2022-01-14 22:17:30 +01:00
Romain Vimont
3a4d5c7f18 Use sc_ prefix for controller 2022-01-14 22:17:30 +01:00
Romain Vimont
5f7ddff8ae Use sc_ prefix for input_manager 2022-01-14 22:17:30 +01:00
Romain Vimont
2a0c2e5e99 Use sc_ prefix for screen 2022-01-14 22:17:30 +01:00
Romain Vimont
a6644e831b Fix code style
Limit to 80 chars.
2022-01-14 20:57:03 +01:00
Romain Vimont
75655194fb Do not pass scrcpy_options to keyboard inject
The components should be configurable independently of the global
scrcpy_options instance: their configuration could be provided
separately, like it is the case for example for some screen parameters.

For consistency, keyboard injection should not depend on scrcpy_options.
2022-01-14 20:55:44 +01:00
62 changed files with 2382 additions and 1201 deletions

14
FAQ.md
View File

@@ -219,6 +219,9 @@ scrcpy -m 1024
scrcpy -m 800
```
Since scrcpy v1.22, scrcpy automatically tries again with a lower definition
before failing. This behavior can be disabled with `--no-downsize-on-error`.
You could also try another [encoder](README.md#encoder).
@@ -245,8 +248,15 @@ Caused by: java.lang.IllegalArgumentException: displayToken must not be null
## Command line on Windows
Some Windows users are not familiar with the command line. Here is how to open a
terminal and run `scrcpy` with arguments:
Since v1.22, a "shortcut" has been added to directly open a terminal in the
scrcpy directory. Double-click on `open_a_terminal_here.bat`, then type your
command. For example:
```
scrcpy --record file.mkv
```
You could also open a terminal and go to the scrcpy folder manually:
1. Press <kbd>Windows</kbd>+<kbd>r</kbd>, this opens a dialog box.
2. Type `cmd` and press <kbd>Enter</kbd>, this opens a terminal.

View File

@@ -2,6 +2,8 @@
<img src="data/icon.svg" width="128" height="128" alt="scrcpy" align="right" />
_pronounced "**scr**een **c**o**py**"_
[Read in another language](#translations)
This application provides display and control of Android devices connected via
@@ -33,6 +35,7 @@ Its features include:
(Linux-only)
- [physical mouse simulation (HID)](#physical-mouse-simulation-hid)
(Linux-only)
- [OTG mode](#otg) (Linux-only)
- and more…
## Requirements
@@ -847,6 +850,36 @@ Special capture keys, either <kbd>Alt</kbd> or <kbd>Super</kbd>, toggle
the mouse back to the computer.
#### OTG
It is possible to run _scrcpy_ with only physical keyboard and mouse simulation
(HID), as if the computer keyboard and mouse were plugged directly to the device
via an OTG cable.
In this mode, _adb_ (USB debugging) is not necessary, and mirroring is disabled.
To enable OTG mode:
```bash
scrcpy --otg
# Pass the serial if several USB devices are available
scrcpy --otg -s 0123456789abcdef
```
It is possible to enable only HID keyboard or HID mouse:
```bash
scrcpy --otg --hid-keyboard # keyboard only
scrcpy --otg --hid-mouse # mouse only
scrcpy --otg --hid-keyboard --hid-mouse # keyboard and mouse
# for convenience, enable both by default
scrcpy --otg # keyboard and mouse
```
Like `--hid-keyboard` and `--hid-mouse`, it only works if the device is
connected by USB, and is currently only supported on Linux.
#### Text injection preference
There are two kinds of [events][textevents] generated when typing text:

View File

@@ -11,7 +11,7 @@ src = [
'src/decoder.c',
'src/device_msg.c',
'src/icon.c',
'src/file_handler.c',
'src/file_pusher.c',
'src/fps_counter.c',
'src/frame_buffer.c',
'src/input_manager.c',
@@ -72,12 +72,15 @@ if v4l2_support
src += [ 'src/v4l2_sink.c' ]
endif
aoa_hid_support = host_machine.system() == 'linux'
if aoa_hid_support
usb_support = host_machine.system() == 'linux'
if usb_support
src += [
'src/aoa_hid.c',
'src/hid_keyboard.c',
'src/hid_mouse.c',
'src/usb/aoa_hid.c',
'src/usb/hid_keyboard.c',
'src/usb/hid_mouse.c',
'src/usb/scrcpy_otg.c',
'src/usb/screen_otg.c',
'src/usb/usb.c',
]
endif
@@ -99,7 +102,7 @@ if not crossbuild_windows
dependencies += dependency('libavdevice')
endif
if aoa_hid_support
if usb_support
dependencies += dependency('libusb-1.0')
endif
@@ -118,15 +121,20 @@ else
include_directories: include_directories(sdl2_include_dir)
)
prebuilt_ffmpeg_shared = meson.get_cross_property('prebuilt_ffmpeg_shared')
prebuilt_ffmpeg_dev = meson.get_cross_property('prebuilt_ffmpeg_dev')
ffmpeg_bin_dir = meson.current_source_dir() + '/../prebuilt-deps/' + prebuilt_ffmpeg_shared + '/bin'
ffmpeg_include_dir = '../prebuilt-deps/' + prebuilt_ffmpeg_dev + '/include'
prebuilt_ffmpeg = meson.get_cross_property('prebuilt_ffmpeg')
ffmpeg_bin_dir = meson.current_source_dir() + '/../prebuilt-deps/' + prebuilt_ffmpeg + '/bin'
ffmpeg_include_dir = '../prebuilt-deps/' + prebuilt_ffmpeg + '/include'
# ffmpeg versions are different for win32 and win64 builds
ffmpeg_avcodec = meson.get_cross_property('ffmpeg_avcodec')
ffmpeg_avformat = meson.get_cross_property('ffmpeg_avformat')
ffmpeg_avutil = meson.get_cross_property('ffmpeg_avutil')
ffmpeg = declare_dependency(
dependencies: [
cc.find_library('avcodec-58', dirs: ffmpeg_bin_dir),
cc.find_library('avformat-58', dirs: ffmpeg_bin_dir),
cc.find_library('avutil-56', dirs: ffmpeg_bin_dir),
cc.find_library(ffmpeg_avcodec, dirs: ffmpeg_bin_dir),
cc.find_library(ffmpeg_avformat, dirs: ffmpeg_bin_dir),
cc.find_library(ffmpeg_avutil, dirs: ffmpeg_bin_dir),
],
include_directories: include_directories(ffmpeg_include_dir)
)
@@ -188,7 +196,7 @@ conf.set('SERVER_DEBUGGER_METHOD_NEW', get_option('server_debugger_method') == '
conf.set('HAVE_V4L2', v4l2_support)
# enable HID over AOA support (linux only)
conf.set('HAVE_AOA_HID', aoa_hid_support)
conf.set('HAVE_USB', usb_support)
configure_file(configuration: conf, output: 'config.h')

View File

@@ -140,6 +140,12 @@ By default, scrcpy automatically synchronizes the computer clipboard to the devi
This option disables this automatic synchronization.
.TP
.B \-\-no\-downsize\-on\-error
By default, on MediaCodec error, scrcpy automatically tries again with a lower definition.
This option disables this behavior.
.TP
.B \-n, \-\-no\-control
Disable device control (mirror the device in read\-only).
@@ -156,6 +162,20 @@ Do not forward repeated key events when a key is held down.
.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
.B \-\-otg
Run in OTG mode: simulate physical keyboard and mouse, as if the computer keyboard and mouse were plugged directly to the device via an OTG cable.
In this mode, adb (USB debugging) is not necessary, and mirroring is disabled.
LAlt, LSuper or RSuper toggle the mouse capture mode, to give control of the mouse back to the computer.
If any of \fB\-\-hid\-keyboard\fR or \fB\-\-hid\-mouse\fR is set, only enable keyboard or mouse respectively, otherwise enable both.
It may only work over USB, and is currently only supported on Linux.
See \fB\-\-hid\-keyboard\fR and \fB\-\-hid\-mouse\fR.
.TP
.BI "\-p, \-\-port " port[:port]
Set the TCP port (range) used by the client to listen.

View File

@@ -425,7 +425,7 @@ adb_get_serialno(struct sc_intr *intr, unsigned flags) {
}
if (r == -1) {
return false;
return NULL;
}
sc_str_truncate(buf, r, " \r\n");
@@ -455,7 +455,7 @@ adb_get_device_ip(struct sc_intr *intr, const char *serial, unsigned flags) {
}
if (r == -1) {
return false;
return NULL;
}
assert((size_t) r <= sizeof(buf));

View File

@@ -52,6 +52,8 @@
#define OPT_NO_CLIPBOARD_AUTOSYNC 1032
#define OPT_TCPIP 1033
#define OPT_RAW_KEY_EVENTS 1034
#define OPT_NO_DOWNSIZE_ON_ERROR 1035
#define OPT_OTG 1036
struct sc_option {
char shortopt;
@@ -236,6 +238,13 @@ static const struct sc_option options[] = {
"is preserved.\n"
"Default is 0 (unlimited).",
},
{
.longopt_id = OPT_NO_DOWNSIZE_ON_ERROR,
.longopt = "no-downsize-on-error",
.text = "By default, on MediaCodec error, scrcpy automatically tries "
"again with a lower definition.\n"
"This option disables this behavior.",
},
{
.longopt_id = OPT_NO_CLIPBOARD_AUTOSYNC,
.longopt = "no-clipboard-autosync",
@@ -268,6 +277,22 @@ static const struct sc_option options[] = {
"mipmaps are automatically generated to improve downscaling "
"quality. This option disables the generation of mipmaps.",
},
{
.longopt_id = OPT_OTG,
.longopt = "otg",
.text = "Run in OTG mode: simulate physical keyboard and mouse, "
"as if the computer keyboard and mouse were plugged directly "
"to the device via an OTG cable.\n"
"In this mode, adb (USB debugging) is not necessary, and "
"mirroring is disabled.\n"
"LAlt, LSuper or RSuper toggle the mouse capture mode, to give "
"control of the mouse back to the computer.\n"
"If any of --hid-keyboard or --hid-mouse is set, only enable "
"keyboard or mouse respectively, otherwise enable both."
"It may only work over USB, and is currently only supported "
"on Linux.\n"
"See --hid-keyboard and --hid-mouse.",
},
{
.shortopt = 'p',
.longopt = "port",
@@ -1310,14 +1335,14 @@ parse_args_with_getopt(struct scrcpy_cli_args *args, int argc, char *argv[],
args->help = true;
break;
case 'K':
#ifdef HAVE_AOA_HID
#ifdef HAVE_USB
opts->keyboard_input_mode = SC_KEYBOARD_INPUT_MODE_HID;
break;
#else
LOGE("HID over AOA (-K/--hid-keyboard) is not supported on "
"this platform. It is only available on Linux.");
return false;
#endif
break;
case OPT_MAX_FPS:
if (!parse_max_fps(optarg, &opts->max_fps)) {
return false;
@@ -1329,14 +1354,14 @@ parse_args_with_getopt(struct scrcpy_cli_args *args, int argc, char *argv[],
}
break;
case 'M':
#ifdef HAVE_AOA_HID
#ifdef HAVE_USB
opts->mouse_input_mode = SC_MOUSE_INPUT_MODE_HID;
break;
#else
LOGE("HID over AOA (-M/--hid-mouse) is not supported on this"
"platform. It is only available on Linux.");
return false;
#endif
break;
case OPT_LOCK_VIDEO_ORIENTATION:
if (!parse_lock_video_orientation(optarg,
&opts->lock_video_orientation)) {
@@ -1489,24 +1514,36 @@ parse_args_with_getopt(struct scrcpy_cli_args *args, int argc, char *argv[],
opts->tcpip = true;
opts->tcpip_dst = optarg;
break;
case OPT_NO_DOWNSIZE_ON_ERROR:
opts->downsize_on_error = false;
break;
case OPT_OTG:
#ifdef HAVE_USB
opts->otg = true;
break;
#else
LOGE("OTG mode (--otg) is not supported on this platform. It "
"is only available on Linux.");
return false;
#endif
case OPT_V4L2_SINK:
#ifdef HAVE_V4L2
opts->v4l2_device = optarg;
break;
#else
LOGE("V4L2 (--v4l2-sink) is only available on Linux.");
return false;
#endif
break;
case OPT_V4L2_BUFFER:
#ifdef HAVE_V4L2
if (!parse_buffering_time(optarg, &opts->v4l2_buffer)) {
return false;
}
break;
#else
LOGE("V4L2 (--v4l2-buffer) is only available on Linux.");
return false;
#endif
break;
default:
// getopt prints the error message on stderr
return false;
@@ -1534,11 +1571,18 @@ parse_args_with_getopt(struct scrcpy_cli_args *args, int argc, char *argv[],
return false;
}
if (opts->v4l2_device && opts->lock_video_orientation
== SC_LOCK_VIDEO_ORIENTATION_UNLOCKED) {
LOGI("Video orientation is locked for v4l2 sink. "
"See --lock-video-orientation.");
opts->lock_video_orientation = SC_LOCK_VIDEO_ORIENTATION_INITIAL;
if (opts->v4l2_device) {
if (opts->lock_video_orientation ==
SC_LOCK_VIDEO_ORIENTATION_UNLOCKED) {
LOGI("Video orientation is locked for v4l2 sink. "
"See --lock-video-orientation.");
opts->lock_video_orientation = SC_LOCK_VIDEO_ORIENTATION_INITIAL;
}
// V4L2 could not handle size change.
// Do not log because downsizing on error is the default behavior,
// not an explicit request from the user.
opts->downsize_on_error = false;
}
if (opts->v4l2_buffer && !opts->v4l2_device) {
@@ -1573,15 +1617,61 @@ parse_args_with_getopt(struct scrcpy_cli_args *args, int argc, char *argv[],
}
}
if (!opts->control && opts->turn_screen_off) {
LOGE("Could not request to turn screen off if control is disabled");
return false;
if (!opts->control) {
if (opts->turn_screen_off) {
LOGE("Could not request to turn screen off if control is disabled");
return false;
}
if (opts->stay_awake) {
LOGE("Could not request to stay awake if control is disabled");
return false;
}
if (opts->show_touches) {
LOGE("Could not request to show touches if control is disabled");
return false;
}
if (opts->power_off_on_close) {
LOGE("Could not request power off on close if control is disabled");
return false;
}
}
if (!opts->control && opts->stay_awake) {
LOGE("Could not request to stay awake if control is disabled");
return false;
#ifdef HAVE_USB
if (opts->otg) {
// OTG mode is compatible with only very few options.
// Only report obvious errors.
if (opts->record_filename) {
LOGE("OTG mode: could not record");
return false;
}
if (opts->turn_screen_off) {
LOGE("OTG mode: could not turn screen off");
return false;
}
if (opts->stay_awake) {
LOGE("OTG mode: could not stay awake");
return false;
}
if (opts->show_touches) {
LOGE("OTG mode: could not request to show touches");
return false;
}
if (opts->power_off_on_close) {
LOGE("OTG mode: could not request power off on close");
return false;
}
if (opts->display_id) {
LOGE("OTG mode: could not select display");
return false;
}
#ifdef HAVE_V4L2
if (opts->v4l2_device) {
LOGE("OTG mode: could not sink to V4L2 device");
return false;
}
#endif
}
#endif
return true;
}

View File

@@ -89,22 +89,22 @@ to_fixed_point_16(float f) {
}
size_t
control_msg_serialize(const struct control_msg *msg, unsigned char *buf) {
sc_control_msg_serialize(const struct sc_control_msg *msg, unsigned char *buf) {
buf[0] = msg->type;
switch (msg->type) {
case CONTROL_MSG_TYPE_INJECT_KEYCODE:
case SC_CONTROL_MSG_TYPE_INJECT_KEYCODE:
buf[1] = msg->inject_keycode.action;
buffer_write32be(&buf[2], msg->inject_keycode.keycode);
buffer_write32be(&buf[6], msg->inject_keycode.repeat);
buffer_write32be(&buf[10], msg->inject_keycode.metastate);
return 14;
case CONTROL_MSG_TYPE_INJECT_TEXT: {
case SC_CONTROL_MSG_TYPE_INJECT_TEXT: {
size_t len =
write_string(msg->inject_text.text,
CONTROL_MSG_INJECT_TEXT_MAX_LENGTH, &buf[1]);
SC_CONTROL_MSG_INJECT_TEXT_MAX_LENGTH, &buf[1]);
return 1 + len;
}
case CONTROL_MSG_TYPE_INJECT_TOUCH_EVENT:
case SC_CONTROL_MSG_TYPE_INJECT_TOUCH_EVENT:
buf[1] = msg->inject_touch_event.action;
buffer_write64be(&buf[2], msg->inject_touch_event.pointer_id);
write_position(&buf[10], &msg->inject_touch_event.position);
@@ -113,7 +113,7 @@ control_msg_serialize(const struct control_msg *msg, unsigned char *buf) {
buffer_write16be(&buf[22], pressure);
buffer_write32be(&buf[24], msg->inject_touch_event.buttons);
return 28;
case CONTROL_MSG_TYPE_INJECT_SCROLL_EVENT:
case SC_CONTROL_MSG_TYPE_INJECT_SCROLL_EVENT:
write_position(&buf[1], &msg->inject_scroll_event.position);
buffer_write32be(&buf[13],
(uint32_t) msg->inject_scroll_event.hscroll);
@@ -121,27 +121,26 @@ control_msg_serialize(const struct control_msg *msg, unsigned char *buf) {
(uint32_t) msg->inject_scroll_event.vscroll);
buffer_write32be(&buf[21], msg->inject_scroll_event.buttons);
return 25;
case CONTROL_MSG_TYPE_BACK_OR_SCREEN_ON:
case SC_CONTROL_MSG_TYPE_BACK_OR_SCREEN_ON:
buf[1] = msg->inject_keycode.action;
return 2;
case CONTROL_MSG_TYPE_GET_CLIPBOARD:
case SC_CONTROL_MSG_TYPE_GET_CLIPBOARD:
buf[1] = msg->get_clipboard.copy_key;
return 2;
case CONTROL_MSG_TYPE_SET_CLIPBOARD: {
case SC_CONTROL_MSG_TYPE_SET_CLIPBOARD:
buffer_write64be(&buf[1], msg->set_clipboard.sequence);
buf[9] = !!msg->set_clipboard.paste;
size_t len = write_string(msg->set_clipboard.text,
CONTROL_MSG_CLIPBOARD_TEXT_MAX_LENGTH,
SC_CONTROL_MSG_CLIPBOARD_TEXT_MAX_LENGTH,
&buf[10]);
return 10 + len;
}
case CONTROL_MSG_TYPE_SET_SCREEN_POWER_MODE:
case SC_CONTROL_MSG_TYPE_SET_SCREEN_POWER_MODE:
buf[1] = msg->set_screen_power_mode.mode;
return 2;
case CONTROL_MSG_TYPE_EXPAND_NOTIFICATION_PANEL:
case CONTROL_MSG_TYPE_EXPAND_SETTINGS_PANEL:
case CONTROL_MSG_TYPE_COLLAPSE_PANELS:
case CONTROL_MSG_TYPE_ROTATE_DEVICE:
case SC_CONTROL_MSG_TYPE_EXPAND_NOTIFICATION_PANEL:
case SC_CONTROL_MSG_TYPE_EXPAND_SETTINGS_PANEL:
case SC_CONTROL_MSG_TYPE_COLLAPSE_PANELS:
case SC_CONTROL_MSG_TYPE_ROTATE_DEVICE:
// no additional data
return 1;
default:
@@ -151,20 +150,20 @@ control_msg_serialize(const struct control_msg *msg, unsigned char *buf) {
}
void
control_msg_log(const struct control_msg *msg) {
sc_control_msg_log(const struct sc_control_msg *msg) {
#define LOG_CMSG(fmt, ...) LOGV("input: " fmt, ## __VA_ARGS__)
switch (msg->type) {
case CONTROL_MSG_TYPE_INJECT_KEYCODE:
case SC_CONTROL_MSG_TYPE_INJECT_KEYCODE:
LOG_CMSG("key %-4s code=%d repeat=%" PRIu32 " meta=%06lx",
KEYEVENT_ACTION_LABEL(msg->inject_keycode.action),
(int) msg->inject_keycode.keycode,
msg->inject_keycode.repeat,
(long) msg->inject_keycode.metastate);
break;
case CONTROL_MSG_TYPE_INJECT_TEXT:
case SC_CONTROL_MSG_TYPE_INJECT_TEXT:
LOG_CMSG("text \"%s\"", msg->inject_text.text);
break;
case CONTROL_MSG_TYPE_INJECT_TOUCH_EVENT: {
case SC_CONTROL_MSG_TYPE_INJECT_TOUCH_EVENT: {
int action = msg->inject_touch_event.action
& AMOTION_EVENT_ACTION_MASK;
uint64_t id = msg->inject_touch_event.pointer_id;
@@ -191,7 +190,7 @@ control_msg_log(const struct control_msg *msg) {
}
break;
}
case CONTROL_MSG_TYPE_INJECT_SCROLL_EVENT:
case SC_CONTROL_MSG_TYPE_INJECT_SCROLL_EVENT:
LOG_CMSG("scroll position=%" PRIi32 ",%" PRIi32 " hscroll=%" PRIi32
" vscroll=%" PRIi32 " buttons=%06lx",
msg->inject_scroll_event.position.point.x,
@@ -200,34 +199,34 @@ control_msg_log(const struct control_msg *msg) {
msg->inject_scroll_event.vscroll,
(long) msg->inject_scroll_event.buttons);
break;
case CONTROL_MSG_TYPE_BACK_OR_SCREEN_ON:
case SC_CONTROL_MSG_TYPE_BACK_OR_SCREEN_ON:
LOG_CMSG("back-or-screen-on %s",
KEYEVENT_ACTION_LABEL(msg->inject_keycode.action));
break;
case CONTROL_MSG_TYPE_GET_CLIPBOARD:
case SC_CONTROL_MSG_TYPE_GET_CLIPBOARD:
LOG_CMSG("get clipboard copy_key=%s",
copy_key_labels[msg->get_clipboard.copy_key]);
break;
case CONTROL_MSG_TYPE_SET_CLIPBOARD:
case SC_CONTROL_MSG_TYPE_SET_CLIPBOARD:
LOG_CMSG("clipboard %" PRIu64_ " %s \"%s\"",
msg->set_clipboard.sequence,
msg->set_clipboard.paste ? "paste" : "nopaste",
msg->set_clipboard.text);
break;
case CONTROL_MSG_TYPE_SET_SCREEN_POWER_MODE:
case SC_CONTROL_MSG_TYPE_SET_SCREEN_POWER_MODE:
LOG_CMSG("power mode %s",
SCREEN_POWER_MODE_LABEL(msg->set_screen_power_mode.mode));
break;
case CONTROL_MSG_TYPE_EXPAND_NOTIFICATION_PANEL:
case SC_CONTROL_MSG_TYPE_EXPAND_NOTIFICATION_PANEL:
LOG_CMSG("expand notification panel");
break;
case CONTROL_MSG_TYPE_EXPAND_SETTINGS_PANEL:
case SC_CONTROL_MSG_TYPE_EXPAND_SETTINGS_PANEL:
LOG_CMSG("expand settings panel");
break;
case CONTROL_MSG_TYPE_COLLAPSE_PANELS:
case SC_CONTROL_MSG_TYPE_COLLAPSE_PANELS:
LOG_CMSG("collapse panels");
break;
case CONTROL_MSG_TYPE_ROTATE_DEVICE:
case SC_CONTROL_MSG_TYPE_ROTATE_DEVICE:
LOG_CMSG("rotate device");
break;
default:
@@ -237,12 +236,12 @@ control_msg_log(const struct control_msg *msg) {
}
void
control_msg_destroy(struct control_msg *msg) {
sc_control_msg_destroy(struct sc_control_msg *msg) {
switch (msg->type) {
case CONTROL_MSG_TYPE_INJECT_TEXT:
case SC_CONTROL_MSG_TYPE_INJECT_TEXT:
free(msg->inject_text.text);
break;
case CONTROL_MSG_TYPE_SET_CLIPBOARD:
case SC_CONTROL_MSG_TYPE_SET_CLIPBOARD:
free(msg->set_clipboard.text);
break;
default:

View File

@@ -11,44 +11,44 @@
#include "android/keycodes.h"
#include "coords.h"
#define CONTROL_MSG_MAX_SIZE (1 << 18) // 256k
#define SC_CONTROL_MSG_MAX_SIZE (1 << 18) // 256k
#define CONTROL_MSG_INJECT_TEXT_MAX_LENGTH 300
#define SC_CONTROL_MSG_INJECT_TEXT_MAX_LENGTH 300
// type: 1 byte; sequence: 8 bytes; paste flag: 1 byte; length: 4 bytes
#define CONTROL_MSG_CLIPBOARD_TEXT_MAX_LENGTH (CONTROL_MSG_MAX_SIZE - 14)
#define SC_CONTROL_MSG_CLIPBOARD_TEXT_MAX_LENGTH (SC_CONTROL_MSG_MAX_SIZE - 14)
#define POINTER_ID_MOUSE UINT64_C(-1)
#define POINTER_ID_VIRTUAL_FINGER UINT64_C(-2)
enum control_msg_type {
CONTROL_MSG_TYPE_INJECT_KEYCODE,
CONTROL_MSG_TYPE_INJECT_TEXT,
CONTROL_MSG_TYPE_INJECT_TOUCH_EVENT,
CONTROL_MSG_TYPE_INJECT_SCROLL_EVENT,
CONTROL_MSG_TYPE_BACK_OR_SCREEN_ON,
CONTROL_MSG_TYPE_EXPAND_NOTIFICATION_PANEL,
CONTROL_MSG_TYPE_EXPAND_SETTINGS_PANEL,
CONTROL_MSG_TYPE_COLLAPSE_PANELS,
CONTROL_MSG_TYPE_GET_CLIPBOARD,
CONTROL_MSG_TYPE_SET_CLIPBOARD,
CONTROL_MSG_TYPE_SET_SCREEN_POWER_MODE,
CONTROL_MSG_TYPE_ROTATE_DEVICE,
enum sc_control_msg_type {
SC_CONTROL_MSG_TYPE_INJECT_KEYCODE,
SC_CONTROL_MSG_TYPE_INJECT_TEXT,
SC_CONTROL_MSG_TYPE_INJECT_TOUCH_EVENT,
SC_CONTROL_MSG_TYPE_INJECT_SCROLL_EVENT,
SC_CONTROL_MSG_TYPE_BACK_OR_SCREEN_ON,
SC_CONTROL_MSG_TYPE_EXPAND_NOTIFICATION_PANEL,
SC_CONTROL_MSG_TYPE_EXPAND_SETTINGS_PANEL,
SC_CONTROL_MSG_TYPE_COLLAPSE_PANELS,
SC_CONTROL_MSG_TYPE_GET_CLIPBOARD,
SC_CONTROL_MSG_TYPE_SET_CLIPBOARD,
SC_CONTROL_MSG_TYPE_SET_SCREEN_POWER_MODE,
SC_CONTROL_MSG_TYPE_ROTATE_DEVICE,
};
enum screen_power_mode {
enum sc_screen_power_mode {
// see <https://android.googlesource.com/platform/frameworks/base.git/+/pie-release-2/core/java/android/view/SurfaceControl.java#305>
SCREEN_POWER_MODE_OFF = 0,
SCREEN_POWER_MODE_NORMAL = 2,
SC_SCREEN_POWER_MODE_OFF = 0,
SC_SCREEN_POWER_MODE_NORMAL = 2,
};
enum get_clipboard_copy_key {
GET_CLIPBOARD_COPY_KEY_NONE,
GET_CLIPBOARD_COPY_KEY_COPY,
GET_CLIPBOARD_COPY_KEY_CUT,
enum sc_copy_key {
SC_COPY_KEY_NONE,
SC_COPY_KEY_COPY,
SC_COPY_KEY_CUT,
};
struct control_msg {
enum control_msg_type type;
struct sc_control_msg {
enum sc_control_msg_type type;
union {
struct {
enum android_keyevent_action action;
@@ -77,7 +77,7 @@ struct control_msg {
// screen may only be turned on on ACTION_DOWN
} back_or_screen_on;
struct {
enum get_clipboard_copy_key copy_key;
enum sc_copy_key copy_key;
} get_clipboard;
struct {
uint64_t sequence;
@@ -85,7 +85,7 @@ struct control_msg {
bool paste;
} set_clipboard;
struct {
enum screen_power_mode mode;
enum sc_screen_power_mode mode;
} set_screen_power_mode;
};
};
@@ -93,12 +93,12 @@ struct control_msg {
// buf size must be at least CONTROL_MSG_MAX_SIZE
// return the number of bytes written
size_t
control_msg_serialize(const struct control_msg *msg, unsigned char *buf);
sc_control_msg_serialize(const struct sc_control_msg *msg, unsigned char *buf);
void
control_msg_log(const struct control_msg *msg);
sc_control_msg_log(const struct sc_control_msg *msg);
void
control_msg_destroy(struct control_msg *msg);
sc_control_msg_destroy(struct sc_control_msg *msg);
#endif

View File

@@ -5,8 +5,8 @@
#include "util/log.h"
bool
controller_init(struct controller *controller, sc_socket control_socket,
struct sc_acksync *acksync) {
sc_controller_init(struct sc_controller *controller, sc_socket control_socket,
struct sc_acksync *acksync) {
cbuf_init(&controller->queue);
bool ok = receiver_init(&controller->receiver, control_socket, acksync);
@@ -34,23 +34,23 @@ controller_init(struct controller *controller, sc_socket control_socket,
}
void
controller_destroy(struct controller *controller) {
sc_controller_destroy(struct sc_controller *controller) {
sc_cond_destroy(&controller->msg_cond);
sc_mutex_destroy(&controller->mutex);
struct control_msg msg;
struct sc_control_msg msg;
while (cbuf_take(&controller->queue, &msg)) {
control_msg_destroy(&msg);
sc_control_msg_destroy(&msg);
}
receiver_destroy(&controller->receiver);
}
bool
controller_push_msg(struct controller *controller,
const struct control_msg *msg) {
sc_controller_push_msg(struct sc_controller *controller,
const struct sc_control_msg *msg) {
if (sc_get_log_level() <= SC_LOG_LEVEL_VERBOSE) {
control_msg_log(msg);
sc_control_msg_log(msg);
}
sc_mutex_lock(&controller->mutex);
@@ -64,9 +64,10 @@ controller_push_msg(struct controller *controller,
}
static bool
process_msg(struct controller *controller, const struct control_msg *msg) {
static unsigned char serialized_msg[CONTROL_MSG_MAX_SIZE];
size_t length = control_msg_serialize(msg, serialized_msg);
process_msg(struct sc_controller *controller,
const struct sc_control_msg *msg) {
static unsigned char serialized_msg[SC_CONTROL_MSG_MAX_SIZE];
size_t length = sc_control_msg_serialize(msg, serialized_msg);
if (!length) {
return false;
}
@@ -77,7 +78,7 @@ process_msg(struct controller *controller, const struct control_msg *msg) {
static int
run_controller(void *data) {
struct controller *controller = data;
struct sc_controller *controller = data;
for (;;) {
sc_mutex_lock(&controller->mutex);
@@ -89,14 +90,14 @@ run_controller(void *data) {
sc_mutex_unlock(&controller->mutex);
break;
}
struct control_msg msg;
struct sc_control_msg msg;
bool non_empty = cbuf_take(&controller->queue, &msg);
assert(non_empty);
(void) non_empty;
sc_mutex_unlock(&controller->mutex);
bool ok = process_msg(controller, &msg);
control_msg_destroy(&msg);
sc_control_msg_destroy(&msg);
if (!ok) {
LOGD("Could not write msg to socket");
break;
@@ -106,7 +107,7 @@ run_controller(void *data) {
}
bool
controller_start(struct controller *controller) {
sc_controller_start(struct sc_controller *controller) {
LOGD("Starting controller thread");
bool ok = sc_thread_create(&controller->thread, run_controller,
@@ -117,7 +118,7 @@ controller_start(struct controller *controller) {
}
if (!receiver_start(&controller->receiver)) {
controller_stop(controller);
sc_controller_stop(controller);
sc_thread_join(&controller->thread, NULL);
return false;
}
@@ -126,7 +127,7 @@ controller_start(struct controller *controller) {
}
void
controller_stop(struct controller *controller) {
sc_controller_stop(struct sc_controller *controller) {
sc_mutex_lock(&controller->mutex);
controller->stopped = true;
sc_cond_signal(&controller->msg_cond);
@@ -134,7 +135,7 @@ controller_stop(struct controller *controller) {
}
void
controller_join(struct controller *controller) {
sc_controller_join(struct sc_controller *controller) {
sc_thread_join(&controller->thread, NULL);
receiver_join(&controller->receiver);
}

View File

@@ -12,36 +12,36 @@
#include "util/net.h"
#include "util/thread.h"
struct control_msg_queue CBUF(struct control_msg, 64);
struct sc_control_msg_queue CBUF(struct sc_control_msg, 64);
struct controller {
struct sc_controller {
sc_socket control_socket;
sc_thread thread;
sc_mutex mutex;
sc_cond msg_cond;
bool stopped;
struct control_msg_queue queue;
struct sc_control_msg_queue queue;
struct receiver receiver;
};
bool
controller_init(struct controller *controller, sc_socket control_socket,
struct sc_acksync *acksync);
sc_controller_init(struct sc_controller *controller, sc_socket control_socket,
struct sc_acksync *acksync);
void
controller_destroy(struct controller *controller);
sc_controller_destroy(struct sc_controller *controller);
bool
controller_start(struct controller *controller);
sc_controller_start(struct sc_controller *controller);
void
controller_stop(struct controller *controller);
sc_controller_stop(struct sc_controller *controller);
void
controller_join(struct controller *controller);
sc_controller_join(struct sc_controller *controller);
bool
controller_push_msg(struct controller *controller,
const struct control_msg *msg);
sc_controller_push_msg(struct sc_controller *controller,
const struct sc_control_msg *msg);
#endif

View File

@@ -6,6 +6,7 @@
#include "trait/packet_sink.h"
#include <stdbool.h>
#include <libavcodec/avcodec.h>
#include <libavformat/avformat.h>
#define DECODER_MAX_SINKS 2

View File

@@ -2,3 +2,4 @@
#define EVENT_STREAM_STOPPED (SDL_USEREVENT + 1)
#define EVENT_SERVER_CONNECTION_FAILED (SDL_USEREVENT + 2)
#define EVENT_SERVER_CONNECTED (SDL_USEREVENT + 3)
#define EVENT_USB_DEVICE_DISCONNECTED (SDL_USEREVENT + 4)

View File

@@ -1,178 +0,0 @@
#include "file_handler.h"
#include <assert.h>
#include <string.h>
#include "adb.h"
#include "util/log.h"
#include "util/process_intr.h"
#define DEFAULT_PUSH_TARGET "/sdcard/Download/"
static void
file_handler_request_destroy(struct file_handler_request *req) {
free(req->file);
}
bool
file_handler_init(struct file_handler *file_handler, const char *serial,
const char *push_target) {
assert(serial);
cbuf_init(&file_handler->queue);
bool ok = sc_mutex_init(&file_handler->mutex);
if (!ok) {
return false;
}
ok = sc_cond_init(&file_handler->event_cond);
if (!ok) {
sc_mutex_destroy(&file_handler->mutex);
return false;
}
ok = sc_intr_init(&file_handler->intr);
if (!ok) {
sc_cond_destroy(&file_handler->event_cond);
sc_mutex_destroy(&file_handler->mutex);
return false;
}
file_handler->serial = strdup(serial);
if (!file_handler->serial) {
LOG_OOM();
sc_intr_destroy(&file_handler->intr);
sc_cond_destroy(&file_handler->event_cond);
sc_mutex_destroy(&file_handler->mutex);
return false;
}
// lazy initialization
file_handler->initialized = false;
file_handler->stopped = false;
file_handler->push_target = push_target ? push_target : DEFAULT_PUSH_TARGET;
return true;
}
void
file_handler_destroy(struct file_handler *file_handler) {
sc_cond_destroy(&file_handler->event_cond);
sc_mutex_destroy(&file_handler->mutex);
sc_intr_destroy(&file_handler->intr);
free(file_handler->serial);
struct file_handler_request req;
while (cbuf_take(&file_handler->queue, &req)) {
file_handler_request_destroy(&req);
}
}
bool
file_handler_request(struct file_handler *file_handler,
file_handler_action_t action, char *file) {
// start file_handler if it's used for the first time
if (!file_handler->initialized) {
if (!file_handler_start(file_handler)) {
return false;
}
file_handler->initialized = true;
}
LOGI("Request to %s %s", action == ACTION_INSTALL_APK ? "install" : "push",
file);
struct file_handler_request req = {
.action = action,
.file = file,
};
sc_mutex_lock(&file_handler->mutex);
bool was_empty = cbuf_is_empty(&file_handler->queue);
bool res = cbuf_push(&file_handler->queue, req);
if (was_empty) {
sc_cond_signal(&file_handler->event_cond);
}
sc_mutex_unlock(&file_handler->mutex);
return res;
}
static int
run_file_handler(void *data) {
struct file_handler *file_handler = data;
struct sc_intr *intr = &file_handler->intr;
const char *serial = file_handler->serial;
assert(serial);
const char *push_target = file_handler->push_target;
assert(push_target);
for (;;) {
sc_mutex_lock(&file_handler->mutex);
while (!file_handler->stopped && cbuf_is_empty(&file_handler->queue)) {
sc_cond_wait(&file_handler->event_cond, &file_handler->mutex);
}
if (file_handler->stopped) {
// stop immediately, do not process further events
sc_mutex_unlock(&file_handler->mutex);
break;
}
struct file_handler_request req;
bool non_empty = cbuf_take(&file_handler->queue, &req);
assert(non_empty);
(void) non_empty;
sc_mutex_unlock(&file_handler->mutex);
if (req.action == ACTION_INSTALL_APK) {
LOGI("Installing %s...", req.file);
bool ok = adb_install(intr, serial, req.file, 0);
if (ok) {
LOGI("%s successfully installed", req.file);
} else {
LOGE("Failed to install %s", req.file);
}
} else {
LOGI("Pushing %s...", req.file);
bool ok = adb_push(intr, serial, req.file, push_target, 0);
if (ok) {
LOGI("%s successfully pushed to %s", req.file, push_target);
} else {
LOGE("Failed to push %s to %s", req.file, push_target);
}
}
file_handler_request_destroy(&req);
}
return 0;
}
bool
file_handler_start(struct file_handler *file_handler) {
LOGD("Starting file_handler thread");
bool ok = sc_thread_create(&file_handler->thread, run_file_handler,
"scrcpy-file", file_handler);
if (!ok) {
LOGC("Could not start file_handler thread");
return false;
}
return true;
}
void
file_handler_stop(struct file_handler *file_handler) {
sc_mutex_lock(&file_handler->mutex);
file_handler->stopped = true;
sc_cond_signal(&file_handler->event_cond);
sc_intr_interrupt(&file_handler->intr);
sc_mutex_unlock(&file_handler->mutex);
}
void
file_handler_join(struct file_handler *file_handler) {
sc_thread_join(&file_handler->thread, NULL);
}

View File

@@ -1,60 +0,0 @@
#ifndef FILE_HANDLER_H
#define FILE_HANDLER_H
#include "common.h"
#include <stdbool.h>
#include "adb.h"
#include "util/cbuf.h"
#include "util/thread.h"
#include "util/intr.h"
typedef enum {
ACTION_INSTALL_APK,
ACTION_PUSH_FILE,
} file_handler_action_t;
struct file_handler_request {
file_handler_action_t action;
char *file;
};
struct file_handler_request_queue CBUF(struct file_handler_request, 16);
struct file_handler {
char *serial;
const char *push_target;
sc_thread thread;
sc_mutex mutex;
sc_cond event_cond;
bool stopped;
bool initialized;
struct file_handler_request_queue queue;
struct sc_intr intr;
};
bool
file_handler_init(struct file_handler *file_handler, const char *serial,
const char *push_target);
void
file_handler_destroy(struct file_handler *file_handler);
bool
file_handler_start(struct file_handler *file_handler);
void
file_handler_stop(struct file_handler *file_handler);
void
file_handler_join(struct file_handler *file_handler);
// take ownership of file, and will free() it
bool
file_handler_request(struct file_handler *file_handler,
file_handler_action_t action,
char *file);
#endif

178
app/src/file_pusher.c Normal file
View File

@@ -0,0 +1,178 @@
#include "file_pusher.h"
#include <assert.h>
#include <string.h>
#include "adb.h"
#include "util/log.h"
#include "util/process_intr.h"
#define DEFAULT_PUSH_TARGET "/sdcard/Download/"
static void
sc_file_pusher_request_destroy(struct sc_file_pusher_request *req) {
free(req->file);
}
bool
sc_file_pusher_init(struct sc_file_pusher *fp, const char *serial,
const char *push_target) {
assert(serial);
cbuf_init(&fp->queue);
bool ok = sc_mutex_init(&fp->mutex);
if (!ok) {
return false;
}
ok = sc_cond_init(&fp->event_cond);
if (!ok) {
sc_mutex_destroy(&fp->mutex);
return false;
}
ok = sc_intr_init(&fp->intr);
if (!ok) {
sc_cond_destroy(&fp->event_cond);
sc_mutex_destroy(&fp->mutex);
return false;
}
fp->serial = strdup(serial);
if (!fp->serial) {
LOG_OOM();
sc_intr_destroy(&fp->intr);
sc_cond_destroy(&fp->event_cond);
sc_mutex_destroy(&fp->mutex);
return false;
}
// lazy initialization
fp->initialized = false;
fp->stopped = false;
fp->push_target = push_target ? push_target : DEFAULT_PUSH_TARGET;
return true;
}
void
sc_file_pusher_destroy(struct sc_file_pusher *fp) {
sc_cond_destroy(&fp->event_cond);
sc_mutex_destroy(&fp->mutex);
sc_intr_destroy(&fp->intr);
free(fp->serial);
struct sc_file_pusher_request req;
while (cbuf_take(&fp->queue, &req)) {
sc_file_pusher_request_destroy(&req);
}
}
bool
sc_file_pusher_request(struct sc_file_pusher *fp,
enum sc_file_pusher_action action, char *file) {
// start file_pusher if it's used for the first time
if (!fp->initialized) {
if (!sc_file_pusher_start(fp)) {
return false;
}
fp->initialized = true;
}
LOGI("Request to %s %s", action == SC_FILE_PUSHER_ACTION_INSTALL_APK
? "install" : "push",
file);
struct sc_file_pusher_request req = {
.action = action,
.file = file,
};
sc_mutex_lock(&fp->mutex);
bool was_empty = cbuf_is_empty(&fp->queue);
bool res = cbuf_push(&fp->queue, req);
if (was_empty) {
sc_cond_signal(&fp->event_cond);
}
sc_mutex_unlock(&fp->mutex);
return res;
}
static int
run_file_pusher(void *data) {
struct sc_file_pusher *fp = data;
struct sc_intr *intr = &fp->intr;
const char *serial = fp->serial;
assert(serial);
const char *push_target = fp->push_target;
assert(push_target);
for (;;) {
sc_mutex_lock(&fp->mutex);
while (!fp->stopped && cbuf_is_empty(&fp->queue)) {
sc_cond_wait(&fp->event_cond, &fp->mutex);
}
if (fp->stopped) {
// stop immediately, do not process further events
sc_mutex_unlock(&fp->mutex);
break;
}
struct sc_file_pusher_request req;
bool non_empty = cbuf_take(&fp->queue, &req);
assert(non_empty);
(void) non_empty;
sc_mutex_unlock(&fp->mutex);
if (req.action == SC_FILE_PUSHER_ACTION_INSTALL_APK) {
LOGI("Installing %s...", req.file);
bool ok = adb_install(intr, serial, req.file, 0);
if (ok) {
LOGI("%s successfully installed", req.file);
} else {
LOGE("Failed to install %s", req.file);
}
} else {
LOGI("Pushing %s...", req.file);
bool ok = adb_push(intr, serial, req.file, push_target, 0);
if (ok) {
LOGI("%s successfully pushed to %s", req.file, push_target);
} else {
LOGE("Failed to push %s to %s", req.file, push_target);
}
}
sc_file_pusher_request_destroy(&req);
}
return 0;
}
bool
sc_file_pusher_start(struct sc_file_pusher *fp) {
LOGD("Starting file_pusher thread");
bool ok = sc_thread_create(&fp->thread, run_file_pusher, "scrcpy-file", fp);
if (!ok) {
LOGC("Could not start file_pusher thread");
return false;
}
return true;
}
void
sc_file_pusher_stop(struct sc_file_pusher *fp) {
sc_mutex_lock(&fp->mutex);
fp->stopped = true;
sc_cond_signal(&fp->event_cond);
sc_intr_interrupt(&fp->intr);
sc_mutex_unlock(&fp->mutex);
}
void
sc_file_pusher_join(struct sc_file_pusher *fp) {
sc_thread_join(&fp->thread, NULL);
}

59
app/src/file_pusher.h Normal file
View File

@@ -0,0 +1,59 @@
#ifndef SC_FILE_PUSHER_H
#define SC_FILE_PUSHER_H
#include "common.h"
#include <stdbool.h>
#include "adb.h"
#include "util/cbuf.h"
#include "util/thread.h"
#include "util/intr.h"
enum sc_file_pusher_action {
SC_FILE_PUSHER_ACTION_INSTALL_APK,
SC_FILE_PUSHER_ACTION_PUSH_FILE,
};
struct sc_file_pusher_request {
enum sc_file_pusher_action action;
char *file;
};
struct sc_file_pusher_request_queue CBUF(struct sc_file_pusher_request, 16);
struct sc_file_pusher {
char *serial;
const char *push_target;
sc_thread thread;
sc_mutex mutex;
sc_cond event_cond;
bool stopped;
bool initialized;
struct sc_file_pusher_request_queue queue;
struct sc_intr intr;
};
bool
sc_file_pusher_init(struct sc_file_pusher *fp, const char *serial,
const char *push_target);
void
sc_file_pusher_destroy(struct sc_file_pusher *fp);
bool
sc_file_pusher_start(struct sc_file_pusher *fp);
void
sc_file_pusher_stop(struct sc_file_pusher *fp);
void
sc_file_pusher_join(struct sc_file_pusher *fp);
// take ownership of file, and will free() it
bool
sc_file_pusher_request(struct sc_file_pusher *fp,
enum sc_file_pusher_action action, char *file);
#endif

View File

@@ -50,9 +50,7 @@ swap_frames(AVFrame **lhs, AVFrame **rhs) {
bool
sc_frame_buffer_push(struct sc_frame_buffer *fb, const AVFrame *frame,
bool *previous_frame_skipped) {
sc_mutex_lock(&fb->mutex);
bool *previous_frame_skipped) {
// Use a temporary frame to preserve pending_frame in case of error.
// tmp_frame is an empty frame, no need to call av_frame_unref() beforehand.
int r = av_frame_ref(fb->tmp_frame, frame);
@@ -61,6 +59,8 @@ sc_frame_buffer_push(struct sc_frame_buffer *fb, const AVFrame *frame,
return false;
}
sc_mutex_lock(&fb->mutex);
// Now that av_frame_ref() succeeded, we can replace the previous
// pending_frame
swap_frames(&fb->pending_frame, &fb->tmp_frame);

View File

@@ -2,6 +2,7 @@
#include <assert.h>
#include <stdbool.h>
#include <libavcodec/avcodec.h>
#include <libavformat/avformat.h>
#include <libavutil/pixdesc.h>
#include <libavutil/pixfmt.h>
@@ -85,7 +86,7 @@ decode_image(const char *path) {
AVCodecParameters *params = ctx->streams[stream]->codecpar;
AVCodec *codec = avcodec_find_decoder(params->codec_id);
const AVCodec *codec = avcodec_find_decoder(params->codec_id);
if (!codec) {
LOGE("Could not find image decoder");
goto close_input;

View File

@@ -377,4 +377,76 @@ struct sc_touch_event {
float pressure;
};
static inline uint16_t
sc_mods_state_from_sdl(uint16_t mods_state) {
return mods_state;
}
static inline enum sc_keycode
sc_keycode_from_sdl(SDL_Keycode keycode) {
return (enum sc_keycode) keycode;
}
static inline enum sc_scancode
sc_scancode_from_sdl(SDL_Scancode scancode) {
return (enum sc_scancode) scancode;
}
static inline enum sc_action
sc_action_from_sdl_keyboard_type(uint32_t type) {
assert(type == SDL_KEYDOWN || type == SDL_KEYUP);
if (type == SDL_KEYDOWN) {
return SC_ACTION_DOWN;
}
return SC_ACTION_UP;
}
static inline enum sc_action
sc_action_from_sdl_mousebutton_type(uint32_t type) {
assert(type == SDL_MOUSEBUTTONDOWN || type == SDL_MOUSEBUTTONUP);
if (type == SDL_MOUSEBUTTONDOWN) {
return SC_ACTION_DOWN;
}
return SC_ACTION_UP;
}
static inline enum sc_touch_action
sc_touch_action_from_sdl(uint32_t type) {
assert(type == SDL_FINGERMOTION || type == SDL_FINGERDOWN ||
type == SDL_FINGERUP);
if (type == SDL_FINGERMOTION) {
return SC_TOUCH_ACTION_MOVE;
}
if (type == SDL_FINGERDOWN) {
return SC_TOUCH_ACTION_DOWN;
}
return SC_TOUCH_ACTION_UP;
}
static inline enum sc_mouse_button
sc_mouse_button_from_sdl(uint8_t button) {
if (button >= SDL_BUTTON_LEFT && button <= SDL_BUTTON_X2) {
// SC_MOUSE_BUTTON_* constants are initialized from SDL_BUTTON(index)
return SDL_BUTTON(button);
}
return SC_MOUSE_BUTTON_UNKNOWN;
}
static inline uint8_t
sc_mouse_buttons_state_from_sdl(uint32_t buttons_state,
bool forward_all_clicks) {
assert(buttons_state < 0x100); // fits in uint8_t
uint8_t mask = SC_MOUSE_BUTTON_LEFT;
if (forward_all_clicks) {
mask |= SC_MOUSE_BUTTON_RIGHT
| SC_MOUSE_BUTTON_MIDDLE
| SC_MOUSE_BUTTON_X1
| SC_MOUSE_BUTTON_X2;
}
return buttons_state & mask;
}
#endif

View File

@@ -7,78 +7,6 @@
#include "screen.h"
#include "util/log.h"
static inline uint16_t
sc_mods_state_from_sdl(uint16_t mods_state) {
return mods_state;
}
static inline enum sc_keycode
sc_keycode_from_sdl(SDL_Keycode keycode) {
return (enum sc_keycode) keycode;
}
static inline enum sc_scancode
sc_scancode_from_sdl(SDL_Scancode scancode) {
return (enum sc_scancode) scancode;
}
static inline enum sc_action
sc_action_from_sdl_keyboard_type(uint32_t type) {
assert(type == SDL_KEYDOWN || type == SDL_KEYUP);
if (type == SDL_KEYDOWN) {
return SC_ACTION_DOWN;
}
return SC_ACTION_UP;
}
static inline enum sc_action
sc_action_from_sdl_mousebutton_type(uint32_t type) {
assert(type == SDL_MOUSEBUTTONDOWN || type == SDL_MOUSEBUTTONUP);
if (type == SDL_MOUSEBUTTONDOWN) {
return SC_ACTION_DOWN;
}
return SC_ACTION_UP;
}
static inline enum sc_touch_action
sc_touch_action_from_sdl(uint32_t type) {
assert(type == SDL_FINGERMOTION || type == SDL_FINGERDOWN ||
type == SDL_FINGERUP);
if (type == SDL_FINGERMOTION) {
return SC_TOUCH_ACTION_MOVE;
}
if (type == SDL_FINGERDOWN) {
return SC_TOUCH_ACTION_DOWN;
}
return SC_TOUCH_ACTION_UP;
}
static inline enum sc_mouse_button
sc_mouse_button_from_sdl(uint8_t button) {
if (button >= SDL_BUTTON_LEFT && button <= SDL_BUTTON_X2) {
// SC_MOUSE_BUTTON_* constants are initialized from SDL_BUTTON(index)
return SDL_BUTTON(button);
}
return SC_MOUSE_BUTTON_UNKNOWN;
}
static inline uint8_t
sc_mouse_buttons_state_from_sdl(uint32_t buttons_state,
bool forward_all_clicks) {
assert(buttons_state < 0x100); // fits in uint8_t
uint8_t mask = SC_MOUSE_BUTTON_LEFT;
if (forward_all_clicks) {
mask |= SC_MOUSE_BUTTON_RIGHT
| SC_MOUSE_BUTTON_MIDDLE
| SC_MOUSE_BUTTON_X1
| SC_MOUSE_BUTTON_X2;
}
return buttons_state & mask;
}
#define SC_SDL_SHORTCUT_MODS_MASK (KMOD_CTRL | KMOD_ALT | KMOD_GUI)
static inline uint16_t
@@ -106,7 +34,7 @@ to_sdl_mod(unsigned shortcut_mod) {
}
static bool
is_shortcut_mod(struct input_manager *im, uint16_t sdl_mod) {
is_shortcut_mod(struct sc_input_manager *im, uint16_t sdl_mod) {
// keep only the relevant modifier keys
sdl_mod &= SC_SDL_SHORTCUT_MODS_MASK;
@@ -122,17 +50,17 @@ is_shortcut_mod(struct input_manager *im, uint16_t sdl_mod) {
}
void
input_manager_init(struct input_manager *im,
const struct input_manager_params *params) {
assert(!params->control || (params->kp && params->kp->ops));
assert(!params->control || (params->mp && params->mp->ops));
sc_input_manager_init(struct sc_input_manager *im,
const struct sc_input_manager_params *params) {
assert(!params->controller || (params->kp && params->kp->ops));
assert(!params->controller || (params->mp && params->mp->ops));
im->controller = params->controller;
im->fp = params->fp;
im->screen = params->screen;
im->kp = params->kp;
im->mp = params->mp;
im->control = params->control;
im->forward_all_clicks = params->forward_all_clicks;
im->legacy_paste = params->legacy_paste;
im->clipboard_autosync = params->clipboard_autosync;
@@ -157,11 +85,11 @@ input_manager_init(struct input_manager *im,
}
static void
send_keycode(struct controller *controller, enum android_keycode keycode,
send_keycode(struct sc_controller *controller, enum android_keycode keycode,
enum sc_action action, const char *name) {
// send DOWN event
struct control_msg msg;
msg.type = CONTROL_MSG_TYPE_INJECT_KEYCODE;
struct sc_control_msg msg;
msg.type = SC_CONTROL_MSG_TYPE_INJECT_KEYCODE;
msg.inject_keycode.action = action == SC_ACTION_DOWN
? AKEY_EVENT_ACTION_DOWN
: AKEY_EVENT_ACTION_UP;
@@ -169,100 +97,100 @@ send_keycode(struct controller *controller, enum android_keycode keycode,
msg.inject_keycode.metastate = 0;
msg.inject_keycode.repeat = 0;
if (!controller_push_msg(controller, &msg)) {
if (!sc_controller_push_msg(controller, &msg)) {
LOGW("Could not request 'inject %s'", name);
}
}
static inline void
action_home(struct controller *controller, enum sc_action action) {
action_home(struct sc_controller *controller, enum sc_action action) {
send_keycode(controller, AKEYCODE_HOME, action, "HOME");
}
static inline void
action_back(struct controller *controller, enum sc_action action) {
action_back(struct sc_controller *controller, enum sc_action action) {
send_keycode(controller, AKEYCODE_BACK, action, "BACK");
}
static inline void
action_app_switch(struct controller *controller, enum sc_action action) {
action_app_switch(struct sc_controller *controller, enum sc_action action) {
send_keycode(controller, AKEYCODE_APP_SWITCH, action, "APP_SWITCH");
}
static inline void
action_power(struct controller *controller, enum sc_action action) {
action_power(struct sc_controller *controller, enum sc_action action) {
send_keycode(controller, AKEYCODE_POWER, action, "POWER");
}
static inline void
action_volume_up(struct controller *controller, enum sc_action action) {
action_volume_up(struct sc_controller *controller, enum sc_action action) {
send_keycode(controller, AKEYCODE_VOLUME_UP, action, "VOLUME_UP");
}
static inline void
action_volume_down(struct controller *controller, enum sc_action action) {
action_volume_down(struct sc_controller *controller, enum sc_action action) {
send_keycode(controller, AKEYCODE_VOLUME_DOWN, action, "VOLUME_DOWN");
}
static inline void
action_menu(struct controller *controller, enum sc_action action) {
action_menu(struct sc_controller *controller, enum sc_action action) {
send_keycode(controller, AKEYCODE_MENU, action, "MENU");
}
// turn the screen on if it was off, press BACK otherwise
// If the screen is off, it is turned on only on ACTION_DOWN
static void
press_back_or_turn_screen_on(struct controller *controller,
press_back_or_turn_screen_on(struct sc_controller *controller,
enum sc_action action) {
struct control_msg msg;
msg.type = CONTROL_MSG_TYPE_BACK_OR_SCREEN_ON;
struct sc_control_msg msg;
msg.type = SC_CONTROL_MSG_TYPE_BACK_OR_SCREEN_ON;
msg.back_or_screen_on.action = action == SC_ACTION_DOWN
? AKEY_EVENT_ACTION_DOWN
: AKEY_EVENT_ACTION_UP;
if (!controller_push_msg(controller, &msg)) {
if (!sc_controller_push_msg(controller, &msg)) {
LOGW("Could not request 'press back or turn screen on'");
}
}
static void
expand_notification_panel(struct controller *controller) {
struct control_msg msg;
msg.type = CONTROL_MSG_TYPE_EXPAND_NOTIFICATION_PANEL;
expand_notification_panel(struct sc_controller *controller) {
struct sc_control_msg msg;
msg.type = SC_CONTROL_MSG_TYPE_EXPAND_NOTIFICATION_PANEL;
if (!controller_push_msg(controller, &msg)) {
if (!sc_controller_push_msg(controller, &msg)) {
LOGW("Could not request 'expand notification panel'");
}
}
static void
expand_settings_panel(struct controller *controller) {
struct control_msg msg;
msg.type = CONTROL_MSG_TYPE_EXPAND_SETTINGS_PANEL;
expand_settings_panel(struct sc_controller *controller) {
struct sc_control_msg msg;
msg.type = SC_CONTROL_MSG_TYPE_EXPAND_SETTINGS_PANEL;
if (!controller_push_msg(controller, &msg)) {
if (!sc_controller_push_msg(controller, &msg)) {
LOGW("Could not request 'expand settings panel'");
}
}
static void
collapse_panels(struct controller *controller) {
struct control_msg msg;
msg.type = CONTROL_MSG_TYPE_COLLAPSE_PANELS;
collapse_panels(struct sc_controller *controller) {
struct sc_control_msg msg;
msg.type = SC_CONTROL_MSG_TYPE_COLLAPSE_PANELS;
if (!controller_push_msg(controller, &msg)) {
if (!sc_controller_push_msg(controller, &msg)) {
LOGW("Could not request 'collapse notification panel'");
}
}
static bool
get_device_clipboard(struct controller *controller,
enum get_clipboard_copy_key copy_key) {
struct control_msg msg;
msg.type = CONTROL_MSG_TYPE_GET_CLIPBOARD;
get_device_clipboard(struct sc_controller *controller,
enum sc_copy_key copy_key) {
struct sc_control_msg msg;
msg.type = SC_CONTROL_MSG_TYPE_GET_CLIPBOARD;
msg.get_clipboard.copy_key = copy_key;
if (!controller_push_msg(controller, &msg)) {
if (!sc_controller_push_msg(controller, &msg)) {
LOGW("Could not request 'get device clipboard'");
return false;
}
@@ -271,7 +199,7 @@ get_device_clipboard(struct controller *controller,
}
static bool
set_device_clipboard(struct controller *controller, bool paste,
set_device_clipboard(struct sc_controller *controller, bool paste,
uint64_t sequence) {
char *text = SDL_GetClipboardText();
if (!text) {
@@ -286,13 +214,13 @@ set_device_clipboard(struct controller *controller, bool paste,
return false;
}
struct control_msg msg;
msg.type = CONTROL_MSG_TYPE_SET_CLIPBOARD;
struct sc_control_msg msg;
msg.type = SC_CONTROL_MSG_TYPE_SET_CLIPBOARD;
msg.set_clipboard.sequence = sequence;
msg.set_clipboard.text = text_dup;
msg.set_clipboard.paste = paste;
if (!controller_push_msg(controller, &msg)) {
if (!sc_controller_push_msg(controller, &msg)) {
free(text_dup);
LOGW("Could not request 'set device clipboard'");
return false;
@@ -302,13 +230,13 @@ set_device_clipboard(struct controller *controller, bool paste,
}
static void
set_screen_power_mode(struct controller *controller,
enum screen_power_mode mode) {
struct control_msg msg;
msg.type = CONTROL_MSG_TYPE_SET_SCREEN_POWER_MODE;
set_screen_power_mode(struct sc_controller *controller,
enum sc_screen_power_mode mode) {
struct sc_control_msg msg;
msg.type = SC_CONTROL_MSG_TYPE_SET_SCREEN_POWER_MODE;
msg.set_screen_power_mode.mode = mode;
if (!controller_push_msg(controller, &msg)) {
if (!sc_controller_push_msg(controller, &msg)) {
LOGW("Could not request 'set screen power mode'");
}
}
@@ -330,7 +258,7 @@ switch_fps_counter_state(struct fps_counter *fps_counter) {
}
static void
clipboard_paste(struct controller *controller) {
clipboard_paste(struct sc_controller *controller) {
char *text = SDL_GetClipboardText();
if (!text) {
LOGW("Could not get clipboard text: %s", SDL_GetError());
@@ -349,40 +277,40 @@ clipboard_paste(struct controller *controller) {
return;
}
struct control_msg msg;
msg.type = CONTROL_MSG_TYPE_INJECT_TEXT;
struct sc_control_msg msg;
msg.type = SC_CONTROL_MSG_TYPE_INJECT_TEXT;
msg.inject_text.text = text_dup;
if (!controller_push_msg(controller, &msg)) {
if (!sc_controller_push_msg(controller, &msg)) {
free(text_dup);
LOGW("Could not request 'paste clipboard'");
}
}
static void
rotate_device(struct controller *controller) {
struct control_msg msg;
msg.type = CONTROL_MSG_TYPE_ROTATE_DEVICE;
rotate_device(struct sc_controller *controller) {
struct sc_control_msg msg;
msg.type = SC_CONTROL_MSG_TYPE_ROTATE_DEVICE;
if (!controller_push_msg(controller, &msg)) {
if (!sc_controller_push_msg(controller, &msg)) {
LOGW("Could not request device rotation");
}
}
static void
rotate_client_left(struct screen *screen) {
rotate_client_left(struct sc_screen *screen) {
unsigned new_rotation = (screen->rotation + 1) % 4;
screen_set_rotation(screen, new_rotation);
sc_screen_set_rotation(screen, new_rotation);
}
static void
rotate_client_right(struct screen *screen) {
rotate_client_right(struct sc_screen *screen) {
unsigned new_rotation = (screen->rotation + 3) % 4;
screen_set_rotation(screen, new_rotation);
sc_screen_set_rotation(screen, new_rotation);
}
static void
input_manager_process_text_input(struct input_manager *im,
const SDL_TextInputEvent *event) {
sc_input_manager_process_text_input(struct sc_input_manager *im,
const SDL_TextInputEvent *event) {
if (!im->kp->ops->process_text) {
// The key processor does not support text input
return;
@@ -401,13 +329,13 @@ input_manager_process_text_input(struct input_manager *im,
}
static bool
simulate_virtual_finger(struct input_manager *im,
simulate_virtual_finger(struct sc_input_manager *im,
enum android_motionevent_action action,
struct sc_point point) {
bool up = action == AMOTION_EVENT_ACTION_UP;
struct control_msg msg;
msg.type = CONTROL_MSG_TYPE_INJECT_TOUCH_EVENT;
struct sc_control_msg msg;
msg.type = SC_CONTROL_MSG_TYPE_INJECT_TOUCH_EVENT;
msg.inject_touch_event.action = action;
msg.inject_touch_event.position.screen_size = im->screen->frame_size;
msg.inject_touch_event.position.point = point;
@@ -415,7 +343,7 @@ simulate_virtual_finger(struct input_manager *im,
msg.inject_touch_event.pressure = up ? 0.0f : 1.0f;
msg.inject_touch_event.buttons = 0;
if (!controller_push_msg(im->controller, &msg)) {
if (!sc_controller_push_msg(im->controller, &msg)) {
LOGW("Could not request 'inject virtual finger event'");
return false;
}
@@ -431,12 +359,10 @@ inverse_point(struct sc_point point, struct sc_size size) {
}
static void
input_manager_process_key(struct input_manager *im,
const SDL_KeyboardEvent *event) {
// control: indicates the state of the command-line option --no-control
bool control = im->control;
struct controller *controller = im->controller;
sc_input_manager_process_key(struct sc_input_manager *im,
const SDL_KeyboardEvent *event) {
// controller is NULL if --no-control is requested
struct sc_controller *controller = im->controller;
SDL_Keycode keycode = event->keysym.sym;
uint16_t mod = event->keysym.mod;
@@ -462,47 +388,47 @@ input_manager_process_key(struct input_manager *im,
enum sc_action action = down ? SC_ACTION_DOWN : SC_ACTION_UP;
switch (keycode) {
case SDLK_h:
if (control && !shift && !repeat) {
if (controller && !shift && !repeat) {
action_home(controller, action);
}
return;
case SDLK_b: // fall-through
case SDLK_BACKSPACE:
if (control && !shift && !repeat) {
if (controller && !shift && !repeat) {
action_back(controller, action);
}
return;
case SDLK_s:
if (control && !shift && !repeat) {
if (controller && !shift && !repeat) {
action_app_switch(controller, action);
}
return;
case SDLK_m:
if (control && !shift && !repeat) {
if (controller && !shift && !repeat) {
action_menu(controller, action);
}
return;
case SDLK_p:
if (control && !shift && !repeat) {
if (controller && !shift && !repeat) {
action_power(controller, action);
}
return;
case SDLK_o:
if (control && !repeat && down) {
enum screen_power_mode mode = shift
? SCREEN_POWER_MODE_NORMAL
: SCREEN_POWER_MODE_OFF;
if (controller && !repeat && down) {
enum sc_screen_power_mode mode = shift
? SC_SCREEN_POWER_MODE_NORMAL
: SC_SCREEN_POWER_MODE_OFF;
set_screen_power_mode(controller, mode);
}
return;
case SDLK_DOWN:
if (control && !shift) {
if (controller && !shift) {
// forward repeated events
action_volume_down(controller, action);
}
return;
case SDLK_UP:
if (control && !shift) {
if (controller && !shift) {
// forward repeated events
action_volume_up(controller, action);
}
@@ -518,19 +444,17 @@ input_manager_process_key(struct input_manager *im,
}
return;
case SDLK_c:
if (control && !shift && !repeat && down) {
get_device_clipboard(controller,
GET_CLIPBOARD_COPY_KEY_COPY);
if (controller && !shift && !repeat && down) {
get_device_clipboard(controller, SC_COPY_KEY_COPY);
}
return;
case SDLK_x:
if (control && !shift && !repeat && down) {
get_device_clipboard(controller,
GET_CLIPBOARD_COPY_KEY_CUT);
if (controller && !shift && !repeat && down) {
get_device_clipboard(controller, SC_COPY_KEY_CUT);
}
return;
case SDLK_v:
if (control && !repeat && down) {
if (controller && !repeat && down) {
if (shift || im->legacy_paste) {
// inject the text as input events
clipboard_paste(controller);
@@ -544,17 +468,17 @@ input_manager_process_key(struct input_manager *im,
return;
case SDLK_f:
if (!shift && !repeat && down) {
screen_switch_fullscreen(im->screen);
sc_screen_switch_fullscreen(im->screen);
}
return;
case SDLK_w:
if (!shift && !repeat && down) {
screen_resize_to_fit(im->screen);
sc_screen_resize_to_fit(im->screen);
}
return;
case SDLK_g:
if (!shift && !repeat && down) {
screen_resize_to_pixel_perfect(im->screen);
sc_screen_resize_to_pixel_perfect(im->screen);
}
return;
case SDLK_i:
@@ -563,7 +487,7 @@ input_manager_process_key(struct input_manager *im,
}
return;
case SDLK_n:
if (control && !repeat && down) {
if (controller && !repeat && down) {
if (shift) {
collapse_panels(controller);
} else if (im->key_repeat == 0) {
@@ -574,7 +498,7 @@ input_manager_process_key(struct input_manager *im,
}
return;
case SDLK_r:
if (control && !shift && !repeat && down) {
if (controller && !shift && !repeat && down) {
rotate_device(controller);
}
return;
@@ -583,7 +507,7 @@ input_manager_process_key(struct input_manager *im,
return;
}
if (!control) {
if (!controller) {
return;
}
@@ -629,8 +553,8 @@ input_manager_process_key(struct input_manager *im,
}
static void
input_manager_process_mouse_motion(struct input_manager *im,
const SDL_MouseMotionEvent *event) {
sc_input_manager_process_mouse_motion(struct sc_input_manager *im,
const SDL_MouseMotionEvent *event) {
if (event->which == SDL_TOUCH_MOUSEID) {
// simulated from touch events, so it's a duplicate
@@ -640,8 +564,9 @@ input_manager_process_mouse_motion(struct input_manager *im,
struct sc_mouse_motion_event evt = {
.position = {
.screen_size = im->screen->frame_size,
.point = screen_convert_window_to_frame_coords(im->screen,
event->x, event->y),
.point = sc_screen_convert_window_to_frame_coords(im->screen,
event->x,
event->y),
},
.xrel = event->xrel,
.yrel = event->yrel,
@@ -659,16 +584,16 @@ input_manager_process_mouse_motion(struct input_manager *im,
if (im->vfinger_down) {
assert(!im->mp->relative_mode); // assert one more time
struct sc_point mouse =
screen_convert_window_to_frame_coords(im->screen, event->x,
event->y);
sc_screen_convert_window_to_frame_coords(im->screen, event->x,
event->y);
struct sc_point vfinger = inverse_point(mouse, im->screen->frame_size);
simulate_virtual_finger(im, AMOTION_EVENT_ACTION_MOVE, vfinger);
}
}
static void
input_manager_process_touch(struct input_manager *im,
const SDL_TouchFingerEvent *event) {
sc_input_manager_process_touch(struct sc_input_manager *im,
const SDL_TouchFingerEvent *event) {
if (!im->mp->ops->process_touch) {
// The mouse processor does not support touch events
return;
@@ -685,7 +610,8 @@ input_manager_process_touch(struct input_manager *im,
struct sc_touch_event evt = {
.position = {
.screen_size = im->screen->frame_size,
.point = screen_convert_drawable_to_frame_coords(im->screen, x, y),
.point =
sc_screen_convert_drawable_to_frame_coords(im->screen, x, y),
},
.action = sc_touch_action_from_sdl(event->type),
.pointer_id = event->fingerId,
@@ -696,9 +622,9 @@ input_manager_process_touch(struct input_manager *im,
}
static void
input_manager_process_mouse_button(struct input_manager *im,
const SDL_MouseButtonEvent *event) {
bool control = im->control;
sc_input_manager_process_mouse_button(struct sc_input_manager *im,
const SDL_MouseButtonEvent *event) {
struct sc_controller *controller = im->controller;
if (event->which == SDL_TOUCH_MOUSEID) {
// simulated from touch events, so it's a duplicate
@@ -707,40 +633,42 @@ input_manager_process_mouse_button(struct input_manager *im,
bool down = event->type == SDL_MOUSEBUTTONDOWN;
if (!im->forward_all_clicks) {
enum sc_action action = down ? SC_ACTION_DOWN : SC_ACTION_UP;
if (controller) {
enum sc_action action = down ? SC_ACTION_DOWN : SC_ACTION_UP;
if (control && event->button == SDL_BUTTON_X1) {
action_app_switch(im->controller, action);
return;
}
if (control && event->button == SDL_BUTTON_X2 && down) {
if (event->clicks < 2) {
expand_notification_panel(im->controller);
} else {
expand_settings_panel(im->controller);
if (event->button == SDL_BUTTON_X1) {
action_app_switch(controller, action);
return;
}
if (event->button == SDL_BUTTON_X2 && down) {
if (event->clicks < 2) {
expand_notification_panel(controller);
} else {
expand_settings_panel(controller);
}
return;
}
if (event->button == SDL_BUTTON_RIGHT) {
press_back_or_turn_screen_on(controller, action);
return;
}
if (event->button == SDL_BUTTON_MIDDLE) {
action_home(controller, action);
return;
}
return;
}
if (control && event->button == SDL_BUTTON_RIGHT) {
press_back_or_turn_screen_on(im->controller, action);
return;
}
if (control && event->button == SDL_BUTTON_MIDDLE) {
action_home(im->controller, action);
return;
}
// double-click on black borders resize to fit the device screen
if (event->button == SDL_BUTTON_LEFT && event->clicks == 2) {
int32_t x = event->x;
int32_t y = event->y;
screen_hidpi_scale_coords(im->screen, &x, &y);
sc_screen_hidpi_scale_coords(im->screen, &x, &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 (down) {
screen_resize_to_fit(im->screen);
sc_screen_resize_to_fit(im->screen);
}
return;
}
@@ -748,7 +676,7 @@ input_manager_process_mouse_button(struct input_manager *im,
// otherwise, send the click event to the device
}
if (!control) {
if (!controller) {
return;
}
@@ -757,8 +685,9 @@ input_manager_process_mouse_button(struct input_manager *im,
struct sc_mouse_click_event evt = {
.position = {
.screen_size = im->screen->frame_size,
.point = screen_convert_window_to_frame_coords(im->screen, event->x,
event->y),
.point = sc_screen_convert_window_to_frame_coords(im->screen,
event->x,
event->y),
},
.action = sc_action_from_sdl_mousebutton_type(event->type),
.button = sc_mouse_button_from_sdl(event->button),
@@ -790,8 +719,8 @@ input_manager_process_mouse_button(struct input_manager *im,
((down && !im->vfinger_down && CTRL_PRESSED) ||
(!down && im->vfinger_down))) {
struct sc_point mouse =
screen_convert_window_to_frame_coords(im->screen, event->x,
event->y);
sc_screen_convert_window_to_frame_coords(im->screen, event->x,
event->y);
struct sc_point vfinger = inverse_point(mouse, im->screen->frame_size);
enum android_motionevent_action action = down
? AMOTION_EVENT_ACTION_DOWN
@@ -804,8 +733,8 @@ input_manager_process_mouse_button(struct input_manager *im,
}
static void
input_manager_process_mouse_wheel(struct input_manager *im,
const SDL_MouseWheelEvent *event) {
sc_input_manager_process_mouse_wheel(struct sc_input_manager *im,
const SDL_MouseWheelEvent *event) {
if (!im->mp->ops->process_mouse_scroll) {
// The mouse processor does not support scroll events
return;
@@ -819,8 +748,8 @@ input_manager_process_mouse_wheel(struct input_manager *im,
struct sc_mouse_scroll_event evt = {
.position = {
.screen_size = im->screen->frame_size,
.point = screen_convert_window_to_frame_coords(im->screen,
mouse_x, mouse_y),
.point = sc_screen_convert_window_to_frame_coords(im->screen,
mouse_x, mouse_y),
},
.hscroll = event->x,
.vscroll = event->y,
@@ -831,45 +760,81 @@ input_manager_process_mouse_wheel(struct input_manager *im,
im->mp->ops->process_mouse_scroll(im->mp, &evt);
}
bool
input_manager_handle_event(struct input_manager *im, SDL_Event *event) {
static bool
is_apk(const char *file) {
const char *ext = strrchr(file, '.');
return ext && !strcmp(ext, ".apk");
}
static void
sc_input_manager_process_file(struct sc_input_manager *im,
const SDL_DropEvent *event) {
char *file = strdup(event->file);
SDL_free(event->file);
if (!file) {
LOG_OOM();
return;
}
enum sc_file_pusher_action action;
if (is_apk(file)) {
action = SC_FILE_PUSHER_ACTION_INSTALL_APK;
} else {
action = SC_FILE_PUSHER_ACTION_PUSH_FILE;
}
bool ok = sc_file_pusher_request(im->fp, action, file);
if (!ok) {
free(file);
}
}
void
sc_input_manager_handle_event(struct sc_input_manager *im, SDL_Event *event) {
bool control = im->controller;
switch (event->type) {
case SDL_TEXTINPUT:
if (!im->control) {
return true;
if (!control) {
break;
}
input_manager_process_text_input(im, &event->text);
return true;
sc_input_manager_process_text_input(im, &event->text);
break;
case SDL_KEYDOWN:
case SDL_KEYUP:
// some key events do not interact with the device, so process the
// event even if control is disabled
input_manager_process_key(im, &event->key);
return true;
sc_input_manager_process_key(im, &event->key);
break;
case SDL_MOUSEMOTION:
if (!im->control) {
if (!control) {
break;
}
input_manager_process_mouse_motion(im, &event->motion);
return true;
sc_input_manager_process_mouse_motion(im, &event->motion);
break;
case SDL_MOUSEWHEEL:
if (!im->control) {
if (!control) {
break;
}
input_manager_process_mouse_wheel(im, &event->wheel);
return true;
sc_input_manager_process_mouse_wheel(im, &event->wheel);
break;
case SDL_MOUSEBUTTONDOWN:
case SDL_MOUSEBUTTONUP:
// some mouse events do not interact with the device, so process
// the event even if control is disabled
input_manager_process_mouse_button(im, &event->button);
return true;
sc_input_manager_process_mouse_button(im, &event->button);
break;
case SDL_FINGERMOTION:
case SDL_FINGERDOWN:
case SDL_FINGERUP:
input_manager_process_touch(im, &event->tfinger);
return true;
if (!control) {
break;
}
sc_input_manager_process_touch(im, &event->tfinger);
break;
case SDL_DROPFILE: {
if (!control) {
break;
}
sc_input_manager_process_file(im, &event->drop);
}
}
return false;
}

View File

@@ -8,19 +8,20 @@
#include <SDL2/SDL.h>
#include "controller.h"
#include "file_pusher.h"
#include "fps_counter.h"
#include "options.h"
#include "trait/key_processor.h"
#include "trait/mouse_processor.h"
struct input_manager {
struct controller *controller;
struct screen *screen;
struct sc_input_manager {
struct sc_controller *controller;
struct sc_file_pusher *fp;
struct sc_screen *screen;
struct sc_key_processor *kp;
struct sc_mouse_processor *mp;
bool control;
bool forward_all_clicks;
bool legacy_paste;
bool clipboard_autosync;
@@ -42,13 +43,13 @@ struct input_manager {
uint64_t next_sequence; // used for request acknowledgements
};
struct input_manager_params {
struct controller *controller;
struct screen *screen;
struct sc_input_manager_params {
struct sc_controller *controller;
struct sc_file_pusher *fp;
struct sc_screen *screen;
struct sc_key_processor *kp;
struct sc_mouse_processor *mp;
bool control;
bool forward_all_clicks;
bool legacy_paste;
bool clipboard_autosync;
@@ -56,10 +57,10 @@ struct input_manager_params {
};
void
input_manager_init(struct input_manager *im,
const struct input_manager_params *params);
sc_input_manager_init(struct sc_input_manager *im,
const struct sc_input_manager_params *params);
bool
input_manager_handle_event(struct input_manager *im, SDL_Event *event);
void
sc_input_manager_handle_event(struct sc_input_manager *im, SDL_Event *event);
#endif

View File

@@ -246,9 +246,9 @@ convert_meta_state(uint16_t mod) {
}
static bool
convert_input_key(const struct sc_key_event *event, struct control_msg *msg,
convert_input_key(const struct sc_key_event *event, struct sc_control_msg *msg,
enum sc_key_inject_mode key_inject_mode, uint32_t repeat) {
msg->type = CONTROL_MSG_TYPE_INJECT_KEYCODE;
msg->type = SC_CONTROL_MSG_TYPE_INJECT_KEYCODE;
if (!convert_keycode(event->keycode, &msg->inject_keycode.keycode,
event->mods_state, key_inject_mode)) {
@@ -282,9 +282,9 @@ sc_key_processor_process_key(struct sc_key_processor *kp,
ki->repeat = 0;
}
struct control_msg msg;
struct sc_control_msg msg;
if (convert_input_key(event, &msg, ki->key_inject_mode, ki->repeat)) {
if (!controller_push_msg(ki->controller, &msg)) {
if (!sc_controller_push_msg(ki->controller, &msg)) {
LOGW("Could not request 'inject keycode'");
}
}
@@ -309,14 +309,14 @@ sc_key_processor_process_text(struct sc_key_processor *kp,
}
}
struct control_msg msg;
msg.type = CONTROL_MSG_TYPE_INJECT_TEXT;
struct sc_control_msg msg;
msg.type = SC_CONTROL_MSG_TYPE_INJECT_TEXT;
msg.inject_text.text = strdup(event->text);
if (!msg.inject_text.text) {
LOGW("Could not strdup input text");
return;
}
if (!controller_push_msg(ki->controller, &msg)) {
if (!sc_controller_push_msg(ki->controller, &msg)) {
free(msg.inject_text.text);
LOGW("Could not request 'inject text'");
}
@@ -324,11 +324,12 @@ sc_key_processor_process_text(struct sc_key_processor *kp,
void
sc_keyboard_inject_init(struct sc_keyboard_inject *ki,
struct controller *controller,
const struct scrcpy_options *options) {
struct sc_controller *controller,
enum sc_key_inject_mode key_inject_mode,
bool forward_key_repeat) {
ki->controller = controller;
ki->key_inject_mode = options->key_inject_mode;
ki->forward_key_repeat = options->forward_key_repeat;
ki->key_inject_mode = key_inject_mode;
ki->forward_key_repeat = forward_key_repeat;
ki->repeat = 0;

View File

@@ -12,7 +12,7 @@
struct sc_keyboard_inject {
struct sc_key_processor key_processor; // key processor trait
struct controller *controller;
struct sc_controller *controller;
// SDL reports repeated events as a boolean, but Android expects the actual
// number of repetitions. This variable keeps track of the count.
@@ -24,7 +24,8 @@ struct sc_keyboard_inject {
void
sc_keyboard_inject_init(struct sc_keyboard_inject *ki,
struct controller *controller,
const struct scrcpy_options *options);
struct sc_controller *controller,
enum sc_key_inject_mode key_inject_mode,
bool forward_key_repeat);
#endif

View File

@@ -13,28 +13,27 @@
#include "cli.h"
#include "options.h"
#include "scrcpy.h"
#include "usb/scrcpy_otg.h"
#include "util/log.h"
static void
print_version(void) {
fprintf(stderr, "scrcpy %s\n\n", SCRCPY_VERSION);
fprintf(stderr, "dependencies:\n");
fprintf(stderr, " - SDL %d.%d.%d\n", SDL_MAJOR_VERSION, SDL_MINOR_VERSION,
SDL_PATCHLEVEL);
fprintf(stderr, " - libavcodec %d.%d.%d\n", LIBAVCODEC_VERSION_MAJOR,
LIBAVCODEC_VERSION_MINOR,
LIBAVCODEC_VERSION_MICRO);
fprintf(stderr, " - libavformat %d.%d.%d\n", LIBAVFORMAT_VERSION_MAJOR,
LIBAVFORMAT_VERSION_MINOR,
LIBAVFORMAT_VERSION_MICRO);
fprintf(stderr, " - libavutil %d.%d.%d\n", LIBAVUTIL_VERSION_MAJOR,
LIBAVUTIL_VERSION_MINOR,
LIBAVUTIL_VERSION_MICRO);
printf("\ndependencies:\n");
printf(" - SDL %d.%d.%d\n", SDL_MAJOR_VERSION, SDL_MINOR_VERSION,
SDL_PATCHLEVEL);
printf(" - libavcodec %d.%d.%d\n", LIBAVCODEC_VERSION_MAJOR,
LIBAVCODEC_VERSION_MINOR,
LIBAVCODEC_VERSION_MICRO);
printf(" - libavformat %d.%d.%d\n", LIBAVFORMAT_VERSION_MAJOR,
LIBAVFORMAT_VERSION_MINOR,
LIBAVFORMAT_VERSION_MICRO);
printf(" - libavutil %d.%d.%d\n", LIBAVUTIL_VERSION_MAJOR,
LIBAVUTIL_VERSION_MINOR,
LIBAVUTIL_VERSION_MICRO);
#ifdef HAVE_V4L2
fprintf(stderr, " - libavdevice %d.%d.%d\n", LIBAVDEVICE_VERSION_MAJOR,
LIBAVDEVICE_VERSION_MINOR,
LIBAVDEVICE_VERSION_MICRO);
printf(" - libavdevice %d.%d.%d\n", LIBAVDEVICE_VERSION_MAJOR,
LIBAVDEVICE_VERSION_MINOR,
LIBAVDEVICE_VERSION_MICRO);
#endif
}
@@ -90,9 +89,14 @@ main(int argc, char *argv[]) {
return 1;
}
int res = scrcpy(&args.opts) ? 0 : 1;
#ifdef HAVE_USB
bool ok = args.opts.otg ? scrcpy_otg(&args.opts)
: scrcpy(&args.opts);
#else
bool ok = scrcpy(&args.opts);
#endif
avformat_network_deinit(); // ignore failure
return res;
return ok ? 0 : 1;
}

View File

@@ -65,8 +65,8 @@ sc_mouse_processor_process_mouse_motion(struct sc_mouse_processor *mp,
struct sc_mouse_inject *mi = DOWNCAST(mp);
struct control_msg msg = {
.type = CONTROL_MSG_TYPE_INJECT_TOUCH_EVENT,
struct sc_control_msg msg = {
.type = SC_CONTROL_MSG_TYPE_INJECT_TOUCH_EVENT,
.inject_touch_event = {
.action = AMOTION_EVENT_ACTION_MOVE,
.pointer_id = POINTER_ID_MOUSE,
@@ -76,7 +76,7 @@ sc_mouse_processor_process_mouse_motion(struct sc_mouse_processor *mp,
},
};
if (!controller_push_msg(mi->controller, &msg)) {
if (!sc_controller_push_msg(mi->controller, &msg)) {
LOGW("Could not request 'inject mouse motion event'");
}
}
@@ -86,8 +86,8 @@ sc_mouse_processor_process_mouse_click(struct sc_mouse_processor *mp,
const struct sc_mouse_click_event *event) {
struct sc_mouse_inject *mi = DOWNCAST(mp);
struct control_msg msg = {
.type = CONTROL_MSG_TYPE_INJECT_TOUCH_EVENT,
struct sc_control_msg msg = {
.type = SC_CONTROL_MSG_TYPE_INJECT_TOUCH_EVENT,
.inject_touch_event = {
.action = convert_mouse_action(event->action),
.pointer_id = POINTER_ID_MOUSE,
@@ -97,7 +97,7 @@ sc_mouse_processor_process_mouse_click(struct sc_mouse_processor *mp,
},
};
if (!controller_push_msg(mi->controller, &msg)) {
if (!sc_controller_push_msg(mi->controller, &msg)) {
LOGW("Could not request 'inject mouse click event'");
}
}
@@ -107,8 +107,8 @@ sc_mouse_processor_process_mouse_scroll(struct sc_mouse_processor *mp,
const struct sc_mouse_scroll_event *event) {
struct sc_mouse_inject *mi = DOWNCAST(mp);
struct control_msg msg = {
.type = CONTROL_MSG_TYPE_INJECT_SCROLL_EVENT,
struct sc_control_msg msg = {
.type = SC_CONTROL_MSG_TYPE_INJECT_SCROLL_EVENT,
.inject_scroll_event = {
.position = event->position,
.hscroll = event->hscroll,
@@ -117,7 +117,7 @@ sc_mouse_processor_process_mouse_scroll(struct sc_mouse_processor *mp,
},
};
if (!controller_push_msg(mi->controller, &msg)) {
if (!sc_controller_push_msg(mi->controller, &msg)) {
LOGW("Could not request 'inject mouse scroll event'");
}
}
@@ -127,8 +127,8 @@ sc_mouse_processor_process_touch(struct sc_mouse_processor *mp,
const struct sc_touch_event *event) {
struct sc_mouse_inject *mi = DOWNCAST(mp);
struct control_msg msg = {
.type = CONTROL_MSG_TYPE_INJECT_TOUCH_EVENT,
struct sc_control_msg msg = {
.type = SC_CONTROL_MSG_TYPE_INJECT_TOUCH_EVENT,
.inject_touch_event = {
.action = convert_touch_action(event->action),
.pointer_id = event->pointer_id,
@@ -138,14 +138,14 @@ sc_mouse_processor_process_touch(struct sc_mouse_processor *mp,
},
};
if (!controller_push_msg(mi->controller, &msg)) {
if (!sc_controller_push_msg(mi->controller, &msg)) {
LOGW("Could not request 'inject touch event'");
}
}
void
sc_mouse_inject_init(struct sc_mouse_inject *mi,
struct controller *controller) {
struct sc_controller *controller) {
mi->controller = controller;
static const struct sc_mouse_processor_ops ops = {

View File

@@ -12,10 +12,11 @@
struct sc_mouse_inject {
struct sc_mouse_processor mouse_processor; // mouse processor trait
struct controller *controller;
struct sc_controller *controller;
};
void
sc_mouse_inject_init(struct sc_mouse_inject *mi, struct controller *controller);
sc_mouse_inject_init(struct sc_mouse_inject *mi,
struct sc_controller *controller);
#endif

View File

@@ -37,6 +37,9 @@ const struct scrcpy_options scrcpy_options_default = {
.display_id = 0,
.display_buffer = 0,
.v4l2_buffer = 0,
#ifdef HAVE_USB
.otg = false,
#endif
.show_touches = false,
.fullscreen = false,
.always_on_top = false,
@@ -54,6 +57,7 @@ const struct scrcpy_options scrcpy_options_default = {
.legacy_paste = false,
.power_off_on_close = false,
.clipboard_autosync = true,
.downsize_on_error = true,
.tcpip = false,
.tcpip_dst = NULL,
};

View File

@@ -112,6 +112,9 @@ struct scrcpy_options {
uint32_t display_id;
sc_tick display_buffer;
sc_tick v4l2_buffer;
#ifdef HAVE_USB
bool otg;
#endif
bool show_touches;
bool fullscreen;
bool always_on_top;
@@ -129,6 +132,7 @@ struct scrcpy_options {
bool legacy_paste;
bool power_off_on_close;
bool clipboard_autosync;
bool downsize_on_error;
bool tcpip;
const char *tcpip_dst;
};

View File

@@ -16,17 +16,19 @@
#include "controller.h"
#include "decoder.h"
#include "events.h"
#include "file_handler.h"
#ifdef HAVE_AOA_HID
# include "hid_keyboard.h"
# include "hid_mouse.h"
#endif
#include "file_pusher.h"
#include "keyboard_inject.h"
#include "mouse_inject.h"
#include "recorder.h"
#include "screen.h"
#include "server.h"
#include "stream.h"
#ifdef HAVE_USB
# include "usb/aoa_hid.h"
# include "usb/hid_keyboard.h"
# include "usb/hid_mouse.h"
# include "usb/usb.h"
#endif
#include "util/acksync.h"
#include "util/log.h"
#include "util/net.h"
@@ -36,29 +38,30 @@
struct scrcpy {
struct sc_server server;
struct screen screen;
struct sc_screen screen;
struct stream stream;
struct decoder decoder;
struct recorder recorder;
#ifdef HAVE_V4L2
struct sc_v4l2_sink v4l2_sink;
#endif
struct controller controller;
struct file_handler file_handler;
#ifdef HAVE_AOA_HID
struct sc_controller controller;
struct sc_file_pusher file_pusher;
#ifdef HAVE_USB
struct sc_usb usb;
struct sc_aoa aoa;
// sequence/ack helper to synchronize clipboard and Ctrl+v via HID
struct sc_acksync acksync;
#endif
union {
struct sc_keyboard_inject keyboard_inject;
#ifdef HAVE_AOA_HID
#ifdef HAVE_USB
struct sc_hid_keyboard keyboard_hid;
#endif
};
union {
struct sc_mouse_inject mouse_inject;
#ifdef HAVE_AOA_HID
#ifdef HAVE_USB
struct sc_hid_mouse mouse_hid;
#endif
};
@@ -149,68 +152,18 @@ sdl_configure(bool display, bool disable_screensaver) {
}
static bool
is_apk(const char *file) {
const char *ext = strrchr(file, '.');
return ext && !strcmp(ext, ".apk");
}
enum event_result {
EVENT_RESULT_CONTINUE,
EVENT_RESULT_STOPPED_BY_USER,
EVENT_RESULT_STOPPED_BY_EOS,
};
static enum event_result
handle_event(struct scrcpy *s, const struct scrcpy_options *options,
SDL_Event *event) {
switch (event->type) {
case EVENT_STREAM_STOPPED:
LOGD("Video stream stopped");
return EVENT_RESULT_STOPPED_BY_EOS;
case SDL_QUIT:
LOGD("User requested to quit");
return EVENT_RESULT_STOPPED_BY_USER;
case SDL_DROPFILE: {
if (!options->control) {
break;
}
char *file = strdup(event->drop.file);
SDL_free(event->drop.file);
if (!file) {
LOGW("Could not strdup drop filename\n");
break;
}
file_handler_action_t action;
if (is_apk(file)) {
action = ACTION_INSTALL_APK;
} else {
action = ACTION_PUSH_FILE;
}
file_handler_request(&s->file_handler, action, file);
goto end;
}
}
bool consumed = screen_handle_event(&s->screen, event);
(void) consumed;
end:
return EVENT_RESULT_CONTINUE;
}
static bool
event_loop(struct scrcpy *s, const struct scrcpy_options *options) {
event_loop(struct scrcpy *s) {
SDL_Event event;
while (SDL_WaitEvent(&event)) {
enum event_result result = handle_event(s, options, &event);
switch (result) {
case EVENT_RESULT_STOPPED_BY_USER:
return true;
case EVENT_RESULT_STOPPED_BY_EOS:
switch (event.type) {
case EVENT_STREAM_STOPPED:
LOGW("Device disconnected");
return false;
case EVENT_RESULT_CONTINUE:
case SDL_QUIT:
LOGD("User requested to quit");
return true;
default:
sc_screen_handle_event(&s->screen, &event);
break;
}
}
@@ -327,13 +280,13 @@ scrcpy(struct scrcpy_options *options) {
bool ret = false;
bool server_started = false;
bool file_handler_initialized = false;
bool file_pusher_initialized = false;
bool recorder_initialized = false;
#ifdef HAVE_V4L2
bool v4l2_sink_initialized = false;
#endif
bool stream_started = false;
#ifdef HAVE_AOA_HID
#ifdef HAVE_USB
bool aoa_hid_initialized = false;
bool hid_keyboard_initialized = false;
bool hid_mouse_initialized = false;
@@ -364,6 +317,7 @@ scrcpy(struct scrcpy_options *options) {
.force_adb_forward = options->force_adb_forward,
.power_off_on_close = options->power_off_on_close,
.clipboard_autosync = options->clipboard_autosync,
.downsize_on_error = options->downsize_on_error,
.tcpip = options->tcpip,
.tcpip_dst = options->tcpip_dst,
};
@@ -406,12 +360,15 @@ scrcpy(struct scrcpy_options *options) {
const char *serial = s->server.params.serial;
assert(serial);
struct sc_file_pusher *fp = NULL;
if (options->display && options->control) {
if (!file_handler_init(&s->file_handler, serial,
options->push_target)) {
if (!sc_file_pusher_init(&s->file_pusher, serial,
options->push_target)) {
goto end;
}
file_handler_initialized = true;
fp = &s->file_pusher;
file_pusher_initialized = true;
}
struct decoder *dec = NULL;
@@ -451,11 +408,12 @@ scrcpy(struct scrcpy_options *options) {
stream_add_sink(&s->stream, &rec->packet_sink);
}
struct sc_controller *controller = NULL;
struct sc_key_processor *kp = NULL;
struct sc_mouse_processor *mp = NULL;
if (options->control) {
#ifdef HAVE_AOA_HID
#ifdef HAVE_USB
bool use_hid_keyboard =
options->keyboard_input_mode == SC_KEYBOARD_INPUT_MODE_HID;
bool use_hid_mouse =
@@ -466,9 +424,52 @@ scrcpy(struct scrcpy_options *options) {
goto end;
}
ok = sc_aoa_init(&s->aoa, serial, &s->acksync);
ok = sc_usb_init(&s->usb);
if (!ok) {
LOGE("Failed to initialize USB");
sc_acksync_destroy(&s->acksync);
goto aoa_hid_end;
}
assert(serial);
struct sc_usb_device usb_devices[16];
ssize_t count = sc_usb_find_devices(&s->usb, serial, usb_devices,
ARRAY_LEN(usb_devices));
if (count <= 0) {
LOGE("Could not find USB device %s", serial);
sc_usb_destroy(&s->usb);
sc_acksync_destroy(&s->acksync);
goto aoa_hid_end;
}
if (count > 1) {
LOGE("Multiple (%d) devices with serial %s", (int) count, serial);
sc_usb_device_destroy_all(usb_devices, count);
sc_usb_destroy(&s->usb);
sc_acksync_destroy(&s->acksync);
goto aoa_hid_end;
}
struct sc_usb_device *usb_device = &usb_devices[0];
LOGI("USB device: %s (%04" PRIx16 ":%04" PRIx16 ") %s %s",
usb_device->serial, usb_device->vid, usb_device->pid,
usb_device->manufacturer, usb_device->product);
ok = sc_usb_connect(&s->usb, usb_device->device, NULL, NULL);
sc_usb_device_destroy(usb_device);
if (!ok) {
LOGE("Failed to connect to USB device %s", serial);
sc_usb_destroy(&s->usb);
sc_acksync_destroy(&s->acksync);
goto aoa_hid_end;
}
ok = sc_aoa_init(&s->aoa, &s->usb, &s->acksync);
if (!ok) {
LOGE("Failed to enable HID over AOA");
sc_usb_disconnect(&s->usb);
sc_usb_destroy(&s->usb);
sc_acksync_destroy(&s->acksync);
goto aoa_hid_end;
}
@@ -495,6 +496,8 @@ scrcpy(struct scrcpy_options *options) {
if (!need_aoa || !sc_aoa_start(&s->aoa)) {
sc_acksync_destroy(&s->acksync);
sc_usb_disconnect(&s->usb);
sc_usb_destroy(&s->usb);
sc_aoa_destroy(&s->aoa);
goto aoa_hid_end;
}
@@ -535,7 +538,8 @@ aoa_hid_end:
// keyboard_input_mode may have been reset if HID mode failed
if (options->keyboard_input_mode == SC_KEYBOARD_INPUT_MODE_INJECT) {
sc_keyboard_inject_init(&s->keyboard_inject, &s->controller,
options);
options->key_inject_mode,
options->forward_key_repeat);
kp = &s->keyboard_inject.key_processor;
}
@@ -545,38 +549,42 @@ aoa_hid_end:
mp = &s->mouse_inject.mouse_processor;
}
if (!controller_init(&s->controller, s->server.control_socket,
acksync)) {
if (!sc_controller_init(&s->controller, s->server.control_socket,
acksync)) {
goto end;
}
controller_initialized = true;
if (!controller_start(&s->controller)) {
if (!sc_controller_start(&s->controller)) {
goto end;
}
controller_started = true;
controller = &s->controller;
if (options->turn_screen_off) {
struct control_msg msg;
msg.type = CONTROL_MSG_TYPE_SET_SCREEN_POWER_MODE;
msg.set_screen_power_mode.mode = SCREEN_POWER_MODE_OFF;
struct sc_control_msg msg;
msg.type = SC_CONTROL_MSG_TYPE_SET_SCREEN_POWER_MODE;
msg.set_screen_power_mode.mode = SC_SCREEN_POWER_MODE_OFF;
if (!controller_push_msg(&s->controller, &msg)) {
if (!sc_controller_push_msg(&s->controller, &msg)) {
LOGW("Could not request 'set screen power mode'");
}
}
}
// There is a controller if and only if control is enabled
assert(options->control == !!controller);
if (options->display) {
const char *window_title =
options->window_title ? options->window_title : info->device_name;
struct screen_params screen_params = {
.controller = &s->controller,
struct sc_screen_params screen_params = {
.controller = controller,
.fp = fp,
.kp = kp,
.mp = mp,
.control = options->control,
.forward_all_clicks = options->forward_all_clicks,
.legacy_paste = options->legacy_paste,
.clipboard_autosync = options->clipboard_autosync,
@@ -595,7 +603,7 @@ aoa_hid_end:
.buffering_time = options->display_buffer,
};
if (!screen_init(&s->screen, &screen_params)) {
if (!sc_screen_init(&s->screen, &screen_params)) {
goto end;
}
screen_initialized = true;
@@ -623,35 +631,39 @@ aoa_hid_end:
}
stream_started = true;
ret = event_loop(s, options);
ret = event_loop(s);
LOGD("quit...");
// Close the window immediately on closing, because screen_destroy() may
// only be called once the stream thread is joined (it may take time)
screen_hide_window(&s->screen);
sc_screen_hide_window(&s->screen);
end:
// The stream is not stopped explicitly, because it will stop by itself on
// end-of-stream
#ifdef HAVE_AOA_HID
#ifdef HAVE_USB
if (aoa_hid_initialized) {
if (hid_keyboard_initialized) {
sc_hid_keyboard_destroy(&s->keyboard_hid);
}
if (hid_mouse_initialized) {
sc_hid_mouse_destroy(&s->mouse_hid);
}
sc_aoa_stop(&s->aoa);
sc_usb_stop(&s->usb);
}
if (acksync) {
sc_acksync_destroy(acksync);
}
#endif
if (controller_started) {
controller_stop(&s->controller);
sc_controller_stop(&s->controller);
}
if (file_handler_initialized) {
file_handler_stop(&s->file_handler);
if (file_pusher_initialized) {
sc_file_pusher_stop(&s->file_pusher);
}
if (screen_initialized) {
screen_interrupt(&s->screen);
sc_screen_interrupt(&s->screen);
}
if (server_started) {
@@ -671,34 +683,37 @@ end:
}
#endif
#ifdef HAVE_AOA_HID
#ifdef HAVE_USB
if (aoa_hid_initialized) {
sc_aoa_join(&s->aoa);
sc_aoa_destroy(&s->aoa);
sc_usb_join(&s->usb);
sc_usb_disconnect(&s->usb);
sc_usb_destroy(&s->usb);
}
#endif
// Destroy the screen only after the stream is guaranteed to be finished,
// because otherwise the screen could receive new frames after destruction
if (screen_initialized) {
screen_join(&s->screen);
screen_destroy(&s->screen);
sc_screen_join(&s->screen);
sc_screen_destroy(&s->screen);
}
if (controller_started) {
controller_join(&s->controller);
sc_controller_join(&s->controller);
}
if (controller_initialized) {
controller_destroy(&s->controller);
sc_controller_destroy(&s->controller);
}
if (recorder_initialized) {
recorder_destroy(&s->recorder);
}
if (file_handler_initialized) {
file_handler_join(&s->file_handler);
file_handler_destroy(&s->file_handler);
if (file_pusher_initialized) {
sc_file_pusher_join(&s->file_pusher);
sc_file_pusher_destroy(&s->file_pusher);
}
sc_server_destroy(&s->server);

View File

@@ -12,7 +12,7 @@
#define DISPLAY_MARGINS 96
#define DOWNCAST(SINK) container_of(SINK, struct screen, frame_sink)
#define DOWNCAST(SINK) container_of(SINK, struct sc_screen, frame_sink)
static inline struct sc_size
get_rotated_size(struct sc_size size, int rotation) {
@@ -29,7 +29,7 @@ get_rotated_size(struct sc_size size, int rotation) {
// get the window size in a struct sc_size
static struct sc_size
get_window_size(const struct screen *screen) {
get_window_size(const struct sc_screen *screen) {
int width;
int height;
SDL_GetWindowSize(screen->window, &width, &height);
@@ -41,7 +41,7 @@ get_window_size(const struct screen *screen) {
}
static struct sc_point
get_window_position(const struct screen *screen) {
get_window_position(const struct sc_screen *screen) {
int x;
int y;
SDL_GetWindowPosition(screen->window, &x, &y);
@@ -54,7 +54,7 @@ get_window_position(const struct screen *screen) {
// set the window size to be applied when fullscreen is disabled
static void
set_window_size(struct screen *screen, struct sc_size new_size) {
set_window_size(struct sc_screen *screen, struct sc_size new_size) {
assert(!screen->fullscreen);
assert(!screen->maximized);
SDL_SetWindowSize(screen->window, new_size.width, new_size.height);
@@ -156,8 +156,14 @@ get_initial_optimal_size(struct sc_size content_size, uint16_t req_width,
return window_size;
}
static inline void
screen_capture_mouse(struct screen *screen, bool capture) {
static inline bool
sc_screen_is_relative_mode(struct sc_screen *screen) {
// screen->im.mp may be NULL if --no-control
return screen->im.mp && screen->im.mp->relative_mode;
}
static void
sc_screen_capture_mouse(struct sc_screen *screen, bool capture) {
if (SDL_SetRelativeMouseMode(capture)) {
LOGE("Could not set relative mouse mode to %s: %s",
capture ? "true" : "false", SDL_GetError());
@@ -168,7 +174,7 @@ screen_capture_mouse(struct screen *screen, bool capture) {
}
static void
screen_update_content_rect(struct screen *screen) {
sc_screen_update_content_rect(struct sc_screen *screen) {
int dw;
int dh;
SDL_GL_GetDrawableSize(screen->window, &dw, &dh);
@@ -205,7 +211,7 @@ screen_update_content_rect(struct screen *screen) {
}
static inline SDL_Texture *
create_texture(struct screen *screen) {
create_texture(struct sc_screen *screen) {
SDL_Renderer *renderer = screen->renderer;
struct sc_size size = screen->frame_size;
SDL_Texture *texture = SDL_CreateTexture(renderer, SDL_PIXELFORMAT_YV12,
@@ -236,9 +242,9 @@ create_texture(struct screen *screen) {
// Set the update_content_rect flag if the window or content size may have
// changed, so that the content rectangle is recomputed
static void
screen_render(struct screen *screen, bool update_content_rect) {
sc_screen_render(struct sc_screen *screen, bool update_content_rect) {
if (update_content_rect) {
screen_update_content_rect(screen);
sc_screen_update_content_rect(screen);
}
SDL_RenderClear(screen->renderer);
@@ -282,20 +288,20 @@ screen_render(struct screen *screen, bool update_content_rect) {
// <https://stackoverflow.com/a/40693139/1987178>
static int
event_watcher(void *data, SDL_Event *event) {
struct screen *screen = data;
struct sc_screen *screen = data;
if (event->type == SDL_WINDOWEVENT
&& event->window.event == SDL_WINDOWEVENT_RESIZED) {
// In practice, it seems to always be called from the same thread in
// that specific case. Anyway, it's just a workaround.
screen_render(screen, true);
sc_screen_render(screen, true);
}
return 0;
}
#endif
static bool
screen_frame_sink_open(struct sc_frame_sink *sink) {
struct screen *screen = DOWNCAST(sink);
sc_screen_frame_sink_open(struct sc_frame_sink *sink) {
struct sc_screen *screen = DOWNCAST(sink);
(void) screen;
#ifndef NDEBUG
screen->open = true;
@@ -306,8 +312,8 @@ screen_frame_sink_open(struct sc_frame_sink *sink) {
}
static void
screen_frame_sink_close(struct sc_frame_sink *sink) {
struct screen *screen = DOWNCAST(sink);
sc_screen_frame_sink_close(struct sc_frame_sink *sink) {
struct sc_screen *screen = DOWNCAST(sink);
(void) screen;
#ifndef NDEBUG
screen->open = false;
@@ -317,8 +323,8 @@ screen_frame_sink_close(struct sc_frame_sink *sink) {
}
static bool
screen_frame_sink_push(struct sc_frame_sink *sink, const AVFrame *frame) {
struct screen *screen = DOWNCAST(sink);
sc_screen_frame_sink_push(struct sc_frame_sink *sink, const AVFrame *frame) {
struct sc_screen *screen = DOWNCAST(sink);
return sc_video_buffer_push(&screen->vb, frame);
}
@@ -326,7 +332,7 @@ static void
sc_video_buffer_on_new_frame(struct sc_video_buffer *vb, bool previous_skipped,
void *userdata) {
(void) vb;
struct screen *screen = userdata;
struct sc_screen *screen = userdata;
// event_failed implies previous_skipped (the previous frame may not have
// been consumed if the event was not sent)
@@ -359,7 +365,8 @@ sc_video_buffer_on_new_frame(struct sc_video_buffer *vb, bool previous_skipped,
}
bool
screen_init(struct screen *screen, const struct screen_params *params) {
sc_screen_init(struct sc_screen *screen,
const struct sc_screen_params *params) {
screen->resize_pending = false;
screen->has_frame = false;
screen->fullscreen = false;
@@ -368,6 +375,12 @@ screen_init(struct screen *screen, const struct screen_params *params) {
screen->mouse_captured = false;
screen->mouse_capture_key_pressed = 0;
screen->req.x = params->window_x;
screen->req.y = params->window_y;
screen->req.width = params->window_width;
screen->req.height = params->window_height;
screen->req.fullscreen = params->fullscreen;
static const struct sc_video_buffer_callbacks cbs = {
.on_new_frame = sc_video_buffer_on_new_frame,
};
@@ -396,9 +409,6 @@ screen_init(struct screen *screen, const struct screen_params *params) {
get_rotated_size(screen->frame_size, screen->rotation);
screen->content_size = content_size;
struct sc_size window_size =
get_initial_optimal_size(content_size,params->window_width,
params->window_height);
uint32_t window_flags = SDL_WINDOW_HIDDEN
| SDL_WINDOW_RESIZABLE
| SDL_WINDOW_ALLOW_HIGHDPI;
@@ -409,13 +419,9 @@ screen_init(struct screen *screen, const struct screen_params *params) {
window_flags |= SDL_WINDOW_BORDERLESS;
}
int x = params->window_x != SC_WINDOW_POSITION_UNDEFINED
? params->window_x : (int) SDL_WINDOWPOS_UNDEFINED;
int y = params->window_y != SC_WINDOW_POSITION_UNDEFINED
? params->window_y : (int) SDL_WINDOWPOS_UNDEFINED;
screen->window = SDL_CreateWindow(params->window_title, x, y,
window_size.width, window_size.height,
window_flags);
// The window will be positioned and sized on first video frame
screen->window =
SDL_CreateWindow(params->window_title, 0, 0, 0, 0, window_flags);
if (!screen->window) {
LOGC("Could not create window: %s", SDL_GetError());
goto error_destroy_fps_counter;
@@ -483,39 +489,28 @@ screen_init(struct screen *screen, const struct screen_params *params) {
goto error_destroy_texture;
}
struct input_manager_params im_params = {
struct sc_input_manager_params im_params = {
.controller = params->controller,
.fp = params->fp,
.screen = screen,
.kp = params->kp,
.mp = params->mp,
.control = params->control,
.forward_all_clicks = params->forward_all_clicks,
.legacy_paste = params->legacy_paste,
.clipboard_autosync = params->clipboard_autosync,
.shortcut_mods = params->shortcut_mods,
};
input_manager_init(&screen->im, &im_params);
// Reset the window size to trigger a SIZE_CHANGED event, to workaround
// HiDPI issues with some SDL renderers when several displays having
// different HiDPI scaling are connected
SDL_SetWindowSize(screen->window, window_size.width, window_size.height);
screen_update_content_rect(screen);
if (params->fullscreen) {
screen_switch_fullscreen(screen);
}
sc_input_manager_init(&screen->im, &im_params);
#ifdef CONTINUOUS_RESIZING_WORKAROUND
SDL_AddEventWatch(event_watcher, screen);
#endif
static const struct sc_frame_sink_ops ops = {
.open = screen_frame_sink_open,
.close = screen_frame_sink_close,
.push = screen_frame_sink_push,
.open = sc_screen_frame_sink_open,
.close = sc_screen_frame_sink_close,
.push = sc_screen_frame_sink_push,
};
screen->frame_sink.ops = &ops;
@@ -544,29 +539,45 @@ error_destroy_video_buffer:
}
static void
screen_show_window(struct screen *screen) {
sc_screen_show_initial_window(struct sc_screen *screen) {
int x = screen->req.x != SC_WINDOW_POSITION_UNDEFINED
? screen->req.x : (int) SDL_WINDOWPOS_CENTERED;
int y = screen->req.y != SC_WINDOW_POSITION_UNDEFINED
? screen->req.y : (int) SDL_WINDOWPOS_CENTERED;
struct sc_size window_size =
get_initial_optimal_size(screen->content_size, screen->req.width,
screen->req.height);
set_window_size(screen, window_size);
SDL_SetWindowPosition(screen->window, x, y);
if (screen->req.fullscreen) {
sc_screen_switch_fullscreen(screen);
}
SDL_ShowWindow(screen->window);
}
void
screen_hide_window(struct screen *screen) {
sc_screen_hide_window(struct sc_screen *screen) {
SDL_HideWindow(screen->window);
}
void
screen_interrupt(struct screen *screen) {
sc_screen_interrupt(struct sc_screen *screen) {
sc_video_buffer_stop(&screen->vb);
fps_counter_interrupt(&screen->fps_counter);
}
void
screen_join(struct screen *screen) {
sc_screen_join(struct sc_screen *screen) {
sc_video_buffer_join(&screen->vb);
fps_counter_join(&screen->fps_counter);
}
void
screen_destroy(struct screen *screen) {
sc_screen_destroy(struct sc_screen *screen) {
#ifndef NDEBUG
assert(!screen->open);
#endif
@@ -579,7 +590,7 @@ screen_destroy(struct screen *screen) {
}
static void
resize_for_content(struct screen *screen, struct sc_size old_content_size,
resize_for_content(struct sc_screen *screen, struct sc_size old_content_size,
struct sc_size new_content_size) {
struct sc_size window_size = get_window_size(screen);
struct sc_size target_size = {
@@ -593,7 +604,7 @@ resize_for_content(struct screen *screen, struct sc_size old_content_size,
}
static void
set_content_size(struct screen *screen, struct sc_size new_content_size) {
set_content_size(struct sc_screen *screen, struct sc_size new_content_size) {
if (!screen->fullscreen && !screen->maximized) {
resize_for_content(screen, screen->content_size, new_content_size);
} else if (!screen->resize_pending) {
@@ -607,7 +618,7 @@ set_content_size(struct screen *screen, struct sc_size new_content_size) {
}
static void
apply_pending_resize(struct screen *screen) {
apply_pending_resize(struct sc_screen *screen) {
assert(!screen->fullscreen);
assert(!screen->maximized);
if (screen->resize_pending) {
@@ -618,7 +629,7 @@ apply_pending_resize(struct screen *screen) {
}
void
screen_set_rotation(struct screen *screen, unsigned rotation) {
sc_screen_set_rotation(struct sc_screen *screen, unsigned rotation) {
assert(rotation < 4);
if (rotation == screen->rotation) {
return;
@@ -632,12 +643,12 @@ screen_set_rotation(struct screen *screen, unsigned rotation) {
screen->rotation = rotation;
LOGI("Display rotation set to %u", rotation);
screen_render(screen, true);
sc_screen_render(screen, true);
}
// recreate the texture and resize the window if the frame size has changed
static bool
prepare_for_frame(struct screen *screen, struct sc_size new_frame_size) {
prepare_for_frame(struct sc_screen *screen, struct sc_size new_frame_size) {
if (screen->frame_size.width != new_frame_size.width
|| screen->frame_size.height != new_frame_size.height) {
// frame dimension changed, destroy texture
@@ -649,7 +660,7 @@ prepare_for_frame(struct screen *screen, struct sc_size new_frame_size) {
get_rotated_size(new_frame_size, screen->rotation);
set_content_size(screen, new_content_size);
screen_update_content_rect(screen);
sc_screen_update_content_rect(screen);
LOGI("New texture: %" PRIu16 "x%" PRIu16,
screen->frame_size.width, screen->frame_size.height);
@@ -665,7 +676,7 @@ prepare_for_frame(struct screen *screen, struct sc_size new_frame_size) {
// write the frame into the texture
static void
update_texture(struct screen *screen, const AVFrame *frame) {
update_texture(struct sc_screen *screen, const AVFrame *frame) {
SDL_UpdateYUVTexture(screen->texture, NULL,
frame->data[0], frame->linesize[0],
frame->data[1], frame->linesize[1],
@@ -679,7 +690,7 @@ update_texture(struct screen *screen, const AVFrame *frame) {
}
static bool
screen_update_frame(struct screen *screen) {
sc_screen_update_frame(struct sc_screen *screen) {
av_frame_unref(screen->frame);
sc_video_buffer_consume(&screen->vb, screen->frame);
AVFrame *frame = screen->frame;
@@ -692,12 +703,23 @@ screen_update_frame(struct screen *screen) {
}
update_texture(screen, frame);
screen_render(screen, false);
if (!screen->has_frame) {
screen->has_frame = true;
// this is the very first frame, show the window
sc_screen_show_initial_window(screen);
if (sc_screen_is_relative_mode(screen)) {
// Capture mouse on start
sc_screen_capture_mouse(screen, true);
}
}
sc_screen_render(screen, false);
return true;
}
void
screen_switch_fullscreen(struct screen *screen) {
sc_screen_switch_fullscreen(struct sc_screen *screen) {
uint32_t new_mode = screen->fullscreen ? 0 : SDL_WINDOW_FULLSCREEN_DESKTOP;
if (SDL_SetWindowFullscreen(screen->window, new_mode)) {
LOGW("Could not switch fullscreen mode: %s", SDL_GetError());
@@ -710,11 +732,11 @@ screen_switch_fullscreen(struct screen *screen) {
}
LOGD("Switched to %s mode", screen->fullscreen ? "fullscreen" : "windowed");
screen_render(screen, true);
sc_screen_render(screen, true);
}
void
screen_resize_to_fit(struct screen *screen) {
sc_screen_resize_to_fit(struct sc_screen *screen) {
if (screen->fullscreen || screen->maximized) {
return;
}
@@ -738,7 +760,7 @@ screen_resize_to_fit(struct screen *screen) {
}
void
screen_resize_to_pixel_perfect(struct screen *screen) {
sc_screen_resize_to_pixel_perfect(struct sc_screen *screen) {
if (screen->fullscreen) {
return;
}
@@ -755,35 +777,33 @@ screen_resize_to_pixel_perfect(struct screen *screen) {
}
static inline bool
screen_is_mouse_capture_key(SDL_Keycode key) {
sc_screen_is_mouse_capture_key(SDL_Keycode key) {
return key == SDLK_LALT || key == SDLK_LGUI || key == SDLK_RGUI;
}
bool
screen_handle_event(struct screen *screen, SDL_Event *event) {
void
sc_screen_handle_event(struct sc_screen *screen, SDL_Event *event) {
bool relative_mode = sc_screen_is_relative_mode(screen);
switch (event->type) {
case EVENT_NEW_FRAME:
if (!screen->has_frame) {
screen->has_frame = true;
// this is the very first frame, show the window
screen_show_window(screen);
}
bool ok = screen_update_frame(screen);
case EVENT_NEW_FRAME: {
bool ok = sc_screen_update_frame(screen);
if (!ok) {
LOGW("Frame update failed\n");
}
return true;
return;
}
case SDL_WINDOWEVENT:
if (!screen->has_frame) {
// Do nothing
return true;
return;
}
switch (event->window.event) {
case SDL_WINDOWEVENT_EXPOSED:
screen_render(screen, true);
sc_screen_render(screen, true);
break;
case SDL_WINDOWEVENT_SIZE_CHANGED:
screen_render(screen, true);
sc_screen_render(screen, true);
break;
case SDL_WINDOWEVENT_MAXIMIZED:
screen->maximized = true;
@@ -799,78 +819,80 @@ screen_handle_event(struct screen *screen, SDL_Event *event) {
}
screen->maximized = false;
apply_pending_resize(screen);
screen_render(screen, true);
sc_screen_render(screen, true);
break;
case SDL_WINDOWEVENT_FOCUS_LOST:
if (screen->im.mp->relative_mode) {
screen_capture_mouse(screen, false);
if (relative_mode) {
sc_screen_capture_mouse(screen, false);
}
break;
}
return true;
return;
case SDL_KEYDOWN:
if (screen->im.mp->relative_mode) {
if (relative_mode) {
SDL_Keycode key = event->key.keysym.sym;
if (screen_is_mouse_capture_key(key)) {
if (sc_screen_is_mouse_capture_key(key)) {
if (!screen->mouse_capture_key_pressed) {
screen->mouse_capture_key_pressed = key;
return true;
} else {
// Another mouse capture key has been pressed, cancel
// mouse (un)capture
screen->mouse_capture_key_pressed = 0;
// Do not return, the event must be forwarded to the
// input manager
}
// Mouse capture keys are never forwarded to the device
return;
}
}
break;
case SDL_KEYUP:
if (screen->im.mp->relative_mode) {
if (relative_mode) {
SDL_Keycode key = event->key.keysym.sym;
SDL_Keycode cap = screen->mouse_capture_key_pressed;
screen->mouse_capture_key_pressed = 0;
if (key == cap) {
// A mouse capture key has been pressed then released:
// toggle the capture mouse mode
screen_capture_mouse(screen, !screen->mouse_captured);
return true;
if (sc_screen_is_mouse_capture_key(key)) {
if (key == cap) {
// A mouse capture key has been pressed then released:
// toggle the capture mouse mode
sc_screen_capture_mouse(screen,
!screen->mouse_captured);
}
// Mouse capture keys are never forwarded to the device
return;
}
// Do not return, the event must be forwarded to the input
// manager
}
break;
case SDL_MOUSEWHEEL:
case SDL_MOUSEMOTION:
case SDL_MOUSEBUTTONDOWN:
if (screen->im.mp->relative_mode && !screen->mouse_captured) {
if (relative_mode && !screen->mouse_captured) {
// Do not forward to input manager, the mouse will be captured
// on SDL_MOUSEBUTTONUP
return true;
return;
}
break;
case SDL_FINGERMOTION:
case SDL_FINGERDOWN:
case SDL_FINGERUP:
if (screen->im.mp->relative_mode) {
if (relative_mode) {
// Touch events are not compatible with relative mode
// (coordinates are not relative)
return true;
return;
}
break;
case SDL_MOUSEBUTTONUP:
if (screen->im.mp->relative_mode && !screen->mouse_captured) {
screen_capture_mouse(screen, true);
return true;
if (relative_mode && !screen->mouse_captured) {
sc_screen_capture_mouse(screen, true);
return;
}
break;
}
return input_manager_handle_event(&screen->im, event);
sc_input_manager_handle_event(&screen->im, event);
}
struct sc_point
screen_convert_drawable_to_frame_coords(struct screen *screen,
int32_t x, int32_t y) {
sc_screen_convert_drawable_to_frame_coords(struct sc_screen *screen,
int32_t x, int32_t y) {
unsigned rotation = screen->rotation;
assert(rotation < 4);
@@ -906,14 +928,14 @@ screen_convert_drawable_to_frame_coords(struct screen *screen,
}
struct sc_point
screen_convert_window_to_frame_coords(struct screen *screen,
int32_t x, int32_t y) {
screen_hidpi_scale_coords(screen, &x, &y);
return screen_convert_drawable_to_frame_coords(screen, x, y);
sc_screen_convert_window_to_frame_coords(struct sc_screen *screen,
int32_t x, int32_t y) {
sc_screen_hidpi_scale_coords(screen, &x, &y);
return sc_screen_convert_drawable_to_frame_coords(screen, x, y);
}
void
screen_hidpi_scale_coords(struct screen *screen, int32_t *x, int32_t *y) {
sc_screen_hidpi_scale_coords(struct sc_screen *screen, int32_t *x, int32_t *y) {
// take the HiDPI scaling (dw/ww and dh/wh) into account
int ww, wh, dw, dh;
SDL_GetWindowSize(screen->window, &ww, &wh);

View File

@@ -17,17 +17,26 @@
#include "trait/mouse_processor.h"
#include "video_buffer.h"
struct screen {
struct sc_screen {
struct sc_frame_sink frame_sink; // frame sink trait
#ifndef NDEBUG
bool open; // track the open/close state to assert correct behavior
#endif
struct input_manager im;
struct sc_input_manager im;
struct sc_video_buffer vb;
struct fps_counter fps_counter;
// The initial requested window properties
struct {
int16_t x;
int16_t y;
uint16_t width;
uint16_t height;
bool fullscreen;
} req;
SDL_Window *window;
SDL_Renderer *renderer;
SDL_Texture *texture;
@@ -59,12 +68,12 @@ struct screen {
AVFrame *frame;
};
struct screen_params {
struct controller *controller;
struct sc_screen_params {
struct sc_controller *controller;
struct sc_file_pusher *fp;
struct sc_key_processor *kp;
struct sc_mouse_processor *mp;
bool control;
bool forward_all_clicks;
bool legacy_paste;
bool clipboard_autosync;
@@ -74,10 +83,10 @@ struct screen_params {
struct sc_size frame_size;
bool always_on_top;
int16_t window_x;
int16_t window_y;
uint16_t window_width; // accepts SC_WINDOW_POSITION_UNDEFINED
uint16_t window_height; // accepts SC_WINDOW_POSITION_UNDEFINED
int16_t window_x; // accepts SC_WINDOW_POSITION_UNDEFINED
int16_t window_y; // accepts SC_WINDOW_POSITION_UNDEFINED
uint16_t window_width;
uint16_t window_height;
bool window_borderless;
@@ -91,65 +100,65 @@ struct screen_params {
// initialize screen, create window, renderer and texture (window is hidden)
bool
screen_init(struct screen *screen, const struct screen_params *params);
sc_screen_init(struct sc_screen *screen, const struct sc_screen_params *params);
// request to interrupt any inner thread
// must be called before screen_join()
void
screen_interrupt(struct screen *screen);
sc_screen_interrupt(struct sc_screen *screen);
// join any inner thread
void
screen_join(struct screen *screen);
sc_screen_join(struct sc_screen *screen);
// destroy window, renderer and texture (if any)
void
screen_destroy(struct screen *screen);
sc_screen_destroy(struct sc_screen *screen);
// hide the window
//
// It is used to hide the window immediately on closing without waiting for
// screen_destroy()
void
screen_hide_window(struct screen *screen);
sc_screen_hide_window(struct sc_screen *screen);
// switch the fullscreen mode
void
screen_switch_fullscreen(struct screen *screen);
sc_screen_switch_fullscreen(struct sc_screen *screen);
// resize window to optimal size (remove black borders)
void
screen_resize_to_fit(struct screen *screen);
sc_screen_resize_to_fit(struct sc_screen *screen);
// resize window to 1:1 (pixel-perfect)
void
screen_resize_to_pixel_perfect(struct screen *screen);
sc_screen_resize_to_pixel_perfect(struct sc_screen *screen);
// set the display rotation (0, 1, 2 or 3, x90 degrees counterclockwise)
void
screen_set_rotation(struct screen *screen, unsigned rotation);
sc_screen_set_rotation(struct sc_screen *screen, unsigned rotation);
// react to SDL events
bool
screen_handle_event(struct screen *screen, SDL_Event *event);
void
sc_screen_handle_event(struct sc_screen *screen, SDL_Event *event);
// convert point from window coordinates to frame coordinates
// x and y are expressed in pixels
struct sc_point
screen_convert_window_to_frame_coords(struct screen *screen,
int32_t x, int32_t y);
sc_screen_convert_window_to_frame_coords(struct sc_screen *screen,
int32_t x, int32_t y);
// convert point from drawable coordinates to frame coordinates
// x and y are expressed in pixels
struct sc_point
screen_convert_drawable_to_frame_coords(struct screen *screen,
int32_t x, int32_t y);
sc_screen_convert_drawable_to_frame_coords(struct sc_screen *screen,
int32_t x, int32_t y);
// Convert coordinates from window to drawable.
// Events are expressed in window coordinates, but content is expressed in
// drawable coordinates. They are the same if HiDPI scaling is 1, but differ
// otherwise.
void
screen_hidpi_scale_coords(struct screen *screen, int32_t *x, int32_t *y);
sc_screen_hidpi_scale_coords(struct sc_screen *screen, int32_t *x, int32_t *y);
#endif

View File

@@ -188,7 +188,6 @@ execute_server(struct sc_server *server,
} \
cmd[count++] = p; \
}
#define STRBOOL(v) (v ? "true" : "false")
ADD_PARAM("log_level=%s", log_level_to_server_string(params->log_level));
ADD_PARAM("bit_rate=%" PRIu32, params->bit_rate);
@@ -204,23 +203,23 @@ execute_server(struct sc_server *server,
params->lock_video_orientation);
}
if (server->tunnel.forward) {
ADD_PARAM("tunnel_forward=%s", STRBOOL(server->tunnel.forward));
ADD_PARAM("tunnel_forward=true");
}
if (params->crop) {
ADD_PARAM("crop=%s", params->crop);
}
if (!params->control) {
// By default, control is true
ADD_PARAM("control=%s", STRBOOL(params->control));
ADD_PARAM("control=false");
}
if (params->display_id) {
ADD_PARAM("display_id=%" PRIu32, params->display_id);
}
if (params->show_touches) {
ADD_PARAM("show_touches=%s", STRBOOL(params->show_touches));
ADD_PARAM("show_touches=true");
}
if (params->stay_awake) {
ADD_PARAM("stay_awake=%s", STRBOOL(params->stay_awake));
ADD_PARAM("stay_awake=true");
}
if (params->codec_options) {
ADD_PARAM("codec_options=%s", params->codec_options);
@@ -229,11 +228,15 @@ execute_server(struct sc_server *server,
ADD_PARAM("encoder_name=%s", params->encoder_name);
}
if (params->power_off_on_close) {
ADD_PARAM("power_off_on_close=%s", STRBOOL(params->power_off_on_close));
ADD_PARAM("power_off_on_close=true");
}
if (!params->clipboard_autosync) {
// By default, clipboard_autosync is true
ADD_PARAM("clipboard_autosync=%s", STRBOOL(params->clipboard_autosync));
ADD_PARAM("clipboard_autosync=false");
}
if (!params->downsize_on_error) {
// By default, downsize_on_error is true
ADD_PARAM("downsize_on_error=false");
}
#undef ADD_PARAM
@@ -766,12 +769,10 @@ run_server(void *data) {
// Interrupt sockets to wake up socket blocking calls on the server
assert(server->video_socket != SC_SOCKET_NONE);
net_interrupt(server->video_socket);
net_close(server->video_socket);
if (server->control_socket != SC_SOCKET_NONE) {
// There is no control_socket if --no-control is set
net_interrupt(server->control_socket);
net_close(server->control_socket);
}
// Give some delay for the server to terminate properly
@@ -827,6 +828,13 @@ sc_server_stop(struct sc_server *server) {
void
sc_server_destroy(struct sc_server *server) {
if (server->video_socket != SC_SOCKET_NONE) {
net_close(server->video_socket);
}
if (server->control_socket != SC_SOCKET_NONE) {
net_close(server->control_socket);
}
sc_server_params_destroy(&server->params);
sc_intr_destroy(&server->intr);
sc_cond_destroy(&server->cond_stopped);

View File

@@ -42,6 +42,7 @@ struct sc_server_params {
bool force_adb_forward;
bool power_off_on_close;
bool clipboard_autosync;
bool downsize_on_error;
bool tcpip;
const char *tcpip_dst;
};

View File

@@ -1,7 +1,6 @@
#include "stream.h"
#include <assert.h>
#include <libavformat/avformat.h>
#include <libavutil/time.h>
#include <unistd.h>
@@ -192,7 +191,7 @@ static int
run_stream(void *data) {
struct stream *stream = data;
AVCodec *codec = avcodec_find_decoder(AV_CODEC_ID_H264);
const AVCodec *codec = avcodec_find_decoder(AV_CODEC_ID_H264);
if (!codec) {
LOGE("H.264 decoder not found");
goto end;

View File

@@ -5,6 +5,7 @@
#include <stdbool.h>
#include <stdint.h>
#include <libavcodec/avcodec.h>
#include <libavformat/avformat.h>
#include "trait/packet_sink.h"

View File

@@ -50,80 +50,9 @@ log_libusb_error(enum libusb_error errcode) {
LOGW("libusb error: %s", libusb_strerror(errcode));
}
static bool
accept_device(libusb_device *device, const char *serial) {
// do not log any USB error in this function, it is expected that many USB
// devices available on the computer have permission restrictions
struct libusb_device_descriptor desc;
libusb_get_device_descriptor(device, &desc);
if (!desc.iSerialNumber) {
return false;
}
libusb_device_handle *handle;
int result = libusb_open(device, &handle);
if (result < 0) {
return false;
}
char buffer[128];
result = libusb_get_string_descriptor_ascii(handle, desc.iSerialNumber,
(unsigned char *) buffer,
sizeof(buffer));
libusb_close(handle);
if (result < 0) {
return false;
}
buffer[sizeof(buffer) - 1] = '\0'; // just in case
// accept the device if its serial matches
return !strcmp(buffer, serial);
}
static libusb_device *
sc_aoa_find_usb_device(const char *serial) {
if (!serial) {
return NULL;
}
libusb_device **list;
libusb_device *result = NULL;
ssize_t count = libusb_get_device_list(NULL, &list);
if (count < 0) {
log_libusb_error((enum libusb_error) count);
return NULL;
}
for (size_t i = 0; i < (size_t) count; ++i) {
libusb_device *device = list[i];
if (accept_device(device, serial)) {
result = libusb_ref_device(device);
break;
}
}
libusb_free_device_list(list, 1);
return result;
}
static int
sc_aoa_open_usb_handle(libusb_device *device, libusb_device_handle **handle) {
int result = libusb_open(device, handle);
if (result < 0) {
log_libusb_error((enum libusb_error) result);
return result;
}
return 0;
}
bool
sc_aoa_init(struct sc_aoa *aoa, const char *serial,
sc_aoa_init(struct sc_aoa *aoa, struct sc_usb *usb,
struct sc_acksync *acksync) {
assert(acksync);
cbuf_init(&aoa->queue);
if (!sc_mutex_init(&aoa->mutex)) {
@@ -135,32 +64,9 @@ sc_aoa_init(struct sc_aoa *aoa, const char *serial,
return false;
}
if (libusb_init(&aoa->usb_context) != LIBUSB_SUCCESS) {
sc_cond_destroy(&aoa->event_cond);
sc_mutex_destroy(&aoa->mutex);
return false;
}
aoa->usb_device = sc_aoa_find_usb_device(serial);
if (!aoa->usb_device) {
LOGW("USB device of serial %s not found", serial);
libusb_exit(aoa->usb_context);
sc_mutex_destroy(&aoa->mutex);
sc_cond_destroy(&aoa->event_cond);
return false;
}
if (sc_aoa_open_usb_handle(aoa->usb_device, &aoa->usb_handle) < 0) {
LOGW("Open USB handle failed");
libusb_unref_device(aoa->usb_device);
libusb_exit(aoa->usb_context);
sc_cond_destroy(&aoa->event_cond);
sc_mutex_destroy(&aoa->mutex);
return false;
}
aoa->stopped = false;
aoa->acksync = acksync;
aoa->usb = usb;
return true;
}
@@ -173,9 +79,6 @@ sc_aoa_destroy(struct sc_aoa *aoa) {
sc_hid_event_destroy(&event);
}
libusb_close(aoa->usb_handle);
libusb_unref_device(aoa->usb_device);
libusb_exit(aoa->usb_context);
sc_cond_destroy(&aoa->event_cond);
sc_mutex_destroy(&aoa->mutex);
}
@@ -192,8 +95,8 @@ sc_aoa_register_hid(struct sc_aoa *aoa, uint16_t accessory_id,
uint16_t index = report_desc_size;
unsigned char *buffer = NULL;
uint16_t length = 0;
int result = libusb_control_transfer(aoa->usb_handle, request_type, request,
value, index, buffer, length,
int result = libusb_control_transfer(aoa->usb->handle, request_type,
request, value, index, buffer, length,
DEFAULT_TIMEOUT);
if (result < 0) {
log_libusb_error((enum libusb_error) result);
@@ -228,8 +131,8 @@ sc_aoa_set_hid_report_desc(struct sc_aoa *aoa, uint16_t accessory_id,
// libusb_control_transfer expects a pointer to non-const
unsigned char *buffer = (unsigned char *) report_desc;
uint16_t length = report_desc_size;
int result = libusb_control_transfer(aoa->usb_handle, request_type, request,
value, index, buffer, length,
int result = libusb_control_transfer(aoa->usb->handle, request_type,
request, value, index, buffer, length,
DEFAULT_TIMEOUT);
if (result < 0) {
log_libusb_error((enum libusb_error) result);
@@ -270,8 +173,8 @@ sc_aoa_send_hid_event(struct sc_aoa *aoa, const struct sc_hid_event *event) {
uint16_t index = 0;
unsigned char *buffer = event->buffer;
uint16_t length = event->size;
int result = libusb_control_transfer(aoa->usb_handle, request_type, request,
value, index, buffer, length,
int result = libusb_control_transfer(aoa->usb->handle, request_type,
request, value, index, buffer, length,
DEFAULT_TIMEOUT);
if (result < 0) {
log_libusb_error((enum libusb_error) result);
@@ -292,8 +195,8 @@ sc_aoa_unregister_hid(struct sc_aoa *aoa, const uint16_t accessory_id) {
uint16_t index = 0;
unsigned char *buffer = NULL;
uint16_t length = 0;
int result = libusb_control_transfer(aoa->usb_handle, request_type, request,
value, index, buffer, length,
int result = libusb_control_transfer(aoa->usb->handle, request_type,
request, value, index, buffer, length,
DEFAULT_TIMEOUT);
if (result < 0) {
log_libusb_error((enum libusb_error) result);
@@ -343,8 +246,13 @@ run_aoa_thread(void *data) {
if (ack_to_wait != SC_SEQUENCE_INVALID) {
LOGD("Waiting ack from server sequence=%" PRIu64_, ack_to_wait);
// Do not block the loop indefinitely if the ack never comes (it should
// never happen)
// If some events have ack_to_wait set, then sc_aoa must have been
// initialized with a non NULL acksync
assert(aoa->acksync);
// Do not block the loop indefinitely if the ack never comes (it
// should never happen)
sc_tick deadline = sc_tick_now() + SC_TICK_FROM_MS(500);
enum sc_acksync_wait_result result =
sc_acksync_wait(aoa->acksync, ack_to_wait, deadline);
@@ -389,7 +297,9 @@ sc_aoa_stop(struct sc_aoa *aoa) {
sc_cond_signal(&aoa->event_cond);
sc_mutex_unlock(&aoa->mutex);
sc_acksync_interrupt(aoa->acksync);
if (aoa->acksync) {
sc_acksync_interrupt(aoa->acksync);
}
}
void

View File

@@ -6,6 +6,7 @@
#include <libusb-1.0/libusb.h>
#include "usb.h"
#include "util/acksync.h"
#include "util/cbuf.h"
#include "util/thread.h"
@@ -29,9 +30,7 @@ sc_hid_event_destroy(struct sc_hid_event *hid_event);
struct sc_hid_event_queue CBUF(struct sc_hid_event, 64);
struct sc_aoa {
libusb_context *usb_context;
libusb_device *usb_device;
libusb_device_handle *usb_handle;
struct sc_usb *usb;
sc_thread thread;
sc_mutex mutex;
sc_cond event_cond;
@@ -42,7 +41,7 @@ struct sc_aoa {
};
bool
sc_aoa_init(struct sc_aoa *aoa, const char *serial, struct sc_acksync *acksync);
sc_aoa_init(struct sc_aoa *aoa, struct sc_usb *usb, struct sc_acksync *acksync);
void
sc_aoa_destroy(struct sc_aoa *aoa);

View File

@@ -262,6 +262,6 @@ void
sc_hid_mouse_destroy(struct sc_hid_mouse *mouse) {
bool ok = sc_aoa_unregister_hid(mouse->aoa, HID_MOUSE_ACCESSORY_ID);
if (!ok) {
LOGW("Could not unregister HID");
LOGW("Could not unregister HID mouse");
}
}

View File

@@ -1,5 +1,5 @@
#ifndef HID_MOUSE_H
#define HID_MOUSE_H
#ifndef SC_HID_MOUSE_H
#define SC_HID_MOUSE_H
#include "common.h"

232
app/src/usb/scrcpy_otg.c Normal file
View File

@@ -0,0 +1,232 @@
#include "scrcpy_otg.h"
#include <SDL2/SDL.h>
#include "events.h"
#include "screen_otg.h"
#include "util/log.h"
struct scrcpy_otg {
struct sc_usb usb;
struct sc_aoa aoa;
struct sc_hid_keyboard keyboard;
struct sc_hid_mouse mouse;
struct sc_screen_otg screen_otg;
};
static void
sc_usb_on_disconnected(struct sc_usb *usb, void *userdata) {
(void) usb;
(void) userdata;
SDL_Event event;
event.type = EVENT_USB_DEVICE_DISCONNECTED;
int ret = SDL_PushEvent(&event);
if (ret < 0) {
LOGE("Could not post USB disconnection event: %s", SDL_GetError());
}
}
static bool
event_loop(struct scrcpy_otg *s) {
SDL_Event event;
while (SDL_WaitEvent(&event)) {
switch (event.type) {
case EVENT_USB_DEVICE_DISCONNECTED:
LOGW("Device disconnected");
return false;
case SDL_QUIT:
LOGD("User requested to quit");
return true;
default:
sc_screen_otg_handle_event(&s->screen_otg, &event);
break;
}
}
return false;
}
bool
scrcpy_otg(struct scrcpy_options *options) {
static struct scrcpy_otg scrcpy_otg;
struct scrcpy_otg *s = &scrcpy_otg;
const char *serial = options->serial;
// Minimal SDL initialization
if (SDL_Init(SDL_INIT_EVENTS)) {
LOGC("Could not initialize SDL: %s", SDL_GetError());
return false;
}
atexit(SDL_Quit);
if (!SDL_SetHint(SDL_HINT_MOUSE_FOCUS_CLICKTHROUGH, "1")) {
LOGW("Could not enable mouse focus clickthrough");
}
bool ret = false;
struct sc_hid_keyboard *keyboard = NULL;
struct sc_hid_mouse *mouse = NULL;
bool usb_device_initialized = false;
bool usb_connected = false;
bool aoa_started = false;
bool aoa_initialized = false;
static const struct sc_usb_callbacks cbs = {
.on_disconnected = sc_usb_on_disconnected,
};
bool ok = sc_usb_init(&s->usb);
if (!ok) {
return false;
}
struct sc_usb_device usb_devices[16];
ssize_t count = sc_usb_find_devices(&s->usb, serial, usb_devices,
ARRAY_LEN(usb_devices));
if (count < 0) {
LOGE("Could not list USB devices");
goto end;
}
if (count == 0) {
if (serial) {
LOGE("Could not find USB device %s", serial);
} else {
LOGE("Could not find any USB device");
}
goto end;
}
if (count > 1) {
if (serial) {
LOGE("Multiple (%d) USB devices with serial %s:", (int) count,
serial);
} else {
LOGE("Multiple (%d) USB devices:", (int) count);
}
for (size_t i = 0; i < (size_t) count; ++i) {
struct sc_usb_device *d = &usb_devices[i];
LOGE(" %-18s (%04" PRIx16 ":%04" PRIx16 ") %s %s",
d->serial, d->vid, d->pid, d->manufacturer, d->product);
}
if (!serial) {
LOGE("Specify the device via -s or --serial");
}
sc_usb_device_destroy_all(usb_devices, count);
goto end;
}
usb_device_initialized = true;
struct sc_usb_device *usb_device = &usb_devices[0];
LOGI("USB device: %s (%04" PRIx16 ":%04" PRIx16 ") %s %s",
usb_device->serial, usb_device->vid, usb_device->pid,
usb_device->manufacturer, usb_device->product);
ok = sc_usb_connect(&s->usb, usb_device->device, &cbs, NULL);
if (!ok) {
goto end;
}
usb_connected = true;
ok = sc_aoa_init(&s->aoa, &s->usb, NULL);
if (!ok) {
goto end;
}
aoa_initialized = true;
bool enable_keyboard =
options->keyboard_input_mode == SC_KEYBOARD_INPUT_MODE_HID;
bool enable_mouse =
options->mouse_input_mode == SC_MOUSE_INPUT_MODE_HID;
// If neither --hid-keyboard or --hid-mouse is passed, enable both
if (!enable_keyboard && !enable_mouse) {
enable_keyboard = true;
enable_mouse = true;
}
if (enable_keyboard) {
ok = sc_hid_keyboard_init(&s->keyboard, &s->aoa);
if (!ok) {
goto end;
}
keyboard = &s->keyboard;
}
if (enable_mouse) {
ok = sc_hid_mouse_init(&s->mouse, &s->aoa);
if (!ok) {
goto end;
}
mouse = &s->mouse;
}
ok = sc_aoa_start(&s->aoa);
if (!ok) {
goto end;
}
aoa_started = true;
const char *window_title = options->window_title;
if (!window_title) {
window_title = usb_device->product ? usb_device->product : "scrcpy";
}
struct sc_screen_otg_params params = {
.keyboard = keyboard,
.mouse = mouse,
.window_title = window_title,
.always_on_top = options->always_on_top,
.window_x = options->window_x,
.window_y = options->window_y,
.window_borderless = options->window_borderless,
};
ok = sc_screen_otg_init(&s->screen_otg, &params);
if (!ok) {
goto end;
}
// usb_device not needed anymore
sc_usb_device_destroy(usb_device);
usb_device_initialized = false;
ret = event_loop(s);
LOGD("quit...");
end:
if (aoa_started) {
sc_aoa_stop(&s->aoa);
}
sc_usb_stop(&s->usb);
if (mouse) {
sc_hid_mouse_destroy(&s->mouse);
}
if (keyboard) {
sc_hid_keyboard_destroy(&s->keyboard);
}
if (aoa_initialized) {
sc_aoa_join(&s->aoa);
sc_aoa_destroy(&s->aoa);
}
sc_usb_join(&s->usb);
if (usb_connected) {
sc_usb_disconnect(&s->usb);
}
if (usb_device_initialized) {
sc_usb_device_destroy(usb_device);
}
sc_usb_destroy(&s->usb);
return ret;
}

12
app/src/usb/scrcpy_otg.h Normal file
View File

@@ -0,0 +1,12 @@
#ifndef SCRCPY_OTG_H
#define SCRCPY_OTG_H
#include "common.h"
#include <stdbool.h>
#include "options.h"
bool
scrcpy_otg(struct scrcpy_options *options);
#endif

267
app/src/usb/screen_otg.c Normal file
View File

@@ -0,0 +1,267 @@
#include "screen_otg.h"
#include "icon.h"
#include "options.h"
#include "util/log.h"
static void
sc_screen_otg_capture_mouse(struct sc_screen_otg *screen, bool capture) {
assert(screen->mouse);
if (SDL_SetRelativeMouseMode(capture)) {
LOGE("Could not set relative mouse mode to %s: %s",
capture ? "true" : "false", SDL_GetError());
return;
}
screen->mouse_captured = capture;
}
static void
sc_screen_otg_render(struct sc_screen_otg *screen) {
SDL_RenderClear(screen->renderer);
if (screen->texture) {
SDL_RenderCopy(screen->renderer, screen->texture, NULL, NULL);
}
SDL_RenderPresent(screen->renderer);
}
bool
sc_screen_otg_init(struct sc_screen_otg *screen,
const struct sc_screen_otg_params *params) {
screen->keyboard = params->keyboard;
screen->mouse = params->mouse;
screen->mouse_captured = false;
screen->mouse_capture_key_pressed = 0;
const char *title = params->window_title;
assert(title);
int x = params->window_x != SC_WINDOW_POSITION_UNDEFINED
? params->window_x : (int) SDL_WINDOWPOS_UNDEFINED;
int y = params->window_y != SC_WINDOW_POSITION_UNDEFINED
? params->window_y : (int) SDL_WINDOWPOS_UNDEFINED;
int width = 256;
int height = 256;
uint32_t window_flags = 0;
if (params->always_on_top) {
window_flags |= SDL_WINDOW_ALWAYS_ON_TOP;
}
if (params->window_borderless) {
window_flags |= SDL_WINDOW_BORDERLESS;
}
screen->window = SDL_CreateWindow(title, x, y, width, height, window_flags);
if (!screen->window) {
LOGE("Could not create window: %s", SDL_GetError());
return false;
}
screen->renderer = SDL_CreateRenderer(screen->window, -1, 0);
if (!screen->renderer) {
LOGE("Could not create renderer: %s", SDL_GetError());
goto error_destroy_window;
}
SDL_Surface *icon = scrcpy_icon_load();
if (icon) {
SDL_SetWindowIcon(screen->window, icon);
screen->texture = SDL_CreateTextureFromSurface(screen->renderer, icon);
scrcpy_icon_destroy(icon);
if (!screen->texture) {
goto error_destroy_renderer;
}
} else {
screen->texture = NULL;
LOGW("Could not load icon");
}
if (screen->mouse) {
// Capture mouse on start
sc_screen_otg_capture_mouse(screen, true);
}
return true;
error_destroy_window:
SDL_DestroyWindow(screen->window);
error_destroy_renderer:
SDL_DestroyRenderer(screen->renderer);
return false;
}
void
sc_screen_otg_destroy(struct sc_screen_otg *screen) {
if (screen->texture) {
SDL_DestroyTexture(screen->texture);
}
SDL_DestroyRenderer(screen->renderer);
SDL_DestroyWindow(screen->window);
}
static inline bool
sc_screen_otg_is_mouse_capture_key(SDL_Keycode key) {
return key == SDLK_LALT || key == SDLK_LGUI || key == SDLK_RGUI;
}
static void
sc_screen_otg_process_key(struct sc_screen_otg *screen,
const SDL_KeyboardEvent *event) {
assert(screen->keyboard);
struct sc_key_processor *kp = &screen->keyboard->key_processor;
struct sc_key_event evt = {
.action = sc_action_from_sdl_keyboard_type(event->type),
.keycode = sc_keycode_from_sdl(event->keysym.sym),
.scancode = sc_scancode_from_sdl(event->keysym.scancode),
.repeat = event->repeat,
.mods_state = sc_mods_state_from_sdl(event->keysym.mod),
};
assert(kp->ops->process_key);
kp->ops->process_key(kp, &evt, SC_SEQUENCE_INVALID);
}
static void
sc_screen_otg_process_mouse_motion(struct sc_screen_otg *screen,
const SDL_MouseMotionEvent *event) {
assert(screen->mouse);
struct sc_mouse_processor *mp = &screen->mouse->mouse_processor;
struct sc_mouse_motion_event evt = {
// .position not used for HID events
.xrel = event->xrel,
.yrel = event->yrel,
.buttons_state = sc_mouse_buttons_state_from_sdl(event->state, true),
};
assert(mp->ops->process_mouse_motion);
mp->ops->process_mouse_motion(mp, &evt);
}
static void
sc_screen_otg_process_mouse_button(struct sc_screen_otg *screen,
const SDL_MouseButtonEvent *event) {
assert(screen->mouse);
struct sc_mouse_processor *mp = &screen->mouse->mouse_processor;
uint32_t sdl_buttons_state = SDL_GetMouseState(NULL, NULL);
struct sc_mouse_click_event evt = {
// .position not used for HID events
.action = sc_action_from_sdl_mousebutton_type(event->type),
.button = sc_mouse_button_from_sdl(event->button),
.buttons_state =
sc_mouse_buttons_state_from_sdl(sdl_buttons_state, true),
};
assert(mp->ops->process_mouse_click);
mp->ops->process_mouse_click(mp, &evt);
}
static void
sc_screen_otg_process_mouse_wheel(struct sc_screen_otg *screen,
const SDL_MouseWheelEvent *event) {
assert(screen->mouse);
struct sc_mouse_processor *mp = &screen->mouse->mouse_processor;
uint32_t sdl_buttons_state = SDL_GetMouseState(NULL, NULL);
struct sc_mouse_scroll_event evt = {
// .position not used for HID events
.hscroll = event->x,
.vscroll = event->y,
.buttons_state =
sc_mouse_buttons_state_from_sdl(sdl_buttons_state, true),
};
assert(mp->ops->process_mouse_scroll);
mp->ops->process_mouse_scroll(mp, &evt);
}
void
sc_screen_otg_handle_event(struct sc_screen_otg *screen, SDL_Event *event) {
switch (event->type) {
case SDL_WINDOWEVENT:
switch (event->window.event) {
case SDL_WINDOWEVENT_EXPOSED:
sc_screen_otg_render(screen);
break;
case SDL_WINDOWEVENT_FOCUS_LOST:
if (screen->mouse) {
sc_screen_otg_capture_mouse(screen, false);
}
break;
}
return;
case SDL_KEYDOWN:
if (screen->mouse) {
SDL_Keycode key = event->key.keysym.sym;
if (sc_screen_otg_is_mouse_capture_key(key)) {
if (!screen->mouse_capture_key_pressed) {
screen->mouse_capture_key_pressed = key;
} else {
// Another mouse capture key has been pressed, cancel
// mouse (un)capture
screen->mouse_capture_key_pressed = 0;
}
// Mouse capture keys are never forwarded to the device
return;
}
}
if (screen->keyboard) {
sc_screen_otg_process_key(screen, &event->key);
}
break;
case SDL_KEYUP:
if (screen->mouse) {
SDL_Keycode key = event->key.keysym.sym;
SDL_Keycode cap = screen->mouse_capture_key_pressed;
screen->mouse_capture_key_pressed = 0;
if (sc_screen_otg_is_mouse_capture_key(key)) {
if (key == cap) {
// A mouse capture key has been pressed then released:
// toggle the capture mouse mode
sc_screen_otg_capture_mouse(screen,
!screen->mouse_captured);
}
// Mouse capture keys are never forwarded to the device
return;
}
}
if (screen->keyboard) {
sc_screen_otg_process_key(screen, &event->key);
}
break;
case SDL_MOUSEMOTION:
if (screen->mouse && screen->mouse_captured) {
sc_screen_otg_process_mouse_motion(screen, &event->motion);
}
break;
case SDL_MOUSEBUTTONDOWN:
if (screen->mouse && screen->mouse_captured) {
sc_screen_otg_process_mouse_button(screen, &event->button);
}
break;
case SDL_MOUSEBUTTONUP:
if (screen->mouse) {
if (screen->mouse_captured) {
sc_screen_otg_process_mouse_button(screen, &event->button);
} else {
sc_screen_otg_capture_mouse(screen, true);
}
}
break;
case SDL_MOUSEWHEEL:
if (screen->mouse && screen->mouse_captured) {
sc_screen_otg_process_mouse_wheel(screen, &event->wheel);
}
break;
}
}

46
app/src/usb/screen_otg.h Normal file
View File

@@ -0,0 +1,46 @@
#ifndef SC_SCREEN_OTG_H
#define SC_SCREEN_OTG_H
#include "common.h"
#include <stdbool.h>
#include <SDL2/SDL.h>
#include "hid_keyboard.h"
#include "hid_mouse.h"
struct sc_screen_otg {
struct sc_hid_keyboard *keyboard;
struct sc_hid_mouse *mouse;
SDL_Window *window;
SDL_Renderer *renderer;
SDL_Texture *texture;
// See equivalent mechanism in screen.h
bool mouse_captured;
SDL_Keycode mouse_capture_key_pressed;
};
struct sc_screen_otg_params {
struct sc_hid_keyboard *keyboard;
struct sc_hid_mouse *mouse;
const char *window_title;
bool always_on_top;
int16_t window_x; // accepts SC_WINDOW_POSITION_UNDEFINED
int16_t window_y; // accepts SC_WINDOW_POSITION_UNDEFINED
bool window_borderless;
};
bool
sc_screen_otg_init(struct sc_screen_otg *screen,
const struct sc_screen_otg_params *params);
void
sc_screen_otg_destroy(struct sc_screen_otg *screen);
void
sc_screen_otg_handle_event(struct sc_screen_otg *screen, SDL_Event *event);
#endif

264
app/src/usb/usb.c Normal file
View File

@@ -0,0 +1,264 @@
#include "usb.h"
#include <assert.h>
#include "util/log.h"
static inline void
log_libusb_error(enum libusb_error errcode) {
LOGW("libusb error: %s", libusb_strerror(errcode));
}
static char *
read_string(libusb_device_handle *handle, uint8_t desc_index) {
char buffer[128];
int result =
libusb_get_string_descriptor_ascii(handle, desc_index,
(unsigned char *) buffer,
sizeof(buffer));
if (result < 0) {
return NULL;
}
assert((size_t) result <= sizeof(buffer));
// When non-negative, 'result' contains the number of bytes written
char *s = malloc(result + 1);
memcpy(s, buffer, result);
s[result] = '\0';
return s;
}
static bool
accept_device(libusb_device *device, const char *serial,
struct sc_usb_device *out) {
// Do not log any USB error in this function, it is expected that many USB
// devices available on the computer have permission restrictions
struct libusb_device_descriptor desc;
int result = libusb_get_device_descriptor(device, &desc);
if (result < 0 || !desc.iSerialNumber) {
return false;
}
libusb_device_handle *handle;
result = libusb_open(device, &handle);
if (result < 0) {
return false;
}
char *device_serial = read_string(handle, desc.iSerialNumber);
if (!device_serial) {
libusb_close(handle);
return false;
}
if (serial) {
// Filter by serial
bool matches = !strcmp(serial, device_serial);
if (!matches) {
free(device_serial);
libusb_close(handle);
return false;
}
}
out->device = libusb_ref_device(device);
out->serial = device_serial;
out->vid = desc.idVendor;
out->pid = desc.idProduct;
out->manufacturer = read_string(handle, desc.iManufacturer);
out->product = read_string(handle, desc.iProduct);
libusb_close(handle);
return true;
}
void
sc_usb_device_destroy(struct sc_usb_device *usb_device) {
libusb_unref_device(usb_device->device);
free(usb_device->serial);
free(usb_device->manufacturer);
free(usb_device->product);
}
void
sc_usb_device_destroy_all(struct sc_usb_device *usb_devices, size_t count) {
for (size_t i = 0; i < count; ++i) {
sc_usb_device_destroy(&usb_devices[i]);
}
}
ssize_t
sc_usb_find_devices(struct sc_usb *usb, const char *serial,
struct sc_usb_device *devices, size_t len) {
libusb_device **list;
ssize_t count = libusb_get_device_list(usb->context, &list);
if (count < 0) {
log_libusb_error((enum libusb_error) count);
return -1;
}
size_t idx = 0;
for (size_t i = 0; i < (size_t) count && idx < len; ++i) {
libusb_device *device = list[i];
if (accept_device(device, serial, &devices[idx])) {
++idx;
}
}
libusb_free_device_list(list, 1);
return idx;
}
static libusb_device_handle *
sc_usb_open_handle(libusb_device *device) {
libusb_device_handle *handle;
int result = libusb_open(device, &handle);
if (result < 0) {
log_libusb_error((enum libusb_error) result);
return NULL;
}
return handle;
}
bool
sc_usb_init(struct sc_usb *usb) {
usb->handle = NULL;
return libusb_init(&usb->context) == LIBUSB_SUCCESS;
}
void
sc_usb_destroy(struct sc_usb *usb) {
libusb_exit(usb->context);
}
static int
sc_usb_libusb_callback(libusb_context *ctx, libusb_device *device,
libusb_hotplug_event event, void *userdata) {
(void) ctx;
(void) device;
(void) event;
struct sc_usb *usb = userdata;
libusb_device *dev = libusb_get_device(usb->handle);
assert(dev);
if (dev != device) {
// Not the connected device
return 0;
}
assert(usb->cbs && usb->cbs->on_disconnected);
usb->cbs->on_disconnected(usb, usb->cbs_userdata);
// Do not automatically deregister the callback by returning 1. Instead,
// manually deregister to interrupt libusb_handle_events() from the libusb
// event thread: <https://stackoverflow.com/a/60119225/1987178>
return 0;
}
static int
run_libusb_event_handler(void *data) {
struct sc_usb *usb = data;
while (!atomic_load(&usb->stopped)) {
// Interrupted by events or by libusb_hotplug_deregister_callback()
libusb_handle_events(usb->context);
}
return 0;
}
static bool
sc_usb_register_callback(struct sc_usb *usb) {
if (!libusb_has_capability(LIBUSB_CAP_HAS_HOTPLUG)) {
LOGW("libusb does not have hotplug capability");
return false;
}
libusb_device *device = libusb_get_device(usb->handle);
assert(device);
struct libusb_device_descriptor desc;
int result = libusb_get_device_descriptor(device, &desc);
if (result < 0) {
log_libusb_error((enum libusb_error) result);
LOGW("Could not read USB device descriptor");
return false;
}
int events = LIBUSB_HOTPLUG_EVENT_DEVICE_LEFT;
int flags = LIBUSB_HOTPLUG_NO_FLAGS;
int vendor_id = desc.idVendor;
int product_id = desc.idProduct;
int dev_class = LIBUSB_HOTPLUG_MATCH_ANY;
result = libusb_hotplug_register_callback(usb->context, events, flags,
vendor_id, product_id, dev_class,
sc_usb_libusb_callback, usb,
&usb->callback_handle);
if (result < 0) {
log_libusb_error((enum libusb_error) result);
LOGW("Could not register USB callback");
return false;
}
usb->has_callback_handle = true;
return true;
}
bool
sc_usb_connect(struct sc_usb *usb, libusb_device *device,
const struct sc_usb_callbacks *cbs, void *cbs_userdata) {
usb->handle = sc_usb_open_handle(device);
if (!usb->handle) {
return false;
}
usb->has_callback_handle = false;
usb->has_libusb_event_thread = false;
// If cbs is set, then cbs->on_disconnected must be set
assert(!cbs || cbs->on_disconnected);
usb->cbs = cbs;
usb->cbs_userdata = cbs_userdata;
if (cbs) {
atomic_init(&usb->stopped, false);
if (sc_usb_register_callback(usb)) {
// Create a thread to process libusb events, so that device
// disconnection could be detected immediately
usb->has_libusb_event_thread =
sc_thread_create(&usb->libusb_event_thread,
run_libusb_event_handler, "scrcpy-usbev", usb);
if (!usb->has_libusb_event_thread) {
LOGW("Libusb event thread handler could not be created, USB "
"device disconnection might not be detected immediately");
}
} else {
LOGW("Could not register USB device disconnection callback");
}
}
return true;
}
void
sc_usb_disconnect(struct sc_usb *usb) {
libusb_close(usb->handle);
}
void
sc_usb_stop(struct sc_usb *usb) {
if (usb->has_callback_handle) {
atomic_store(&usb->stopped, true);
libusb_hotplug_deregister_callback(usb->context, usb->callback_handle);
}
}
void
sc_usb_join(struct sc_usb *usb) {
if (usb->has_libusb_event_thread) {
sc_thread_join(&usb->libusb_event_thread, NULL);
}
}

69
app/src/usb/usb.h Normal file
View File

@@ -0,0 +1,69 @@
#ifndef SC_USB_H
#define SC_USB_H
#include "common.h"
#include <stdbool.h>
#include <libusb-1.0/libusb.h>
#include "util/thread.h"
struct sc_usb {
libusb_context *context;
libusb_device_handle *handle;
const struct sc_usb_callbacks *cbs;
void *cbs_userdata;
bool has_callback_handle;
libusb_hotplug_callback_handle callback_handle;
bool has_libusb_event_thread;
sc_thread libusb_event_thread;
atomic_bool stopped; // only used if cbs != NULL
};
struct sc_usb_callbacks {
void (*on_disconnected)(struct sc_usb *usb, void *userdata);
};
struct sc_usb_device {
libusb_device *device;
char *serial;
char *manufacturer;
char *product;
uint16_t vid;
uint16_t pid;
};
void
sc_usb_device_destroy(struct sc_usb_device *usb_device);
void
sc_usb_device_destroy_all(struct sc_usb_device *usb_devices, size_t count);
bool
sc_usb_init(struct sc_usb *usb);
void
sc_usb_destroy(struct sc_usb *usb);
ssize_t
sc_usb_find_devices(struct sc_usb *usb, const char *serial,
struct sc_usb_device *devices, size_t len);
bool
sc_usb_connect(struct sc_usb *usb, libusb_device *device,
const struct sc_usb_callbacks *cbs, void *cbs_userdata);
void
sc_usb_disconnect(struct sc_usb *usb);
void
sc_usb_stop(struct sc_usb *usb);
void
sc_usb_join(struct sc_usb *usb);
#endif

View File

@@ -3,13 +3,14 @@
#include "common.h"
#include <libavcodec/avcodec.h>
#include <libavformat/avformat.h>
#include "coords.h"
#include "trait/frame_sink.h"
#include "video_buffer.h"
#include "util/tick.h"
#include <libavformat/avformat.h>
struct sc_v4l2_sink {
struct sc_frame_sink frame_sink; // frame sink trait

View File

@@ -6,8 +6,8 @@
#include "control_msg.h"
static void test_serialize_inject_keycode(void) {
struct control_msg msg = {
.type = CONTROL_MSG_TYPE_INJECT_KEYCODE,
struct sc_control_msg msg = {
.type = SC_CONTROL_MSG_TYPE_INJECT_KEYCODE,
.inject_keycode = {
.action = AKEY_EVENT_ACTION_UP,
.keycode = AKEYCODE_ENTER,
@@ -16,12 +16,12 @@ static void test_serialize_inject_keycode(void) {
},
};
unsigned char buf[CONTROL_MSG_MAX_SIZE];
size_t size = control_msg_serialize(&msg, buf);
unsigned char buf[SC_CONTROL_MSG_MAX_SIZE];
size_t size = sc_control_msg_serialize(&msg, buf);
assert(size == 14);
const unsigned char expected[] = {
CONTROL_MSG_TYPE_INJECT_KEYCODE,
SC_CONTROL_MSG_TYPE_INJECT_KEYCODE,
0x01, // AKEY_EVENT_ACTION_UP
0x00, 0x00, 0x00, 0x42, // AKEYCODE_ENTER
0x00, 0x00, 0x00, 0X05, // repeat
@@ -31,19 +31,19 @@ static void test_serialize_inject_keycode(void) {
}
static void test_serialize_inject_text(void) {
struct control_msg msg = {
.type = CONTROL_MSG_TYPE_INJECT_TEXT,
struct sc_control_msg msg = {
.type = SC_CONTROL_MSG_TYPE_INJECT_TEXT,
.inject_text = {
.text = "hello, world!",
},
};
unsigned char buf[CONTROL_MSG_MAX_SIZE];
size_t size = control_msg_serialize(&msg, buf);
unsigned char buf[SC_CONTROL_MSG_MAX_SIZE];
size_t size = sc_control_msg_serialize(&msg, buf);
assert(size == 18);
const unsigned char expected[] = {
CONTROL_MSG_TYPE_INJECT_TEXT,
SC_CONTROL_MSG_TYPE_INJECT_TEXT,
0x00, 0x00, 0x00, 0x0d, // text length
'h', 'e', 'l', 'l', 'o', ',', ' ', 'w', 'o', 'r', 'l', 'd', '!', // text
};
@@ -51,31 +51,31 @@ static void test_serialize_inject_text(void) {
}
static void test_serialize_inject_text_long(void) {
struct control_msg msg;
msg.type = CONTROL_MSG_TYPE_INJECT_TEXT;
char text[CONTROL_MSG_INJECT_TEXT_MAX_LENGTH + 1];
memset(text, 'a', CONTROL_MSG_INJECT_TEXT_MAX_LENGTH);
text[CONTROL_MSG_INJECT_TEXT_MAX_LENGTH] = '\0';
struct sc_control_msg msg;
msg.type = SC_CONTROL_MSG_TYPE_INJECT_TEXT;
char text[SC_CONTROL_MSG_INJECT_TEXT_MAX_LENGTH + 1];
memset(text, 'a', SC_CONTROL_MSG_INJECT_TEXT_MAX_LENGTH);
text[SC_CONTROL_MSG_INJECT_TEXT_MAX_LENGTH] = '\0';
msg.inject_text.text = text;
unsigned char buf[CONTROL_MSG_MAX_SIZE];
size_t size = control_msg_serialize(&msg, buf);
assert(size == 5 + CONTROL_MSG_INJECT_TEXT_MAX_LENGTH);
unsigned char buf[SC_CONTROL_MSG_MAX_SIZE];
size_t size = sc_control_msg_serialize(&msg, buf);
assert(size == 5 + SC_CONTROL_MSG_INJECT_TEXT_MAX_LENGTH);
unsigned char expected[5 + CONTROL_MSG_INJECT_TEXT_MAX_LENGTH];
expected[0] = CONTROL_MSG_TYPE_INJECT_TEXT;
unsigned char expected[5 + SC_CONTROL_MSG_INJECT_TEXT_MAX_LENGTH];
expected[0] = SC_CONTROL_MSG_TYPE_INJECT_TEXT;
expected[1] = 0x00;
expected[2] = 0x00;
expected[3] = 0x01;
expected[4] = 0x2c; // text length (32 bits)
memset(&expected[5], 'a', CONTROL_MSG_INJECT_TEXT_MAX_LENGTH);
memset(&expected[5], 'a', SC_CONTROL_MSG_INJECT_TEXT_MAX_LENGTH);
assert(!memcmp(buf, expected, sizeof(expected)));
}
static void test_serialize_inject_touch_event(void) {
struct control_msg msg = {
.type = CONTROL_MSG_TYPE_INJECT_TOUCH_EVENT,
struct sc_control_msg msg = {
.type = SC_CONTROL_MSG_TYPE_INJECT_TOUCH_EVENT,
.inject_touch_event = {
.action = AMOTION_EVENT_ACTION_DOWN,
.pointer_id = UINT64_C(0x1234567887654321),
@@ -94,12 +94,12 @@ static void test_serialize_inject_touch_event(void) {
},
};
unsigned char buf[CONTROL_MSG_MAX_SIZE];
size_t size = control_msg_serialize(&msg, buf);
unsigned char buf[SC_CONTROL_MSG_MAX_SIZE];
size_t size = sc_control_msg_serialize(&msg, buf);
assert(size == 28);
const unsigned char expected[] = {
CONTROL_MSG_TYPE_INJECT_TOUCH_EVENT,
SC_CONTROL_MSG_TYPE_INJECT_TOUCH_EVENT,
0x00, // AKEY_EVENT_ACTION_DOWN
0x12, 0x34, 0x56, 0x78, 0x87, 0x65, 0x43, 0x21, // pointer id
0x00, 0x00, 0x00, 0x64, 0x00, 0x00, 0x00, 0xc8, // 100 200
@@ -111,8 +111,8 @@ static void test_serialize_inject_touch_event(void) {
}
static void test_serialize_inject_scroll_event(void) {
struct control_msg msg = {
.type = CONTROL_MSG_TYPE_INJECT_SCROLL_EVENT,
struct sc_control_msg msg = {
.type = SC_CONTROL_MSG_TYPE_INJECT_SCROLL_EVENT,
.inject_scroll_event = {
.position = {
.point = {
@@ -130,12 +130,12 @@ static void test_serialize_inject_scroll_event(void) {
},
};
unsigned char buf[CONTROL_MSG_MAX_SIZE];
size_t size = control_msg_serialize(&msg, buf);
unsigned char buf[SC_CONTROL_MSG_MAX_SIZE];
size_t size = sc_control_msg_serialize(&msg, buf);
assert(size == 25);
const unsigned char expected[] = {
CONTROL_MSG_TYPE_INJECT_SCROLL_EVENT,
SC_CONTROL_MSG_TYPE_INJECT_SCROLL_EVENT,
0x00, 0x00, 0x01, 0x04, 0x00, 0x00, 0x04, 0x02, // 260 1026
0x04, 0x38, 0x07, 0x80, // 1080 1920
0x00, 0x00, 0x00, 0x01, // 1
@@ -146,91 +146,91 @@ static void test_serialize_inject_scroll_event(void) {
}
static void test_serialize_back_or_screen_on(void) {
struct control_msg msg = {
.type = CONTROL_MSG_TYPE_BACK_OR_SCREEN_ON,
struct sc_control_msg msg = {
.type = SC_CONTROL_MSG_TYPE_BACK_OR_SCREEN_ON,
.back_or_screen_on = {
.action = AKEY_EVENT_ACTION_UP,
},
};
unsigned char buf[CONTROL_MSG_MAX_SIZE];
size_t size = control_msg_serialize(&msg, buf);
unsigned char buf[SC_CONTROL_MSG_MAX_SIZE];
size_t size = sc_control_msg_serialize(&msg, buf);
assert(size == 2);
const unsigned char expected[] = {
CONTROL_MSG_TYPE_BACK_OR_SCREEN_ON,
SC_CONTROL_MSG_TYPE_BACK_OR_SCREEN_ON,
0x01, // AKEY_EVENT_ACTION_UP
};
assert(!memcmp(buf, expected, sizeof(expected)));
}
static void test_serialize_expand_notification_panel(void) {
struct control_msg msg = {
.type = CONTROL_MSG_TYPE_EXPAND_NOTIFICATION_PANEL,
struct sc_control_msg msg = {
.type = SC_CONTROL_MSG_TYPE_EXPAND_NOTIFICATION_PANEL,
};
unsigned char buf[CONTROL_MSG_MAX_SIZE];
size_t size = control_msg_serialize(&msg, buf);
unsigned char buf[SC_CONTROL_MSG_MAX_SIZE];
size_t size = sc_control_msg_serialize(&msg, buf);
assert(size == 1);
const unsigned char expected[] = {
CONTROL_MSG_TYPE_EXPAND_NOTIFICATION_PANEL,
SC_CONTROL_MSG_TYPE_EXPAND_NOTIFICATION_PANEL,
};
assert(!memcmp(buf, expected, sizeof(expected)));
}
static void test_serialize_expand_settings_panel(void) {
struct control_msg msg = {
.type = CONTROL_MSG_TYPE_EXPAND_SETTINGS_PANEL,
struct sc_control_msg msg = {
.type = SC_CONTROL_MSG_TYPE_EXPAND_SETTINGS_PANEL,
};
unsigned char buf[CONTROL_MSG_MAX_SIZE];
size_t size = control_msg_serialize(&msg, buf);
unsigned char buf[SC_CONTROL_MSG_MAX_SIZE];
size_t size = sc_control_msg_serialize(&msg, buf);
assert(size == 1);
const unsigned char expected[] = {
CONTROL_MSG_TYPE_EXPAND_SETTINGS_PANEL,
SC_CONTROL_MSG_TYPE_EXPAND_SETTINGS_PANEL,
};
assert(!memcmp(buf, expected, sizeof(expected)));
}
static void test_serialize_collapse_panels(void) {
struct control_msg msg = {
.type = CONTROL_MSG_TYPE_COLLAPSE_PANELS,
struct sc_control_msg msg = {
.type = SC_CONTROL_MSG_TYPE_COLLAPSE_PANELS,
};
unsigned char buf[CONTROL_MSG_MAX_SIZE];
size_t size = control_msg_serialize(&msg, buf);
unsigned char buf[SC_CONTROL_MSG_MAX_SIZE];
size_t size = sc_control_msg_serialize(&msg, buf);
assert(size == 1);
const unsigned char expected[] = {
CONTROL_MSG_TYPE_COLLAPSE_PANELS,
SC_CONTROL_MSG_TYPE_COLLAPSE_PANELS,
};
assert(!memcmp(buf, expected, sizeof(expected)));
}
static void test_serialize_get_clipboard(void) {
struct control_msg msg = {
.type = CONTROL_MSG_TYPE_GET_CLIPBOARD,
struct sc_control_msg msg = {
.type = SC_CONTROL_MSG_TYPE_GET_CLIPBOARD,
.get_clipboard = {
.copy_key = GET_CLIPBOARD_COPY_KEY_COPY,
.copy_key = SC_COPY_KEY_COPY,
},
};
unsigned char buf[CONTROL_MSG_MAX_SIZE];
size_t size = control_msg_serialize(&msg, buf);
unsigned char buf[SC_CONTROL_MSG_MAX_SIZE];
size_t size = sc_control_msg_serialize(&msg, buf);
assert(size == 2);
const unsigned char expected[] = {
CONTROL_MSG_TYPE_GET_CLIPBOARD,
GET_CLIPBOARD_COPY_KEY_COPY,
SC_CONTROL_MSG_TYPE_GET_CLIPBOARD,
SC_COPY_KEY_COPY,
};
assert(!memcmp(buf, expected, sizeof(expected)));
}
static void test_serialize_set_clipboard(void) {
struct control_msg msg = {
.type = CONTROL_MSG_TYPE_SET_CLIPBOARD,
struct sc_control_msg msg = {
.type = SC_CONTROL_MSG_TYPE_SET_CLIPBOARD,
.set_clipboard = {
.sequence = UINT64_C(0x0102030405060708),
.paste = true,
@@ -238,12 +238,12 @@ static void test_serialize_set_clipboard(void) {
},
};
unsigned char buf[CONTROL_MSG_MAX_SIZE];
size_t size = control_msg_serialize(&msg, buf);
unsigned char buf[SC_CONTROL_MSG_MAX_SIZE];
size_t size = sc_control_msg_serialize(&msg, buf);
assert(size == 27);
const unsigned char expected[] = {
CONTROL_MSG_TYPE_SET_CLIPBOARD,
SC_CONTROL_MSG_TYPE_SET_CLIPBOARD,
0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, // sequence
1, // paste
0x00, 0x00, 0x00, 0x0d, // text length
@@ -253,8 +253,8 @@ static void test_serialize_set_clipboard(void) {
}
static void test_serialize_set_clipboard_long(void) {
struct control_msg msg = {
.type = CONTROL_MSG_TYPE_SET_CLIPBOARD,
struct sc_control_msg msg = {
.type = SC_CONTROL_MSG_TYPE_SET_CLIPBOARD,
.set_clipboard = {
.sequence = UINT64_C(0x0102030405060708),
.paste = true,
@@ -262,60 +262,60 @@ static void test_serialize_set_clipboard_long(void) {
},
};
char text[CONTROL_MSG_CLIPBOARD_TEXT_MAX_LENGTH + 1];
memset(text, 'a', CONTROL_MSG_CLIPBOARD_TEXT_MAX_LENGTH);
text[CONTROL_MSG_CLIPBOARD_TEXT_MAX_LENGTH] = '\0';
char text[SC_CONTROL_MSG_CLIPBOARD_TEXT_MAX_LENGTH + 1];
memset(text, 'a', SC_CONTROL_MSG_CLIPBOARD_TEXT_MAX_LENGTH);
text[SC_CONTROL_MSG_CLIPBOARD_TEXT_MAX_LENGTH] = '\0';
msg.set_clipboard.text = text;
unsigned char buf[CONTROL_MSG_MAX_SIZE];
size_t size = control_msg_serialize(&msg, buf);
assert(size == CONTROL_MSG_MAX_SIZE);
unsigned char buf[SC_CONTROL_MSG_MAX_SIZE];
size_t size = sc_control_msg_serialize(&msg, buf);
assert(size == SC_CONTROL_MSG_MAX_SIZE);
unsigned char expected[CONTROL_MSG_MAX_SIZE] = {
CONTROL_MSG_TYPE_SET_CLIPBOARD,
unsigned char expected[SC_CONTROL_MSG_MAX_SIZE] = {
SC_CONTROL_MSG_TYPE_SET_CLIPBOARD,
0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, // sequence
1, // paste
// text length
CONTROL_MSG_CLIPBOARD_TEXT_MAX_LENGTH >> 24,
(CONTROL_MSG_CLIPBOARD_TEXT_MAX_LENGTH >> 16) & 0xff,
(CONTROL_MSG_CLIPBOARD_TEXT_MAX_LENGTH >> 8) & 0xff,
CONTROL_MSG_CLIPBOARD_TEXT_MAX_LENGTH & 0xff,
SC_CONTROL_MSG_CLIPBOARD_TEXT_MAX_LENGTH >> 24,
(SC_CONTROL_MSG_CLIPBOARD_TEXT_MAX_LENGTH >> 16) & 0xff,
(SC_CONTROL_MSG_CLIPBOARD_TEXT_MAX_LENGTH >> 8) & 0xff,
SC_CONTROL_MSG_CLIPBOARD_TEXT_MAX_LENGTH & 0xff,
};
memset(expected + 14, 'a', CONTROL_MSG_CLIPBOARD_TEXT_MAX_LENGTH);
memset(expected + 14, 'a', SC_CONTROL_MSG_CLIPBOARD_TEXT_MAX_LENGTH);
assert(!memcmp(buf, expected, sizeof(expected)));
}
static void test_serialize_set_screen_power_mode(void) {
struct control_msg msg = {
.type = CONTROL_MSG_TYPE_SET_SCREEN_POWER_MODE,
struct sc_control_msg msg = {
.type = SC_CONTROL_MSG_TYPE_SET_SCREEN_POWER_MODE,
.set_screen_power_mode = {
.mode = SCREEN_POWER_MODE_NORMAL,
.mode = SC_SCREEN_POWER_MODE_NORMAL,
},
};
unsigned char buf[CONTROL_MSG_MAX_SIZE];
size_t size = control_msg_serialize(&msg, buf);
unsigned char buf[SC_CONTROL_MSG_MAX_SIZE];
size_t size = sc_control_msg_serialize(&msg, buf);
assert(size == 2);
const unsigned char expected[] = {
CONTROL_MSG_TYPE_SET_SCREEN_POWER_MODE,
0x02, // SCREEN_POWER_MODE_NORMAL
SC_CONTROL_MSG_TYPE_SET_SCREEN_POWER_MODE,
0x02, // SC_SCREEN_POWER_MODE_NORMAL
};
assert(!memcmp(buf, expected, sizeof(expected)));
}
static void test_serialize_rotate_device(void) {
struct control_msg msg = {
.type = CONTROL_MSG_TYPE_ROTATE_DEVICE,
struct sc_control_msg msg = {
.type = SC_CONTROL_MSG_TYPE_ROTATE_DEVICE,
};
unsigned char buf[CONTROL_MSG_MAX_SIZE];
size_t size = control_msg_serialize(&msg, buf);
unsigned char buf[SC_CONTROL_MSG_MAX_SIZE];
size_t size = sc_control_msg_serialize(&msg, buf);
assert(size == 1);
const unsigned char expected[] = {
CONTROL_MSG_TYPE_ROTATE_DEVICE,
SC_CONTROL_MSG_TYPE_ROTATE_DEVICE,
};
assert(!memcmp(buf, expected, sizeof(expected)));
}

View File

@@ -16,6 +16,8 @@ cpu = 'i686'
endian = 'little'
[properties]
prebuilt_ffmpeg_shared = 'ffmpeg-4.3.1-win32-shared'
prebuilt_ffmpeg_dev = 'ffmpeg-4.3.1-win32-dev'
prebuilt_sdl2 = 'SDL2-2.0.18/i686-w64-mingw32'
ffmpeg_avcodec = 'avcodec-58'
ffmpeg_avformat = 'avformat-58'
ffmpeg_avutil = 'avutil-56'
prebuilt_ffmpeg = 'ffmpeg-4.3.1-win32-shared'
prebuilt_sdl2 = 'SDL2-2.0.20/i686-w64-mingw32'

View File

@@ -16,6 +16,8 @@ cpu = 'x86_64'
endian = 'little'
[properties]
prebuilt_ffmpeg_shared = 'ffmpeg-4.3.1-win64-shared'
prebuilt_ffmpeg_dev = 'ffmpeg-4.3.1-win64-dev'
prebuilt_sdl2 = 'SDL2-2.0.18/x86_64-w64-mingw32'
ffmpeg_avcodec = 'avcodec-59'
ffmpeg_avformat = 'avformat-59'
ffmpeg_avutil = 'avutil-57'
prebuilt_ffmpeg = 'ffmpeg-5.0-full_build-shared'
prebuilt_sdl2 = 'SDL2-2.0.20/x86_64-w64-mingw32'

View File

@@ -0,0 +1 @@
@cmd

View File

@@ -1,40 +1,33 @@
.PHONY: prepare-win32 prepare-win64 \
prepare-ffmpeg-shared-win32 \
prepare-ffmpeg-dev-win32 \
prepare-ffmpeg-shared-win64 \
prepare-ffmpeg-dev-win64 \
prepare-ffmpeg-win32 \
prepare-ffmpeg-win64 \
prepare-sdl2 \
prepare-adb
prepare-win32: prepare-sdl2 prepare-ffmpeg-shared-win32 prepare-ffmpeg-dev-win32 prepare-adb
prepare-win64: prepare-sdl2 prepare-ffmpeg-shared-win64 prepare-ffmpeg-dev-win64 prepare-adb
prepare-win32: prepare-sdl2 prepare-ffmpeg-win32 prepare-adb
prepare-win64: prepare-sdl2 prepare-ffmpeg-win64 prepare-adb
prepare-ffmpeg-shared-win32:
# Use old FFmpeg version for win32, there are no new prebuilts
prepare-ffmpeg-win32:
@./prepare-dep https://github.com/Genymobile/scrcpy/releases/download/v1.16/ffmpeg-4.3.1-win32-shared.zip \
357af9901a456f4dcbacd107e83a934d344c9cb07ddad8aaf80612eeab7d26d2 \
ffmpeg-4.3.1-win32-shared
prepare-ffmpeg-dev-win32:
@./prepare-dep https://github.com/Genymobile/scrcpy/releases/download/v1.16/ffmpeg-4.3.1-win32-dev.zip \
230efb08e9bcf225bd474da29676c70e591fc94d8790a740ca801408fddcb78b \
ffmpeg-4.3.1-win32-dev
ln -sf ../ffmpeg-4.3.1-win32-dev/include ffmpeg-4.3.1-win32-shared/
prepare-ffmpeg-shared-win64:
@./prepare-dep https://github.com/Genymobile/scrcpy/releases/download/v1.16/ffmpeg-4.3.1-win64-shared.zip \
dd29b7f92f48dead4dd940492c7509138c0f99db445076d0a597007298a79940 \
ffmpeg-4.3.1-win64-shared
prepare-ffmpeg-dev-win64:
@./prepare-dep https://github.com/Genymobile/scrcpy/releases/download/v1.16/ffmpeg-4.3.1-win64-dev.zip \
2e8038242cf8e1bd095c2978f196ff0462b122cc6ef7e74626a6af15459d8b81 \
ffmpeg-4.3.1-win64-dev
prepare-ffmpeg-win64:
@./prepare-dep https://github.com/GyanD/codexffmpeg/releases/download/5.0/ffmpeg-5.0-full_build-shared.7z \
e5900f6cecd4c438d398bd2fc308736c10b857cd8dd61c11bcfb05bff5d1211a \
ffmpeg-5.0-full_build-shared
prepare-sdl2:
@./prepare-dep https://libsdl.org/release/SDL2-devel-2.0.18-mingw.tar.gz \
bbad7c6947f6ca3e05292f065852ed8b62f319fc5533047e7708769c4dbae394 \
SDL2-2.0.18
@./prepare-dep https://libsdl.org/release/SDL2-devel-2.0.20-mingw.tar.gz \
38094d82a857d6c62352e5c5cdec74948c5b4d25c59cbd298d6d233568976bd1 \
SDL2-2.0.20
prepare-adb:
@./prepare-dep https://dl.google.com/android/repository/platform-tools_r31.0.3-windows.zip \
0f4b8fdd26af2c3733539d6eebb3c2ed499ea1d4bb1f4e0ecc2d6016961a6e24 \
@./prepare-dep https://dl.google.com/android/repository/platform-tools_r32.0.0-windows.zip \
41f4c7512b32cbb3f8c624c20b56326abb692a6f169b03b4b63b6c5a6fdbb08c \
platform-tools

View File

@@ -34,6 +34,9 @@ extract() {
elif [[ "$file" == *.tar.gz ]]
then
tar xf "$file"
elif [[ "$file" == *.7z ]]
then
7z x "$file"
else
echo "Unsupported file: $file"
return 1

View File

@@ -93,6 +93,7 @@ dist-win32: build-server build-win32
cp data/scrcpy-console.bat "$(DIST)/$(WIN32_TARGET_DIR)"
cp data/scrcpy-noconsole.vbs "$(DIST)/$(WIN32_TARGET_DIR)"
cp data/icon.png "$(DIST)/$(WIN32_TARGET_DIR)"
cp data/open_a_terminal_here.bat "$(DIST)/$(WIN32_TARGET_DIR)"
cp prebuilt-deps/ffmpeg-4.3.1-win32-shared/bin/avutil-56.dll "$(DIST)/$(WIN32_TARGET_DIR)/"
cp prebuilt-deps/ffmpeg-4.3.1-win32-shared/bin/avcodec-58.dll "$(DIST)/$(WIN32_TARGET_DIR)/"
cp prebuilt-deps/ffmpeg-4.3.1-win32-shared/bin/avformat-58.dll "$(DIST)/$(WIN32_TARGET_DIR)/"
@@ -101,7 +102,7 @@ dist-win32: build-server build-win32
cp prebuilt-deps/platform-tools/adb.exe "$(DIST)/$(WIN32_TARGET_DIR)/"
cp prebuilt-deps/platform-tools/AdbWinApi.dll "$(DIST)/$(WIN32_TARGET_DIR)/"
cp prebuilt-deps/platform-tools/AdbWinUsbApi.dll "$(DIST)/$(WIN32_TARGET_DIR)/"
cp prebuilt-deps/SDL2-2.0.18/i686-w64-mingw32/bin/SDL2.dll "$(DIST)/$(WIN32_TARGET_DIR)/"
cp prebuilt-deps/SDL2-2.0.20/i686-w64-mingw32/bin/SDL2.dll "$(DIST)/$(WIN32_TARGET_DIR)/"
dist-win64: build-server build-win64
mkdir -p "$(DIST)/$(WIN64_TARGET_DIR)"
@@ -110,15 +111,16 @@ dist-win64: build-server build-win64
cp data/scrcpy-console.bat "$(DIST)/$(WIN64_TARGET_DIR)"
cp data/scrcpy-noconsole.vbs "$(DIST)/$(WIN64_TARGET_DIR)"
cp data/icon.png "$(DIST)/$(WIN64_TARGET_DIR)"
cp prebuilt-deps/ffmpeg-4.3.1-win64-shared/bin/avutil-56.dll "$(DIST)/$(WIN64_TARGET_DIR)/"
cp prebuilt-deps/ffmpeg-4.3.1-win64-shared/bin/avcodec-58.dll "$(DIST)/$(WIN64_TARGET_DIR)/"
cp prebuilt-deps/ffmpeg-4.3.1-win64-shared/bin/avformat-58.dll "$(DIST)/$(WIN64_TARGET_DIR)/"
cp prebuilt-deps/ffmpeg-4.3.1-win64-shared/bin/swresample-3.dll "$(DIST)/$(WIN64_TARGET_DIR)/"
cp prebuilt-deps/ffmpeg-4.3.1-win64-shared/bin/swscale-5.dll "$(DIST)/$(WIN64_TARGET_DIR)/"
cp data/open_a_terminal_here.bat "$(DIST)/$(WIN64_TARGET_DIR)"
cp prebuilt-deps/ffmpeg-5.0-full_build-shared/bin/avutil-57.dll "$(DIST)/$(WIN64_TARGET_DIR)/"
cp prebuilt-deps/ffmpeg-5.0-full_build-shared/bin/avcodec-59.dll "$(DIST)/$(WIN64_TARGET_DIR)/"
cp prebuilt-deps/ffmpeg-5.0-full_build-shared/bin/avformat-59.dll "$(DIST)/$(WIN64_TARGET_DIR)/"
cp prebuilt-deps/ffmpeg-5.0-full_build-shared/bin/swresample-4.dll "$(DIST)/$(WIN64_TARGET_DIR)/"
cp prebuilt-deps/ffmpeg-5.0-full_build-shared/bin/swscale-6.dll "$(DIST)/$(WIN64_TARGET_DIR)/"
cp prebuilt-deps/platform-tools/adb.exe "$(DIST)/$(WIN64_TARGET_DIR)/"
cp prebuilt-deps/platform-tools/AdbWinApi.dll "$(DIST)/$(WIN64_TARGET_DIR)/"
cp prebuilt-deps/platform-tools/AdbWinUsbApi.dll "$(DIST)/$(WIN64_TARGET_DIR)/"
cp prebuilt-deps/SDL2-2.0.18/x86_64-w64-mingw32/bin/SDL2.dll "$(DIST)/$(WIN64_TARGET_DIR)/"
cp prebuilt-deps/SDL2-2.0.20/x86_64-w64-mingw32/bin/SDL2.dll "$(DIST)/$(WIN64_TARGET_DIR)/"
zip-win32: dist-win32
cd "$(DIST)/$(WIN32_TARGET_DIR)"; \

View File

@@ -46,15 +46,17 @@ public final class DesktopConnection implements Closeable {
return localSocket;
}
public static DesktopConnection open(Device device, boolean tunnelForward, boolean control) throws IOException {
public static DesktopConnection open(boolean tunnelForward, boolean control, boolean sendDummyByte) throws IOException {
LocalSocket videoSocket;
LocalSocket controlSocket = null;
if (tunnelForward) {
LocalServerSocket localServerSocket = new LocalServerSocket(SOCKET_NAME);
try {
videoSocket = localServerSocket.accept();
// send one byte so the client may read() to detect a connection error
videoSocket.getOutputStream().write(0);
if (sendDummyByte) {
// send one byte so the client may read() to detect a connection error
videoSocket.getOutputStream().write(0);
}
if (control) {
try {
controlSocket = localServerSocket.accept();
@@ -78,10 +80,7 @@ public final class DesktopConnection implements Closeable {
}
}
DesktopConnection connection = new DesktopConnection(videoSocket, controlSocket);
Size videoSize = device.getScreenInfo().getVideoSize();
connection.send(Device.getDeviceName(), videoSize.getWidth(), videoSize.getHeight());
return connection;
return new DesktopConnection(videoSocket, controlSocket);
}
public void close() throws IOException {
@@ -95,7 +94,7 @@ public final class DesktopConnection implements Closeable {
}
}
private void send(String deviceName, int width, int height) throws IOException {
public void sendDeviceMeta(String deviceName, int width, int height) throws IOException {
byte[] buffer = new byte[DEVICE_NAME_FIELD_LENGTH + 4];
byte[] deviceNameBytes = deviceName.getBytes(StandardCharsets.UTF_8);

View File

@@ -42,6 +42,11 @@ public final class Device {
void onClipboardTextChanged(String text);
}
private final Size deviceSize;
private final Rect crop;
private int maxSize;
private final int lockVideoOrientation;
private ScreenInfo screenInfo;
private RotationListener rotationListener;
private ClipboardListener clipboardListener;
@@ -69,7 +74,12 @@ public final class Device {
int displayInfoFlags = displayInfo.getFlags();
screenInfo = ScreenInfo.computeScreenInfo(displayInfo, options.getCrop(), options.getMaxSize(), options.getLockVideoOrientation());
deviceSize = displayInfo.getSize();
crop = options.getCrop();
maxSize = options.getMaxSize();
lockVideoOrientation = options.getLockVideoOrientation();
screenInfo = ScreenInfo.computeScreenInfo(displayInfo.getRotation(), deviceSize, crop, maxSize, lockVideoOrientation);
layerStack = displayInfo.getLayerStack();
SERVICE_MANAGER.getWindowManager().registerRotationWatcher(new IRotationWatcher.Stub() {
@@ -123,6 +133,11 @@ public final class Device {
}
}
public synchronized void setMaxSize(int newMaxSize) {
maxSize = newMaxSize;
screenInfo = ScreenInfo.computeScreenInfo(screenInfo.getReverseVideoRotation(), deviceSize, crop, newMaxSize, lockVideoOrientation);
}
public synchronized ScreenInfo getScreenInfo() {
return screenInfo;
}

View File

@@ -12,7 +12,6 @@ public class Options {
private int lockVideoOrientation = -1;
private boolean tunnelForward;
private Rect crop;
private boolean sendFrameMeta = true; // send PTS so that the client may record properly
private boolean control = true;
private int displayId;
private boolean showTouches;
@@ -21,6 +20,12 @@ public class Options {
private String encoderName;
private boolean powerOffScreenOnClose;
private boolean clipboardAutosync = true;
private boolean downsizeOnError = true;
// Options not used by the scrcpy client, but useful to use scrcpy-server directly
private boolean sendDeviceMeta = true; // send device name and size
private boolean sendFrameMeta = true; // send PTS so that the client may record properly
private boolean sendDummyByte = true; // write a byte on start to detect connection issues
public Ln.Level getLogLevel() {
return logLevel;
@@ -78,14 +83,6 @@ public class Options {
this.crop = crop;
}
public boolean getSendFrameMeta() {
return sendFrameMeta;
}
public void setSendFrameMeta(boolean sendFrameMeta) {
this.sendFrameMeta = sendFrameMeta;
}
public boolean getControl() {
return control;
}
@@ -149,4 +146,36 @@ public class Options {
public void setClipboardAutosync(boolean clipboardAutosync) {
this.clipboardAutosync = clipboardAutosync;
}
public boolean getDownsizeOnError() {
return downsizeOnError;
}
public void setDownsizeOnError(boolean downsizeOnError) {
this.downsizeOnError = downsizeOnError;
}
public boolean getSendDeviceMeta() {
return sendDeviceMeta;
}
public void setSendDeviceMeta(boolean sendDeviceMeta) {
this.sendDeviceMeta = sendDeviceMeta;
}
public boolean getSendFrameMeta() {
return sendFrameMeta;
}
public void setSendFrameMeta(boolean sendFrameMeta) {
this.sendFrameMeta = sendFrameMeta;
}
public boolean getSendDummyByte() {
return sendDummyByte;
}
public void setSendDummyByte(boolean sendDummyByte) {
this.sendDummyByte = sendDummyByte;
}
}

View File

@@ -25,24 +25,32 @@ public class ScreenEncoder implements Device.RotationListener {
private static final int REPEAT_FRAME_DELAY_US = 100_000; // repeat after 100ms
private static final String KEY_MAX_FPS_TO_ENCODER = "max-fps-to-encoder";
// Keep the values in descending order
private static final int[] MAX_SIZE_FALLBACK = {2560, 1920, 1600, 1280, 1024, 800};
private static final int NO_PTS = -1;
private final AtomicBoolean rotationChanged = new AtomicBoolean();
private final ByteBuffer headerBuffer = ByteBuffer.allocate(12);
private String encoderName;
private List<CodecOption> codecOptions;
private int bitRate;
private int maxFps;
private boolean sendFrameMeta;
private final String encoderName;
private final List<CodecOption> codecOptions;
private final int bitRate;
private final int maxFps;
private final boolean sendFrameMeta;
private final boolean downsizeOnError;
private long ptsOrigin;
public ScreenEncoder(boolean sendFrameMeta, int bitRate, int maxFps, List<CodecOption> codecOptions, String encoderName) {
private boolean firstFrameSent;
public ScreenEncoder(boolean sendFrameMeta, int bitRate, int maxFps, List<CodecOption> codecOptions, String encoderName,
boolean downsizeOnError) {
this.sendFrameMeta = sendFrameMeta;
this.bitRate = bitRate;
this.maxFps = maxFps;
this.codecOptions = codecOptions;
this.encoderName = encoderName;
this.downsizeOnError = downsizeOnError;
}
@Override
@@ -91,6 +99,23 @@ public class ScreenEncoder implements Device.RotationListener {
alive = encode(codec, fd);
// do not call stop() on exception, it would trigger an IllegalStateException
codec.stop();
} catch (IllegalStateException e) {
Ln.e("Encoding error: " + e.getClass().getName() + ": " + e.getMessage());
if (!downsizeOnError || firstFrameSent) {
// Fail immediately
throw e;
}
int newMaxSize = chooseMaxSizeFallback(screenInfo.getVideoSize());
if (newMaxSize == 0) {
// Definitively fail
throw e;
}
// Retry with a smaller device size
Ln.i("Retrying with -m" + newMaxSize + "...");
device.setMaxSize(newMaxSize);
alive = true;
} finally {
destroyDisplay(display);
codec.release();
@@ -102,6 +127,18 @@ public class ScreenEncoder implements Device.RotationListener {
}
}
private static int chooseMaxSizeFallback(Size failedSize) {
int currentMaxSize = Math.max(failedSize.getWidth(), failedSize.getHeight());
for (int value : MAX_SIZE_FALLBACK) {
if (value < currentMaxSize) {
// We found a smaller value to reduce the video size
return value;
}
}
// No fallback, fail definitively
return 0;
}
private boolean encode(MediaCodec codec, FileDescriptor fd) throws IOException {
boolean eof = false;
MediaCodec.BufferInfo bufferInfo = new MediaCodec.BufferInfo();
@@ -122,6 +159,10 @@ public class ScreenEncoder implements Device.RotationListener {
}
IO.writeFully(fd, codecBuffer);
if ((bufferInfo.flags & MediaCodec.BUFFER_FLAG_CODEC_CONFIG) == 0) {
// If this is not a config packet, then it contains a frame
firstFrameSent = true;
}
}
} finally {
if (outputBufferId >= 0) {

View File

@@ -80,15 +80,12 @@ public final class ScreenInfo {
return new ScreenInfo(newContentRect, newUnlockedVideoSize, newDeviceRotation, lockedVideoOrientation);
}
public static ScreenInfo computeScreenInfo(DisplayInfo displayInfo, Rect crop, int maxSize, int lockedVideoOrientation) {
int rotation = displayInfo.getRotation();
public static ScreenInfo computeScreenInfo(int rotation, Size deviceSize, Rect crop, int maxSize, int lockedVideoOrientation) {
if (lockedVideoOrientation == Device.LOCK_VIDEO_ORIENTATION_INITIAL) {
// The user requested to lock the video orientation to the current orientation
lockedVideoOrientation = rotation;
}
Size deviceSize = displayInfo.getSize();
Rect contentRect = new Rect(0, 0, deviceSize.getWidth(), deviceSize.getHeight());
if (crop != null) {
if (rotation % 2 != 0) { // 180s preserve dimensions

View File

@@ -1,7 +1,6 @@
package com.genymobile.scrcpy;
import android.graphics.Rect;
import android.media.MediaCodec;
import android.media.MediaCodecInfo;
import android.os.BatteryManager;
import android.os.Build;
@@ -20,6 +19,7 @@ public final class Server {
private static void initAndCleanUp(Options options) {
boolean mustDisableShowTouchesOnCleanUp = false;
int restoreStayOn = -1;
boolean restoreNormalPowerMode = options.getControl(); // only restore power mode if control is enabled
if (options.getShowTouches() || options.getStayAwake()) {
Settings settings = Device.getSettings();
if (options.getShowTouches()) {
@@ -52,7 +52,8 @@ public final class Server {
}
try {
CleanUp.configure(options.getDisplayId(), restoreStayOn, mustDisableShowTouchesOnCleanUp, true, options.getPowerOffScreenOnClose());
CleanUp.configure(options.getDisplayId(), restoreStayOn, mustDisableShowTouchesOnCleanUp, restoreNormalPowerMode,
options.getPowerOffScreenOnClose());
} catch (IOException e) {
Ln.e("Could not configure cleanup", e);
}
@@ -67,10 +68,15 @@ public final class Server {
boolean tunnelForward = options.isTunnelForward();
boolean control = options.getControl();
boolean sendDummyByte = options.getSendDummyByte();
try (DesktopConnection connection = DesktopConnection.open(device, tunnelForward, control)) {
try (DesktopConnection connection = DesktopConnection.open(tunnelForward, control, sendDummyByte)) {
if (options.getSendDeviceMeta()) {
Size videoSize = device.getScreenInfo().getVideoSize();
connection.sendDeviceMeta(Device.getDeviceName(), videoSize.getWidth(), videoSize.getHeight());
}
ScreenEncoder screenEncoder = new ScreenEncoder(options.getSendFrameMeta(), options.getBitRate(), options.getMaxFps(), codecOptions,
options.getEncoderName());
options.getEncoderName(), options.getDownsizeOnError());
Thread controllerThread = null;
Thread deviceMessageSenderThread = null;
@@ -200,10 +206,6 @@ public final class Server {
Rect crop = parseCrop(value);
options.setCrop(crop);
break;
case "send_frame_meta":
boolean sendFrameMeta = Boolean.parseBoolean(value);
options.setSendFrameMeta(sendFrameMeta);
break;
case "control":
boolean control = Boolean.parseBoolean(value);
options.setControl(control);
@@ -237,6 +239,30 @@ public final class Server {
boolean clipboardAutosync = Boolean.parseBoolean(value);
options.setClipboardAutosync(clipboardAutosync);
break;
case "downsize_on_error":
boolean downsizeOnError = Boolean.parseBoolean(value);
options.setDownsizeOnError(downsizeOnError);
break;
case "send_device_meta":
boolean sendDeviceMeta = Boolean.parseBoolean(value);
options.setSendDeviceMeta(sendDeviceMeta);
break;
case "send_frame_meta":
boolean sendFrameMeta = Boolean.parseBoolean(value);
options.setSendFrameMeta(sendFrameMeta);
break;
case "send_dummy_byte":
boolean sendDummyByte = Boolean.parseBoolean(value);
options.setSendDummyByte(sendDummyByte);
break;
case "raw_video_stream":
boolean rawVideoStream = Boolean.parseBoolean(value);
if (rawVideoStream) {
options.setSendDeviceMeta(false);
options.setSendFrameMeta(false);
options.setSendDummyByte(false);
}
break;
default:
Ln.w("Unknown server option: " + key);
break;
@@ -263,16 +289,6 @@ public final class Server {
}
private static void suggestFix(Throwable e) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
if (e instanceof MediaCodec.CodecException) {
MediaCodec.CodecException mce = (MediaCodec.CodecException) e;
if (mce.getErrorCode() == 0xfffffc0e) {
Ln.e("The hardware encoder is not able to encode at the given definition.");
Ln.e("Try with a lower definition:");
Ln.e(" scrcpy -m 1024");
}
}
}
if (e instanceof InvalidDisplayIdException) {
InvalidDisplayIdException idie = (InvalidDisplayIdException) e;
int[] displayIds = idie.getAvailableDisplayIds();

View File

@@ -16,6 +16,7 @@ public final class InputManager {
private final IInterface manager;
private Method injectInputEventMethod;
private boolean alternativeInjectInputEventMethod;
private static Method setDisplayIdMethod;
@@ -25,7 +26,12 @@ public final class InputManager {
private Method getInjectInputEventMethod() throws NoSuchMethodException {
if (injectInputEventMethod == null) {
injectInputEventMethod = manager.getClass().getMethod("injectInputEvent", InputEvent.class, int.class);
try {
injectInputEventMethod = manager.getClass().getMethod("injectInputEvent", InputEvent.class, int.class);
} catch (NoSuchMethodException e) {
injectInputEventMethod = manager.getClass().getMethod("injectInputEvent", InputEvent.class, int.class, int.class);
alternativeInjectInputEventMethod = true;
}
}
return injectInputEventMethod;
}
@@ -33,6 +39,10 @@ public final class InputManager {
public boolean injectInputEvent(InputEvent inputEvent, int mode) {
try {
Method method = getInjectInputEventMethod();
if (alternativeInjectInputEventMethod) {
// See <https://github.com/Genymobile/scrcpy/issues/2250>
return (boolean) method.invoke(manager, inputEvent, mode, 0);
}
return (boolean) method.invoke(manager, inputEvent, mode);
} catch (InvocationTargetException | IllegalAccessException | NoSuchMethodException e) {
Ln.e("Could not invoke method", e);