Compare commits

...

139 Commits

Author SHA1 Message Date
c0a75ca746 Downscale and retry also on early MediaCodec error
The new retry mechanism with a lower definition only worked if the error
occurred during encode(). For example:

    java.lang.IllegalStateException
        at android.media.MediaCodec.native_dequeueOutputBuffer(Native Method)
        at android.media.MediaCodec.dequeueOutputBuffer(MediaCodec.java:3452)
        at com.genymobile.scrcpy.ScreenEncoder.encode(ScreenEncoder.java:114)
        at com.genymobile.scrcpy.ScreenEncoder.internalStreamScreen(ScreenEncoder.java:95)
        at com.genymobile.scrcpy.ScreenEncoder.streamScreen(ScreenEncoder.java:61)
        at com.genymobile.scrcpy.Server.scrcpy(Server.java:80)
        at com.genymobile.scrcpy.Server.main(Server.java:255)

However, MediaCodec may also fail before encoding, during configure() or
start(). For example:

    android.media.MediaCodec$CodecException: Error 0xfffffc0e
        at android.media.MediaCodec.native_configure(Native Method)
        at android.media.MediaCodec.configure(MediaCodec.java:1956)
        at android.media.MediaCodec.configure(MediaCodec.java:1885)
        at com.genymobile.scrcpy.ScreenEncoder.configure(ScreenEncoder.java:158)
        at com.genymobile.scrcpy.ScreenEncoder.streamScreen(ScreenEncoder.java:68)
        at com.genymobile.scrcpy.Server.scrcpy(Server.java:28)
        at com.genymobile.scrcpy.Server.main(Server.java:110)

Also downscale and retry in these cases.

Refs #2947 <https://github.com/Genymobile/scrcpy/pull/2947>
Refs #2988 <https://github.com/Genymobile/scrcpy/issues/2988>
PR #2990 <https://github.com/Genymobile/scrcpy/pull/2990>
2022-02-02 08:22:17 +01:00
f02f2135cd Fix include for standard library header 2022-02-01 21:40:15 +01:00
9b4360b6b8 Add warning in function documentation
The function parsing "ip route" output modifies the input buffer to
tokenize in place. This must be mentioned in the function documentation.
2022-02-01 21:39:14 +01:00
c8d0f5cdeb Fix sc_str_truncate() documentation
The function was initially implemented to truncate lines, but was later
generalized to accept custom delimiters. The whole documentation has not
been updated accordingly.

Refs 9619ade706
2022-02-01 21:38:50 +01:00
22d9f0faf4 Fix comment typo 2022-02-01 21:09:02 +01:00
a1967b4dfd Update FAQ.zh-Hans.md to v1.22
PR #2989 <https://github.com/Genymobile/scrcpy/pull/2989>

Signed-off-by: Romain Vimont <rom@rom1v.com>
2022-01-30 16:56:18 +01:00
a86deab3d4 Update README.zh-Hans.md to v1.22
PR #2989 <https://github.com/Genymobile/scrcpy/pull/2989>

Signed-off-by: Romain Vimont <rom@rom1v.com>
2022-01-30 16:55:45 +01:00
f4c7044b46 Update links to v1.22 2022-01-29 16:16:41 +01:00
64a09513ae Bump version to 1.22 2022-01-29 15:44:52 +01:00
28054cd471 Merge branch 'master' into dev 2022-01-29 15:40:48 +01:00
334f46995a Update README.zh-Hans.md to v1.21
PR #2978 <https://github.com/Genymobile/scrcpy/pull/2978>

Signed-off-by: Romain Vimont <rom@rom1v.com>
2022-01-29 15:10:05 +01:00
38cdcdda50 Improve prebuilt system
This aims to fix two issues with the previous implementation:
 1. the whole content of downloaded archives were extracted, while only
    few files are necessary;
 2. the archives were extracted in the prebuild-deps/ directory as is.

As a consequence of (2), the actual directory name relied on the root
directory of the archive. For adb, this root directory was always
"platform-tools", so when bumping the adb version, the target directory
already existed and the dependency was not upgraded (the old one had to
be removed manually).

Expose common function to download a file and check its checksum, but
let the custom script for each dependency extract only the needed files
and reorganize the content if necessary.
2022-01-29 14:40:21 +01:00
eaba613633 Revert "Upgrade platform-tools (32.0.0) for Windows"
This reverts commit c0de365f67.

The new adb.exe crashes.

Refs #2981 comment <https://github.com/Genymobile/scrcpy/issues/2981#issuecomment-1024569778>
2022-01-29 11:46:39 +01:00
b8d7f36ba3 Fix SC_EXIT_CODE_NONE value
The exit code on windows is stored in a DWORD, an unsigned long:
<https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-dtyp/262627d8-3418-4627-9218-4ffe110850b2>

Use the max value of this type for SC_EXIT_CODE_NONE.
2022-01-29 08:05:59 +01:00
80bec70852 Add helper to log Windows system errors
It will help to log errors returned by GetLastError() or
WSAGetLastError():
 - <https://docs.microsoft.com/en-us/windows/win32/api/errhandlingapi/nf-errhandlingapi-getlasterror>
 - <https://docs.microsoft.com/en-us/windows/win32/api/winsock/nf-winsock-wsagetlasterror>
 - <https://docs.microsoft.com/en-us/windows/win32/debug/system-error-codes#system-error-codes>

Always log the errors in English to be able to read them in bug reports.
2022-01-28 09:15:18 +01:00
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
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
c5be0d6438 Document OTG mode in README
PR #2974 <https://github.com/Genymobile/scrcpy/pull/2974>
2022-01-27 23:36:25 +01:00
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
2762f5d183 Move AOA/HID code to usb/
PR #2974 <https://github.com/Genymobile/scrcpy/pull/2974>
2022-01-27 23:35:34 +01:00
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
8ea6fb1f0f Print version on stdout
Refs b25404ee4b
2022-01-27 21:26:56 +01:00
b546c33eff Do not print scrcpy version twice on --version
Refs 6da6d905c2
2022-01-27 21:12:46 +01:00
8615813005 Update README.sp.md to v1.21
PR #2962 <https://github.com/Genymobile/scrcpy/pull/2962>

Signed-off-by: Romain Vimont <rom@rom1v.com>
2022-01-27 19:14:07 +01:00
f51c53e913 Mention "screen copy" in README
Help users make sense of the app name :)
2022-01-27 19:05:33 +01:00
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
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
4817cadd09 Fix code style
Align function parameters.
2022-01-27 19:02:07 +01:00
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
8e4d3beb01 Fix return value on adb commands error 2022-01-27 16:47:51 +01:00
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
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
9d2e00697e Use sc_ prefix for control_msg enums
Refs afa4a1b728
2022-01-27 16:47:51 +01:00
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
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
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
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
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
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
ca516f4318 Refactor if-block in cli
Several tests must be performed if opts->control is false.
2022-01-24 21:44:28 +01:00
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
e0bce1725b Fix header guard prefix 2022-01-24 21:37:40 +01:00
ae8fdda09e Improve FAQ explanations 2022-01-24 08:55:27 +01:00
1ff69e21c2 Update error messages in FAQ
With the recent versions, scrcpy first executes "adb get-serialno", so
the adb error messages are not exactly the same.
2022-01-24 08:54:09 +01:00
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
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
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
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
8c7f0ed5ea Fix warning message
Make the message consistent for HID keyboard and HID mouse.
2022-01-23 21:16:35 +01:00
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
1f65b1bf87 Remove inline hint
There is no reason to request inlining here.
2022-01-23 15:01:11 +01:00
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
308a1f8192 Simplify error handling in sc_aoa_init()
Use goto to avoid many repetitions.
2022-01-23 12:32:37 +01:00
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
7e35bfe382 Refactor if-blocks
Group all conditions requiring a controller in a single if-block.
2022-01-23 12:16:24 +01:00
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
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
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
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
81ff7ebd06 Simplify event loop
Merge single event handling with the event loop function.
2022-01-21 21:52:41 +01:00
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
ebef027c4f Do not return status for event handling
It is never read. Simplify.
2022-01-21 21:52:41 +01:00
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
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
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
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
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
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
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
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
8fa9e6b01a Mention auto-downsize feature in FAQ
PR #2947 <https://github.com/Genymobile/scrcpy/pull/2947>
2022-01-21 18:42:38 +01:00
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
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
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
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
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
117fe32626 Fix visibility modifier
Refs b7a06278fe
2022-01-21 18:36:44 +01:00
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
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
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
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
479abc8c77 Reference Windows USB driver for Google devices
PR #2945 <https://github.com/Genymobile/scrcpy/pull/2945>

Signed-off-by: Romain Vimont <rom@rom1v.com>
2022-01-16 17:46:43 +01:00
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
60bf133ac2 Add final modifier to ScreenEncoder fields
These fields are only set from the constructor.
2022-01-15 23:25:55 +01:00
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
afa4a1b728 Use sc_ prefix for control_msg 2022-01-14 22:17:30 +01:00
3a4d5c7f18 Use sc_ prefix for controller 2022-01-14 22:17:30 +01:00
5f7ddff8ae Use sc_ prefix for input_manager 2022-01-14 22:17:30 +01:00
2a0c2e5e99 Use sc_ prefix for screen 2022-01-14 22:17:30 +01:00
a6644e831b Fix code style
Limit to 80 chars.
2022-01-14 20:57:03 +01:00
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
43aff4af73 Document HID mouse in README 2022-01-04 17:41:40 +01:00
cba84f6999 Add support for HID mouse 2022-01-04 17:41:40 +01:00
ed2e45ee29 Refactor AOA/HID keyboard initialization
This paves the way to add support for HID mouse initialization.
2022-01-04 17:41:40 +01:00
aee1b39790 Add CLAMP() macro 2022-01-04 17:41:40 +01:00
17d01b5bf7 Add UI/UX support for relative mouse mode
In relative mouse mode, the mouse pointer must be "captured" from the
computer.

Toggle (disable/enable) relative mouse mode using any of the hardcoded
capture keys:
 - left-Alt
 - left-Super
 - right-Super

These capture keys do not conflict with shortcuts, since a shortcut is
always a combination of the MOD key and some other key, while the
capture key triggers an action only if it is pressed and released alone.

The relative mouse mode is also automatically enabled on any click in
the window, and automatically disabled on focus lost (it is possible to
lose focus even without the mouse).
2022-01-04 17:41:40 +01:00
40fca82b60 Forward all motion events to mouse processors
The decision to not send motion events when no click is pressed is
specific to Android mouse injection. Other mouse processors (e.g. for
HID mouse) will need to receive all events.
2022-01-04 17:41:40 +01:00
643293752d Provide relative mouse motion vector in event
This will allow the mouse processor to handle relative motion easily.
2022-01-04 17:41:40 +01:00
b5855e5deb Add relative mode flag to mouse processors
The default mouse injection works in absolute mode: it forwards clicks
at a specific position on screen.

To support HID mouse, add a flag to indicate that the mouse processor
works in relative mode: it forwards mouse motion vectors, without any
absolute reference to the screen.
2022-01-04 17:41:40 +01:00
924375487e Pass buttons state in scroll events
A scroll event might be produced when a mouse button is pressed (for
example when scrolling while selecting a text). For consistency, pass
the actual buttons state (instead of 0).

In practice, it seems that this use case does not work properly with
Android event injection, but it will work with HID mouse.
2022-01-04 17:41:40 +01:00
7121a0dc53 Destroy acksync immediately on error
If AOA or HID keyboard may not be initialized for some reason, acksync
is useless.
2022-01-04 17:41:40 +01:00
f04812fc71 Remove duplicate boolean
The AOA initialization state is already tracked by aoa_hid_initialized.
2022-01-04 17:41:40 +01:00
5ce1ccde85 Reorder controller and HID initialization
This allows to merge two "#ifdef HAVE_AOA_HID" blocks to simplify.
2022-01-04 17:41:40 +01:00
6102a0b5bb Move input_manager into screen
The input_manager is strongly tied to the screen, it could not work
independently of the specific screen implementation.

To implement a user-friendly HID mouse behavior, some SDL events
will need to be handled both by the screen and by the input manager. For
example, a click must typically be handled by the input_manager so that
it is forwarded to the device, but in HID mouse mode, the first click
should be handled by the screen to capture the mouse (enable relative
mouse mode).

Make the input_manager a descendant of the screen, so that the screen
decides what to do on SDL events.

Concretely, replace this structure hierarchy:

     +- struct scrcpy
        +- struct input_manager
        +- struct screen

by this one:

     +- struct scrcpy
        +- struct screen
           +- struct input_manager
2022-01-04 17:41:35 +01:00
2b34e1224e Use separate struct for input manager params
This avoids to directly pass the options instance (which contains more
data than strictly necessary), and limit the number of parameters for
the init function.
2022-01-04 15:14:38 +01:00
a9d23400cd Remove unused enum value requiring SDL 2.0.18
Refs b8fed50639
Fixes #2924 <https://github.com/Genymobile/scrcpy/issues/2924>
2022-01-04 15:11:33 +01:00
cca3c953da Enable virtual finger only on left click
The pinch-to-zoom feature must only be enabled with Ctrl+left_click.
2022-01-02 00:00:33 +01:00
57f1655d4b Make some mouse processors ops optional
Do not force all mouse processors to implement scroll events or touch
events.
2022-01-01 23:34:56 +01:00
bc674721dc Make process_text() optional
Not all key processors support text injection (HID keyboard does not
support it).

Instead of providing a dummy op function, set it to NULL and check on
the caller side before calling it.
2022-01-01 23:31:01 +01:00
63e29b1782 Apply buttons mask if not --forward-all-clicks
If --forward-all-clicks is not set, then only left clicks are forwarded.
For consistency, also mask the buttons state in other events.
2022-01-01 23:31:01 +01:00
3c15cbdaf8 Reorder mouse processor ops
Group the mouse events callbacks before the touch event callback.
2022-01-01 23:31:01 +01:00
96e0e89740 Simplify mouse injection implementation
The static functions are now so simple that they become unnecessary: the
control message may be initialized directly instead.
2022-01-01 23:30:55 +01:00
a1f2f5fbd3 Make some event conversions infallible
When the implementation handles all possible input values, it may never
fail.
2022-01-01 23:28:45 +01:00
9460bdd87b Use scrcpy input events for mouse processors
Pass scrcpy input events instead of SDL input events to mouse
processors.

These events represent exactly what mouse processors need, abstracted
from any visual orientation and scaling applied on the SDL window.

This makes the mouse processors independent of the "screen" instance,
and the implementation source code independent of the SDL API.
2022-01-01 23:28:45 +01:00
b4b638e8fe Use scrcpy input events for key processors
Pass scrcpy input events instead of SDL input events to key processors.

This makes the source code of key processors independent of the SDL API.
2022-01-01 23:28:45 +01:00
e4396e34c2 Use common sc_action in input manager
Now that the scrcpy input events API exposes a sc_action enum, use the
same from the input manager.
2022-01-01 23:28:45 +01:00
b8fed50639 Add intermediate input events layer
This aims to make the key/mouse processors independent of the "screen",
by processing scrcpy-specific input events instead of SDL events.

In particular, these scrcpy events are not impacted by any UI window
scaling or rotation (contrary to SDL events).
2022-01-01 23:28:45 +01:00
d540c72e7c Rename SC_MOD_* to SC_SHORTCUT_MOD_*
This will avoid conflicts with new SC_MOD_* constants.
2022-01-01 23:28:45 +01:00
cd5891fee6 Remove actions bitset
The input manager exposed functions taking an "actions" parameter,
containing a bitmask-OR of ACTION_UP and ACTION_DOWN.

But they are never called with both actions simultaneously anymore, so
simplify.

Refs 964b6d2243
Refs d0739911a3
2022-01-01 23:28:45 +01:00
26ee7ce566 Expose V4L2 option on all platforms
This allows to report a meaningful error message if an unsupported
feature is used on an incompatible platform. This is consistent with the
behavior of -K/--hid-keyboard.
2022-01-01 23:28:45 +01:00
ba28d817fb Fail on unsupported HID option
If the feature is not supported on the platform, fail during command
line parsing instead of using a fallback.
2022-01-01 23:28:45 +01:00
37124e1452 Avoid unused function warning
If HAVE_SOCK_CLOEXEC is not defined, then sc_raw_socket_close() is never
used. Add an #ifndef block to remove the warning.
2022-01-01 23:27:34 +01:00
25a4135935 Mention react-native menu shortcut in README
PR #2879 <https://github.com/Genymobile/scrcpy/pull/2879>

Signed-off-by: Romain Vimont <rom@rom1v.com>
2021-12-18 16:48:44 +01:00
5704ec6967 AUR to official Arch Repository
PR #2844 <https://github.com/Genymobile/scrcpy/pull/2844>

Signed-off-by: Romain Vimont <rom@rom1v.com>
2021-12-02 08:48:47 +01:00
92 changed files with 4585 additions and 1861 deletions

View File

@ -270,10 +270,10 @@ install` must be run as root)._
#### Option 2: Use prebuilt server
- [`scrcpy-server-v1.21`][direct-scrcpy-server]
_(SHA-256: dbcccab523ee26796e55ea33652649e4b7af498edae9aa75e4d4d7869c0ab848)_
- [`scrcpy-server-v1.22`][direct-scrcpy-server]
_(SHA-256: c05d273eec7533c0e106282e0254cf04e7f5e8f0c2920ca39448865fab2a419b)_
[direct-scrcpy-server]: https://github.com/Genymobile/scrcpy/releases/download/v1.21/scrcpy-server-v1.21
[direct-scrcpy-server]: https://github.com/Genymobile/scrcpy/releases/download/v1.22/scrcpy-server-v1.22
Download the prebuilt server somewhere, and specify its path during the Meson
configuration:

38
FAQ.md
View File

@ -12,7 +12,7 @@ Here are the common reported problems and their status.
In that case, it will print this error:
> ERROR: "adb push" returned with value 1
> ERROR: "adb get-serialno" returned with value 1
This is typically not a bug in _scrcpy_, but a problem in your environment.
@ -32,28 +32,38 @@ in the release, so it should work out-of-the-box.
### Device unauthorized
Check [stackoverflow][device-unauthorized].
> error: device unauthorized.
> This adb server's $ADB_VENDOR_KEYS is not set
> Try 'adb kill-server' if that seems wrong.
> Otherwise check for a confirmation dialog on your device.
When connecting, a popup should open on the device. You must authorize USB
debugging.
If it does not open, check [stackoverflow][device-unauthorized].
[device-unauthorized]: https://stackoverflow.com/questions/23081263/adb-android-device-unauthorized
### Device not detected
> adb: error: failed to get feature set: no devices/emulators found
> error: no devices/emulators found
Check that you correctly enabled [adb debugging][enable-adb].
If your device is not detected, you may need some [drivers] (on Windows).
If your device is not detected, you may need some [drivers] (on Windows). There is a separate [USB driver for Google devices][google-usb-driver].
[enable-adb]: https://developer.android.com/studio/command-line/adb.html#Enabling
[drivers]: https://developer.android.com/studio/run/oem-usb.html
[google-usb-driver]: https://developer.android.com/studio/run/win-usb
### Several devices connected
If several devices are connected, you will encounter this error:
> adb: error: failed to get feature set: more than one device/emulator
> error: more than one device/emulator
the identifier of the device you want to mirror must be provided:
@ -61,7 +71,7 @@ the identifier of the device you want to mirror must be provided:
scrcpy -s 01234567890abcdef
```
Note that if your device is connected over TCP/IP, you'll get this message:
Note that if your device is connected over TCP/IP, you might get this message:
> adb: error: more than one device/emulator
> ERROR: "adb reverse" returned with value 1
@ -219,6 +229,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 +258,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.
@ -285,4 +305,4 @@ This FAQ is available in other languages:
- [Italiano (Italiano, `it`) - v1.19](FAQ.it.md)
- [한국어 (Korean, `ko`) - v1.11](FAQ.ko.md)
- [简体中文 (Simplified Chinese, `zh-Hans`) - v1.18](FAQ.zh-Hans.md)
- [简体中文 (Simplified Chinese, `zh-Hans`) - v1.22](FAQ.zh-Hans.md)

View File

@ -1,7 +1,12 @@
只有原版的[FAQ](FAQ.md)会保持更新。
本文根据[d6aaa5]翻译。
_Only the original [FAQ.md](FAQ.md) is guaranteed to be up-to-date._
[d6aaa5]:https://github.com/Genymobile/scrcpy/blob/d6aaa5bf9aa3710660c683b6e3e0ed971ee44af5/FAQ.md
_只有原版的 [FAQ.md](FAQ.md)是保证最新的。_
Current version is based on [28054cd]
本文根据[28054cd]进行翻译。
[28054cd]: https://github.com/Genymobile/scrcpy/blob/28054cd471f848733e11372c9d745cd5d71e6ce7/FAQ.md
# 常见问题
@ -9,11 +14,11 @@
## `adb` 相关问题
`scrcpy` 执行 `adb` 命令来初始化和设备之间的连接。如果`adb` 执行失败了, scrcpy 就无法工作。
`scrcpy` 执行 `adb` 命令来初始化和设备之间的连接。如果 `adb` 执行失败了, scrcpy 就无法工作。
在这种情况中,将会输出这个错误:
> ERROR: "adb push" returned with value 1
> ERROR: "adb get-serialno" returned with value 1
这通常不是 _scrcpy_ 的bug而是你的环境的问题。
@ -33,28 +38,37 @@ adb devices
### 设备未授权
参见这里 [stackoverflow][device-unauthorized].
> error: device unauthorized.
> This adb server's $ADB_VENDOR_KEYS is not set
> Try 'adb kill-server' if that seems wrong.
> Otherwise check for a confirmation dialog on your device.
连接时,在设备上应该会打开一个弹出窗口。 您必须授权 USB 调试。
如果没有打开,参见[stackoverflow][device-unauthorized].
[device-unauthorized]: https://stackoverflow.com/questions/23081263/adb-android-device-unauthorized
### 未检测到设备
> adb: error: failed to get feature set: no devices/emulators found
> error: no devices/emulators found
确认已经正确启用 [adb debugging][enable-adb].
如果你的设备没有被检测到,你可能需要一些[驱动][drivers] (在 Windows上).
如果你的设备没有被检测到,你可能需要一些[驱动][drivers] (在 Windows上)。这里有一个单独的 [适用于Google设备的USB驱动][google-usb-driver].
[enable-adb]: https://developer.android.com/studio/command-line/adb.html#Enabling
[drivers]: https://developer.android.com/studio/run/oem-usb.html
[google-usb-driver]: https://developer.android.com/studio/run/win-usb
### 已连接多个设备
如果连接了多个设备,您将遇到以下错误:
> adb: error: failed to get feature set: more than one device/emulator
> error: more than one device/emulator
必须提供要镜像的设备的标识符:
@ -90,19 +104,19 @@ scrcpy
### 设备断开连接
如果 _scrcpy_ 在警告“设备连接断开”的情况下自动中止,那就意味着`adb`连接已经断开了。
请尝试使用另一条USB线或者电脑上的另一个USB接口。
请参看 [#281] 和 [#283]。
请尝试使用另一条USB线或者电脑上的另一个USB接口。请参看 [#281] 和 [#283]。
[#281]: https://github.com/Genymobile/scrcpy/issues/281
[#283]: https://github.com/Genymobile/scrcpy/issues/283
## 控制相关问题
### 鼠标和键盘不起作用
在某些设备上,您可能需要启用一个选项以允许 [模拟输入][simulating input]。
在开发者选项中,打开:
> **USB调试 (安全设置)**
@ -115,10 +129,12 @@ scrcpy
可输入的文本[被限制为ASCII字符][text-input]。也可以用一些小技巧输入一些[带重音符号的字符][accented-characters],但是仅此而已。参见[#37]。
自 Linux 上的 scrcpy v1.20 之后,可以模拟[物理键盘][hid] (HID)。
[text-input]: https://github.com/Genymobile/scrcpy/issues?q=is%3Aopen+is%3Aissue+label%3Aunicode
[accented-characters]: https://blog.rom1v.com/2018/03/introducing-scrcpy/#handle-accented-characters
[#37]: https://github.com/Genymobile/scrcpy/issues/37
[hid]: README.md#physical-keyboard-simulation-hid
## 客户端相关问题
@ -129,7 +145,6 @@ scrcpy
[#40]: https://github.com/Genymobile/scrcpy/issues/40
为了提升降尺度的质量如果渲染器是OpenGL并且支持mip映射就会自动开启三线性过滤。
在Windows上你可能希望强制使用OpenGL
@ -177,6 +192,7 @@ scrcpy
## 崩溃
### 异常
可能有很多原因。一个常见的原因是您的设备无法按给定清晰度进行编码:
> ```
@ -204,12 +220,40 @@ scrcpy -m 1024
scrcpy -m 800
```
自 scrcpy v1.22以来scrcpy 会自动在失败前以更低的分辨率重试。这种行为可以用`--no-downsize-on-error`关闭。
你也可以尝试另一种 [编码器](README.md#encoder)。
如果您在 Android 12 上遇到此异常,则只需升级到 scrcpy >= 1.18 (见 [#2129])
```
> ERROR: Exception on thread Thread[main,5,main]
java.lang.AssertionError: java.lang.reflect.InvocationTargetException
at com.genymobile.scrcpy.wrappers.SurfaceControl.setDisplaySurface(SurfaceControl.java:75)
...
Caused by: java.lang.reflect.InvocationTargetException
at java.lang.reflect.Method.invoke(Native Method)
at com.genymobile.scrcpy.wrappers.SurfaceControl.setDisplaySurface(SurfaceControl.java:73)
... 7 more
Caused by: java.lang.IllegalArgumentException: displayToken must not be null
at android.view.SurfaceControl$Transaction.setDisplaySurface(SurfaceControl.java:3067)
at android.view.SurfaceControl.setDisplaySurface(SurfaceControl.java:2147)
... 9 more
```
[#2129]: https://github.com/Genymobile/scrcpy/issues/2129
## Windows命令行
一些Windows用户不熟悉命令行。以下是如何打开终端并带参数执行`scrcpy`
从 v1.22 开始,增加了一个“快捷方式”,可以直接在 scrcpy 目录打开一个终端。双击`open_a_terminal_here.bat`,然后输入你的命令。 例如
```
scrcpy --record file.mkv
```
您也可以打开终端并手动转到 scrcpy 文件夹:
1. 按下 <kbd>Windows</kbd>+<kbd>r</kbd>,打开一个对话框。
2. 输入 `cmd` 并按 <kbd>Enter</kbd>,这样就打开了一个终端。
@ -233,7 +277,7 @@ scrcpy -m 800
scrcpy --prefer-text --turn-screen-off --stay-awake
```
然后双击刚刚创建的文件。
然后只需双击刚刚创建的文件。
你也可以编辑 `scrcpy-console.bat` 或者 `scrcpy-noconsole.vbs`(的副本)来添加参数。

View File

@ -1,7 +1,9 @@
# scrcpy (v1.21)
# scrcpy (v1.22)
<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
@ -31,6 +33,9 @@ Its features include:
- device screen [as a webcam (V4L2)](#v4l2loopback) (Linux-only)
- [physical keyboard simulation (HID)](#physical-keyboard-simulation-hid)
(Linux-only)
- [physical mouse simulation (HID)](#physical-mouse-simulation-hid)
(Linux-only)
- [OTG mode](#otg) (Linux-only)
- and more…
## Requirements
@ -71,6 +76,12 @@ On Debian and Ubuntu:
apt install scrcpy
```
On Arch Linux:
```
pacman -S scrcpy
```
A [Snap] package is available: [`scrcpy`][snap-link].
[snap-link]: https://snapstats.org/snaps/scrcpy
@ -82,10 +93,6 @@ For Fedora, a [COPR] package is available: [`scrcpy`][copr-link].
[COPR]: https://fedoraproject.org/wiki/Category:Copr
[copr-link]: https://copr.fedorainfracloud.org/coprs/zeno/scrcpy/
For Arch Linux, an [AUR] package is available: [`scrcpy`][aur-link].
[AUR]: https://wiki.archlinux.org/index.php/Arch_User_Repository
[aur-link]: https://aur.archlinux.org/packages/scrcpy/
For Gentoo, an [Ebuild] is available: [`scrcpy/`][ebuild-link].
@ -101,10 +108,10 @@ process][BUILD_simple]).
For Windows, for simplicity, a prebuilt archive with all the dependencies
(including `adb`) is available:
- [`scrcpy-win64-v1.21.zip`][direct-win64]
_(SHA-256: fdab0c1421353b592a9bbcebd6e252675eadccca65cca8105686feaa9c1ded53)_
- [`scrcpy-win64-v1.22.zip`][direct-win64]
_(SHA-256: ce4d9b8cc761e29862c4a72d8ad6f538bdd1f1831d15fd1f36633cd3b403db82)_
[direct-win64]: https://github.com/Genymobile/scrcpy/releases/download/v1.21/scrcpy-win64-v1.21.zip
[direct-win64]: https://github.com/Genymobile/scrcpy/releases/download/v1.22/scrcpy-win64-v1.22.zip
It is also available in [Chocolatey]:
@ -815,6 +822,65 @@ a physical keyboard is connected).
[Physical keyboard]: https://github.com/Genymobile/scrcpy/pull/2632#issuecomment-923756915
#### Physical mouse simulation (HID)
Similarly to the physical keyboard simulation, it is possible to simulate a
physical mouse. Likewise, it only works if the device is connected by USB, and
is currently only supported on Linux.
By default, scrcpy uses Android mouse events injection, using absolute
coordinates. By simulating a physical mouse, a mouse pointer appears on the
Android device, and relative mouse motion, clicks and scrolls are injected.
To enable this mode:
```bash
scrcpy --hid-mouse
scrcpy -M # short version
```
You could also add `--forward-all-clicks` to [forward all mouse
buttons][forward_all_clicks].
[forward_all_clicks]: #right-click-and-middle-click
When this mode is enabled, the computer mouse is "captured" (the mouse pointer
disappears from the computer and appears on the Android device instead).
Special capture keys, either <kbd>Alt</kbd> or <kbd>Super</kbd>, toggle
(disable or enable) the mouse capture. Use one of them to give the control of
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
@ -936,7 +1002,7 @@ _<kbd>[Super]</kbd> is typically the <kbd>Windows</kbd> or <kbd>Cmd</kbd> key._
| Click on `HOME` | <kbd>MOD</kbd>+<kbd>h</kbd> \| _Middle-click_
| Click on `BACK` | <kbd>MOD</kbd>+<kbd>b</kbd> \| _Right-click²_
| Click on `APP_SWITCH` | <kbd>MOD</kbd>+<kbd>s</kbd> \| _4th-click³_
| Click on `MENU` (unlock screen) | <kbd>MOD</kbd>+<kbd>m</kbd>
| Click on `MENU` (unlock screen) | <kbd>MOD</kbd>+<kbd>m</kbd>
| Click on `VOLUME_UP` | <kbd>MOD</kbd>+<kbd>↑</kbd> _(up)_
| Click on `VOLUME_DOWN` | <kbd>MOD</kbd>+<kbd>↓</kbd> _(down)_
| Click on `POWER` | <kbd>MOD</kbd>+<kbd>p</kbd>
@ -947,9 +1013,9 @@ _<kbd>[Super]</kbd> is typically the <kbd>Windows</kbd> or <kbd>Cmd</kbd> key._
| Expand notification panel | <kbd>MOD</kbd>+<kbd>n</kbd> \| _5th-click³_
| Expand settings panel | <kbd>MOD</kbd>+<kbd>n</kbd>+<kbd>n</kbd> \| _Double-5th-click³_
| Collapse panels | <kbd>MOD</kbd>+<kbd>Shift</kbd>+<kbd>n</kbd>
| Copy to clipboard | <kbd>MOD</kbd>+<kbd>c</kbd>
| Cut to clipboard | <kbd>MOD</kbd>+<kbd>x</kbd>
| Synchronize clipboards and paste | <kbd>MOD</kbd>+<kbd>v</kbd>
| Copy to clipboard | <kbd>MOD</kbd>+<kbd>c</kbd>
| Cut to clipboard | <kbd>MOD</kbd>+<kbd>x</kbd>
| Synchronize clipboards and paste | <kbd>MOD</kbd>+<kbd>v</kbd>
| Inject computer clipboard text | <kbd>MOD</kbd>+<kbd>Shift</kbd>+<kbd>v</kbd>
| Enable/disable FPS counter (on stdout) | <kbd>MOD</kbd>+<kbd>i</kbd>
| Pinch-to-zoom | <kbd>Ctrl</kbd>+_click-and-move_
@ -959,7 +1025,8 @@ _<kbd>[Super]</kbd> is typically the <kbd>Windows</kbd> or <kbd>Cmd</kbd> key._
_¹Double-click on black borders to remove them._
_²Right-click turns the screen on if it was off, presses BACK otherwise._
_³4th and 5th mouse buttons, if your mouse has them._
_⁴Only on Android >= 7._
_⁴For react-native apps in development, `MENU` triggers development menu._
_⁵Only on Android >= 7._
Shortcuts with repeated keys are executted by releasing and pressing the key a
second time. For example, to execute "Expand settings panel":
@ -1048,8 +1115,8 @@ This README is available in other languages:
- [日本語 (Japanese, `jp`) - v1.19](README.jp.md)
- [한국어 (Korean, `ko`) - v1.11](README.ko.md)
- [Português Brasileiro (Brazilian Portuguese, `pt-BR`) - v1.19](README.pt-br.md)
- [Español (Spanish, `sp`) - v1.17](README.sp.md)
- [简体中文 (Simplified Chinese, `zh-Hans`) - v1.20](README.zh-Hans.md)
- [Español (Spanish, `sp`) - v1.21](README.sp.md)
- [简体中文 (Simplified Chinese, `zh-Hans`) - v1.22](README.zh-Hans.md)
- [繁體中文 (Traditional Chinese, `zh-Hant`) - v1.15](README.zh-Hant.md)
- [Turkish (Turkish, `tr`) - v1.18](README.tr.md)

View File

@ -1,24 +1,36 @@
Solo se garantiza que el archivo [README](README.md) original esté actualizado.
# scrcpy (v1.17)
# scrcpy (v1.21)
Esta aplicación proporciona imagen y control de un dispositivo Android conectado
por USB (o [por TCP/IP][article-tcpip]). No requiere acceso _root_.
<img src="data/icon.svg" width="128" height="128" alt="scrcpy" align="right" />
Esta aplicación proporciona control e imagen de un dispositivo Android conectado
por USB (o [por TCP/IP](#conexión)). No requiere acceso _root_.
Compatible con _GNU/Linux_, _Windows_ y _macOS_.
![screenshot](assets/screenshot-debian-600.jpg)
Sus características principales son:
- **ligero** (nativo, solo muestra la imagen del dispositivo)
- **desempeño** (30~60fps)
- **calidad** (1920×1080 o superior)
- **baja latencia** ([35~70ms][lowlatency])
- **corto tiempo de inicio** (~1 segundo para mostrar la primera imagen)
- **no intrusivo** (no se deja nada instalado en el dispositivo)
Se enfoca en:
- **ser ligera**: aplicación nativa, solo muestra la imagen del dispositivo
- **rendimiento**: 30~120fps, dependiendo del dispositivo
- **calidad**: 1920×1080 o superior
- **baja latencia**: [35~70ms][lowlatency]
- **inicio rápido**: ~1 segundo para mostrar la primera imagen
- **no intrusivo**: no deja nada instalado en el dispositivo
- **beneficios**: sin cuentas, sin anuncios, no requiere acceso a internet
- **libertad**: software gratis y de código abierto
[lowlatency]: https://github.com/Genymobile/scrcpy/pull/646
Con la aplicación puede:
- [grabar la pantalla](#capturas-y-grabaciones)
- duplicar la imagen con [la pantalla apagada](#apagar-la-pantalla)
- [copiar y pegar](#copiar-y-pegar) en ambos sentidos
- [configurar la calidad](#configuración-de-captura)
- usar la pantalla del dispositivo [como webcam (V4L2)](#v4l2loopback) (solo en Linux)
- [emular un teclado físico (HID)](#emular-teclado-físico-hid)
(solo en Linux)
- y mucho más…
## Requisitos
@ -51,7 +63,7 @@ Construir desde la fuente: [BUILD] ([proceso simplificado][BUILD_simple])
### Linux
En Debian (_test_ y _sid_ por ahora) y Ubuntu (20.04):
En Debian y Ubuntu:
```
apt install scrcpy
@ -125,7 +137,7 @@ Necesitarás `adb`, accesible desde `PATH`. Si aún no lo tienes:
brew install android-platform-tools
```
También está disponible en [MacPorts], que configura el adb automáticamente:
También está disponible en [MacPorts], que configura el adb automáticamente:
```bash
sudo port install scrcpy
@ -153,7 +165,7 @@ scrcpy --help
## Características
### Capturar configuración
### Configuración de captura
#### Reducir la definición
@ -208,10 +220,11 @@ Si `--max-size` también está especificado, el cambio de tamaño es aplicado de
Para fijar la rotación de la transmisión:
```bash
scrcpy --lock-video-orientation 0 # orientación normal
scrcpy --lock-video-orientation 1 # 90° contrarreloj
scrcpy --lock-video-orientation 2 # 180°
scrcpy --lock-video-orientation 3 # 90° sentido de las agujas del reloj
scrcpy --lock-video-orientation # orientación inicial
scrcpy --lock-video-orientation=0 # orientación normal
scrcpy --lock-video-orientation=1 # 90° contrarreloj
scrcpy --lock-video-orientation=2 # 180°
scrcpy --lock-video-orientation=3 # 90° sentido de las agujas del reloj
```
Esto afecta la rotación de la grabación.
@ -233,7 +246,10 @@ Para listar los codificadores disponibles, puedes pasar un nombre de codificador
scrcpy --encoder _
```
### Grabación
### Capturas y grabaciones
#### Grabación
Es posible grabar la pantalla mientras se transmite:
@ -250,17 +266,117 @@ scrcpy -Nr file.mkv
# interrumpe la grabación con Ctrl+C
```
"Skipped frames" son grabados, incluso si no son mostrados en tiempo real (por razones de desempeño). Los frames tienen _marcas de tiempo_ en el dispositivo, por lo que el "[packet delay
Los "skipped frames" son grabados, incluso si no se mostrados en tiempo real (por razones de desempeño). Los frames tienen _marcas de tiempo_ en el dispositivo, por lo que el "[packet delay
variation]" no impacta el archivo grabado.
[packet delay variation]: https://en.wikipedia.org/wiki/Packet_delay_variation
#### v4l2loopback
En Linux se puede mandar el stream del video a un dispositivo loopback v4l2, por
lo que se puede abrir el dispositivo Android como una webcam con cualquier
programa compatible con v4l2.
Se debe instalar el modulo `v4l2loopback`:
```bash
sudo apt install v4l2loopback-dkms
```
Para crear un dispositivo v4l2:
```bash
sudo modprobe v4l2loopback
```
Esto va a crear un nuevo dispositivo de video en `/dev/videoN`, donde `N` es un número
(hay más [opciones](https://github.com/umlaeute/v4l2loopback#options) disponibles
para crear múltiples dispositivos o usar un ID en específico).
Para ver los dispositivos disponibles:
```bash
# requiere el paquete v4l-utils
v4l2-ctl --list-devices
# simple pero generalmente suficiente
ls /dev/video*
```
Para iniciar scrcpy usando una fuente v4l2:
```bash
scrcpy --v4l2-sink=/dev/videoN
scrcpy --v4l2-sink=/dev/videoN --no-display # deshabilita la transmisión de imagen
scrcpy --v4l2-sink=/dev/videoN -N # más corto
```
(reemplace `N` con el ID del dispositivo, compruebe con `ls /dev/video*`)
Una vez habilitado, podés abrir el stream del video con una herramienta compatible con v4l2:
```bash
ffplay -i /dev/videoN
vlc v4l2:///dev/videoN # VLC puede agregar un delay por buffering
```
Por ejemplo, podrías capturar el video usando [OBS].
[OBS]: https://obsproject.com/
#### Buffering
Es posible agregar buffering al video. Esto reduce el ruido en la imagen ("jitter")
pero aumenta la latencia (vea [#2464]).
[#2464]: https://github.com/Genymobile/scrcpy/issues/2464
La opción de buffering está disponible para la transmisión de imagen:
```bash
scrcpy --display-buffer=50 # agrega 50 ms de buffering a la imagen
```
y las fuentes V4L2:
```bash
scrcpy --v4l2-buffer=500 # agrega 500 ms de buffering a la fuente v4l2
```
### Conexión
#### Inalámbrica
#### TCP/IP (Inalámbrica)
_Scrcpy_ usa `adb` para comunicarse con el dispositivo, y `adb` puede [conectarse] vía TCP/IP:
_Scrcpy_ usa `adb` para comunicarse con el dispositivo, y `adb` puede [conectarse] vía TCP/IP.
El dispositivo debe estar conectado a la misma red que la computadora:
##### Automático
La opción `--tcpip` permite configurar la conexión automáticamente. Hay 2 variables.
Si el dispositivo (accesible en 192.168.1.1 para este ejemplo) ya está escuchando
en un puerto (generalmente 5555) esperando una conexión adb entrante, entonces corré:
```bash
scrcpy --tcpip=192.168.1.1 # el puerto default es 5555
scrcpy --tcpip=192.168.1.1:5555
```
Si el dispositivo no tiene habilitado el modo adb TCP/IP (o si no sabés la dirección IP),
entonces conectá el dispositivo por USB y corré:
```bash
scrcpy --tcpip # sin argumentos
```
El programa buscará automáticamente la IP del dispositivo, habilitará el modo TCP/IP, y
se conectará al dispositivo antes de comenzar a transmitir la imagen.
##### Manual
Como alternativa, se puede habilitar la conexión TCP/IP manualmente usando `adb`:
1. Conecta el dispositivo al mismo Wi-Fi que tu computadora.
2. Obtén la dirección IP del dispositivo, en Ajustes → Acerca del dispositivo → Estado, o ejecutando este comando:
@ -302,7 +418,7 @@ scrcpy -s 192.168.0.1:5555 # versión breve
Puedes iniciar múltiples instancias de _scrcpy_ para múltiples dispositivos.
#### Autoiniciar al detectar dispositivo
#### Iniciar automáticamente al detectar dispositivo
Puedes utilizar [AutoAdb]:
@ -312,37 +428,82 @@ autoadb scrcpy -s '{}'
[AutoAdb]: https://github.com/rom1v/autoadb
#### Túnel SSH
#### Túneles
Para conectarse a un dispositivo remoto, es posible conectar un cliente local de `adb` a un servidor remoto `adb` (siempre y cuando utilicen la misma versión de protocolos _adb_):
Para conectarse a un dispositivo remoto, es posible conectar un cliente local `adb` a un servidor remoto `adb` (siempre y cuando utilicen la misma versión de protocolos _adb_).
##### Servidor ADB remoto
Para conectarse a un servidor ADB remoto, haz que el servidor escuche en todas las interfaces:
```bash
adb kill-server # cierra el servidor local adb en 5037
ssh -CN -L5037:localhost:5037 -R27183:localhost:27183 your_remote_computer
adb kill-server
adb -a nodaemon server start
# conserva este servidor abierto
```
Desde otra terminal:
**Advertencia: todas las comunicaciones entre los clientes y el servidor ADB están desencriptadas.**
Supondremos que este servidor se puede acceder desde 192.168.1.2. Entonces, desde otra
terminal, corré scrcpy:
```bash
export ADB_SERVER_SOCKET=tcp:192.168.1.2:5037
scrcpy --tunnel-host=192.168.1.2
```
Por default, scrcpy usa el puerto local que se usó para establecer el tunel
`adb forward` (típicamente `27183`, vea `--port`). También es posible forzar un
puerto diferente (puede resultar útil en situaciones más complejas, donde haya
múltiples redirecciones):
```
scrcpy --tunnel-port=1234
```
##### Túnel SSH
Para comunicarse con un servidor ADB remoto de forma segura, es preferible usar un túnel SSH.
Primero, asegurate que el servidor ADB está corriendo en la computadora remota:
```bash
adb start-server
```
Después, establecé el túnel SSH:
```bash
# local 5038 --> remoto 5037
# local 27183 <-- remoto 27183
ssh -CN -L5038:localhost:5037 -R27183:localhost:27183 your_remote_computer
# conserva este servidor abierto
```
Desde otra terminal, corré scrcpy:
```bash
export ADB_SERVER_SOCKET=tcp:localhost:5038
scrcpy
```
Para evitar habilitar "remote port forwarding", puedes forzar una "forward connection" (nótese el argumento `-L` en vez de `-R`):
```bash
adb kill-server # cierra el servidor local adb en 5037
ssh -CN -L5037:localhost:5037 -L27183:localhost:27183 your_remote_computer
# local 5038 --> remoto 5037
# local 27183 --> remoto 27183
ssh -CN -L5038:localhost:5037 -L27183:localhost:27183 your_remote_computer
# conserva este servidor abierto
```
Desde otra terminal:
Desde otra terminal, corré scrcpy:
```bash
export ADB_SERVER_SOCKET=tcp:localhost:5038
scrcpy --force-adb-forward
```
Al igual que las conexiones inalámbricas, puede resultar útil reducir la calidad:
```
@ -402,7 +563,7 @@ Se puede rotar la ventana:
scrcpy --rotation 1
```
Los valores posibles son:
Los posibles valores son:
- `0`: sin rotación
- `1`: 90 grados contrarreloj
- `2`: 180 grados
@ -416,7 +577,7 @@ Nótese que _scrcpy_ maneja 3 diferentes rotaciones:
- `--rotation` (o <kbd>MOD</kbd>+<kbd>←</kbd>/<kbd>MOD</kbd>+<kbd>→</kbd>) rota solo el contenido de la imagen. Esto solo afecta a la imagen mostrada, no a la grabación.
### Otras opciones menores
### Otras opciones
#### Solo lectura ("Read-only")
@ -479,14 +640,12 @@ scrcpy -Sw # versión breve
```
#### Renderizar frames vencidos
#### Apagar al cerrar la aplicación
Por defecto, para minimizar la latencia, _scrcpy_ siempre renderiza el último frame disponible decodificado, e ignora cualquier frame anterior.
Para forzar el renderizado de todos los frames (a costo de posible aumento de latencia), use:
Para apagar la pantalla del dispositivo al cerrar scrcpy:
```bash
scrcpy --render-expired-frames
scrcpy --power-off-on-close
```
#### Mostrar clicks
@ -548,6 +707,8 @@ Además, <kbd>MOD</kbd>+<kbd>Shift</kbd>+<kbd>v</kbd> permite inyectar el texto
Algunos dispositivos no se comportan como es esperado al establecer el portapapeles programáticamente. La opción `--legacy-paste` está disponible para cambiar el comportamiento de <kbd>Ctrl</kbd>+<kbd>v</kbd> y <kbd>MOD</kbd>+<kbd>v</kbd> para que también inyecten el texto del portapapeles de la computadora como una secuencia de teclas (de la misma forma que <kbd>MOD</kbd>+<kbd>Shift</kbd>+<kbd>v</kbd>).
Para deshabilitar la auto-sincronización del portapapeles, use `--no-clipboard-autosync`.
#### Pellizcar para zoom
Para simular "pinch-to-zoom": <kbd>Ctrl</kbd>+_click-y-mover_.
@ -556,6 +717,48 @@ Más precisamente, mantén <kbd>Ctrl</kbd> mientras presionas botón izquierdo.
Concretamente, scrcpy genera clicks adicionales con un "dedo virtual" en la posición invertida respecto al centro de la pantalla.
#### Emular teclado físico (HID)
Por default, scrcpy usa el sistema de Android para la injección de teclas o texto:
funciona en todas partes, pero está limitado a ASCII.
En Linux, scrcpy puede emular un teclado USB físico en Android para proveer
una mejor experiencia al enviar _inputs_ (usando [USB HID vía AOAv2][hid-aoav2]):
deshabilita el teclado virtual y funciona para todos los caracteres y IME.
[hid-aoav2]: https://source.android.com/devices/accessories/aoa2#hid-support
Sin embargo, solo funciona si el dispositivo está conectado por USB, y por ahora
solo funciona en Linux.
Para habilitar este modo:
```bash
scrcpy --hid-keyboard
scrcpy -K # más corto
```
Si por alguna razón falla (por ejemplo si el dispositivo no está conectado vía
USB), automáticamente vuelve al modo default (un mensaje se escribirá en la consola).
Se puede usar los mismos argumentos en la línea de comandos tanto si se conecta con
USB o vía TCP/IP.
En este modo, los _raw key events_ (_scancodes_) se envían al dispositivo, independientemente
del mapeo del teclado en el host. Por eso, si el diseño de tu teclado no concuerda, debe ser
configurado en el dispositivo Android, en Ajustes → Sistema → Idioma y Entrada de Texto
→ [Teclado Físico].
Se puede iniciar automáticamente en esta página de ajustes:
```bash
adb shell am start -a android.settings.HARD_KEYBOARD_SETTINGS
```
Sin embargo, la opción solo está disponible cuando el teclado HID está activo
(o cuando se conecta un teclado físico).
[Teclado Físico]: https://github.com/Genymobile/scrcpy/pull/2632#issuecomment-923756915
#### Preferencias de inyección de texto
@ -573,13 +776,23 @@ scrcpy --prefer-text
(Pero esto romperá el comportamiento del teclado en los juegos)
Por el contrario, se puede forzar scrcpy para siempre injectar _raw key events_:
```bash
scrcpy --raw-key-events
```
Estas opciones no tienen efecto en los teclados HID (todos los _key events_ son enviados como
_scancodes_ en este modo).
[textevents]: https://blog.rom1v.com/2018/03/introducing-scrcpy/#handle-text-input
[prefertext]: https://github.com/Genymobile/scrcpy/issues/650#issuecomment-512945343
#### Repetir tecla
Por defecto, mantener una tecla presionada genera múltiples _key events_. Esto puede causar problemas de desempeño en algunos juegos, donde estos eventos no tienen sentido de todos modos.
Por defecto, mantener una tecla presionada genera múltiples _key events_. Esto puede
causar problemas de desempeño en algunos juegos, donde estos eventos no tienen sentido de todos modos.
Para evitar enviar _key events_ repetidos:
@ -587,6 +800,9 @@ Para evitar enviar _key events_ repetidos:
scrcpy --no-key-repeat
```
Estas opciones no tienen efecto en los teclados HID (Android maneja directamente
las repeticiones de teclas en este modo)
#### Botón derecho y botón del medio
@ -608,14 +824,15 @@ No hay respuesta visual, un mensaje se escribirá en la consola.
#### Enviar archivos al dispositivo
Para enviar un archivo a `/sdcard/` en el dispositivo, arrastre y suelte un archivo (no APK) a la ventana de _scrcpy_.
Para enviar un archivo a `/sdcard/Download/` en el dispositivo, arrastre y suelte
un archivo (no APK) a la ventana de _scrcpy_.
No hay respuesta visual, un mensaje se escribirá en la consola.
No hay ninguna respuesta visual, un mensaje se escribirá en la consola.
El directorio de destino puede ser modificado al iniciar:
```bash
scrcpy --push-target=/sdcard/Download/
scrcpy --push-target=/sdcard/Movies/
```
@ -647,36 +864,48 @@ _<kbd>[Super]</kbd> es generalmente la tecla <kbd>Windows</kbd> o <kbd>Cmd</kbd>
[Super]: https://en.wikipedia.org/wiki/Super_key_(keyboard_button)
| Acción | Atajo
| ------------------------------------------- |:-----------------------------
| Alterne entre pantalla compelta | <kbd>MOD</kbd>+<kbd>f</kbd>
| Rotar pantalla hacia la izquierda | <kbd>MOD</kbd>+<kbd>←</kbd> _(izquierda)_
| Rotar pantalla hacia la derecha | <kbd>MOD</kbd>+<kbd>→</kbd> _(derecha)_
| Ajustar ventana a 1:1 ("pixel-perfect") | <kbd>MOD</kbd>+<kbd>g</kbd>
| Ajustar ventana para quitar los bordes negros| <kbd>MOD</kbd>+<kbd>w</kbd> \| _Doble click¹_
| Click en `INICIO` | <kbd>MOD</kbd>+<kbd>h</kbd> \| _Botón del medio_
| Click en `RETROCEDER` | <kbd>MOD</kbd>+<kbd>b</kbd> \| _Botón derecho²_
| Click en `CAMBIAR APLICACIÓN` | <kbd>MOD</kbd>+<kbd>s</kbd>
| Click en `MENÚ` (desbloquear pantalla) | <kbd>MOD</kbd>+<kbd>m</kbd>
| Click en `SUBIR VOLUMEN` | <kbd>MOD</kbd>+<kbd>↑</kbd> _(arriba)_
| Click en `BAJAR VOLUME` | <kbd>MOD</kbd>+<kbd>↓</kbd> _(abajo)_
| Click en `ENCENDIDO` | <kbd>MOD</kbd>+<kbd>p</kbd>
| Encendido | _Botón derecho²_
| Apagar pantalla (manteniendo la transmisión)| <kbd>MOD</kbd>+<kbd>o</kbd>
| Encender pantalla | <kbd>MOD</kbd>+<kbd>Shift</kbd>+<kbd>o</kbd>
| Rotar pantalla del dispositivo | <kbd>MOD</kbd>+<kbd>r</kbd>
| Abrir panel de notificaciones | <kbd>MOD</kbd>+<kbd>n</kbd>
| Cerrar panel de notificaciones | <kbd>MOD</kbd>+<kbd>Shift</kbd>+<kbd>n</kbd>
| Copiar al portapapeles³ | <kbd>MOD</kbd>+<kbd>c</kbd>
| Cortar al portapapeles³ | <kbd>MOD</kbd>+<kbd>x</kbd>
| Synchronizar portapapeles y pegar³ | <kbd>MOD</kbd>+<kbd>v</kbd>
| inyectar texto del portapapeles de la PC | <kbd>MOD</kbd>+<kbd>Shift</kbd>+<kbd>v</kbd>
| Acción | Atajo
| ------------------------------------------- |:-----------------------------
| Alterne entre pantalla compelta | <kbd>MOD</kbd>+<kbd>f</kbd>
| Rotar pantalla hacia la izquierda | <kbd>MOD</kbd>+<kbd>←</kbd> _(izquierda)_
| Rotar pantalla hacia la derecha | <kbd>MOD</kbd>+<kbd>→</kbd> _(derecha)_
| Ajustar ventana a 1:1 ("pixel-perfect") | <kbd>MOD</kbd>+<kbd>g</kbd>
| Ajustar ventana para quitar los bordes negros| <kbd>MOD</kbd>+<kbd>w</kbd> \| _Doble click izquierdo¹_
| Click en `INICIO` | <kbd>MOD</kbd>+<kbd>h</kbd> \| _Click medio_
| Click en `RETROCEDER` | <kbd>MOD</kbd>+<kbd>b</kbd> \| _Click derecho²_
| Click en `CAMBIAR APLICACIÓN` | <kbd>MOD</kbd>+<kbd>s</kbd> \| _Cuarto botón³_
| Click en `MENÚ` (desbloquear pantalla) | <kbd>MOD</kbd>+<kbd>m</kbd>
| Click en `SUBIR VOLUMEN` | <kbd>MOD</kbd>+<kbd>↑</kbd> _(arriba)_
| Click en `BAJAR VOLUME` | <kbd>MOD</kbd>+<kbd>↓</kbd> _(abajo)_
| Click en `ENCENDIDO` | <kbd>MOD</kbd>+<kbd>p</kbd>
| Encendido | _Botón derecho²_
| Apagar pantalla (manteniendo la transmisión) | <kbd>MOD</kbd>+<kbd>o</kbd>
| Encender pantalla | <kbd>MOD</kbd>+<kbd>Shift</kbd>+<kbd>o</kbd>
| Rotar pantalla del dispositivo | <kbd>MOD</kbd>+<kbd>r</kbd>
| Abrir panel de notificaciones | <kbd>MOD</kbd>+<kbd>n</kbd> \| _Quinto botón³_
| Abrir panel de configuración | <kbd>MOD</kbd>+<kbd>n</kbd>+<kbd>n</kbd> \| _Doble quinto botón³_
| Cerrar paneles | <kbd>MOD</kbd>+<kbd>Shift</kbd>+<kbd>n</kbd>
| Copiar al portapapeles | <kbd>MOD</kbd>+<kbd>c</kbd>
| Cortar al portapapeles | <kbd>MOD</kbd>+<kbd>x</kbd>
| Synchronizar portapapeles y pegar⁵ | <kbd>MOD</kbd>+<kbd>v</kbd>
| Inyectar texto del portapapeles de la PC | <kbd>MOD</kbd>+<kbd>Shift</kbd>+<kbd>v</kbd>
| Habilitar/Deshabilitar contador de FPS (en stdout) | <kbd>MOD</kbd>+<kbd>i</kbd>
| Pellizcar para zoom | <kbd>Ctrl</kbd>+_click-y-mover_
| Pellizcar para zoom | <kbd>Ctrl</kbd>+_click-y-mover_
| Arrastrar y soltar un archivo (APK) | Instalar APK desde la computadora
| Arrastrar y soltar un archivo (no APK) | [Mover archivo al dispositivo](#enviar-archivos-al-dispositivo)
_¹Doble click en los bordes negros para eliminarlos._
_²Botón derecho enciende la pantalla si estaba apagada, sino ejecuta RETROCEDER._
Solo en Android >= 7._
Cuarto y quinto botón del mouse, si tu mouse los tiene._
_⁴Para las apps react-native en desarrollo, `MENU` activa el menú de desarrollo._
_⁵Solo en Android >= 7._
Los shortcuts con teclas repetidas se ejecutan soltando y volviendo a apretar la tecla
por segunda vez. Por ejemplo, para ejecutar "Abrir panel de configuración":
1. Apretá y mantené apretado <kbd>MOD</kbd>.
2. Después apretá dos veces la tecla <kbd>n</kbd>.
3. Por último, soltá la tecla <kbd>MOD</kbd>.
Todos los atajos <kbd>Ctrl</kbd>+_tecla_ son enviados al dispositivo para que sean manejados por la aplicación activa.
@ -691,6 +920,8 @@ ADB=/path/to/adb scrcpy
Para sobreescribir el path del archivo `scrcpy-server`, configure el path en `SCRCPY_SERVER_PATH`.
Para sobreescribir el ícono, configure el path en `SCRCPY_ICON_PATH`.
## ¿Por qué _scrcpy_?

View File

@ -1,17 +1,19 @@
_Only the original [README](README.md) is guaranteed to be up-to-date._
只有原版的[README](README.md)会保持最新。
_只有原版的 [README](README.md)是保证最新的。_
Current version is based on [65b023a]
Current version is based on [f4c7044]
本文根据[65b023a]进行翻译。
本文根据[f4c7044]进行翻译。
[65b023a]: https://github.com/Genymobile/scrcpy/blob/65b023ac6d586593193fd5290f65e25603b68e02/README.md
[f4c7044]: https://github.com/Genymobile/scrcpy/blob/f4c7044b46ae28eb64cb5e1a15c9649a44023c70/README.md
# scrcpy (v1.20)
# scrcpy (v1.22)
<img src="data/icon.svg" width="128" height="128" alt="scrcpy" align="right" />
_发音为 "**scr**een **c**o**py**"_
本应用程序可以显示并控制通过 USB (或 [TCP/IP][article-tcpip]) 连接的安卓设备,且不需要任何 _root_ 权限。本程序支持 _GNU/Linux_, _Windows__macOS_
![screenshot](assets/screenshot-debian-600.jpg)
@ -36,6 +38,8 @@ Current version is based on [65b023a]
- [可配置显示质量](#采集设置)
- 以设备屏幕[作为摄像头(V4L2)](#v4l2loopback) (仅限 Linux)
- [模拟物理键盘 (HID)](#物理键盘模拟-hid) (仅限 Linux)
- [物理鼠标模拟 (HID)](#物理鼠标模拟-hid) (仅限 Linux)
- [OTG模式](#otg) (仅限 Linux)
- 更多 ……
## 系统要求
@ -68,12 +72,18 @@ Current version is based on [65b023a]
### Linux
在 Debian (目前仅支持 _testing__sid_ 分支) 和Ubuntu (20.04) 上:
在 Debian 和 Ubuntu 上:
```
apt install scrcpy
```
在 Arch Linux 上:
```
pacman -S scrcpy
```
我们也提供 [Snap] 包: [`scrcpy`][snap-link]。
[snap-link]: https://snapstats.org/snaps/scrcpy
@ -85,11 +95,6 @@ apt install scrcpy
[COPR]: https://fedoraproject.org/wiki/Category:Copr
[copr-link]: https://copr.fedorainfracloud.org/coprs/zeno/scrcpy/
对 Arch Linux 我们提供 [AUR] 包: [`scrcpy`][aur-link]。
[AUR]: https://wiki.archlinux.org/index.php/Arch_User_Repository
[aur-link]: https://aur.archlinux.org/packages/scrcpy/
对 Gentoo 我们提供 [Ebuild] 包:[`scrcpy/`][ebuild-link]。
[Ebuild]: https://wiki.gentoo.org/wiki/Ebuild
@ -343,9 +348,32 @@ scrcpy --v4l2-buffer=500 # 为 v4l2 漏增加 500 毫秒的缓冲
### 连接
#### 无线
#### TCP/IP 无线
_Scrcpy_ 使用 `adb` 与设备通信,并且 `adb` 支持通过 TCP/IP [连接]到设备:
_Scrcpy_ 使用 `adb` 与设备通信,并且 `adb` 支持通过 TCP/IP [连接]到设备(设备必须连接与电脑相同的网络)。
##### 自动配置
参数 `--tcpip` 允许自动配置连接。这里有两种方式。
对于传入的 adb 连接如果设备在这个例子中以192.168.1.1为可用地址已经监听了一个端口通常是5555运行
```bash
scrcpy --tcpip=192.168.1.1 # 默认端口是5555
scrcpy --tcpip=192.168.1.1:5555
```
如果adb TCP/IP无线 模式在某些设备上不被启用或者你不知道IP地址用USB连接设备然后运行
```bash
scrcpy --tcpip # 无需其他参数
```
这将会自动寻找设备IP地址启用TCP/IP模式然后在启动之前连接到设备。
##### 手动配置
或者,可以通过 `adb` 使用手动启用 TCP/IP 连接:
1. 将设备和电脑连接至同一 Wi-Fi。
2. 打开 设置 → 关于手机 → 状态信息,获取设备的 IP 地址,也可以执行以下的命令:
@ -354,12 +382,12 @@ _Scrcpy_ 使用 `adb` 与设备通信,并且 `adb` 支持通过 TCP/IP [连接
adb shell ip route | awk '{print $9}'
```
3. 启用设备的网络 adb 功能: `adb tcpip 5555`。
3. 启用设备的网络 adb 功能:`adb tcpip 5555`。
4. 断开设备的 USB 连接。
5. 连接到您的设备:`adb connect DEVICE_IP:5555` _(将 `DEVICE_IP` 替换为设备 IP)_。
6. 正常运行 `scrcpy`。
可能降低码率和分辨率会更好一些
降低比特率和分辨率可能很有用
```bash
scrcpy --bit-rate 2M --max-size 800
@ -397,33 +425,75 @@ autoadb scrcpy -s '{}'
[AutoAdb]: https://github.com/rom1v/autoadb
#### SSH 隧道
#### 隧道
要远程连接到设备,可以将本地的 adb 客户端连接到远程的 adb 服务端 (需要两端的 _adb_ 协议版本相同)
要远程连接到设备,可以将本地的 adb 客户端连接到远程的 adb 服务端 (需要两端的 _adb_ 协议版本相同)
##### 远程ADB服务器
要连接到一个远程ADB服务器让服务器在所有接口上监听
```bash
adb kill-server # 关闭本地 5037 端口上的 adb 服务端
ssh -CN -L5037:localhost:5037 -R27183:localhost:27183 your_remote_computer
adb kill-server
adb -a nodaemon server start
# 保持该窗口开启
```
在另一个终端:
**警告所有客户端与ADB服务器的交流都是未加密的。**
假设此服务器可在 192.168.1.2 访问。 然后,从另一个终端,运行 scrcpy
```bash
export ADB_SERVER_SOCKET=tcp:192.168.1.2:5037
scrcpy --tunnel-host=192.168.1.2
```
默认情况下scrcpy使用用于 `adb forward` 隧道建立的本地端口(通常是 `27183`,见 `--port` )。它也可以强制使用一个不同的隧道端口(当涉及更多的重定向时,这在更复杂的情况下可能很有用):
```
scrcpy --tunnel-port=1234
```
##### SSH 隧道
为了安全地与远程ADB服务器通信最好使用SSH隧道。
首先确保ADB服务器正在远程计算机上运行
```bash
adb start-server
```
然后建立一个SSH隧道
```bash
# 本地 5038 --> 远程 5037
# 本地 27183 <-- 远程 27183
ssh -CN -L5038:localhost:5037 -R27183:localhost:27183 your_remote_computer
# 保持该窗口开启
```
在另一个终端上运行scrcpy
```bash
export ADB_SERVER_SOCKET=tcp:localhost:5038
scrcpy
```
若要不使用远程端口转发,可以强制使用正向连接 (注意 `-L` `-R` 的区别)
若要不使用远程端口转发,可以强制使用正向连接注意 `-L` 而不是 `-R`
```bash
adb kill-server # 关闭本地 5037 端口上的 adb 服务端
ssh -CN -L5037:localhost:5037 -L27183:localhost:27183 your_remote_computer
# 本地 5038 --> 远程 5037
# 本地 27183 <-- 远程 27183
ssh -CN -L5038:localhost:5037 -L27183:localhost:27183 your_remote_computer
# 保持该窗口开启
```
在另一个终端:
在另一个终端运行scrcpy
```bash
export ADB_SERVER_SOCKET=tcp:localhost:5038
scrcpy --force-adb-forward
```
@ -441,7 +511,7 @@ scrcpy -b2M -m800 --max-fps 15
窗口的标题默认为设备型号。可以通过如下命令修改:
```bash
scrcpy --window-title 'My device'
scrcpy --window-title "我的设备"
```
#### 位置和大小
@ -630,6 +700,8 @@ scrcpy --disable-screensaver
一些设备不支持通过程序设置剪贴板。通过 `--legacy-paste` 选项可以修改 <kbd>Ctrl</kbd>+<kbd>v</kbd> 和 <kbd>MOD</kbd>+<kbd>v</kbd> 的工作方式,使它们通过按键事件 (同 <kbd>MOD</kbd>+<kbd>Shift</kbd>+<kbd>v</kbd>) 来注入电脑剪贴板内容。
要禁用自动剪贴板同步功能,使用`--no-clipboard-autosync`。
#### 双指缩放
模拟“双指缩放”:<kbd>Ctrl</kbd>+_按住并移动鼠标_。
@ -659,11 +731,60 @@ scrcpy -K # 简写
在这种模式下,原始按键事件 (扫描码) 被发送给设备,而与宿主机按键映射无关。因此,若键盘布局不匹配,需要在 Android 设备上进行配置,具体为 设置 → 系统 → 语言和输入法 → [实体键盘]。
[Physical keyboard]: https://github.com/Genymobile/scrcpy/pull/2632#issuecomment-923756915
[实体键盘]: https://github.com/Genymobile/scrcpy/pull/2632#issuecomment-923756915
#### 物理鼠标模拟 (HID)
与物理键盘模拟类似,可以模拟一个物理鼠标。 同样,它仅在设备通过 USB 连接时才有效,并且目前仅在 Linux 上受支持。
默认情况下scrcpy 使用 Android 鼠标事件注入,使用绝对坐标。 通过模拟物理鼠标在Android设备上出现鼠标指针并注入鼠标相对运动、点击和滚动。
启用此模式:
```bash
scrcpy --hid-mouse
scrcpy -M # 简写
```
您还可以将 `--forward-all-clicks` 添加到 [转发所有点击][forward_all_clicks].
[forward_all_clicks]: #右键和中键
启用此模式后,计算机鼠标将被“捕获”(鼠标指针从计算机上消失并出现在 Android 设备上)。
特殊的捕获键,<kbd>Alt</kbd> 或 <kbd>Super</kbd>,切换(禁用或启用)鼠标捕获。 使用其中之一将鼠标的控制权交还给计算机。
#### OTG
可以仅使用物理键盘和鼠标模拟 (HID) 运行 _scrcpy_就好像计算机键盘和鼠标通过 OTG 线直接插入设备一样。
在这个模式下_adb_ (USB 调试)是不必要的,且镜像被禁用。
启用 OTG 模式:
```bash
scrcpy --otg
# 如果有多个 USB 设备可用,则通过序列号选择
scrcpy --otg -s 0123456789abcdef
```
只开启 HID 键盘 或 HID 鼠标 是可行的:
```bash
scrcpy --otg --hid-keyboard # 只开启 HID 键盘
scrcpy --otg --hid-mouse # 只开启 HID 鼠标
scrcpy --otg --hid-keyboard --hid-mouse # 开启 HID 键盘 和 HID 鼠标
# 为了方便,默认两者都开启
scrcpy --otg # 开启 HID 键盘 和 HID 鼠标
```
像 `--hid-keyboard` 和 `--hid-mouse` 一样,它只在设备通过 USB 连接时才有效,且目前仅在 Linux 上支持。
#### 文本注入偏好
字的时候,系统会产生两种[事件][textevents]
输入文字的时候,系统会产生两种[事件][textevents]
- _按键事件_ ,代表一个按键被按下或松开。
- _文本事件_ ,代表一个字符被输入。
@ -675,7 +796,13 @@ scrcpy -K # 简写
scrcpy --prefer-text
```
(这会导致键盘在游戏中工作不正常)
(这会导致键盘在游戏中工作不正常)
相反,您可以强制始终注入原始按键事件:
```bash
scrcpy --raw-key-events
```
该选项不影响 HID 键盘 (该模式下,所有按键都发送为扫描码)。
@ -765,7 +892,7 @@ _<kbd>[Super]</kbd> 键通常是指 <kbd>Windows</kbd> 或 <kbd>Cmd</kbd> 键。
| 点按 `主屏幕` | <kbd>MOD</kbd>+<kbd>h</kbd> \| _中键_
| 点按 `返回` | <kbd>MOD</kbd>+<kbd>b</kbd> \| _右键²_
| 点按 `切换应用` | <kbd>MOD</kbd>+<kbd>s</kbd> \| _第4键³_
| 点按 `菜单` (解锁屏幕) | <kbd>MOD</kbd>+<kbd>m</kbd>
| 点按 `菜单` (解锁屏幕) | <kbd>MOD</kbd>+<kbd>m</kbd>
| 点按 `音量+` | <kbd>MOD</kbd>+<kbd>↑</kbd> _(上箭头)_
| 点按 `音量-` | <kbd>MOD</kbd>+<kbd>↓</kbd> _(下箭头)_
| 点按 `电源` | <kbd>MOD</kbd>+<kbd>p</kbd>
@ -776,9 +903,9 @@ _<kbd>[Super]</kbd> 键通常是指 <kbd>Windows</kbd> 或 <kbd>Cmd</kbd> 键。
| 展开通知面板 | <kbd>MOD</kbd>+<kbd>n</kbd> \| _第5键³_
| 展开设置面板 | <kbd>MOD</kbd>+<kbd>n</kbd>+<kbd>n</kbd> \| _双击第5键³_
| 收起通知面板 | <kbd>MOD</kbd>+<kbd>Shift</kbd>+<kbd>n</kbd>
| 复制到剪贴板 | <kbd>MOD</kbd>+<kbd>c</kbd>
| 剪切到剪贴板 | <kbd>MOD</kbd>+<kbd>x</kbd>
| 同步剪贴板并粘贴 | <kbd>MOD</kbd>+<kbd>v</kbd>
| 复制到剪贴板 | <kbd>MOD</kbd>+<kbd>c</kbd>
| 剪切到剪贴板 | <kbd>MOD</kbd>+<kbd>x</kbd>
| 同步剪贴板并粘贴 | <kbd>MOD</kbd>+<kbd>v</kbd>
| 注入电脑剪贴板文本 | <kbd>MOD</kbd>+<kbd>Shift</kbd>+<kbd>v</kbd>
| 打开/关闭FPS显示 (至标准输出) | <kbd>MOD</kbd>+<kbd>i</kbd>
| 捏拉缩放 | <kbd>Ctrl</kbd>+_按住并移动鼠标_
@ -788,7 +915,8 @@ _<kbd>[Super]</kbd> 键通常是指 <kbd>Windows</kbd> 或 <kbd>Cmd</kbd> 键。
_¹双击黑边可以去除黑边。_
_²点击鼠标右键将在屏幕熄灭时点亮屏幕其余情况则视为按下返回键 。_
_³鼠标的第4键和第5键。_
_⁴需要安卓版本 Android >= 7。_
_⁴对于开发中的 react-native 应用程序,`MENU` 触发开发菜单。_
_⁵需要安卓版本 Android >= 7。_
有重复按键的快捷键通过松开再按下一个按键来进行,如“展开设置面板”:
@ -816,7 +944,7 @@ ADB=/path/to/adb scrcpy
一个同事让我找出一个和 [gnirehtet] 一样难以发音的名字。
[`strcpy`] 复制一个 **str**ing (字符串); `scrcpy` 复制一个 **scr**een (屏幕)。
[`strcpy`] 源于 **str**ing (字符串); `scrcpy` 源于 **scr**een (屏幕)。
[gnirehtet]: https://github.com/Genymobile/gnirehtet
[`strcpy`]: http://man7.org/linux/man-pages/man3/strcpy.3.html

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,11 +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/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
@ -98,16 +102,16 @@ if not crossbuild_windows
dependencies += dependency('libavdevice')
endif
if aoa_hid_support
if usb_support
dependencies += dependency('libusb-1.0')
endif
else
# cross-compile mingw32 build (from Linux to Windows)
prebuilt_sdl2 = meson.get_cross_property('prebuilt_sdl2')
sdl2_bin_dir = meson.current_source_dir() + '/../prebuilt-deps/' + prebuilt_sdl2 + '/bin'
sdl2_lib_dir = meson.current_source_dir() + '/../prebuilt-deps/' + prebuilt_sdl2 + '/lib'
sdl2_include_dir = '../prebuilt-deps/' + prebuilt_sdl2 + '/include'
sdl2_bin_dir = meson.current_source_dir() + '/../prebuilt-deps/data/' + prebuilt_sdl2 + '/bin'
sdl2_lib_dir = meson.current_source_dir() + '/../prebuilt-deps/data/' + prebuilt_sdl2 + '/lib'
sdl2_include_dir = '../prebuilt-deps/data/' + prebuilt_sdl2 + '/include'
sdl2 = declare_dependency(
dependencies: [
@ -117,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/data/' + prebuilt_ffmpeg + '/bin'
ffmpeg_include_dir = '../prebuilt-deps/data/' + 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)
)
@ -187,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

@ -13,7 +13,7 @@ BEGIN
VALUE "LegalCopyright", "Romain Vimont, Genymobile"
VALUE "OriginalFilename", "scrcpy.exe"
VALUE "ProductName", "scrcpy"
VALUE "ProductVersion", "1.21"
VALUE "ProductVersion", "1.22"
END
END
BLOCK "VarFileInfo"

View File

@ -96,6 +96,8 @@ The keyboard layout must be configured (once and for all) on the device, via Set
However, the option is only available when the HID keyboard is enabled (or a physical keyboard is connected).
Also see \fB\-\-hid\-mouse\fR.
.TP
.B \-\-legacy\-paste
Inject computer clipboard text as a sequence of key events on Ctrl+v (like MOD+Shift+v).
@ -120,12 +122,30 @@ Limit both the width and height of the video to \fIvalue\fR. The other dimension
Default is 0 (unlimited).
.TP
.B \-M, \-\-hid\-mouse
Simulate a physical mouse by using HID over AOAv2.
In this mode, the computer mouse is captured to control the device directly (relative mouse mode).
LAlt, LSuper or RSuper toggle the capture mode, to give control of the mouse back to the computer.
It may only work over USB, and is currently only supported on Linux.
Also see \fB\-\-hid\-keyboard\fR.
.TP
.B \-\-no\-clipboard\-autosync
By default, scrcpy automatically synchronizes the computer clipboard to the device clipboard before injecting Ctrl+v, and the device clipboard to the computer clipboard whenever it changes.
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).
@ -142,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

@ -8,7 +8,7 @@
static char *
sc_adb_parse_device_ip_from_line(char *line, size_t len) {
// One line from "ip route" looks lile:
// One line from "ip route" looks like:
// "192.168.1.0/24 dev wlan0 proto kernel scope link src 192.168.1.x"
// Get the location of the device name (index of "wlan0" in the example)

View File

@ -3,10 +3,12 @@
#include "common.h"
#include "stddef.h"
#include <stddef.h>
/**
* Parse the ip from the output of `adb shell ip route`
*
* Warning: this function modifies the buffer for optimization purposes.
*/
char *
sc_adb_parse_device_ip_from_output(char *buf, size_t buf_len);

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;
@ -178,7 +180,8 @@ static const struct sc_option options[] = {
"directly: `adb shell am start -a "
"android.settings.HARD_KEYBOARD_SETTINGS`.\n"
"However, the option is only available when the HID keyboard "
"is enabled (or a physical keyboard is connected).",
"is enabled (or a physical keyboard is connected).\n"
"Also see --hid-mouse.",
},
{
.shortopt = 'h',
@ -214,6 +217,18 @@ static const struct sc_option options[] = {
.text = "Limit the frame rate of screen capture (officially supported "
"since Android 10, but may work on earlier versions).",
},
{
.shortopt = 'M',
.longopt = "hid-mouse",
.text = "Simulate a physical mouse by using HID over AOAv2.\n"
"In this mode, the computer mouse is captured to control the "
"device directly (relative mouse mode).\n"
"LAlt, LSuper or RSuper toggle the capture mode, to give "
"control of the mouse back to the computer.\n"
"It may only work over USB, and is currently only supported "
"on Linux.\n"
"Also see --hid-keyboard.",
},
{
.shortopt = 'm',
.longopt = "max-size",
@ -223,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",
@ -240,11 +262,8 @@ static const struct sc_option options[] = {
{
.shortopt = 'N',
.longopt = "no-display",
.text = "Do not display device (only when screen recording "
#ifdef HAVE_V4L2
"or V4L2 sink "
#endif
"is enabled).",
.text = "Do not display device (only when screen recording or V4L2 "
"sink is enabled).",
},
{
.longopt_id = OPT_NO_KEY_REPEAT,
@ -258,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",
@ -381,14 +416,14 @@ static const struct sc_option options[] = {
"Default is 0 (not forced): the local port used for "
"establishing the tunnel will be used.",
},
#ifdef HAVE_V4L2
{
.longopt_id = OPT_V4L2_SINK,
.longopt = "v4l2-sink",
.argdesc = "/dev/videoN",
.text = "Output to v4l2loopback device.\n"
"It requires to lock the video orientation (see "
"--lock-video-orientation).",
"--lock-video-orientation).\n"
"This feature is only available on Linux.",
},
{
.longopt_id = OPT_V4L2_BUFFER,
@ -398,9 +433,9 @@ static const struct sc_option options[] = {
"frames. This increases latency to compensate for jitter.\n"
"This option is similar to --display-buffer, but specific to "
"V4L2 sink.\n"
"Default is 0 (no buffering).",
"Default is 0 (no buffering).\n"
"This option is only available on Linux.",
},
#endif
{
.shortopt = 'V',
.longopt = "verbosity",
@ -1121,7 +1156,7 @@ parse_log_level(const char *s, enum sc_log_level *log_level) {
}
// item is a list of mod keys separated by '+' (e.g. "lctrl+lalt")
// returns a bitwise-or of SC_MOD_* constants (or 0 on error)
// returns a bitwise-or of SC_SHORTCUT_MOD_* constants (or 0 on error)
static unsigned
parse_shortcut_mods_item(const char *item, size_t len) {
unsigned mod = 0;
@ -1139,17 +1174,17 @@ parse_shortcut_mods_item(const char *item, size_t len) {
((sizeof(literal)-1 == len) && !memcmp(literal, s, len))
if (STREQ("lctrl", item, key_len)) {
mod |= SC_MOD_LCTRL;
mod |= SC_SHORTCUT_MOD_LCTRL;
} else if (STREQ("rctrl", item, key_len)) {
mod |= SC_MOD_RCTRL;
mod |= SC_SHORTCUT_MOD_RCTRL;
} else if (STREQ("lalt", item, key_len)) {
mod |= SC_MOD_LALT;
mod |= SC_SHORTCUT_MOD_LALT;
} else if (STREQ("ralt", item, key_len)) {
mod |= SC_MOD_RALT;
mod |= SC_SHORTCUT_MOD_RALT;
} else if (STREQ("lsuper", item, key_len)) {
mod |= SC_MOD_LSUPER;
mod |= SC_SHORTCUT_MOD_LSUPER;
} else if (STREQ("rsuper", item, key_len)) {
mod |= SC_MOD_RSUPER;
mod |= SC_SHORTCUT_MOD_RSUPER;
} else {
LOGE("Unknown modifier key: %.*s "
"(must be one of: lctrl, rctrl, lalt, ralt, lsuper, rsuper)",
@ -1300,8 +1335,14 @@ parse_args_with_getopt(struct scrcpy_cli_args *args, int argc, char *argv[],
args->help = true;
break;
case 'K':
#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
case OPT_MAX_FPS:
if (!parse_max_fps(optarg, &opts->max_fps)) {
return false;
@ -1312,6 +1353,15 @@ parse_args_with_getopt(struct scrcpy_cli_args *args, int argc, char *argv[],
return false;
}
break;
case 'M':
#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
case OPT_LOCK_VIDEO_ORIENTATION:
if (!parse_lock_video_orientation(optarg,
&opts->lock_video_orientation)) {
@ -1464,15 +1514,35 @@ parse_args_with_getopt(struct scrcpy_cli_args *args, int argc, char *argv[],
opts->tcpip = true;
opts->tcpip_dst = optarg;
break;
#ifdef HAVE_V4L2
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
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
default:
// getopt prints the error message on stderr
@ -1501,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) {
@ -1540,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

@ -7,6 +7,7 @@
#define ARRAY_LEN(a) (sizeof(a) / sizeof(a[0]))
#define MIN(X,Y) (X) < (Y) ? (X) : (Y)
#define MAX(X,Y) (X) > (Y) ? (X) : (Y)
#define CLAMP(V,X,Y) MIN( MAX((V),(X)), (Y) )
#define container_of(ptr, type, member) \
((type *) (((char *) (ptr)) - offsetof(type, member)))

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,34 +113,34 @@ 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);
buffer_write32be(&buf[17],
(uint32_t) msg->inject_scroll_event.vscroll);
return 21;
case CONTROL_MSG_TYPE_BACK_OR_SCREEN_ON:
buffer_write32be(&buf[21], msg->inject_scroll_event.buttons);
return 25;
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:
@ -150,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;
@ -190,42 +190,43 @@ 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,
" vscroll=%" PRIi32 " buttons=%06lx",
msg->inject_scroll_event.position.point.x,
msg->inject_scroll_event.position.point.y,
msg->inject_scroll_event.hscroll,
msg->inject_scroll_event.vscroll);
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:
@ -235,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;
@ -70,13 +70,14 @@ struct control_msg {
struct sc_position position;
int32_t hscroll;
int32_t vscroll;
enum android_motionevent_buttons buttons;
} inject_scroll_event;
struct {
enum android_keyevent_action action; // action for the BACK key
// 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;
@ -84,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;
};
};
@ -92,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;

452
app/src/input_events.h Normal file
View File

@ -0,0 +1,452 @@
#ifndef SC_INPUT_EVENTS_H
#define SC_INPUT_EVENTS_H
#include "common.h"
#include <assert.h>
#include <stdbool.h>
#include <stdint.h>
#include <SDL2/SDL_events.h>
#include "coords.h"
/* The representation of input events in scrcpy is very close to the SDL API,
* for simplicity.
*
* This scrcpy input events API is designed to be consumed by input event
* processors (sc_key_processor and sc_mouse_processor, see app/src/trait/).
*
* One major semantic difference between SDL input events and scrcpy input
* events is their frame of reference (for mouse and touch events): SDL events
* coordinates are expressed in SDL window coordinates (the visible UI), while
* scrcpy events are expressed in device frame coordinates.
*
* In particular, the window may be visually scaled or rotated (with --rotation
* or MOD+Left/Right), but this does not impact scrcpy input events (contrary
* to SDL input events). This allows to abstract these display details from the
* input event processors (and to make them independent from the "screen").
*
* For many enums below, the values are purposely the same as the SDL
* constants (though not all SDL values are represented), so that the
* implementation to convert from the SDL version to the scrcpy version is
* straightforward.
*
* In practice, there are 3 levels of input events:
* 1. SDL input events (as received from SDL)
* 2. scrcpy input events (this API)
* 3. the key/mouse processors input events (Android API or HID events)
*
* An input event is first received (1), then (if accepted) converted to an
* scrcpy input event (2), then submitted to the relevant key/mouse processor,
* which (if accepted) is converted to an Android event (to be sent to the
* server) or to an HID event (to be sent over USB/AOA directly).
*/
enum sc_mod {
SC_MOD_LSHIFT = KMOD_LSHIFT,
SC_MOD_RSHIFT = KMOD_RSHIFT,
SC_MOD_LCTRL = KMOD_LCTRL,
SC_MOD_RCTRL = KMOD_RCTRL,
SC_MOD_LALT = KMOD_LALT,
SC_MOD_RALT = KMOD_RALT,
SC_MOD_LGUI = KMOD_LGUI,
SC_MOD_RGUI = KMOD_RGUI,
SC_MOD_NUM = KMOD_NUM,
SC_MOD_CAPS = KMOD_CAPS,
};
enum sc_action {
SC_ACTION_DOWN, // key or button pressed
SC_ACTION_UP, // key or button released
};
enum sc_keycode {
SC_KEYCODE_UNKNOWN = SDLK_UNKNOWN,
SC_KEYCODE_RETURN = SDLK_RETURN,
SC_KEYCODE_ESCAPE = SDLK_ESCAPE,
SC_KEYCODE_BACKSPACE = SDLK_BACKSPACE,
SC_KEYCODE_TAB = SDLK_TAB,
SC_KEYCODE_SPACE = SDLK_SPACE,
SC_KEYCODE_EXCLAIM = SDLK_EXCLAIM,
SC_KEYCODE_QUOTEDBL = SDLK_QUOTEDBL,
SC_KEYCODE_HASH = SDLK_HASH,
SC_KEYCODE_PERCENT = SDLK_PERCENT,
SC_KEYCODE_DOLLAR = SDLK_DOLLAR,
SC_KEYCODE_AMPERSAND = SDLK_AMPERSAND,
SC_KEYCODE_QUOTE = SDLK_QUOTE,
SC_KEYCODE_LEFTPAREN = SDLK_LEFTPAREN,
SC_KEYCODE_RIGHTPAREN = SDLK_RIGHTPAREN,
SC_KEYCODE_ASTERISK = SDLK_ASTERISK,
SC_KEYCODE_PLUS = SDLK_PLUS,
SC_KEYCODE_COMMA = SDLK_COMMA,
SC_KEYCODE_MINUS = SDLK_MINUS,
SC_KEYCODE_PERIOD = SDLK_PERIOD,
SC_KEYCODE_SLASH = SDLK_SLASH,
SC_KEYCODE_0 = SDLK_0,
SC_KEYCODE_1 = SDLK_1,
SC_KEYCODE_2 = SDLK_2,
SC_KEYCODE_3 = SDLK_3,
SC_KEYCODE_4 = SDLK_4,
SC_KEYCODE_5 = SDLK_5,
SC_KEYCODE_6 = SDLK_6,
SC_KEYCODE_7 = SDLK_7,
SC_KEYCODE_8 = SDLK_8,
SC_KEYCODE_9 = SDLK_9,
SC_KEYCODE_COLON = SDLK_COLON,
SC_KEYCODE_SEMICOLON = SDLK_SEMICOLON,
SC_KEYCODE_LESS = SDLK_LESS,
SC_KEYCODE_EQUALS = SDLK_EQUALS,
SC_KEYCODE_GREATER = SDLK_GREATER,
SC_KEYCODE_QUESTION = SDLK_QUESTION,
SC_KEYCODE_AT = SDLK_AT,
SC_KEYCODE_LEFTBRACKET = SDLK_LEFTBRACKET,
SC_KEYCODE_BACKSLASH = SDLK_BACKSLASH,
SC_KEYCODE_RIGHTBRACKET = SDLK_RIGHTBRACKET,
SC_KEYCODE_CARET = SDLK_CARET,
SC_KEYCODE_UNDERSCORE = SDLK_UNDERSCORE,
SC_KEYCODE_BACKQUOTE = SDLK_BACKQUOTE,
SC_KEYCODE_a = SDLK_a,
SC_KEYCODE_b = SDLK_b,
SC_KEYCODE_c = SDLK_c,
SC_KEYCODE_d = SDLK_d,
SC_KEYCODE_e = SDLK_e,
SC_KEYCODE_f = SDLK_f,
SC_KEYCODE_g = SDLK_g,
SC_KEYCODE_h = SDLK_h,
SC_KEYCODE_i = SDLK_i,
SC_KEYCODE_j = SDLK_j,
SC_KEYCODE_k = SDLK_k,
SC_KEYCODE_l = SDLK_l,
SC_KEYCODE_m = SDLK_m,
SC_KEYCODE_n = SDLK_n,
SC_KEYCODE_o = SDLK_o,
SC_KEYCODE_p = SDLK_p,
SC_KEYCODE_q = SDLK_q,
SC_KEYCODE_r = SDLK_r,
SC_KEYCODE_s = SDLK_s,
SC_KEYCODE_t = SDLK_t,
SC_KEYCODE_u = SDLK_u,
SC_KEYCODE_v = SDLK_v,
SC_KEYCODE_w = SDLK_w,
SC_KEYCODE_x = SDLK_x,
SC_KEYCODE_y = SDLK_y,
SC_KEYCODE_z = SDLK_z,
SC_KEYCODE_CAPSLOCK = SDLK_CAPSLOCK,
SC_KEYCODE_F1 = SDLK_F1,
SC_KEYCODE_F2 = SDLK_F2,
SC_KEYCODE_F3 = SDLK_F3,
SC_KEYCODE_F4 = SDLK_F4,
SC_KEYCODE_F5 = SDLK_F5,
SC_KEYCODE_F6 = SDLK_F6,
SC_KEYCODE_F7 = SDLK_F7,
SC_KEYCODE_F8 = SDLK_F8,
SC_KEYCODE_F9 = SDLK_F9,
SC_KEYCODE_F10 = SDLK_F10,
SC_KEYCODE_F11 = SDLK_F11,
SC_KEYCODE_F12 = SDLK_F12,
SC_KEYCODE_PRINTSCREEN = SDLK_PRINTSCREEN,
SC_KEYCODE_SCROLLLOCK = SDLK_SCROLLLOCK,
SC_KEYCODE_PAUSE = SDLK_PAUSE,
SC_KEYCODE_INSERT = SDLK_INSERT,
SC_KEYCODE_HOME = SDLK_HOME,
SC_KEYCODE_PAGEUP = SDLK_PAGEUP,
SC_KEYCODE_DELETE = SDLK_DELETE,
SC_KEYCODE_END = SDLK_END,
SC_KEYCODE_PAGEDOWN = SDLK_PAGEDOWN,
SC_KEYCODE_RIGHT = SDLK_RIGHT,
SC_KEYCODE_LEFT = SDLK_LEFT,
SC_KEYCODE_DOWN = SDLK_DOWN,
SC_KEYCODE_UP = SDLK_UP,
SC_KEYCODE_KP_DIVIDE = SDLK_KP_DIVIDE,
SC_KEYCODE_KP_MULTIPLY = SDLK_KP_MULTIPLY,
SC_KEYCODE_KP_MINUS = SDLK_KP_MINUS,
SC_KEYCODE_KP_PLUS = SDLK_KP_PLUS,
SC_KEYCODE_KP_ENTER = SDLK_KP_ENTER,
SC_KEYCODE_KP_1 = SDLK_KP_1,
SC_KEYCODE_KP_2 = SDLK_KP_2,
SC_KEYCODE_KP_3 = SDLK_KP_3,
SC_KEYCODE_KP_4 = SDLK_KP_4,
SC_KEYCODE_KP_5 = SDLK_KP_5,
SC_KEYCODE_KP_6 = SDLK_KP_6,
SC_KEYCODE_KP_7 = SDLK_KP_7,
SC_KEYCODE_KP_8 = SDLK_KP_8,
SC_KEYCODE_KP_9 = SDLK_KP_9,
SC_KEYCODE_KP_0 = SDLK_KP_0,
SC_KEYCODE_KP_PERIOD = SDLK_KP_PERIOD,
SC_KEYCODE_KP_EQUALS = SDLK_KP_EQUALS,
SC_KEYCODE_KP_LEFTPAREN = SDLK_KP_LEFTPAREN,
SC_KEYCODE_KP_RIGHTPAREN = SDLK_KP_RIGHTPAREN,
SC_KEYCODE_LCTRL = SDLK_LCTRL,
SC_KEYCODE_LSHIFT = SDLK_LSHIFT,
SC_KEYCODE_LALT = SDLK_LALT,
SC_KEYCODE_LGUI = SDLK_LGUI,
SC_KEYCODE_RCTRL = SDLK_RCTRL,
SC_KEYCODE_RSHIFT = SDLK_RSHIFT,
SC_KEYCODE_RALT = SDLK_RALT,
SC_KEYCODE_RGUI = SDLK_RGUI,
};
enum sc_scancode {
SC_SCANCODE_UNKNOWN = SDL_SCANCODE_UNKNOWN,
SC_SCANCODE_A = SDL_SCANCODE_A,
SC_SCANCODE_B = SDL_SCANCODE_B,
SC_SCANCODE_C = SDL_SCANCODE_C,
SC_SCANCODE_D = SDL_SCANCODE_D,
SC_SCANCODE_E = SDL_SCANCODE_E,
SC_SCANCODE_F = SDL_SCANCODE_F,
SC_SCANCODE_G = SDL_SCANCODE_G,
SC_SCANCODE_H = SDL_SCANCODE_H,
SC_SCANCODE_I = SDL_SCANCODE_I,
SC_SCANCODE_J = SDL_SCANCODE_J,
SC_SCANCODE_K = SDL_SCANCODE_K,
SC_SCANCODE_L = SDL_SCANCODE_L,
SC_SCANCODE_M = SDL_SCANCODE_M,
SC_SCANCODE_N = SDL_SCANCODE_N,
SC_SCANCODE_O = SDL_SCANCODE_O,
SC_SCANCODE_P = SDL_SCANCODE_P,
SC_SCANCODE_Q = SDL_SCANCODE_Q,
SC_SCANCODE_R = SDL_SCANCODE_R,
SC_SCANCODE_S = SDL_SCANCODE_S,
SC_SCANCODE_T = SDL_SCANCODE_T,
SC_SCANCODE_U = SDL_SCANCODE_U,
SC_SCANCODE_V = SDL_SCANCODE_V,
SC_SCANCODE_W = SDL_SCANCODE_W,
SC_SCANCODE_X = SDL_SCANCODE_X,
SC_SCANCODE_Y = SDL_SCANCODE_Y,
SC_SCANCODE_Z = SDL_SCANCODE_Z,
SC_SCANCODE_1 = SDL_SCANCODE_1,
SC_SCANCODE_2 = SDL_SCANCODE_2,
SC_SCANCODE_3 = SDL_SCANCODE_3,
SC_SCANCODE_4 = SDL_SCANCODE_4,
SC_SCANCODE_5 = SDL_SCANCODE_5,
SC_SCANCODE_6 = SDL_SCANCODE_6,
SC_SCANCODE_7 = SDL_SCANCODE_7,
SC_SCANCODE_8 = SDL_SCANCODE_8,
SC_SCANCODE_9 = SDL_SCANCODE_9,
SC_SCANCODE_0 = SDL_SCANCODE_0,
SC_SCANCODE_RETURN = SDL_SCANCODE_RETURN,
SC_SCANCODE_ESCAPE = SDL_SCANCODE_ESCAPE,
SC_SCANCODE_BACKSPACE = SDL_SCANCODE_BACKSPACE,
SC_SCANCODE_TAB = SDL_SCANCODE_TAB,
SC_SCANCODE_SPACE = SDL_SCANCODE_SPACE,
SC_SCANCODE_MINUS = SDL_SCANCODE_MINUS,
SC_SCANCODE_EQUALS = SDL_SCANCODE_EQUALS,
SC_SCANCODE_LEFTBRACKET = SDL_SCANCODE_LEFTBRACKET,
SC_SCANCODE_RIGHTBRACKET = SDL_SCANCODE_RIGHTBRACKET,
SC_SCANCODE_BACKSLASH = SDL_SCANCODE_BACKSLASH,
SC_SCANCODE_NONUSHASH = SDL_SCANCODE_NONUSHASH,
SC_SCANCODE_SEMICOLON = SDL_SCANCODE_SEMICOLON,
SC_SCANCODE_APOSTROPHE = SDL_SCANCODE_APOSTROPHE,
SC_SCANCODE_GRAVE = SDL_SCANCODE_GRAVE,
SC_SCANCODE_COMMA = SDL_SCANCODE_COMMA,
SC_SCANCODE_PERIOD = SDL_SCANCODE_PERIOD,
SC_SCANCODE_SLASH = SDL_SCANCODE_SLASH,
SC_SCANCODE_CAPSLOCK = SDL_SCANCODE_CAPSLOCK,
SC_SCANCODE_F1 = SDL_SCANCODE_F1,
SC_SCANCODE_F2 = SDL_SCANCODE_F2,
SC_SCANCODE_F3 = SDL_SCANCODE_F3,
SC_SCANCODE_F4 = SDL_SCANCODE_F4,
SC_SCANCODE_F5 = SDL_SCANCODE_F5,
SC_SCANCODE_F6 = SDL_SCANCODE_F6,
SC_SCANCODE_F7 = SDL_SCANCODE_F7,
SC_SCANCODE_F8 = SDL_SCANCODE_F8,
SC_SCANCODE_F9 = SDL_SCANCODE_F9,
SC_SCANCODE_F10 = SDL_SCANCODE_F10,
SC_SCANCODE_F11 = SDL_SCANCODE_F11,
SC_SCANCODE_F12 = SDL_SCANCODE_F12,
SC_SCANCODE_PRINTSCREEN = SDL_SCANCODE_PRINTSCREEN,
SC_SCANCODE_SCROLLLOCK = SDL_SCANCODE_SCROLLLOCK,
SC_SCANCODE_PAUSE = SDL_SCANCODE_PAUSE,
SC_SCANCODE_INSERT = SDL_SCANCODE_INSERT,
SC_SCANCODE_HOME = SDL_SCANCODE_HOME,
SC_SCANCODE_PAGEUP = SDL_SCANCODE_PAGEUP,
SC_SCANCODE_DELETE = SDL_SCANCODE_DELETE,
SC_SCANCODE_END = SDL_SCANCODE_END,
SC_SCANCODE_PAGEDOWN = SDL_SCANCODE_PAGEDOWN,
SC_SCANCODE_RIGHT = SDL_SCANCODE_RIGHT,
SC_SCANCODE_LEFT = SDL_SCANCODE_LEFT,
SC_SCANCODE_DOWN = SDL_SCANCODE_DOWN,
SC_SCANCODE_UP = SDL_SCANCODE_UP,
SC_SCANCODE_NUMLOCK = SDL_SCANCODE_NUMLOCKCLEAR,
SC_SCANCODE_KP_DIVIDE = SDL_SCANCODE_KP_DIVIDE,
SC_SCANCODE_KP_MULTIPLY = SDL_SCANCODE_KP_MULTIPLY,
SC_SCANCODE_KP_MINUS = SDL_SCANCODE_KP_MINUS,
SC_SCANCODE_KP_PLUS = SDL_SCANCODE_KP_PLUS,
SC_SCANCODE_KP_ENTER = SDL_SCANCODE_KP_ENTER,
SC_SCANCODE_KP_1 = SDL_SCANCODE_KP_1,
SC_SCANCODE_KP_2 = SDL_SCANCODE_KP_2,
SC_SCANCODE_KP_3 = SDL_SCANCODE_KP_3,
SC_SCANCODE_KP_4 = SDL_SCANCODE_KP_4,
SC_SCANCODE_KP_5 = SDL_SCANCODE_KP_5,
SC_SCANCODE_KP_6 = SDL_SCANCODE_KP_6,
SC_SCANCODE_KP_7 = SDL_SCANCODE_KP_7,
SC_SCANCODE_KP_8 = SDL_SCANCODE_KP_8,
SC_SCANCODE_KP_9 = SDL_SCANCODE_KP_9,
SC_SCANCODE_KP_0 = SDL_SCANCODE_KP_0,
SC_SCANCODE_KP_PERIOD = SDL_SCANCODE_KP_PERIOD,
SC_SCANCODE_LCTRL = SDL_SCANCODE_LCTRL,
SC_SCANCODE_LSHIFT = SDL_SCANCODE_LSHIFT,
SC_SCANCODE_LALT = SDL_SCANCODE_LALT,
SC_SCANCODE_LGUI = SDL_SCANCODE_LGUI,
SC_SCANCODE_RCTRL = SDL_SCANCODE_RCTRL,
SC_SCANCODE_RSHIFT = SDL_SCANCODE_RSHIFT,
SC_SCANCODE_RALT = SDL_SCANCODE_RALT,
SC_SCANCODE_RGUI = SDL_SCANCODE_RGUI,
};
// On purpose, only use the "mask" values (1, 2, 4, 8, 16) for a single button,
// to avoid unnecessary conversions (and confusion).
enum sc_mouse_button {
SC_MOUSE_BUTTON_UNKNOWN = 0,
SC_MOUSE_BUTTON_LEFT = SDL_BUTTON(SDL_BUTTON_LEFT),
SC_MOUSE_BUTTON_RIGHT = SDL_BUTTON(SDL_BUTTON_RIGHT),
SC_MOUSE_BUTTON_MIDDLE = SDL_BUTTON(SDL_BUTTON_MIDDLE),
SC_MOUSE_BUTTON_X1 = SDL_BUTTON(SDL_BUTTON_X1),
SC_MOUSE_BUTTON_X2 = SDL_BUTTON(SDL_BUTTON_X2),
};
static_assert(sizeof(enum sc_mod) >= sizeof(SDL_Keymod),
"SDL_Keymod must be convertible to sc_mod");
static_assert(sizeof(enum sc_keycode) >= sizeof(SDL_Keycode),
"SDL_Keycode must be convertible to sc_keycode");
static_assert(sizeof(enum sc_scancode) >= sizeof(SDL_Scancode),
"SDL_Scancode must be convertible to sc_scancode");
enum sc_touch_action {
SC_TOUCH_ACTION_MOVE,
SC_TOUCH_ACTION_DOWN,
SC_TOUCH_ACTION_UP,
};
struct sc_key_event {
enum sc_action action;
enum sc_keycode keycode;
enum sc_scancode scancode;
uint16_t mods_state; // bitwise-OR of sc_mod values
bool repeat;
};
struct sc_text_event {
const char *text; // not owned
};
struct sc_mouse_click_event {
struct sc_position position;
enum sc_action action;
enum sc_mouse_button button;
uint8_t buttons_state; // bitwise-OR of sc_mouse_button values
};
struct sc_mouse_scroll_event {
struct sc_position position;
int32_t hscroll;
int32_t vscroll;
uint8_t buttons_state; // bitwise-OR of sc_mouse_button values
};
struct sc_mouse_motion_event {
struct sc_position position;
int32_t xrel;
int32_t yrel;
uint8_t buttons_state; // bitwise-OR of sc_mouse_button values
};
struct sc_touch_event {
struct sc_position position;
enum sc_touch_action action;
uint64_t pointer_id;
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

@ -3,39 +3,38 @@
#include <assert.h>
#include <SDL2/SDL_keycode.h>
#include "input_events.h"
#include "screen.h"
#include "util/log.h"
static const int ACTION_DOWN = 1;
static const int ACTION_UP = 1 << 1;
#define SC_SDL_SHORTCUT_MODS_MASK (KMOD_CTRL | KMOD_ALT | KMOD_GUI)
static inline uint16_t
to_sdl_mod(unsigned mod) {
to_sdl_mod(unsigned shortcut_mod) {
uint16_t sdl_mod = 0;
if (mod & SC_MOD_LCTRL) {
if (shortcut_mod & SC_SHORTCUT_MOD_LCTRL) {
sdl_mod |= KMOD_LCTRL;
}
if (mod & SC_MOD_RCTRL) {
if (shortcut_mod & SC_SHORTCUT_MOD_RCTRL) {
sdl_mod |= KMOD_RCTRL;
}
if (mod & SC_MOD_LALT) {
if (shortcut_mod & SC_SHORTCUT_MOD_LALT) {
sdl_mod |= KMOD_LALT;
}
if (mod & SC_MOD_RALT) {
if (shortcut_mod & SC_SHORTCUT_MOD_RALT) {
sdl_mod |= KMOD_RALT;
}
if (mod & SC_MOD_LSUPER) {
if (shortcut_mod & SC_SHORTCUT_MOD_LSUPER) {
sdl_mod |= KMOD_LGUI;
}
if (mod & SC_MOD_RSUPER) {
if (shortcut_mod & SC_SHORTCUT_MOD_RSUPER) {
sdl_mod |= KMOD_RGUI;
}
return sdl_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;
@ -51,24 +50,22 @@ is_shortcut_mod(struct input_manager *im, uint16_t sdl_mod) {
}
void
input_manager_init(struct input_manager *im, struct controller *controller,
struct screen *screen, struct sc_key_processor *kp,
struct sc_mouse_processor *mp,
const struct scrcpy_options *options) {
assert(!options->control || (kp && kp->ops));
assert(!options->control || (mp && 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 = controller;
im->screen = screen;
im->kp = kp;
im->mp = mp;
im->controller = params->controller;
im->fp = params->fp;
im->screen = params->screen;
im->kp = params->kp;
im->mp = params->mp;
im->control = options->control;
im->forward_all_clicks = options->forward_all_clicks;
im->legacy_paste = options->legacy_paste;
im->clipboard_autosync = options->clipboard_autosync;
im->forward_all_clicks = params->forward_all_clicks;
im->legacy_paste = params->legacy_paste;
im->clipboard_autosync = params->clipboard_autosync;
const struct sc_shortcut_mods *shortcut_mods = &options->shortcut_mods;
const struct sc_shortcut_mods *shortcut_mods = params->shortcut_mods;
assert(shortcut_mods->count);
assert(shortcut_mods->count < SC_MAX_SHORTCUT_MODS);
for (unsigned i = 0; i < shortcut_mods->count; ++i) {
@ -88,127 +85,112 @@ input_manager_init(struct input_manager *im, struct controller *controller,
}
static void
send_keycode(struct controller *controller, enum android_keycode keycode,
int actions, const char *name) {
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;
msg.inject_keycode.keycode = keycode;
msg.inject_keycode.metastate = 0;
msg.inject_keycode.repeat = 0;
if (actions & ACTION_DOWN) {
msg.inject_keycode.action = AKEY_EVENT_ACTION_DOWN;
if (!controller_push_msg(controller, &msg)) {
LOGW("Could not request 'inject %s (DOWN)'", name);
return;
}
}
if (actions & ACTION_UP) {
msg.inject_keycode.action = AKEY_EVENT_ACTION_UP;
if (!controller_push_msg(controller, &msg)) {
LOGW("Could not request 'inject %s (UP)'", name);
}
if (!sc_controller_push_msg(controller, &msg)) {
LOGW("Could not request 'inject %s'", name);
}
}
static inline void
action_home(struct controller *controller, int actions) {
send_keycode(controller, AKEYCODE_HOME, actions, "HOME");
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, int actions) {
send_keycode(controller, AKEYCODE_BACK, actions, "BACK");
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, int actions) {
send_keycode(controller, AKEYCODE_APP_SWITCH, actions, "APP_SWITCH");
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, int actions) {
send_keycode(controller, AKEYCODE_POWER, actions, "POWER");
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, int actions) {
send_keycode(controller, AKEYCODE_VOLUME_UP, actions, "VOLUME_UP");
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, int actions) {
send_keycode(controller, AKEYCODE_VOLUME_DOWN, actions, "VOLUME_DOWN");
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, int actions) {
send_keycode(controller, AKEYCODE_MENU, actions, "MENU");
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, int actions) {
struct control_msg msg;
msg.type = CONTROL_MSG_TYPE_BACK_OR_SCREEN_ON;
press_back_or_turn_screen_on(struct sc_controller *controller,
enum sc_action action) {
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 (actions & ACTION_DOWN) {
msg.back_or_screen_on.action = AKEY_EVENT_ACTION_DOWN;
if (!controller_push_msg(controller, &msg)) {
LOGW("Could not request 'press back or turn screen on'");
return;
}
}
if (actions & ACTION_UP) {
msg.back_or_screen_on.action = AKEY_EVENT_ACTION_UP;
if (!controller_push_msg(controller, &msg)) {
LOGW("Could not request 'press back or turn screen on'");
}
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;
}
@ -217,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) {
@ -232,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;
@ -248,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'");
}
}
@ -276,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());
@ -295,56 +277,65 @@ 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;
}
if (is_shortcut_mod(im, SDL_GetModState())) {
// A shortcut must never generate text events
return;
}
im->kp->ops->process_text(im->kp, event);
struct sc_text_event evt = {
.text = event->text,
};
im->kp->ops->process_text(im->kp, &evt);
}
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;
@ -352,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;
}
@ -368,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;
@ -396,50 +385,50 @@ input_manager_process_key(struct input_manager *im,
// The shortcut modifier is pressed
if (smod) {
int action = down ? ACTION_DOWN : ACTION_UP;
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);
}
@ -455,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);
@ -481,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:
@ -500,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) {
@ -511,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;
@ -520,7 +507,7 @@ input_manager_process_key(struct input_manager *im,
return;
}
if (!control) {
if (!controller) {
return;
}
@ -553,46 +540,91 @@ input_manager_process_key(struct input_manager *im,
}
}
im->kp->ops->process_key(im->kp, event, ack_to_wait);
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(im->kp->ops->process_key);
im->kp->ops->process_key(im->kp, &evt, ack_to_wait);
}
static void
input_manager_process_mouse_motion(struct input_manager *im,
const SDL_MouseMotionEvent *event) {
uint32_t mask = SDL_BUTTON_LMASK;
if (im->forward_all_clicks) {
mask |= SDL_BUTTON_MMASK | SDL_BUTTON_RMASK;
}
if (!(event->state & mask)) {
// do not send motion events when no click is pressed
return;
}
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
return;
}
im->mp->ops->process_mouse_motion(im->mp, event);
struct sc_mouse_motion_event evt = {
.position = {
.screen_size = im->screen->frame_size,
.point = sc_screen_convert_window_to_frame_coords(im->screen,
event->x,
event->y),
},
.xrel = event->xrel,
.yrel = event->yrel,
.buttons_state =
sc_mouse_buttons_state_from_sdl(event->state,
im->forward_all_clicks),
};
assert(im->mp->ops->process_mouse_motion);
im->mp->ops->process_mouse_motion(im->mp, &evt);
// vfinger must never be used in relative mode
assert(!im->mp->relative_mode || !im->vfinger_down);
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) {
im->mp->ops->process_touch(im->mp, 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;
}
int dw;
int dh;
SDL_GL_GetDrawableSize(im->screen->window, &dw, &dh);
// SDL touch event coordinates are normalized in the range [0; 1]
int32_t x = event->x * dw;
int32_t y = event->y * dh;
struct sc_touch_event evt = {
.position = {
.screen_size = im->screen->frame_size,
.point =
sc_screen_convert_drawable_to_frame_coords(im->screen, x, y),
},
.action = sc_touch_action_from_sdl(event->type),
.pointer_id = event->fingerId,
.pressure = event->pressure,
};
im->mp->ops->process_touch(im->mp, &evt);
}
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
@ -601,40 +633,42 @@ input_manager_process_mouse_button(struct input_manager *im,
bool down = event->type == SDL_MOUSEBUTTONDOWN;
if (!im->forward_all_clicks) {
int action = down ? ACTION_DOWN : 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;
}
@ -642,11 +676,34 @@ input_manager_process_mouse_button(struct input_manager *im,
// otherwise, send the click event to the device
}
if (!control) {
if (!controller) {
return;
}
im->mp->ops->process_mouse_button(im->mp, event);
uint32_t sdl_buttons_state = SDL_GetMouseState(NULL, NULL);
struct sc_mouse_click_event evt = {
.position = {
.screen_size = im->screen->frame_size,
.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),
.buttons_state =
sc_mouse_buttons_state_from_sdl(sdl_buttons_state,
im->forward_all_clicks),
};
assert(im->mp->ops->process_mouse_click);
im->mp->ops->process_mouse_click(im->mp, &evt);
if (im->mp->relative_mode) {
assert(!im->vfinger_down); // vfinger must not be used in relative mode
// No pinch-to-zoom simulation
return;
}
// Pinch-to-zoom simulation.
//
@ -658,11 +715,12 @@ input_manager_process_mouse_button(struct input_manager *im,
// In other words, the center of the rotation/scaling is the center of the
// screen.
#define CTRL_PRESSED (SDL_GetModState() & (KMOD_LCTRL | KMOD_RCTRL))
if ((down && !im->vfinger_down && CTRL_PRESSED)
|| (!down && im->vfinger_down)) {
if (event->button == SDL_BUTTON_LEFT &&
((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
@ -675,50 +733,108 @@ input_manager_process_mouse_button(struct input_manager *im,
}
static void
input_manager_process_mouse_wheel(struct input_manager *im,
const SDL_MouseWheelEvent *event) {
im->mp->ops->process_mouse_wheel(im->mp, 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;
}
// mouse_x and mouse_y are expressed in pixels relative to the window
int mouse_x;
int mouse_y;
uint32_t buttons = SDL_GetMouseState(&mouse_x, &mouse_y);
struct sc_mouse_scroll_event evt = {
.position = {
.screen_size = im->screen->frame_size,
.point = sc_screen_convert_window_to_frame_coords(im->screen,
mouse_x, mouse_y),
},
.hscroll = event->x,
.vscroll = event->y,
.buttons_state =
sc_mouse_buttons_state_from_sdl(buttons, im->forward_all_clicks),
};
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,20 +8,20 @@
#include <SDL2/SDL.h>
#include "controller.h"
#include "file_pusher.h"
#include "fps_counter.h"
#include "options.h"
#include "screen.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;
@ -43,13 +43,24 @@ struct input_manager {
uint64_t next_sequence; // used for request acknowledgements
};
void
input_manager_init(struct input_manager *im, struct controller *controller,
struct screen *screen, struct sc_key_processor *kp,
struct sc_mouse_processor *mp,
const struct scrcpy_options *options);
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
input_manager_handle_event(struct input_manager *im, SDL_Event *event);
bool forward_all_clicks;
bool legacy_paste;
bool clipboard_autosync;
const struct sc_shortcut_mods *shortcut_mods;
};
void
sc_input_manager_init(struct sc_input_manager *im,
const struct sc_input_manager_params *params);
void
sc_input_manager_handle_event(struct sc_input_manager *im, SDL_Event *event);
#endif

View File

@ -1,153 +1,146 @@
#include "keyboard_inject.h"
#include <assert.h>
#include <SDL2/SDL_events.h>
#include "android/input.h"
#include "control_msg.h"
#include "controller.h"
#include "input_events.h"
#include "util/intmap.h"
#include "util/log.h"
/** Downcast key processor to sc_keyboard_inject */
#define DOWNCAST(KP) container_of(KP, struct sc_keyboard_inject, key_processor)
static bool
convert_keycode_action(SDL_EventType from, enum android_keyevent_action *to) {
static const struct sc_intmap_entry actions[] = {
{SDL_KEYDOWN, AKEY_EVENT_ACTION_DOWN},
{SDL_KEYUP, AKEY_EVENT_ACTION_UP},
};
const struct sc_intmap_entry *entry = SC_INTMAP_FIND_ENTRY(actions, from);
if (entry) {
*to = entry->value;
return true;
static enum android_keyevent_action
convert_keycode_action(enum sc_action action) {
if (action == SC_ACTION_DOWN) {
return AKEY_EVENT_ACTION_DOWN;
}
return false;
assert(action == SC_ACTION_UP);
return AKEY_EVENT_ACTION_UP;
}
static bool
convert_keycode(SDL_Keycode from, enum android_keycode *to, uint16_t mod,
convert_keycode(enum sc_keycode from, enum android_keycode *to, uint16_t mod,
enum sc_key_inject_mode key_inject_mode) {
// Navigation keys and ENTER.
// Used in all modes.
static const struct sc_intmap_entry special_keys[] = {
{SDLK_RETURN, AKEYCODE_ENTER},
{SDLK_KP_ENTER, AKEYCODE_NUMPAD_ENTER},
{SDLK_ESCAPE, AKEYCODE_ESCAPE},
{SDLK_BACKSPACE, AKEYCODE_DEL},
{SDLK_TAB, AKEYCODE_TAB},
{SDLK_PAGEUP, AKEYCODE_PAGE_UP},
{SDLK_DELETE, AKEYCODE_FORWARD_DEL},
{SDLK_HOME, AKEYCODE_MOVE_HOME},
{SDLK_END, AKEYCODE_MOVE_END},
{SDLK_PAGEDOWN, AKEYCODE_PAGE_DOWN},
{SDLK_RIGHT, AKEYCODE_DPAD_RIGHT},
{SDLK_LEFT, AKEYCODE_DPAD_LEFT},
{SDLK_DOWN, AKEYCODE_DPAD_DOWN},
{SDLK_UP, AKEYCODE_DPAD_UP},
{SDLK_LCTRL, AKEYCODE_CTRL_LEFT},
{SDLK_RCTRL, AKEYCODE_CTRL_RIGHT},
{SDLK_LSHIFT, AKEYCODE_SHIFT_LEFT},
{SDLK_RSHIFT, AKEYCODE_SHIFT_RIGHT},
{SC_KEYCODE_RETURN, AKEYCODE_ENTER},
{SC_KEYCODE_KP_ENTER, AKEYCODE_NUMPAD_ENTER},
{SC_KEYCODE_ESCAPE, AKEYCODE_ESCAPE},
{SC_KEYCODE_BACKSPACE, AKEYCODE_DEL},
{SC_KEYCODE_TAB, AKEYCODE_TAB},
{SC_KEYCODE_PAGEUP, AKEYCODE_PAGE_UP},
{SC_KEYCODE_DELETE, AKEYCODE_FORWARD_DEL},
{SC_KEYCODE_HOME, AKEYCODE_MOVE_HOME},
{SC_KEYCODE_END, AKEYCODE_MOVE_END},
{SC_KEYCODE_PAGEDOWN, AKEYCODE_PAGE_DOWN},
{SC_KEYCODE_RIGHT, AKEYCODE_DPAD_RIGHT},
{SC_KEYCODE_LEFT, AKEYCODE_DPAD_LEFT},
{SC_KEYCODE_DOWN, AKEYCODE_DPAD_DOWN},
{SC_KEYCODE_UP, AKEYCODE_DPAD_UP},
{SC_KEYCODE_LCTRL, AKEYCODE_CTRL_LEFT},
{SC_KEYCODE_RCTRL, AKEYCODE_CTRL_RIGHT},
{SC_KEYCODE_LSHIFT, AKEYCODE_SHIFT_LEFT},
{SC_KEYCODE_RSHIFT, AKEYCODE_SHIFT_RIGHT},
};
// Numpad navigation keys.
// Used in all modes, when NumLock and Shift are disabled.
static const struct sc_intmap_entry kp_nav_keys[] = {
{SDLK_KP_0, AKEYCODE_INSERT},
{SDLK_KP_1, AKEYCODE_MOVE_END},
{SDLK_KP_2, AKEYCODE_DPAD_DOWN},
{SDLK_KP_3, AKEYCODE_PAGE_DOWN},
{SDLK_KP_4, AKEYCODE_DPAD_LEFT},
{SDLK_KP_6, AKEYCODE_DPAD_RIGHT},
{SDLK_KP_7, AKEYCODE_MOVE_HOME},
{SDLK_KP_8, AKEYCODE_DPAD_UP},
{SDLK_KP_9, AKEYCODE_PAGE_UP},
{SDLK_KP_PERIOD, AKEYCODE_FORWARD_DEL},
{SC_KEYCODE_KP_0, AKEYCODE_INSERT},
{SC_KEYCODE_KP_1, AKEYCODE_MOVE_END},
{SC_KEYCODE_KP_2, AKEYCODE_DPAD_DOWN},
{SC_KEYCODE_KP_3, AKEYCODE_PAGE_DOWN},
{SC_KEYCODE_KP_4, AKEYCODE_DPAD_LEFT},
{SC_KEYCODE_KP_6, AKEYCODE_DPAD_RIGHT},
{SC_KEYCODE_KP_7, AKEYCODE_MOVE_HOME},
{SC_KEYCODE_KP_8, AKEYCODE_DPAD_UP},
{SC_KEYCODE_KP_9, AKEYCODE_PAGE_UP},
{SC_KEYCODE_KP_PERIOD, AKEYCODE_FORWARD_DEL},
};
// Letters and space.
// Used in non-text mode.
static const struct sc_intmap_entry alphaspace_keys[] = {
{SDLK_a, AKEYCODE_A},
{SDLK_b, AKEYCODE_B},
{SDLK_c, AKEYCODE_C},
{SDLK_d, AKEYCODE_D},
{SDLK_e, AKEYCODE_E},
{SDLK_f, AKEYCODE_F},
{SDLK_g, AKEYCODE_G},
{SDLK_h, AKEYCODE_H},
{SDLK_i, AKEYCODE_I},
{SDLK_j, AKEYCODE_J},
{SDLK_k, AKEYCODE_K},
{SDLK_l, AKEYCODE_L},
{SDLK_m, AKEYCODE_M},
{SDLK_n, AKEYCODE_N},
{SDLK_o, AKEYCODE_O},
{SDLK_p, AKEYCODE_P},
{SDLK_q, AKEYCODE_Q},
{SDLK_r, AKEYCODE_R},
{SDLK_s, AKEYCODE_S},
{SDLK_t, AKEYCODE_T},
{SDLK_u, AKEYCODE_U},
{SDLK_v, AKEYCODE_V},
{SDLK_w, AKEYCODE_W},
{SDLK_x, AKEYCODE_X},
{SDLK_y, AKEYCODE_Y},
{SDLK_z, AKEYCODE_Z},
{SDLK_SPACE, AKEYCODE_SPACE},
{SC_KEYCODE_a, AKEYCODE_A},
{SC_KEYCODE_b, AKEYCODE_B},
{SC_KEYCODE_c, AKEYCODE_C},
{SC_KEYCODE_d, AKEYCODE_D},
{SC_KEYCODE_e, AKEYCODE_E},
{SC_KEYCODE_f, AKEYCODE_F},
{SC_KEYCODE_g, AKEYCODE_G},
{SC_KEYCODE_h, AKEYCODE_H},
{SC_KEYCODE_i, AKEYCODE_I},
{SC_KEYCODE_j, AKEYCODE_J},
{SC_KEYCODE_k, AKEYCODE_K},
{SC_KEYCODE_l, AKEYCODE_L},
{SC_KEYCODE_m, AKEYCODE_M},
{SC_KEYCODE_n, AKEYCODE_N},
{SC_KEYCODE_o, AKEYCODE_O},
{SC_KEYCODE_p, AKEYCODE_P},
{SC_KEYCODE_q, AKEYCODE_Q},
{SC_KEYCODE_r, AKEYCODE_R},
{SC_KEYCODE_s, AKEYCODE_S},
{SC_KEYCODE_t, AKEYCODE_T},
{SC_KEYCODE_u, AKEYCODE_U},
{SC_KEYCODE_v, AKEYCODE_V},
{SC_KEYCODE_w, AKEYCODE_W},
{SC_KEYCODE_x, AKEYCODE_X},
{SC_KEYCODE_y, AKEYCODE_Y},
{SC_KEYCODE_z, AKEYCODE_Z},
{SC_KEYCODE_SPACE, AKEYCODE_SPACE},
};
// Numbers and punctuation keys.
// Used in raw mode only.
static const struct sc_intmap_entry numbers_punct_keys[] = {
{SDLK_HASH, AKEYCODE_POUND},
{SDLK_PERCENT, AKEYCODE_PERIOD},
{SDLK_QUOTE, AKEYCODE_APOSTROPHE},
{SDLK_ASTERISK, AKEYCODE_STAR},
{SDLK_PLUS, AKEYCODE_PLUS},
{SDLK_COMMA, AKEYCODE_COMMA},
{SDLK_MINUS, AKEYCODE_MINUS},
{SDLK_PERIOD, AKEYCODE_PERIOD},
{SDLK_SLASH, AKEYCODE_SLASH},
{SDLK_0, AKEYCODE_0},
{SDLK_1, AKEYCODE_1},
{SDLK_2, AKEYCODE_2},
{SDLK_3, AKEYCODE_3},
{SDLK_4, AKEYCODE_4},
{SDLK_5, AKEYCODE_5},
{SDLK_6, AKEYCODE_6},
{SDLK_7, AKEYCODE_7},
{SDLK_8, AKEYCODE_8},
{SDLK_9, AKEYCODE_9},
{SDLK_SEMICOLON, AKEYCODE_SEMICOLON},
{SDLK_EQUALS, AKEYCODE_EQUALS},
{SDLK_AT, AKEYCODE_AT},
{SDLK_LEFTBRACKET, AKEYCODE_LEFT_BRACKET},
{SDLK_BACKSLASH, AKEYCODE_BACKSLASH},
{SDLK_RIGHTBRACKET, AKEYCODE_RIGHT_BRACKET},
{SDLK_BACKQUOTE, AKEYCODE_GRAVE},
{SDLK_KP_1, AKEYCODE_NUMPAD_1},
{SDLK_KP_2, AKEYCODE_NUMPAD_2},
{SDLK_KP_3, AKEYCODE_NUMPAD_3},
{SDLK_KP_4, AKEYCODE_NUMPAD_4},
{SDLK_KP_5, AKEYCODE_NUMPAD_5},
{SDLK_KP_6, AKEYCODE_NUMPAD_6},
{SDLK_KP_7, AKEYCODE_NUMPAD_7},
{SDLK_KP_8, AKEYCODE_NUMPAD_8},
{SDLK_KP_9, AKEYCODE_NUMPAD_9},
{SDLK_KP_0, AKEYCODE_NUMPAD_0},
{SDLK_KP_DIVIDE, AKEYCODE_NUMPAD_DIVIDE},
{SDLK_KP_MULTIPLY, AKEYCODE_NUMPAD_MULTIPLY},
{SDLK_KP_MINUS, AKEYCODE_NUMPAD_SUBTRACT},
{SDLK_KP_PLUS, AKEYCODE_NUMPAD_ADD},
{SDLK_KP_PERIOD, AKEYCODE_NUMPAD_DOT},
{SDLK_KP_EQUALS, AKEYCODE_NUMPAD_EQUALS},
{SDLK_KP_LEFTPAREN, AKEYCODE_NUMPAD_LEFT_PAREN},
{SDLK_KP_RIGHTPAREN, AKEYCODE_NUMPAD_RIGHT_PAREN},
{SC_KEYCODE_HASH, AKEYCODE_POUND},
{SC_KEYCODE_PERCENT, AKEYCODE_PERIOD},
{SC_KEYCODE_QUOTE, AKEYCODE_APOSTROPHE},
{SC_KEYCODE_ASTERISK, AKEYCODE_STAR},
{SC_KEYCODE_PLUS, AKEYCODE_PLUS},
{SC_KEYCODE_COMMA, AKEYCODE_COMMA},
{SC_KEYCODE_MINUS, AKEYCODE_MINUS},
{SC_KEYCODE_PERIOD, AKEYCODE_PERIOD},
{SC_KEYCODE_SLASH, AKEYCODE_SLASH},
{SC_KEYCODE_0, AKEYCODE_0},
{SC_KEYCODE_1, AKEYCODE_1},
{SC_KEYCODE_2, AKEYCODE_2},
{SC_KEYCODE_3, AKEYCODE_3},
{SC_KEYCODE_4, AKEYCODE_4},
{SC_KEYCODE_5, AKEYCODE_5},
{SC_KEYCODE_6, AKEYCODE_6},
{SC_KEYCODE_7, AKEYCODE_7},
{SC_KEYCODE_8, AKEYCODE_8},
{SC_KEYCODE_9, AKEYCODE_9},
{SC_KEYCODE_SEMICOLON, AKEYCODE_SEMICOLON},
{SC_KEYCODE_EQUALS, AKEYCODE_EQUALS},
{SC_KEYCODE_AT, AKEYCODE_AT},
{SC_KEYCODE_LEFTBRACKET, AKEYCODE_LEFT_BRACKET},
{SC_KEYCODE_BACKSLASH, AKEYCODE_BACKSLASH},
{SC_KEYCODE_RIGHTBRACKET, AKEYCODE_RIGHT_BRACKET},
{SC_KEYCODE_BACKQUOTE, AKEYCODE_GRAVE},
{SC_KEYCODE_KP_1, AKEYCODE_NUMPAD_1},
{SC_KEYCODE_KP_2, AKEYCODE_NUMPAD_2},
{SC_KEYCODE_KP_3, AKEYCODE_NUMPAD_3},
{SC_KEYCODE_KP_4, AKEYCODE_NUMPAD_4},
{SC_KEYCODE_KP_5, AKEYCODE_NUMPAD_5},
{SC_KEYCODE_KP_6, AKEYCODE_NUMPAD_6},
{SC_KEYCODE_KP_7, AKEYCODE_NUMPAD_7},
{SC_KEYCODE_KP_8, AKEYCODE_NUMPAD_8},
{SC_KEYCODE_KP_9, AKEYCODE_NUMPAD_9},
{SC_KEYCODE_KP_0, AKEYCODE_NUMPAD_0},
{SC_KEYCODE_KP_DIVIDE, AKEYCODE_NUMPAD_DIVIDE},
{SC_KEYCODE_KP_MULTIPLY, AKEYCODE_NUMPAD_MULTIPLY},
{SC_KEYCODE_KP_MINUS, AKEYCODE_NUMPAD_SUBTRACT},
{SC_KEYCODE_KP_PLUS, AKEYCODE_NUMPAD_ADD},
{SC_KEYCODE_KP_PERIOD, AKEYCODE_NUMPAD_DOT},
{SC_KEYCODE_KP_EQUALS, AKEYCODE_NUMPAD_EQUALS},
{SC_KEYCODE_KP_LEFTPAREN, AKEYCODE_NUMPAD_LEFT_PAREN},
{SC_KEYCODE_KP_RIGHTPAREN, AKEYCODE_NUMPAD_RIGHT_PAREN},
};
const struct sc_intmap_entry *entry =
@ -157,7 +150,7 @@ convert_keycode(SDL_Keycode from, enum android_keycode *to, uint16_t mod,
return true;
}
if (!(mod & (KMOD_NUM | KMOD_SHIFT))) {
if (!(mod & (SC_MOD_NUM | SC_MOD_LSHIFT | SC_MOD_RSHIFT))) {
// Handle Numpad events when Num Lock is disabled
// If SHIFT is pressed, a text event will be sent instead
entry = SC_INTMAP_FIND_ENTRY(kp_nav_keys, from);
@ -167,12 +160,13 @@ convert_keycode(SDL_Keycode from, enum android_keycode *to, uint16_t mod,
}
}
if (key_inject_mode == SC_KEY_INJECT_MODE_TEXT && !(mod & KMOD_CTRL)) {
if (key_inject_mode == SC_KEY_INJECT_MODE_TEXT &&
!(mod & (SC_MOD_LCTRL | SC_MOD_RCTRL))) {
// do not forward alpha and space key events (unless Ctrl is pressed)
return false;
}
if (mod & (KMOD_LALT | KMOD_RALT | KMOD_LGUI | KMOD_RGUI)) {
if (mod & (SC_MOD_LALT | SC_MOD_RALT | SC_MOD_LGUI | SC_MOD_RGUI)) {
return false;
}
@ -214,70 +208,63 @@ autocomplete_metastate(enum android_metastate metastate) {
}
static enum android_metastate
convert_meta_state(SDL_Keymod mod) {
convert_meta_state(uint16_t mod) {
enum android_metastate metastate = 0;
if (mod & KMOD_LSHIFT) {
if (mod & SC_MOD_LSHIFT) {
metastate |= AMETA_SHIFT_LEFT_ON;
}
if (mod & KMOD_RSHIFT) {
if (mod & SC_MOD_RSHIFT) {
metastate |= AMETA_SHIFT_RIGHT_ON;
}
if (mod & KMOD_LCTRL) {
if (mod & SC_MOD_LCTRL) {
metastate |= AMETA_CTRL_LEFT_ON;
}
if (mod & KMOD_RCTRL) {
if (mod & SC_MOD_RCTRL) {
metastate |= AMETA_CTRL_RIGHT_ON;
}
if (mod & KMOD_LALT) {
if (mod & SC_MOD_LALT) {
metastate |= AMETA_ALT_LEFT_ON;
}
if (mod & KMOD_RALT) {
if (mod & SC_MOD_RALT) {
metastate |= AMETA_ALT_RIGHT_ON;
}
if (mod & KMOD_LGUI) { // Windows key
if (mod & SC_MOD_LGUI) { // Windows key
metastate |= AMETA_META_LEFT_ON;
}
if (mod & KMOD_RGUI) { // Windows key
if (mod & SC_MOD_RGUI) { // Windows key
metastate |= AMETA_META_RIGHT_ON;
}
if (mod & KMOD_NUM) {
if (mod & SC_MOD_NUM) {
metastate |= AMETA_NUM_LOCK_ON;
}
if (mod & KMOD_CAPS) {
if (mod & SC_MOD_CAPS) {
metastate |= AMETA_CAPS_LOCK_ON;
}
if (mod & KMOD_MODE) { // Alt Gr
// no mapping?
}
// fill the dependent fields
return autocomplete_metastate(metastate);
}
static bool
convert_input_key(const SDL_KeyboardEvent *from, struct control_msg *to,
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) {
to->type = CONTROL_MSG_TYPE_INJECT_KEYCODE;
msg->type = SC_CONTROL_MSG_TYPE_INJECT_KEYCODE;
if (!convert_keycode_action(from->type, &to->inject_keycode.action)) {
if (!convert_keycode(event->keycode, &msg->inject_keycode.keycode,
event->mods_state, key_inject_mode)) {
return false;
}
uint16_t mod = from->keysym.mod;
if (!convert_keycode(from->keysym.sym, &to->inject_keycode.keycode, mod,
key_inject_mode)) {
return false;
}
to->inject_keycode.repeat = repeat;
to->inject_keycode.metastate = convert_meta_state(mod);
msg->inject_keycode.action = convert_keycode_action(event->action);
msg->inject_keycode.repeat = repeat;
msg->inject_keycode.metastate = convert_meta_state(event->mods_state);
return true;
}
static void
sc_key_processor_process_key(struct sc_key_processor *kp,
const SDL_KeyboardEvent *event,
const struct sc_key_event *event,
uint64_t ack_to_wait) {
// The device clipboard synchronization and the key event messages are
// serialized, there is nothing special to do to ensure that the clipboard
@ -295,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'");
}
}
@ -305,7 +292,7 @@ sc_key_processor_process_key(struct sc_key_processor *kp,
static void
sc_key_processor_process_text(struct sc_key_processor *kp,
const SDL_TextInputEvent *event) {
const struct sc_text_event *event) {
struct sc_keyboard_inject *ki = DOWNCAST(kp);
if (ki->key_inject_mode == SC_KEY_INJECT_MODE_RAW) {
@ -322,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'");
}
@ -337,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

@ -1,11 +1,11 @@
#include "mouse_inject.h"
#include <assert.h>
#include <SDL2/SDL_events.h>
#include "android/input.h"
#include "control_msg.h"
#include "controller.h"
#include "input_events.h"
#include "util/intmap.h"
#include "util/log.h"
@ -15,210 +15,147 @@
static enum android_motionevent_buttons
convert_mouse_buttons(uint32_t state) {
enum android_motionevent_buttons buttons = 0;
if (state & SDL_BUTTON_LMASK) {
if (state & SC_MOUSE_BUTTON_LEFT) {
buttons |= AMOTION_EVENT_BUTTON_PRIMARY;
}
if (state & SDL_BUTTON_RMASK) {
if (state & SC_MOUSE_BUTTON_RIGHT) {
buttons |= AMOTION_EVENT_BUTTON_SECONDARY;
}
if (state & SDL_BUTTON_MMASK) {
if (state & SC_MOUSE_BUTTON_MIDDLE) {
buttons |= AMOTION_EVENT_BUTTON_TERTIARY;
}
if (state & SDL_BUTTON_X1MASK) {
if (state & SC_MOUSE_BUTTON_X1) {
buttons |= AMOTION_EVENT_BUTTON_BACK;
}
if (state & SDL_BUTTON_X2MASK) {
if (state & SC_MOUSE_BUTTON_X2) {
buttons |= AMOTION_EVENT_BUTTON_FORWARD;
}
return buttons;
}
static bool
convert_mouse_action(SDL_EventType from, enum android_motionevent_action *to) {
static const struct sc_intmap_entry actions[] = {
{SDL_MOUSEBUTTONDOWN, AMOTION_EVENT_ACTION_DOWN},
{SDL_MOUSEBUTTONUP, AMOTION_EVENT_ACTION_UP},
};
const struct sc_intmap_entry *entry = SC_INTMAP_FIND_ENTRY(actions, from);
if (entry) {
*to = entry->value;
return true;
static enum android_motionevent_action
convert_mouse_action(enum sc_action action) {
if (action == SC_ACTION_DOWN) {
return AMOTION_EVENT_ACTION_DOWN;
}
return false;
assert(action == SC_ACTION_UP);
return AMOTION_EVENT_ACTION_UP;
}
static bool
convert_touch_action(SDL_EventType from, enum android_motionevent_action *to) {
static const struct sc_intmap_entry actions[] = {
{SDL_FINGERMOTION, AMOTION_EVENT_ACTION_MOVE},
{SDL_FINGERDOWN, AMOTION_EVENT_ACTION_DOWN},
{SDL_FINGERUP, AMOTION_EVENT_ACTION_UP},
};
const struct sc_intmap_entry *entry = SC_INTMAP_FIND_ENTRY(actions, from);
if (entry) {
*to = entry->value;
return true;
static enum android_motionevent_action
convert_touch_action(enum sc_touch_action action) {
switch (action) {
case SC_TOUCH_ACTION_MOVE:
return AMOTION_EVENT_ACTION_MOVE;
case SC_TOUCH_ACTION_DOWN:
return AMOTION_EVENT_ACTION_DOWN;
default:
assert(action == SC_TOUCH_ACTION_UP);
return AMOTION_EVENT_ACTION_UP;
}
return false;
}
static bool
convert_mouse_motion(const SDL_MouseMotionEvent *from, struct screen *screen,
struct control_msg *to) {
to->type = CONTROL_MSG_TYPE_INJECT_TOUCH_EVENT;
to->inject_touch_event.action = AMOTION_EVENT_ACTION_MOVE;
to->inject_touch_event.pointer_id = POINTER_ID_MOUSE;
to->inject_touch_event.position.screen_size = screen->frame_size;
to->inject_touch_event.position.point =
screen_convert_window_to_frame_coords(screen, from->x, from->y);
to->inject_touch_event.pressure = 1.f;
to->inject_touch_event.buttons = convert_mouse_buttons(from->state);
return true;
}
static bool
convert_touch(const SDL_TouchFingerEvent *from, struct screen *screen,
struct control_msg *to) {
to->type = CONTROL_MSG_TYPE_INJECT_TOUCH_EVENT;
if (!convert_touch_action(from->type, &to->inject_touch_event.action)) {
return false;
}
to->inject_touch_event.pointer_id = from->fingerId;
to->inject_touch_event.position.screen_size = screen->frame_size;
int dw;
int dh;
SDL_GL_GetDrawableSize(screen->window, &dw, &dh);
// SDL touch event coordinates are normalized in the range [0; 1]
int32_t x = from->x * dw;
int32_t y = from->y * dh;
to->inject_touch_event.position.point =
screen_convert_drawable_to_frame_coords(screen, x, y);
to->inject_touch_event.pressure = from->pressure;
to->inject_touch_event.buttons = 0;
return true;
}
static bool
convert_mouse_button(const SDL_MouseButtonEvent *from, struct screen *screen,
struct control_msg *to) {
to->type = CONTROL_MSG_TYPE_INJECT_TOUCH_EVENT;
if (!convert_mouse_action(from->type, &to->inject_touch_event.action)) {
return false;
}
to->inject_touch_event.pointer_id = POINTER_ID_MOUSE;
to->inject_touch_event.position.screen_size = screen->frame_size;
to->inject_touch_event.position.point =
screen_convert_window_to_frame_coords(screen, from->x, from->y);
to->inject_touch_event.pressure =
from->type == SDL_MOUSEBUTTONDOWN ? 1.f : 0.f;
to->inject_touch_event.buttons =
convert_mouse_buttons(SDL_BUTTON(from->button));
return true;
}
static bool
convert_mouse_wheel(const SDL_MouseWheelEvent *from, struct screen *screen,
struct control_msg *to) {
// mouse_x and mouse_y are expressed in pixels relative to the window
int mouse_x;
int mouse_y;
SDL_GetMouseState(&mouse_x, &mouse_y);
struct sc_position position = {
.screen_size = screen->frame_size,
.point = screen_convert_window_to_frame_coords(screen,
mouse_x, mouse_y),
};
to->type = CONTROL_MSG_TYPE_INJECT_SCROLL_EVENT;
to->inject_scroll_event.position = position;
to->inject_scroll_event.hscroll = from->x;
to->inject_scroll_event.vscroll = from->y;
return true;
}
static void
sc_mouse_processor_process_mouse_motion(struct sc_mouse_processor *mp,
const SDL_MouseMotionEvent *event) {
struct sc_mouse_inject *mi = DOWNCAST(mp);
struct control_msg msg;
if (!convert_mouse_motion(event, mi->screen, &msg)) {
const struct sc_mouse_motion_event *event) {
if (!event->buttons_state) {
// Do not send motion events when no click is pressed
return;
}
if (!controller_push_msg(mi->controller, &msg)) {
struct sc_mouse_inject *mi = DOWNCAST(mp);
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,
.position = event->position,
.pressure = 1.f,
.buttons = convert_mouse_buttons(event->buttons_state),
},
};
if (!sc_controller_push_msg(mi->controller, &msg)) {
LOGW("Could not request 'inject mouse motion event'");
}
}
static void
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 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,
.position = event->position,
.pressure = event->action == SC_ACTION_DOWN ? 1.f : 0.f,
.buttons = convert_mouse_buttons(event->buttons_state),
},
};
if (!sc_controller_push_msg(mi->controller, &msg)) {
LOGW("Could not request 'inject mouse click event'");
}
}
static void
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 sc_control_msg msg = {
.type = SC_CONTROL_MSG_TYPE_INJECT_SCROLL_EVENT,
.inject_scroll_event = {
.position = event->position,
.hscroll = event->hscroll,
.vscroll = event->vscroll,
.buttons = convert_mouse_buttons(event->buttons_state),
},
};
if (!sc_controller_push_msg(mi->controller, &msg)) {
LOGW("Could not request 'inject mouse scroll event'");
}
}
static void
sc_mouse_processor_process_touch(struct sc_mouse_processor *mp,
const SDL_TouchFingerEvent *event) {
const struct sc_touch_event *event) {
struct sc_mouse_inject *mi = DOWNCAST(mp);
struct control_msg msg;
if (convert_touch(event, mi->screen, &msg)) {
if (!controller_push_msg(mi->controller, &msg)) {
LOGW("Could not request '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,
.position = event->position,
.pressure = event->pressure,
.buttons = 0,
},
};
static void
sc_mouse_processor_process_mouse_button(struct sc_mouse_processor *mp,
const SDL_MouseButtonEvent *event) {
struct sc_mouse_inject *mi = DOWNCAST(mp);
struct control_msg msg;
if (convert_mouse_button(event, mi->screen, &msg)) {
if (!controller_push_msg(mi->controller, &msg)) {
LOGW("Could not request 'inject mouse button event'");
}
}
}
static void
sc_mouse_processor_process_mouse_wheel(struct sc_mouse_processor *mp,
const SDL_MouseWheelEvent *event) {
struct sc_mouse_inject *mi = DOWNCAST(mp);
struct control_msg msg;
if (convert_mouse_wheel(event, mi->screen, &msg)) {
if (!controller_push_msg(mi->controller, &msg)) {
LOGW("Could not request 'inject mouse wheel event'");
}
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 screen *screen) {
sc_mouse_inject_init(struct sc_mouse_inject *mi,
struct sc_controller *controller) {
mi->controller = controller;
mi->screen = screen;
static const struct sc_mouse_processor_ops ops = {
.process_mouse_motion = sc_mouse_processor_process_mouse_motion,
.process_mouse_click = sc_mouse_processor_process_mouse_click,
.process_mouse_scroll = sc_mouse_processor_process_mouse_scroll,
.process_touch = sc_mouse_processor_process_touch,
.process_mouse_button = sc_mouse_processor_process_mouse_button,
.process_mouse_wheel = sc_mouse_processor_process_mouse_wheel,
};
mi->mouse_processor.ops = &ops;
mi->mouse_processor.relative_mode = false;
}

View File

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

View File

@ -22,7 +22,7 @@ const struct scrcpy_options scrcpy_options_default = {
.tunnel_host = 0,
.tunnel_port = 0,
.shortcut_mods = {
.data = {SC_MOD_LALT, SC_MOD_LSUPER},
.data = {SC_SHORTCUT_MOD_LALT, SC_SHORTCUT_MOD_LSUPER},
.count = 2,
},
.max_size = 0,
@ -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

@ -38,6 +38,11 @@ enum sc_keyboard_input_mode {
SC_KEYBOARD_INPUT_MODE_HID,
};
enum sc_mouse_input_mode {
SC_MOUSE_INPUT_MODE_INJECT,
SC_MOUSE_INPUT_MODE_HID,
};
enum sc_key_inject_mode {
// Inject special keys, letters and space as key events.
// Inject numbers and punctuation as text events.
@ -55,12 +60,12 @@ enum sc_key_inject_mode {
#define SC_MAX_SHORTCUT_MODS 8
enum sc_shortcut_mod {
SC_MOD_LCTRL = 1 << 0,
SC_MOD_RCTRL = 1 << 1,
SC_MOD_LALT = 1 << 2,
SC_MOD_RALT = 1 << 3,
SC_MOD_LSUPER = 1 << 4,
SC_MOD_RSUPER = 1 << 5,
SC_SHORTCUT_MOD_LCTRL = 1 << 0,
SC_SHORTCUT_MOD_RCTRL = 1 << 1,
SC_SHORTCUT_MOD_LALT = 1 << 2,
SC_SHORTCUT_MOD_RALT = 1 << 3,
SC_SHORTCUT_MOD_LSUPER = 1 << 4,
SC_SHORTCUT_MOD_RSUPER = 1 << 5,
};
struct sc_shortcut_mods {
@ -90,6 +95,7 @@ struct scrcpy_options {
enum sc_log_level log_level;
enum sc_record_format record_format;
enum sc_keyboard_input_mode keyboard_input_mode;
enum sc_mouse_input_mode mouse_input_mode;
struct sc_port_range port_range;
uint32_t tunnel_host;
uint16_t tunnel_port;
@ -106,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;
@ -123,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"
#include "input_manager.h"
#ifdef HAVE_AOA_HID
# include "hid_keyboard.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,28 +38,33 @@
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
};
struct sc_mouse_inject mouse_inject;
struct input_manager input_manager;
union {
struct sc_mouse_inject mouse_inject;
#ifdef HAVE_USB
struct sc_hid_mouse mouse_hid;
#endif
};
};
static inline void
@ -145,73 +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);
if (consumed) {
goto end;
}
consumed = input_manager_handle_event(&s->input_manager, 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;
}
}
@ -328,14 +280,16 @@ 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;
#endif
bool controller_initialized = false;
bool controller_started = false;
@ -363,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,
};
@ -405,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;
@ -450,44 +408,187 @@ 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
if (options->keyboard_input_mode == SC_KEYBOARD_INPUT_MODE_HID) {
#ifdef HAVE_USB
bool use_hid_keyboard =
options->keyboard_input_mode == SC_KEYBOARD_INPUT_MODE_HID;
bool use_hid_mouse =
options->mouse_input_mode == SC_MOUSE_INPUT_MODE_HID;
if (use_hid_keyboard || use_hid_mouse) {
bool ok = sc_acksync_init(&s->acksync);
if (!ok) {
goto end;
}
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;
}
if (use_hid_keyboard) {
if (sc_hid_keyboard_init(&s->keyboard_hid, &s->aoa)) {
hid_keyboard_initialized = true;
kp = &s->keyboard_hid.key_processor;
} else {
LOGE("Could not initialize HID keyboard");
}
}
if (use_hid_mouse) {
if (sc_hid_mouse_init(&s->mouse_hid, &s->aoa)) {
hid_mouse_initialized = true;
mp = &s->mouse_hid.mouse_processor;
} else {
LOGE("Could not initialized HID mouse");
}
}
bool need_aoa = hid_keyboard_initialized || hid_mouse_initialized;
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;
}
acksync = &s->acksync;
aoa_hid_initialized = true;
aoa_hid_end:
if (!aoa_hid_initialized) {
if (hid_keyboard_initialized) {
sc_hid_keyboard_destroy(&s->keyboard_hid);
hid_keyboard_initialized = false;
}
if (hid_mouse_initialized) {
sc_hid_mouse_destroy(&s->mouse_hid);
hid_mouse_initialized = false;
}
}
if (use_hid_keyboard && !hid_keyboard_initialized) {
LOGE("Fallback to default keyboard injection method "
"(-K/--hid-keyboard ignored)");
options->keyboard_input_mode = SC_KEYBOARD_INPUT_MODE_INJECT;
}
if (use_hid_mouse && !hid_mouse_initialized) {
LOGE("Fallback to default mouse injection method "
"(-M/--hid-mouse ignored)");
options->mouse_input_mode = SC_MOUSE_INPUT_MODE_INJECT;
}
}
#else
assert(options->keyboard_input_mode != SC_KEYBOARD_INPUT_MODE_HID);
assert(options->mouse_input_mode != SC_MOUSE_INPUT_MODE_HID);
#endif
if (!controller_init(&s->controller, s->server.control_socket,
acksync)) {
// 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->key_inject_mode,
options->forward_key_repeat);
kp = &s->keyboard_inject.key_processor;
}
// mouse_input_mode may have been reset if HID mode failed
if (options->mouse_input_mode == SC_MOUSE_INPUT_MODE_INJECT) {
sc_mouse_inject_init(&s->mouse_inject, &s->controller);
mp = &s->mouse_inject.mouse_processor;
}
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 = {
struct sc_screen_params screen_params = {
.controller = controller,
.fp = fp,
.kp = kp,
.mp = mp,
.forward_all_clicks = options->forward_all_clicks,
.legacy_paste = options->legacy_paste,
.clipboard_autosync = options->clipboard_autosync,
.shortcut_mods = &options->shortcut_mods,
.window_title = window_title,
.frame_size = info->frame_size,
.always_on_top = options->always_on_top,
@ -502,7 +603,7 @@ scrcpy(struct scrcpy_options *options) {
.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;
@ -530,91 +631,39 @@ scrcpy(struct scrcpy_options *options) {
}
stream_started = true;
struct sc_key_processor *kp = NULL;
struct sc_mouse_processor *mp = NULL;
if (options->control) {
if (options->keyboard_input_mode == SC_KEYBOARD_INPUT_MODE_HID) {
#ifdef HAVE_AOA_HID
bool aoa_hid_ok = false;
bool ok = sc_aoa_init(&s->aoa, serial, acksync);
if (!ok) {
goto aoa_hid_end;
}
if (!sc_hid_keyboard_init(&s->keyboard_hid, &s->aoa)) {
sc_aoa_destroy(&s->aoa);
goto aoa_hid_end;
}
if (!sc_aoa_start(&s->aoa)) {
sc_hid_keyboard_destroy(&s->keyboard_hid);
sc_aoa_destroy(&s->aoa);
goto aoa_hid_end;
}
aoa_hid_ok = true;
kp = &s->keyboard_hid.key_processor;
aoa_hid_initialized = true;
aoa_hid_end:
if (!aoa_hid_ok) {
LOGE("Failed to enable HID over AOA, "
"fallback to default keyboard injection method "
"(-K/--hid-keyboard ignored)");
options->keyboard_input_mode = SC_KEYBOARD_INPUT_MODE_INJECT;
}
#else
LOGE("HID over AOA is not supported on this platform, "
"fallback to default keyboard injection method "
"(-K/--hid-keyboard ignored)");
options->keyboard_input_mode = SC_KEYBOARD_INPUT_MODE_INJECT;
#endif
}
// 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);
kp = &s->keyboard_inject.key_processor;
}
sc_mouse_inject_init(&s->mouse_inject, &s->controller, &s->screen);
mp = &s->mouse_inject.mouse_processor;
}
input_manager_init(&s->input_manager, &s->controller, &s->screen, kp, mp,
options);
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) {
sc_hid_keyboard_destroy(&s->keyboard_hid);
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) {
@ -634,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,25 @@ get_initial_optimal_size(struct sc_size content_size, uint16_t req_width,
return window_size;
}
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
screen_update_content_rect(struct screen *screen) {
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());
return;
}
screen->mouse_captured = capture;
}
static void
sc_screen_update_content_rect(struct sc_screen *screen) {
int dw;
int dh;
SDL_GL_GetDrawableSize(screen->window, &dw, &dh);
@ -194,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,
@ -225,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);
@ -271,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;
@ -295,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;
@ -306,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);
}
@ -315,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)
@ -348,12 +365,21 @@ 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;
screen->maximized = false;
screen->event_failed = false;
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,
@ -383,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;
@ -396,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;
@ -470,25 +489,28 @@ screen_init(struct screen *screen, const struct screen_params *params) {
goto error_destroy_texture;
}
// 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);
struct sc_input_manager_params im_params = {
.controller = params->controller,
.fp = params->fp,
.screen = screen,
.kp = params->kp,
.mp = params->mp,
.forward_all_clicks = params->forward_all_clicks,
.legacy_paste = params->legacy_paste,
.clipboard_autosync = params->clipboard_autosync,
.shortcut_mods = params->shortcut_mods,
};
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;
@ -517,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
@ -552,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 = {
@ -566,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) {
@ -580,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) {
@ -591,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;
@ -605,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
@ -622,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);
@ -638,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],
@ -652,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;
@ -665,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());
@ -683,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;
}
@ -711,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;
}
@ -727,31 +776,34 @@ screen_resize_to_pixel_perfect(struct screen *screen) {
content_size.height);
}
bool
screen_handle_event(struct screen *screen, SDL_Event *event) {
static inline bool
sc_screen_is_mouse_capture_key(SDL_Keycode key) {
return key == SDLK_LALT || key == SDLK_LGUI || key == SDLK_RGUI;
}
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;
@ -767,18 +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 (relative_mode) {
sc_screen_capture_mouse(screen, false);
}
break;
}
return true;
return;
case SDL_KEYDOWN:
if (relative_mode) {
SDL_Keycode key = event->key.keysym.sym;
if (sc_screen_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;
}
}
break;
case SDL_KEYUP:
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 (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;
}
}
break;
case SDL_MOUSEWHEEL:
case SDL_MOUSEMOTION:
case SDL_MOUSEBUTTONDOWN:
if (relative_mode && !screen->mouse_captured) {
// Do not forward to input manager, the mouse will be captured
// on SDL_MOUSEBUTTONUP
return;
}
break;
case SDL_FINGERMOTION:
case SDL_FINGERDOWN:
case SDL_FINGERUP:
if (relative_mode) {
// Touch events are not compatible with relative mode
// (coordinates are not relative)
return;
}
break;
case SDL_MOUSEBUTTONUP:
if (relative_mode && !screen->mouse_captured) {
sc_screen_capture_mouse(screen, true);
return;
}
break;
}
return false;
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);
@ -814,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

@ -7,22 +7,36 @@
#include <SDL2/SDL.h>
#include <libavformat/avformat.h>
#include "controller.h"
#include "coords.h"
#include "fps_counter.h"
#include "input_manager.h"
#include "opengl.h"
#include "trait/key_processor.h"
#include "trait/frame_sink.h"
#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 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;
@ -46,18 +60,33 @@ struct screen {
bool event_failed; // in case SDL_PushEvent() returned an error
bool mouse_captured; // only relevant in relative mouse mode
// To enable/disable mouse capture, a mouse capture key (LALT, LGUI or
// RGUI) must be pressed. This variable tracks the pressed capture key.
SDL_Keycode mouse_capture_key_pressed;
AVFrame *frame;
};
struct screen_params {
struct sc_screen_params {
struct sc_controller *controller;
struct sc_file_pusher *fp;
struct sc_key_processor *kp;
struct sc_mouse_processor *mp;
bool forward_all_clicks;
bool legacy_paste;
bool clipboard_autosync;
const struct sc_shortcut_mods *shortcut_mods;
const char *window_title;
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;
@ -71,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

@ -6,7 +6,7 @@
#include <assert.h>
#include <stdbool.h>
#include <SDL2/SDL_events.h>
#include "input_events.h"
/**
* Key processor trait.
@ -29,20 +29,27 @@ struct sc_key_processor {
struct sc_key_processor_ops {
/**
* Process the keyboard event
* Process a keyboard event
*
* The `sequence` number (if different from `SC_SEQUENCE_INVALID`) indicates
* the acknowledgement number to wait for before injecting this event.
* This allows to ensure that the device clipboard is set before injecting
* Ctrl+v on the device.
*
* This function is mandatory.
*/
void
(*process_key)(struct sc_key_processor *kp, const SDL_KeyboardEvent *event,
uint64_t ack_to_wait);
(*process_key)(struct sc_key_processor *kp,
const struct sc_key_event *event, uint64_t ack_to_wait);
/**
* Process an input text
*
* This function is optional.
*/
void
(*process_text)(struct sc_key_processor *kp,
const SDL_TextInputEvent *event);
const struct sc_text_event *event);
};
#endif

View File

@ -6,7 +6,7 @@
#include <assert.h>
#include <stdbool.h>
#include <SDL2/SDL_events.h>
#include "input_events.h"
/**
* Mouse processor trait.
@ -16,24 +16,51 @@
*/
struct sc_mouse_processor {
const struct sc_mouse_processor_ops *ops;
/**
* If set, the mouse processor works in relative mode (the absolute
* position is irrelevant). In particular, it indicates that the mouse
* pointer must be "captured" by the UI.
*/
bool relative_mode;
};
struct sc_mouse_processor_ops {
/**
* Process a mouse motion event
*
* This function is mandatory.
*/
void
(*process_mouse_motion)(struct sc_mouse_processor *mp,
const SDL_MouseMotionEvent *event);
const struct sc_mouse_motion_event *event);
/**
* Process a mouse click event
*
* This function is mandatory.
*/
void
(*process_mouse_click)(struct sc_mouse_processor *mp,
const struct sc_mouse_click_event *event);
/**
* Process a mouse scroll event
*
* This function is optional.
*/
void
(*process_mouse_scroll)(struct sc_mouse_processor *mp,
const struct sc_mouse_scroll_event *event);
/**
* Process a touch event
*
* This function is optional.
*/
void
(*process_touch)(struct sc_mouse_processor *mp,
const SDL_TouchFingerEvent *event);
void
(*process_mouse_button)(struct sc_mouse_processor *mp,
const SDL_MouseButtonEvent *event);
void
(*process_mouse_wheel)(struct sc_mouse_processor *mp,
const SDL_MouseWheelEvent *event);
const struct sc_touch_event *event);
};
#endif

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

@ -1,8 +1,8 @@
#include "hid_keyboard.h"
#include <assert.h>
#include <SDL2/SDL_events.h>
#include "input_events.h"
#include "util/log.h"
/** Downcast key processor to hid_keyboard */
@ -201,30 +201,30 @@ static const unsigned char keyboard_report_desc[] = {
*/
static unsigned char
sdl_keymod_to_hid_modifiers(SDL_Keymod mod) {
sdl_keymod_to_hid_modifiers(uint16_t mod) {
unsigned char modifiers = HID_MODIFIER_NONE;
if (mod & KMOD_LCTRL) {
if (mod & SC_MOD_LCTRL) {
modifiers |= HID_MODIFIER_LEFT_CONTROL;
}
if (mod & KMOD_LSHIFT) {
if (mod & SC_MOD_LSHIFT) {
modifiers |= HID_MODIFIER_LEFT_SHIFT;
}
if (mod & KMOD_LALT) {
if (mod & SC_MOD_LALT) {
modifiers |= HID_MODIFIER_LEFT_ALT;
}
if (mod & KMOD_LGUI) {
if (mod & SC_MOD_LGUI) {
modifiers |= HID_MODIFIER_LEFT_GUI;
}
if (mod & KMOD_RCTRL) {
if (mod & SC_MOD_RCTRL) {
modifiers |= HID_MODIFIER_RIGHT_CONTROL;
}
if (mod & KMOD_RSHIFT) {
if (mod & SC_MOD_RSHIFT) {
modifiers |= HID_MODIFIER_RIGHT_SHIFT;
}
if (mod & KMOD_RALT) {
if (mod & SC_MOD_RALT) {
modifiers |= HID_MODIFIER_RIGHT_ALT;
}
if (mod & KMOD_RGUI) {
if (mod & SC_MOD_RGUI) {
modifiers |= HID_MODIFIER_RIGHT_GUI;
}
return modifiers;
@ -248,15 +248,15 @@ sc_hid_keyboard_event_init(struct sc_hid_event *hid_event) {
}
static inline bool
scancode_is_modifier(SDL_Scancode scancode) {
return scancode >= SDL_SCANCODE_LCTRL && scancode <= SDL_SCANCODE_RGUI;
scancode_is_modifier(enum sc_scancode scancode) {
return scancode >= SC_SCANCODE_LCTRL && scancode <= SC_SCANCODE_RGUI;
}
static bool
convert_hid_keyboard_event(struct sc_hid_keyboard *kb,
struct sc_hid_event *hid_event,
const SDL_KeyboardEvent *event) {
SDL_Scancode scancode = event->keysym.scancode;
const struct sc_key_event *event) {
enum sc_scancode scancode = event->scancode;
assert(scancode >= 0);
// SDL also generates events when only modifiers are pressed, we cannot
@ -272,11 +272,11 @@ convert_hid_keyboard_event(struct sc_hid_keyboard *kb,
return false;
}
unsigned char modifiers = sdl_keymod_to_hid_modifiers(event->keysym.mod);
unsigned char modifiers = sdl_keymod_to_hid_modifiers(event->mods_state);
if (scancode < SC_HID_KEYBOARD_KEYS) {
// Pressed is true and released is false
kb->keys[scancode] = (event->type == SDL_KEYDOWN);
kb->keys[scancode] = (event->action == SC_ACTION_DOWN);
LOGV("keys[%02x] = %s", scancode,
kb->keys[scancode] ? "true" : "false");
}
@ -306,17 +306,17 @@ convert_hid_keyboard_event(struct sc_hid_keyboard *kb,
end:
LOGV("hid keyboard: key %-4s scancode=%02x (%u) mod=%02x",
event->type == SDL_KEYDOWN ? "down" : "up", event->keysym.scancode,
event->keysym.scancode, modifiers);
event->action == SC_ACTION_DOWN ? "down" : "up", event->scancode,
event->scancode, modifiers);
return true;
}
static bool
push_mod_lock_state(struct sc_hid_keyboard *kb, uint16_t sdl_mod) {
bool capslock = sdl_mod & KMOD_CAPS;
bool numlock = sdl_mod & KMOD_NUM;
push_mod_lock_state(struct sc_hid_keyboard *kb, uint16_t mods_state) {
bool capslock = mods_state & SC_MOD_CAPS;
bool numlock = mods_state & SC_MOD_NUM;
if (!capslock && !numlock) {
// Nothing to do
return true;
@ -328,8 +328,6 @@ push_mod_lock_state(struct sc_hid_keyboard *kb, uint16_t sdl_mod) {
return false;
}
#define SC_SCANCODE_CAPSLOCK SDL_SCANCODE_CAPSLOCK
#define SC_SCANCODE_NUMLOCK SDL_SCANCODE_NUMLOCKCLEAR
unsigned i = 0;
if (capslock) {
hid_event.buffer[HID_KEYBOARD_INDEX_KEYS + i] = SC_SCANCODE_CAPSLOCK;
@ -353,7 +351,7 @@ push_mod_lock_state(struct sc_hid_keyboard *kb, uint16_t sdl_mod) {
static void
sc_key_processor_process_key(struct sc_key_processor *kp,
const SDL_KeyboardEvent *event,
const struct sc_key_event *event,
uint64_t ack_to_wait) {
if (event->repeat) {
// In USB HID protocol, key repeat is handled by the host (Android), so
@ -369,7 +367,7 @@ sc_key_processor_process_key(struct sc_key_processor *kp,
if (!kb->mod_lock_synchronized) {
// Inject CAPSLOCK and/or NUMLOCK if necessary to synchronize
// keyboard state
if (push_mod_lock_state(kb, event->keysym.mod)) {
if (push_mod_lock_state(kb, event->mods_state)) {
kb->mod_lock_synchronized = true;
}
}
@ -389,15 +387,6 @@ sc_key_processor_process_key(struct sc_key_processor *kp,
}
}
static void
sc_key_processor_process_text(struct sc_key_processor *kp,
const SDL_TextInputEvent *event) {
(void) kp;
(void) event;
// Never forward text input via HID (all the keys are injected separately)
}
bool
sc_hid_keyboard_init(struct sc_hid_keyboard *kb, struct sc_aoa *aoa) {
kb->aoa = aoa;
@ -417,7 +406,9 @@ sc_hid_keyboard_init(struct sc_hid_keyboard *kb, struct sc_aoa *aoa) {
static const struct sc_key_processor_ops ops = {
.process_key = sc_key_processor_process_key,
.process_text = sc_key_processor_process_text,
// Never forward text input via HID (all the keys are injected
// separately)
.process_text = NULL,
};
// Clipboard synchronization is requested over the control socket, while HID

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

@ -0,0 +1,267 @@
#include "hid_mouse.h"
#include <assert.h>
#include "input_events.h"
#include "util/log.h"
/** Downcast mouse processor to hid_mouse */
#define DOWNCAST(MP) container_of(MP, struct sc_hid_mouse, mouse_processor)
#define HID_MOUSE_ACCESSORY_ID 2
// 1 byte for buttons + padding, 1 byte for X position, 1 byte for Y position
#define HID_MOUSE_EVENT_SIZE 4
/**
* Mouse descriptor from the specification:
* <https://www.usb.org/sites/default/files/hid1_11.pdf>
*
* Appendix E (p71): §E.10 Report Descriptor (Mouse)
*
* The usage tags (like Wheel) are listed in "HID Usage Tables":
* <https://www.usb.org/sites/default/files/documents/hut1_12v2.pdf>
* §4 Generic Desktop Page (0x01) (p26)
*/
static const unsigned char mouse_report_desc[] = {
// Usage Page (Generic Desktop)
0x05, 0x01,
// Usage (Mouse)
0x09, 0x02,
// Collection (Application)
0xA1, 0x01,
// Usage (Pointer)
0x09, 0x01,
// Collection (Physical)
0xA1, 0x00,
// Usage Page (Buttons)
0x05, 0x09,
// Usage Minimum (1)
0x19, 0x01,
// Usage Maximum (5)
0x29, 0x05,
// Logical Minimum (0)
0x15, 0x00,
// Logical Maximum (1)
0x25, 0x01,
// Report Count (5)
0x95, 0x05,
// Report Size (1)
0x75, 0x01,
// Input (Data, Variable, Absolute): 5 buttons bits
0x81, 0x02,
// Report Count (1)
0x95, 0x01,
// Report Size (3)
0x75, 0x03,
// Input (Constant): 3 bits padding
0x81, 0x01,
// Usage Page (Generic Desktop)
0x05, 0x01,
// Usage (X)
0x09, 0x30,
// Usage (Y)
0x09, 0x31,
// Usage (Wheel)
0x09, 0x38,
// Local Minimum (-127)
0x15, 0x81,
// Local Maximum (127)
0x25, 0x7F,
// Report Size (8)
0x75, 0x08,
// Report Count (3)
0x95, 0x03,
// Input (Data, Variable, Relative): 3 position bytes (X, Y, Wheel)
0x81, 0x06,
// End Collection
0xC0,
// End Collection
0xC0,
};
/**
* A mouse HID event is 3 bytes long:
*
* - byte 0: buttons state
* - byte 1: relative x motion (signed byte from -127 to 127)
* - byte 2: relative y motion (signed byte from -127 to 127)
*
* 7 6 5 4 3 2 1 0
* +---------------+
* byte 0: |0 0 0 . . . . .| buttons state
* +---------------+
* ^ ^ ^ ^ ^
* | | | | `- left button
* | | | `--- right button
* | | `----- middle button
* | `------- button 4
* `--------- button 5
*
* +---------------+
* byte 1: |. . . . . . . .| relative x motion
* +---------------+
* byte 2: |. . . . . . . .| relative y motion
* +---------------+
* byte 3: |. . . . . . . .| wheel motion (-1, 0 or 1)
* +---------------+
*
* As an example, here is the report for a motion of (x=5, y=-4) with left
* button pressed:
*
* +---------------+
* |0 0 0 0 0 0 0 1| left button pressed
* +---------------+
* |0 0 0 0 0 1 0 1| horizontal motion (x = 5)
* +---------------+
* |1 1 1 1 1 1 0 0| relative y motion (y = -4)
* +---------------+
* |0 0 0 0 0 0 0 0| wheel motion
* +---------------+
*/
static bool
sc_hid_mouse_event_init(struct sc_hid_event *hid_event) {
unsigned char *buffer = calloc(1, HID_MOUSE_EVENT_SIZE);
if (!buffer) {
LOG_OOM();
return false;
}
sc_hid_event_init(hid_event, HID_MOUSE_ACCESSORY_ID, buffer,
HID_MOUSE_EVENT_SIZE);
return true;
}
static unsigned char
buttons_state_to_hid_buttons(uint8_t buttons_state) {
unsigned char c = 0;
if (buttons_state & SC_MOUSE_BUTTON_LEFT) {
c |= 1 << 0;
}
if (buttons_state & SC_MOUSE_BUTTON_RIGHT) {
c |= 1 << 1;
}
if (buttons_state & SC_MOUSE_BUTTON_MIDDLE) {
c |= 1 << 2;
}
if (buttons_state & SC_MOUSE_BUTTON_X1) {
c |= 1 << 3;
}
if (buttons_state & SC_MOUSE_BUTTON_X2) {
c |= 1 << 4;
}
return c;
}
static void
sc_mouse_processor_process_mouse_motion(struct sc_mouse_processor *mp,
const struct sc_mouse_motion_event *event) {
struct sc_hid_mouse *mouse = DOWNCAST(mp);
struct sc_hid_event hid_event;
if (!sc_hid_mouse_event_init(&hid_event)) {
return;
}
unsigned char *buffer = hid_event.buffer;
buffer[0] = buttons_state_to_hid_buttons(event->buttons_state);
buffer[1] = CLAMP(event->xrel, -127, 127);
buffer[2] = CLAMP(event->yrel, -127, 127);
buffer[3] = 0; // wheel coordinates only used for scrolling
if (!sc_aoa_push_hid_event(mouse->aoa, &hid_event)) {
sc_hid_event_destroy(&hid_event);
LOGW("Could request HID event");
}
}
static void
sc_mouse_processor_process_mouse_click(struct sc_mouse_processor *mp,
const struct sc_mouse_click_event *event) {
struct sc_hid_mouse *mouse = DOWNCAST(mp);
struct sc_hid_event hid_event;
if (!sc_hid_mouse_event_init(&hid_event)) {
return;
}
unsigned char *buffer = hid_event.buffer;
buffer[0] = buttons_state_to_hid_buttons(event->buttons_state);
buffer[1] = 0; // no x motion
buffer[2] = 0; // no y motion
buffer[3] = 0; // wheel coordinates only used for scrolling
if (!sc_aoa_push_hid_event(mouse->aoa, &hid_event)) {
sc_hid_event_destroy(&hid_event);
LOGW("Could request HID event");
}
}
static void
sc_mouse_processor_process_mouse_scroll(struct sc_mouse_processor *mp,
const struct sc_mouse_scroll_event *event) {
struct sc_hid_mouse *mouse = DOWNCAST(mp);
struct sc_hid_event hid_event;
if (!sc_hid_mouse_event_init(&hid_event)) {
return;
}
unsigned char *buffer = hid_event.buffer;
buffer[0] = 0; // buttons state irrelevant (and unknown)
buffer[1] = 0; // no x motion
buffer[2] = 0; // no y motion
// In practice, vscroll is always -1, 0 or 1, but in theory other values
// are possible
buffer[3] = CLAMP(event->vscroll, -127, 127);
// Horizontal scrolling ignored
if (!sc_aoa_push_hid_event(mouse->aoa, &hid_event)) {
sc_hid_event_destroy(&hid_event);
LOGW("Could request HID event");
}
}
bool
sc_hid_mouse_init(struct sc_hid_mouse *mouse, struct sc_aoa *aoa) {
mouse->aoa = aoa;
bool ok = sc_aoa_setup_hid(aoa, HID_MOUSE_ACCESSORY_ID, mouse_report_desc,
ARRAY_LEN(mouse_report_desc));
if (!ok) {
LOGW("Register HID mouse failed");
return false;
}
static const struct sc_mouse_processor_ops ops = {
.process_mouse_motion = sc_mouse_processor_process_mouse_motion,
.process_mouse_click = sc_mouse_processor_process_mouse_click,
.process_mouse_scroll = sc_mouse_processor_process_mouse_scroll,
// Touch events not supported (coordinates are not relative)
.process_touch = NULL,
};
mouse->mouse_processor.ops = &ops;
mouse->mouse_processor.relative_mode = true;
return true;
}
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 mouse");
}
}

23
app/src/usb/hid_mouse.h Normal file
View File

@ -0,0 +1,23 @@
#ifndef SC_HID_MOUSE_H
#define SC_HID_MOUSE_H
#include "common.h"
#include <stdbool.h>
#include "aoa_hid.h"
#include "trait/mouse_processor.h"
struct sc_hid_mouse {
struct sc_mouse_processor mouse_processor; // mouse processor trait
struct sc_aoa *aoa;
};
bool
sc_hid_mouse_init(struct sc_hid_mouse *mouse, struct sc_aoa *aoa);
void
sc_hid_mouse_destroy(struct sc_hid_mouse *mouse);
#endif

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

@ -1,5 +1,8 @@
#include "log.h"
#if _WIN32
# include <windows.h>
#endif
#include <assert.h>
static SDL_LogPriority
@ -51,3 +54,24 @@ sc_get_log_level(void) {
SDL_LogPriority sdl_log = SDL_LogGetPriority(SDL_LOG_CATEGORY_APPLICATION);
return log_level_sdl_to_sc(sdl_log);
}
#ifdef _WIN32
bool
sc_log_windows_error(const char *prefix, int error) {
assert(prefix);
char *message;
DWORD flags = FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM;
DWORD lang_id = MAKELANGID(LANG_ENGLISH, SUBLANG_ENGLISH_US);
int ret =
FormatMessage(flags, NULL, error, lang_id, (char *) &message, 0, NULL);
if (ret <= 0) {
return false;
}
// Note: message already contains a trailing '\n'
LOGE("%s: [%d] %s", prefix, error, message);
LocalFree(message);
return true;
}
#endif

View File

@ -26,4 +26,10 @@ sc_set_log_level(enum sc_log_level level);
enum sc_log_level
sc_get_log_level(void);
#ifdef _WIN32
// Log system error (typically returned by GetLastError() or similar)
bool
sc_log_windows_error(const char *prefix, int error);
#endif
#endif

View File

@ -83,6 +83,7 @@ unwrap(sc_socket socket) {
#endif
}
#ifndef HAVE_SOCK_CLOEXEC // avoid unused-function warning
static inline bool
sc_raw_socket_close(sc_raw_socket raw_sock) {
#ifndef _WIN32
@ -91,6 +92,7 @@ sc_raw_socket_close(sc_raw_socket raw_sock) {
return !closesocket(raw_sock);
#endif
}
#endif
#ifndef HAVE_SOCK_CLOEXEC
// If SOCK_CLOEXEC does not exist, the flag must be set manually once the
@ -115,14 +117,7 @@ set_cloexec_flag(sc_raw_socket raw_sock) {
static void
net_perror(const char *s) {
#ifdef _WIN32
int error = WSAGetLastError();
char *wsa_message;
FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM,
NULL, error, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
(char *) &wsa_message, 0, NULL);
// no explicit '\n', wsa_message already contains a trailing '\n'
fprintf(stderr, "%s: [%d] %s", s, error, wsa_message);
LocalFree(wsa_message);
sc_log_windows_error(s, WSAGetLastError());
#else
perror(s);
#endif

View File

@ -15,7 +15,7 @@
// <https://stackoverflow.com/a/44383330/1987178>
# define SC_PRIsizet "Iu"
# define SC_PROCESS_NONE NULL
# define SC_EXIT_CODE_NONE -1u // max value as unsigned
# define SC_EXIT_CODE_NONE -1UL // max value as unsigned long
typedef HANDLE sc_pid;
typedef DWORD sc_exit_code;
typedef HANDLE sc_pipe;

View File

@ -106,10 +106,10 @@ sc_str_wrap_lines(const char *input, unsigned columns, unsigned indent);
/**
* Truncate the data after any of the characters from `endchars`
*
* An '\0' is always written at the end of the data, even if no newline
* character is encountered.
* An '\0' is always written at the end of the data string, even if no
* character from `endchars` is encountered.
*
* Return the size of the resulting line.
* Return the size of the resulting string (as strlen() would return).
*/
size_t
sc_str_truncate(char *data, size_t len, const char *endchars);

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

@ -129,25 +129,26 @@ static void test_parse_shortcut_mods(void) {
ok = sc_parse_shortcut_mods("lctrl", &mods);
assert(ok);
assert(mods.count == 1);
assert(mods.data[0] == SC_MOD_LCTRL);
assert(mods.data[0] == SC_SHORTCUT_MOD_LCTRL);
ok = sc_parse_shortcut_mods("lctrl+lalt", &mods);
assert(ok);
assert(mods.count == 1);
assert(mods.data[0] == (SC_MOD_LCTRL | SC_MOD_LALT));
assert(mods.data[0] == (SC_SHORTCUT_MOD_LCTRL | SC_SHORTCUT_MOD_LALT));
ok = sc_parse_shortcut_mods("rctrl,lalt", &mods);
assert(ok);
assert(mods.count == 2);
assert(mods.data[0] == SC_MOD_RCTRL);
assert(mods.data[1] == SC_MOD_LALT);
assert(mods.data[0] == SC_SHORTCUT_MOD_RCTRL);
assert(mods.data[1] == SC_SHORTCUT_MOD_LALT);
ok = sc_parse_shortcut_mods("lsuper,rsuper+lalt,lctrl+rctrl+ralt", &mods);
assert(ok);
assert(mods.count == 3);
assert(mods.data[0] == SC_MOD_LSUPER);
assert(mods.data[1] == (SC_MOD_RSUPER | SC_MOD_LALT));
assert(mods.data[2] == (SC_MOD_LCTRL | SC_MOD_RCTRL | SC_MOD_RALT));
assert(mods.data[0] == SC_SHORTCUT_MOD_LSUPER);
assert(mods.data[1] == (SC_SHORTCUT_MOD_RSUPER | SC_SHORTCUT_MOD_LALT));
assert(mods.data[2] == (SC_SHORTCUT_MOD_LCTRL | SC_SHORTCUT_MOD_RCTRL |
SC_SHORTCUT_MOD_RALT));
ok = sc_parse_shortcut_mods("", &mods);
assert(!ok);

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 = {
@ -126,109 +126,111 @@ static void test_serialize_inject_scroll_event(void) {
},
.hscroll = 1,
.vscroll = -1,
.buttons = 1,
},
};
unsigned char buf[CONTROL_MSG_MAX_SIZE];
size_t size = control_msg_serialize(&msg, buf);
assert(size == 21);
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
0xFF, 0xFF, 0xFF, 0xFF, // -1
0x00, 0x00, 0x00, 0x01, // 1
};
assert(!memcmp(buf, expected, sizeof(expected)));
}
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,
@ -236,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
@ -251,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,
@ -260,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-win32-4.3.1'
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-win64-5.0'
prebuilt_sdl2 = 'SDL2-2.0.20/x86_64-w64-mingw32'

View File

@ -0,0 +1 @@
@cmd

View File

@ -2,8 +2,8 @@
set -e
BUILDDIR=build-auto
PREBUILT_SERVER_URL=https://github.com/Genymobile/scrcpy/releases/download/v1.21/scrcpy-server-v1.21
PREBUILT_SERVER_SHA256=dbcccab523ee26796e55ea33652649e4b7af498edae9aa75e4d4d7869c0ab848
PREBUILT_SERVER_URL=https://github.com/Genymobile/scrcpy/releases/download/v1.22/scrcpy-server-v1.22
PREBUILT_SERVER_SHA256=c05d273eec7533c0e106282e0254cf04e7f5e8f0c2920ca39448865fab2a419b
echo "[scrcpy] Downloading prebuilt server..."
wget "$PREBUILT_SERVER_URL" -O scrcpy-server

View File

@ -1,5 +1,5 @@
project('scrcpy', 'c',
version: '1.21',
version: '1.22',
meson_version: '>= 0.48',
default_options: [
'c_std=c11',

View File

@ -1,4 +1 @@
*
!/.gitignore
!/Makefile
!/prepare-dep
/data

View File

@ -1,40 +0,0 @@
.PHONY: prepare-win32 prepare-win64 \
prepare-ffmpeg-shared-win32 \
prepare-ffmpeg-dev-win32 \
prepare-ffmpeg-shared-win64 \
prepare-ffmpeg-dev-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-ffmpeg-shared-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
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-sdl2:
@./prepare-dep https://libsdl.org/release/SDL2-devel-2.0.18-mingw.tar.gz \
bbad7c6947f6ca3e05292f065852ed8b62f319fc5533047e7708769c4dbae394 \
SDL2-2.0.18
prepare-adb:
@./prepare-dep https://dl.google.com/android/repository/platform-tools_r31.0.3-windows.zip \
0f4b8fdd26af2c3733539d6eebb3c2ed499ea1d4bb1f4e0ecc2d6016961a6e24 \
platform-tools

22
prebuilt-deps/common Executable file
View File

@ -0,0 +1,22 @@
PREBUILT_DATA_DIR=data
checksum() {
local file="$1"
local sum="$2"
echo "$file: verifying checksum..."
echo "$sum $file" | sha256sum -c
}
get_file() {
local url="$1"
local file="$2"
local sum="$3"
if [[ -f "$file" ]]
then
echo "$file: found"
else
echo "$file: not found, downloading..."
wget "$url" -O "$file"
fi
checksum "$file" "$sum"
}

32
prebuilt-deps/prepare-adb.sh Executable file
View File

@ -0,0 +1,32 @@
#!/usr/bin/env bash
set -e
DIR=$(dirname ${BASH_SOURCE[0]})
cd "$DIR"
. common
mkdir -p "$PREBUILT_DATA_DIR"
cd "$PREBUILT_DATA_DIR"
DEP_DIR=platform-tools-31.0.3
FILENAME=platform-tools_r31.0.3-windows.zip
SHA256SUM=0f4b8fdd26af2c3733539d6eebb3c2ed499ea1d4bb1f4e0ecc2d6016961a6e24
if [[ -d "$DEP_DIR" ]]
then
echo "$DEP_DIR" found
exit 0
fi
get_file "https://dl.google.com/android/repository/$FILENAME" \
"$FILENAME" "$SHA256SUM"
mkdir "$DEP_DIR"
cd "$DEP_DIR"
ZIP_PREFIX=platform-tools
unzip "../$FILENAME" \
"$ZIP_PREFIX"/AdbWinApi.dll \
"$ZIP_PREFIX"/AdbWinUsbApi.dll \
"$ZIP_PREFIX"/adb.exe
mv "$ZIP_PREFIX"/* .
rmdir "$ZIP_PREFIX"

View File

@ -1,58 +0,0 @@
#!/usr/bin/env bash
set -e
url="$1"
sum="$2"
dir="$3"
checksum() {
local file="$1"
local sum="$2"
echo "$file: verifying checksum..."
echo "$sum $file" | sha256sum -c
}
get_file() {
local url="$1"
local file="$2"
local sum="$3"
if [[ -f "$file" ]]
then
echo "$file: found"
else
echo "$file: not found, downloading..."
wget "$url" -O "$file"
fi
checksum "$file" "$sum"
}
extract() {
local file="$1"
echo "Extracting $file..."
if [[ "$file" == *.zip ]]
then
unzip -q "$file"
elif [[ "$file" == *.tar.gz ]]
then
tar xf "$file"
else
echo "Unsupported file: $file"
return 1
fi
}
get_dep() {
local url="$1"
local sum="$2"
local dir="$3"
local file="${url##*/}"
if [[ -d "$dir" ]]
then
echo "$dir: found"
else
echo "$dir: not found"
get_file "$url" "$file" "$sum"
extract "$file"
fi
}
get_dep "$url" "$sum" "$dir"

View File

@ -0,0 +1,45 @@
#!/usr/bin/env bash
set -e
DIR=$(dirname ${BASH_SOURCE[0]})
cd "$DIR"
. common
mkdir -p "$PREBUILT_DATA_DIR"
cd "$PREBUILT_DATA_DIR"
DEP_DIR=ffmpeg-win32-4.3.1
FILENAME_SHARED=ffmpeg-4.3.1-win32-shared.zip
SHA256SUM_SHARED=357af9901a456f4dcbacd107e83a934d344c9cb07ddad8aaf80612eeab7d26d2
FILENAME_DEV=ffmpeg-4.3.1-win32-dev.zip
SHA256SUM_DEV=230efb08e9bcf225bd474da29676c70e591fc94d8790a740ca801408fddcb78b
if [[ -d "$DEP_DIR" ]]
then
echo "$DEP_DIR" found
exit 0
fi
get_file "https://github.com/Genymobile/scrcpy/releases/download/v1.16/$FILENAME_SHARED" \
"$FILENAME_SHARED" "$SHA256SUM_SHARED"
get_file "https://github.com/Genymobile/scrcpy/releases/download/v1.16/$FILENAME_DEV" \
"$FILENAME_DEV" "$SHA256SUM_DEV"
mkdir "$DEP_DIR"
cd "$DEP_DIR"
ZIP_PREFIX_SHARED=ffmpeg-4.3.1-win32-shared
unzip "../$FILENAME_SHARED" \
"$ZIP_PREFIX_SHARED"/bin/avutil-56.dll \
"$ZIP_PREFIX_SHARED"/bin/avcodec-58.dll \
"$ZIP_PREFIX_SHARED"/bin/avformat-58.dll \
"$ZIP_PREFIX_SHARED"/bin/swresample-3.dll \
"$ZIP_PREFIX_SHARED"/bin/swscale-5.dll
ZIP_PREFIX_DEV=ffmpeg-4.3.1-win32-dev
unzip "../$FILENAME_DEV" \
"$ZIP_PREFIX_DEV/include/*"
mv "$ZIP_PREFIX_SHARED"/* .
mv "$ZIP_PREFIX_DEV"/* .
rmdir "$ZIP_PREFIX_SHARED" "$ZIP_PREFIX_DEV"

View File

@ -0,0 +1,35 @@
#!/usr/bin/env bash
set -e
DIR=$(dirname ${BASH_SOURCE[0]})
cd "$DIR"
. common
mkdir -p "$PREBUILT_DATA_DIR"
cd "$PREBUILT_DATA_DIR"
DEP_DIR=ffmpeg-win64-5.0
FILENAME=ffmpeg-5.0-full_build-shared.7z
SHA256SUM=e5900f6cecd4c438d398bd2fc308736c10b857cd8dd61c11bcfb05bff5d1211a
if [[ -d "$DEP_DIR" ]]
then
echo "$DEP_DIR" found
exit 0
fi
get_file "https://github.com/GyanD/codexffmpeg/releases/download/5.0/$FILENAME" \
"$FILENAME" "$SHA256SUM"
mkdir "$DEP_DIR"
cd "$DEP_DIR"
ZIP_PREFIX=ffmpeg-5.0-full_build-shared
7z x "../$FILENAME" \
"$ZIP_PREFIX"/bin/avutil-57.dll \
"$ZIP_PREFIX"/bin/avcodec-59.dll \
"$ZIP_PREFIX"/bin/avformat-59.dll \
"$ZIP_PREFIX"/bin/swresample-4.dll \
"$ZIP_PREFIX"/bin/swscale-6.dll \
"$ZIP_PREFIX"/include
mv "$ZIP_PREFIX"/* .
rmdir "$ZIP_PREFIX"

32
prebuilt-deps/prepare-sdl.sh Executable file
View File

@ -0,0 +1,32 @@
#!/usr/bin/env bash
set -e
DIR=$(dirname ${BASH_SOURCE[0]})
cd "$DIR"
. common
mkdir -p "$PREBUILT_DATA_DIR"
cd "$PREBUILT_DATA_DIR"
DEP_DIR=SDL2-2.0.20
FILENAME=SDL2-devel-2.0.20-mingw.tar.gz
SHA256SUM=38094d82a857d6c62352e5c5cdec74948c5b4d25c59cbd298d6d233568976bd1
if [[ -d "$DEP_DIR" ]]
then
echo "$DEP_DIR" found
exit 0
fi
get_file "https://libsdl.org/release/$FILENAME" "$FILENAME" "$SHA256SUM"
mkdir "$DEP_DIR"
cd "$DEP_DIR"
TAR_PREFIX="$DEP_DIR" # root directory inside the tar has the same name
tar xf "../$FILENAME" --strip-components=1 \
"$TAR_PREFIX"/i686-w64-mingw32/bin/SDL2.dll \
"$TAR_PREFIX"/i686-w64-mingw32/include/ \
"$TAR_PREFIX"/i686-w64-mingw32/lib/ \
"$TAR_PREFIX"/x86_64-w64-mingw32/bin/SDL2.dll \
"$TAR_PREFIX"/x86_64-w64-mingw32/include/ \
"$TAR_PREFIX"/x86_64-w64-mingw32/lib/ \

View File

@ -63,7 +63,9 @@ build-server:
ninja -C "$(SERVER_BUILD_DIR)"
prepare-deps-win32:
-$(MAKE) -C prebuilt-deps prepare-win32
@prebuilt-deps/prepare-adb.sh
@prebuilt-deps/prepare-sdl.sh
@prebuilt-deps/prepare-ffmpeg-win32.sh
build-win32: prepare-deps-win32
[ -d "$(WIN32_BUILD_DIR)" ] || ( mkdir "$(WIN32_BUILD_DIR)" && \
@ -75,7 +77,9 @@ build-win32: prepare-deps-win32
ninja -C "$(WIN32_BUILD_DIR)"
prepare-deps-win64:
-$(MAKE) -C prebuilt-deps prepare-win64
@prebuilt-deps/prepare-adb.sh
@prebuilt-deps/prepare-sdl.sh
@prebuilt-deps/prepare-ffmpeg-win64.sh
build-win64: prepare-deps-win64
[ -d "$(WIN64_BUILD_DIR)" ] || ( mkdir "$(WIN64_BUILD_DIR)" && \
@ -93,15 +97,16 @@ 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 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)/"
cp prebuilt-deps/ffmpeg-4.3.1-win32-shared/bin/swresample-3.dll "$(DIST)/$(WIN32_TARGET_DIR)/"
cp prebuilt-deps/ffmpeg-4.3.1-win32-shared/bin/swscale-5.dll "$(DIST)/$(WIN32_TARGET_DIR)/"
cp prebuilt-deps/platform-tools/adb.exe "$(DIST)/$(WIN32_TARGET_DIR)/"
cp prebuilt-deps/platform-tools/AdbWinApi.dll "$(DIST)/$(WIN32_TARGET_DIR)/"
cp prebuilt-deps/platform-tools/AdbWinUsbApi.dll "$(DIST)/$(WIN32_TARGET_DIR)/"
cp prebuilt-deps/SDL2-2.0.18/i686-w64-mingw32/bin/SDL2.dll "$(DIST)/$(WIN32_TARGET_DIR)/"
cp data/open_a_terminal_here.bat "$(DIST)/$(WIN32_TARGET_DIR)"
cp prebuilt-deps/data/ffmpeg-win32-4.3.1/bin/avutil-56.dll "$(DIST)/$(WIN32_TARGET_DIR)/"
cp prebuilt-deps/data/ffmpeg-win32-4.3.1/bin/avcodec-58.dll "$(DIST)/$(WIN32_TARGET_DIR)/"
cp prebuilt-deps/data/ffmpeg-win32-4.3.1/bin/avformat-58.dll "$(DIST)/$(WIN32_TARGET_DIR)/"
cp prebuilt-deps/data/ffmpeg-win32-4.3.1/bin/swresample-3.dll "$(DIST)/$(WIN32_TARGET_DIR)/"
cp prebuilt-deps/data/ffmpeg-win32-4.3.1/bin/swscale-5.dll "$(DIST)/$(WIN32_TARGET_DIR)/"
cp prebuilt-deps/data/platform-tools-31.0.3/adb.exe "$(DIST)/$(WIN32_TARGET_DIR)/"
cp prebuilt-deps/data/platform-tools-31.0.3/AdbWinApi.dll "$(DIST)/$(WIN32_TARGET_DIR)/"
cp prebuilt-deps/data/platform-tools-31.0.3/AdbWinUsbApi.dll "$(DIST)/$(WIN32_TARGET_DIR)/"
cp prebuilt-deps/data/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 +115,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 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 data/open_a_terminal_here.bat "$(DIST)/$(WIN64_TARGET_DIR)"
cp prebuilt-deps/data/ffmpeg-win64-5.0/bin/avutil-57.dll "$(DIST)/$(WIN64_TARGET_DIR)/"
cp prebuilt-deps/data/ffmpeg-win64-5.0/bin/avcodec-59.dll "$(DIST)/$(WIN64_TARGET_DIR)/"
cp prebuilt-deps/data/ffmpeg-win64-5.0/bin/avformat-59.dll "$(DIST)/$(WIN64_TARGET_DIR)/"
cp prebuilt-deps/data/ffmpeg-win64-5.0/bin/swresample-4.dll "$(DIST)/$(WIN64_TARGET_DIR)/"
cp prebuilt-deps/data/ffmpeg-win64-5.0/bin/swscale-6.dll "$(DIST)/$(WIN64_TARGET_DIR)/"
cp prebuilt-deps/data/platform-tools-31.0.3/adb.exe "$(DIST)/$(WIN64_TARGET_DIR)/"
cp prebuilt-deps/data/platform-tools-31.0.3/AdbWinApi.dll "$(DIST)/$(WIN64_TARGET_DIR)/"
cp prebuilt-deps/data/platform-tools-31.0.3/AdbWinUsbApi.dll "$(DIST)/$(WIN64_TARGET_DIR)/"
cp prebuilt-deps/data/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

@ -6,8 +6,8 @@ android {
applicationId "com.genymobile.scrcpy"
minSdkVersion 21
targetSdkVersion 31
versionCode 12100
versionName "1.21"
versionCode 12200
versionName "1.22"
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
}
buildTypes {

View File

@ -12,7 +12,7 @@
set -e
SCRCPY_DEBUG=false
SCRCPY_VERSION_NAME=1.21
SCRCPY_VERSION_NAME=1.22
PLATFORM=${ANDROID_PLATFORM:-31}
BUILD_TOOLS=${ANDROID_BUILD_TOOLS:-31.0.0}

View File

@ -71,12 +71,13 @@ public final class ControlMessage {
return msg;
}
public static ControlMessage createInjectScrollEvent(Position position, int hScroll, int vScroll) {
public static ControlMessage createInjectScrollEvent(Position position, int hScroll, int vScroll, int buttons) {
ControlMessage msg = new ControlMessage();
msg.type = TYPE_INJECT_SCROLL_EVENT;
msg.position = position;
msg.hScroll = hScroll;
msg.vScroll = vScroll;
msg.buttons = buttons;
return msg;
}

View File

@ -10,7 +10,7 @@ public class ControlMessageReader {
static final int INJECT_KEYCODE_PAYLOAD_LENGTH = 13;
static final int INJECT_TOUCH_EVENT_PAYLOAD_LENGTH = 27;
static final int INJECT_SCROLL_EVENT_PAYLOAD_LENGTH = 20;
static final int INJECT_SCROLL_EVENT_PAYLOAD_LENGTH = 24;
static final int BACK_OR_SCREEN_ON_LENGTH = 1;
static final int SET_SCREEN_POWER_MODE_PAYLOAD_LENGTH = 1;
static final int GET_CLIPBOARD_LENGTH = 1;
@ -154,7 +154,8 @@ public class ControlMessageReader {
Position position = readPosition(buffer);
int hScroll = buffer.getInt();
int vScroll = buffer.getInt();
return ControlMessage.createInjectScrollEvent(position, hScroll, vScroll);
int buttons = buffer.getInt();
return ControlMessage.createInjectScrollEvent(position, hScroll, vScroll, buttons);
}
private ControlMessage parseBackOrScreenOnEvent() {

View File

@ -98,7 +98,7 @@ public class Controller {
break;
case ControlMessage.TYPE_INJECT_SCROLL_EVENT:
if (device.supportsInputEvents()) {
injectScroll(msg.getPosition(), msg.getHScroll(), msg.getVScroll());
injectScroll(msg.getPosition(), msg.getHScroll(), msg.getVScroll(), msg.getButtons());
}
break;
case ControlMessage.TYPE_BACK_OR_SCREEN_ON:
@ -221,7 +221,7 @@ public class Controller {
return device.injectEvent(event, Device.INJECT_MODE_ASYNC);
}
private boolean injectScroll(Position position, int hScroll, int vScroll) {
private boolean injectScroll(Position position, int hScroll, int vScroll, int buttons) {
long now = SystemClock.uptimeMillis();
Point point = device.getPhysicalPoint(position);
if (point == null) {
@ -239,7 +239,7 @@ public class Controller {
coords.setAxisValue(MotionEvent.AXIS_VSCROLL, vScroll);
MotionEvent event = MotionEvent
.obtain(lastTouchDown, now, MotionEvent.ACTION_SCROLL, 1, pointerProperties, pointerCoords, 0, 0, 1f, 1f, DEFAULT_DEVICE_ID, 0,
.obtain(lastTouchDown, now, MotionEvent.ACTION_SCROLL, 1, pointerProperties, pointerCoords, 0, buttons, 1f, 1f, DEFAULT_DEVICE_ID, 0,
InputDevice.SOURCE_MOUSE, 0);
return device.injectEvent(event, Device.INJECT_MODE_ASYNC);
}

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
@ -81,20 +89,41 @@ public class ScreenEncoder implements Device.RotationListener {
Rect unlockedVideoRect = screenInfo.getUnlockedVideoSize().toRect();
int videoRotation = screenInfo.getVideoRotation();
int layerStack = device.getLayerStack();
setSize(format, videoRect.width(), videoRect.height());
configure(codec, format);
Surface surface = codec.createInputSurface();
setDisplaySurface(display, surface, videoRotation, contentRect, unlockedVideoRect, layerStack);
codec.start();
Surface surface = null;
try {
configure(codec, format);
surface = codec.createInputSurface();
setDisplaySurface(display, surface, videoRotation, contentRect, unlockedVideoRect, layerStack);
codec.start();
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();
surface.release();
if (surface != null) {
surface.release();
}
}
} while (alive);
} finally {
@ -102,6 +131,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 +163,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);

View File

@ -128,6 +128,7 @@ public class ControlMessageReaderTest {
dos.writeShort(1920);
dos.writeInt(1);
dos.writeInt(-1);
dos.writeInt(1);
byte[] packet = bos.toByteArray();
@ -144,6 +145,7 @@ public class ControlMessageReaderTest {
Assert.assertEquals(1920, event.getPosition().getScreenSize().getHeight());
Assert.assertEquals(1, event.getHScroll());
Assert.assertEquals(-1, event.getVScroll());
Assert.assertEquals(1, event.getButtons());
}
@Test