Compare commits

..

38 Commits

Author SHA1 Message Date
Romain Vimont
b587014ccf Add UHID gamepad rumble WIP 2024-09-09 08:38:45 +02:00
Romain Vimont
890ee4d893 Simplify UHID outputs routing
There was a registration mechanism to listen to HID outputs with a
specific HID id.

However, the UHID gamepad processor handles several ids, so it will not
work. We could complexify the registration mechanism, but instead,
directly dispatch to the expected processor based on the UHID id.

Concretely, instead of passing a sc_uhid_devices instance to construct a
sc_keyboard_uhid, so that it can register itself, construct the
sc_uhid_devices with all the UHID instances (currently only
sc_keyboard_uhid) so that it can dispatch HID outputs directly.
2024-09-09 08:38:45 +02:00
Romain Vimont
2e79f4b9ef Expose custom UHID device name
Initialize UHID devices with a custom name, starting with a "scrcpy: "
prefix, followed by:
 - "Keyboard" for the keyboard;
 - "Mouse" for the mouse;
 - the actual gamepad name (as reported by SDL) for gamepads.

This is especially convenient for gamepads.
2024-09-09 08:38:45 +02:00
Romain Vimont
676a83610d Reorder function parameters for consistency
Make the local function write_string() accept the output buffer as a
first parameter, like the other similar functions.
2024-09-09 08:38:45 +02:00
Romain Vimont
f151992e08 Make -K -M and -G use AOA in OTG mode
For convenience, short options were added to select UHID input modes:
 - -K for --keyboard=uhid
 - -M for --mouse=uhid
 - -G for --gamepad=uhid

In OTG mode, UHID is not available, so the short options should select
AOA instead.
2024-09-09 08:38:45 +02:00
Romain Vimont
54b6fa5f6e Add UHID gamepad support
Similar to UHID keyboard and mouse, but for gamepads.

Can be enabled with --gamepad=uhid or -G.

It is not enabled by default because not all devices support UHID
(there is a permission error on old Android versions).
2024-09-09 08:38:45 +02:00
Romain Vimont
2d32cbc716 Add UHID_DESTROY control message
This message will be sent on gamepad disconnection.

Contrary to keyboard and mouse, which are registered once and are
unregistered when scrcpy exists, each gamepad is mapped with its own HID
id, and they can be plugged/unplugged dynamically.
2024-09-09 08:38:45 +02:00
Romain Vimont
29105b240b Add gamepad support in OTG mode
Implement gamepad support for OTG.
2024-09-09 08:38:45 +02:00
Romain Vimont
d14cc19c91 Add AOA gamepad support
Similar to AOA keyboard and mouse, but for gamepads.

Can be enabled with --gamepad=aoa.
2024-09-09 08:38:45 +02:00
Romain Vimont
385b31fb06 Implement HID gamepad
Implement the HID protocol for gamepads, that will be used in further
commits by the AOA and UHID gamepad processor implementations.
2024-09-09 08:38:45 +02:00
Romain Vimont
c4b6bb312c Add util functions to write in little-endian
This will be helpful for writing HID values.
2024-09-09 08:26:24 +02:00
Romain Vimont
abec04e8e7 Add connected gamepads on start
Trigger SDL_CONTROLLERDEVICEADDED for all gamepads already connected
when scrcpy starts. We want to handle both the gamepads initially
connected and the gamepads connected while scrcpy is running.

This is not racy, because this event may not trigged automatically until
SDL events are "pumped" (SDL_PumpEvents/SDL_WaitEvent).
2024-09-09 08:26:24 +02:00
Romain Vimont
0ea20f90be Handle SDL gamepad events
Introduce a gamepad processor trait, similar to the keyboard processor
and mouse processor traits.

Handle gamepad events received from SDL, convert them to scrcpy-specific
gamepad events, and forward them to the gamepad processor.

Further commits will provide AOA and UHID implementations of the gamepad
processor trait.

Co-authored-by: Luiz Henrique Laurini <luizhenriquelaurini@gmail.com>
2024-09-09 08:26:24 +02:00
Romain Vimont
a5ffe5b060 Fix HID comments
Fix typo and reference the latest version of "HID Usage Tables"
specifications.
2024-09-09 08:26:24 +02:00
Romain Vimont
6f1d79ba17 Make AOA keyboard/mouse open error fatal
Now that the AOA open/close are asynchronous, an open error did not make
scrcpy exit anymore.

Add a mechanism to exit if the AOA device could not be opened
asynchronously.
2024-09-09 08:26:24 +02:00
Romain Vimont
44e29989ee Unregister all AOA devices automatically on exit
Pushing a close event from the keyboard_aoa or mouse_aoa implementation
was racy, because the AOA thread might be stopped before these events
were processed.

Instead, keep the list of open AOA devices to close them automatically
from the AOA thread before exiting.
2024-09-09 08:26:24 +02:00
Romain Vimont
00786942be Make HID logs uniform 2024-09-09 08:26:24 +02:00
Romain Vimont
e6017cdc5d Add AOA open/close verbose logs 2024-09-09 08:26:24 +02:00
Romain Vimont
8fb87b5e6b Introduce hid_open and hid_close events
This allows to handle HID open/close at the same place as HID input
events (in the HID layer).

This will be especially useful to manage HID gamepads, to avoid
implementing one part in the HID layer and another part in the gamepad
processor implementation.
2024-09-09 08:26:24 +02:00
Romain Vimont
24f7ea5894 Rename hid_event to hid_input
The sc_hid_event structure represents HID input data. Rename it so that
we can add other hid event structs without confusion.
2024-09-09 08:26:24 +02:00
Romain Vimont
dbdfd9c8bf Make AOA open and close asynchronous
For AOA keyboard and mouse, only input events were asynchronous.
Register/unregister were called from the main thread.

This had the benefit to fail immediately if the AOA registration failed,
but to support gamepads we want to open/close AOA devices dynamically.

Also, it is better to avoid USB I/O from the main thread.
2024-09-09 08:26:24 +02:00
Romain Vimont
d58eb616f0 Reorder AOA functions
This will allow sc_aoa_setup_hid() to compile even when
sc_aoa_unregister_hid() will be made static.
2024-09-09 08:26:24 +02:00
Romain Vimont
230e6b4079 Refactor AOA handling
Extract event processing to a separate function.

This will make the code more readable when more event types will be
added.
2024-09-09 08:26:24 +02:00
Romain Vimont
3bd07aa8ff Move HID ids to common HID code
The HID ids (accessory ids or UHID ids) were defined by the keyboard and
mouse implementations.

Instead, define them in the common HID part, and make that id part of
the sc_hid_event.

This prepares the introduction of gamepad support, which will handle
several gamepads (and ids) in the common HID gamepad code.
2024-09-09 08:26:24 +02:00
Romain Vimont
28c91ecba2 Fix HID mouse header guard 2024-09-09 08:26:24 +02:00
Romain Vimont
9ebb836b20 Add missing SC_ prefix for HID mouse event size 2024-09-09 08:26:24 +02:00
Romain Vimont
1559940cee Remove duplicate definition SC_HID_MAX_SIZE
This constant is defined in hid_event.h.
2024-09-09 08:26:24 +02:00
Romain Vimont
0cc1a855dc Fail on AOA keyboard/mouse initialization error
If the AOA keyboard or the AOA mouse fails to be initialized, this is a
fatal error.
2024-09-09 08:26:24 +02:00
Romain Vimont
b49064064c Introduce non-droppable control messages
Control messages are queued from the main thread and sent to the device
from a separate thread.

When the queue is full, messages are just dropped. This avoids to
accumulate too much delay between the client and the device in case of
network issue.

However, some messages should not be dropped: for example, dropping a
UHID_CREATE message would make invalid all further UHID_INPUT messages.
Therefore, mark these messages as non-droppable.

A non-droppable event is queued anyway (resizing the queue if
necessary, unless the allocation fails).
2024-09-09 08:26:24 +02:00
Romain Vimont
ba342c398d Remove atomics from keyboard_uhid
The UHID output callback is now called from the same (main) thread as
the process_key() function.
2024-09-09 08:26:24 +02:00
Romain Vimont
d7fe119c8e Process UHID outputs events from the main thread
This will guarantee that the callbacks of UHID devices implementations
will always be called from the same (main) thread.
2024-09-09 08:26:24 +02:00
Romain Vimont
c5ccae5538 Set clipboard from the main thread
The clipboard changes from the device are received from a separate
thread, but it must be handled from the main thread.
2024-09-09 08:26:24 +02:00
Romain Vimont
87d9d68c07 Add mechanism to execute code on the main thread
This allows to schedule a runnable to be executed on the main thread,
until the event loop is explicitly terminated.

It is guaranteed that all accepted runnables will be executed (this
avoids possible memory leaks if a runnable owns resources).
2024-09-09 08:26:24 +02:00
Romain Vimont
684e2b632e Expose main thread id
This will allow to assert that a function is called from the main
thread.
2024-09-09 08:26:24 +02:00
Romain Vimont
8598e7d7a8 Extract sc_push_event()
Expose a convenience function to push an event without args to the main
thread.
2024-09-09 08:26:24 +02:00
Romain Vimont
8b372ae809 Store events numbers in an enum
This avoids to manually set an explicit value for each item.
2024-09-09 08:26:24 +02:00
Romain Vimont
28512d3872 Fix deprecated reference in scrcpy manpage
The options --hid-keyboard and --hid-mouse do not exist anymore. They
have been replaced by --keyboard=XXX and --mouse=XXX.
2024-09-09 08:26:24 +02:00
Romain Vimont
c11f07c1e8 Do not send uninitialized HID event
If the function returns false, then there is nothing to send.
2024-09-09 08:26:24 +02:00
53 changed files with 217 additions and 417 deletions

View File

@@ -2,7 +2,7 @@
source for the project. Do not download releases from random websites, even if
their name contains `scrcpy`.**
# scrcpy (v2.7)
# scrcpy (v2.6.1)
<img src="app/data/icon.svg" width="128" height="128" alt="scrcpy" align="right" />
@@ -37,7 +37,6 @@ Its features include:
- [camera mirroring](doc/camera.md) (Android 12+)
- [mirroring as a webcam (V4L2)](doc/v4l2.md) (Linux-only)
- physical [keyboard][hid-keyboard] and [mouse][hid-mouse] simulation (HID)
- [gamepad](doc/gamepad.md) support
- [OTG mode](doc/otg.md)
- and more…
@@ -112,13 +111,6 @@ Here are just some common examples.
scrcpy --otg
```
- Control the device using gamepad controllers plugged into the computer:
```bash
scrcpy --gamepad=uhid
scrcpy -G # short version
```
## User documentation
The application provides a lot of features and configuration options. They are
@@ -130,7 +122,6 @@ documented in the following pages:
- [Control](doc/control.md)
- [Keyboard](doc/keyboard.md)
- [Mouse](doc/mouse.md)
- [Gamepad](doc/gamepad.md)
- [Device](doc/device.md)
- [Window](doc/window.md)
- [Recording](doc/recording.md)

View File

@@ -4,10 +4,10 @@ DEPS_DIR=$(dirname ${BASH_SOURCE[0]})
cd "$DEPS_DIR"
. common
VERSION=7.0.2
VERSION=7.0.1
FILENAME=ffmpeg-$VERSION.tar.xz
PROJECT_DIR=ffmpeg-$VERSION
SHA256SUM=8646515b638a3ad303e23af6a3587734447cb8fc0a0c064ecdb8e95c4fd8b389
SHA256SUM=bce9eeb0f17ef8982390b1f37711a61b4290dc8c2a0c1a37b5857e85bfb0e4ff
cd "$SOURCES_DIR"

View File

@@ -4,10 +4,10 @@ DEPS_DIR=$(dirname ${BASH_SOURCE[0]})
cd "$DEPS_DIR"
. common
VERSION=2.30.7
VERSION=2.30.5
FILENAME=SDL-$VERSION.tar.gz
PROJECT_DIR=SDL-release-$VERSION
SHA256SUM=1578c96f62c9ae36b64e431b2aa0e0b0fd07c275dedbc694afc38e19056688f5
SHA256SUM=be3ca88f8c362704627a0bc5406edb2cd6cc6ba463596d81ebb7c2f18763d3bf
cd "$SOURCES_DIR"

View File

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

View File

@@ -185,9 +185,9 @@ Select how to send gamepad inputs to the device.
Possible values are "disabled", "uhid" and "aoa":
- "disabled" does not send gamepad inputs to the device.
- "uhid" simulates physical HID gamepads using the Linux HID kernel module on the device.
- "aoa" simulates physical HID gamepads using the AOAv2 protocol. It may only work over USB.
- "disabled" does not send keyboard inputs to the device.
- "uhid" simulates a physical HID gamepad using the Linux HID kernel module on the device.
- "aoa" simulates a physical HID gamepad using the AOAv2 protocol. It may only work over USB.
Also see \fB\-\-keyboard\f and R\fB\-\-mouse\fR.
.TP

View File

@@ -5,7 +5,7 @@
#include "util/log.h"
//#define SC_AUDIO_PLAYER_DEBUG // uncomment to debug
#define SC_AUDIO_PLAYER_NDEBUG // comment to debug
/**
* Real-time audio player with configurable latency
@@ -72,7 +72,7 @@ sc_audio_player_sdl_callback(void *userdata, uint8_t *stream, int len_int) {
size_t len = len_int;
uint32_t count = TO_SAMPLES(len);
#ifdef SC_AUDIO_PLAYER_DEBUG
#ifndef SC_AUDIO_PLAYER_NDEBUG
LOGD("[Audio] SDL callback requests %" PRIu32 " samples", count);
#endif
@@ -162,7 +162,7 @@ sc_audio_player_frame_sink_push(struct sc_frame_sink *sink,
// swr_convert() returns the number of samples which would have been
// written if the buffer was big enough.
uint32_t samples = MIN(ret, dst_nb_samples);
#ifdef SC_AUDIO_PLAYER_DEBUG
#ifndef SC_AUDIO_PLAYER_NDEBUG
LOGD("[Audio] %" PRIu32 " samples written to buffer", samples);
#endif
@@ -244,7 +244,7 @@ sc_audio_player_frame_sink_push(struct sc_frame_sink *sink,
if (played) {
LOGD("[Audio] Buffering threshold exceeded, skipping %" PRIu32
" samples", skip_samples);
#ifdef SC_AUDIO_PLAYER_DEBUG
#ifndef SC_AUDIO_PLAYER_NDEBUG
} else {
LOGD("[Audio] Playback not started, skipping %" PRIu32
" samples", skip_samples);
@@ -282,7 +282,7 @@ sc_audio_player_frame_sink_push(struct sc_frame_sink *sink,
// However, the buffering level must be smoothed
sc_average_push(&ap->avg_buffering, can_read);
#ifdef SC_AUDIO_PLAYER_DEBUG
#ifndef SC_AUDIO_PLAYER_NDEBUG
LOGD("[Audio] can_read=%" PRIu32 " avg_buffering=%f",
can_read, sc_average_get(&ap->avg_buffering));
#endif

View File

@@ -384,10 +384,10 @@ static const struct sc_option options[] = {
.text = "Select how to send gamepad inputs to the device.\n"
"Possible values are \"disabled\", \"uhid\" and \"aoa\".\n"
"\"disabled\" does not send gamepad inputs to the device.\n"
"\"uhid\" simulates physical HID gamepads using the Linux UHID "
"kernel module on the device.\n"
"\"aoa\" simulates physical gamepads using the AOAv2 protocol."
"It may only work over USB.\n"
"\"uhid\" simulates a physical HID gamepad using the Linux "
"UHID kernel module on the device.\n"
"\"aoa\" simulates a physical gamepad using the AOAv2 "
"protocol. It may only work over USB.\n"
"Also see --keyboard and --mouse.",
},
{
@@ -1491,6 +1491,18 @@ parse_max_size(const char *s, uint16_t *max_size) {
return true;
}
static bool
parse_max_fps(const char *s, uint16_t *max_fps) {
long value;
bool ok = parse_integer_arg(s, &value, false, 0, 0xFFFF, "max fps");
if (!ok) {
return false;
}
*max_fps = (uint16_t) value;
return true;
}
static bool
parse_buffering_time(const char *s, sc_tick *tick) {
long value;
@@ -2264,7 +2276,9 @@ parse_args_with_getopt(struct scrcpy_cli_args *args, int argc, char *argv[],
"--keyboard=uhid instead.");
return false;
case OPT_MAX_FPS:
opts->max_fps = optarg;
if (!parse_max_fps(optarg, &opts->max_fps)) {
return false;
}
break;
case 'm':
if (!parse_max_size(optarg, &opts->max_size)) {
@@ -2804,7 +2818,14 @@ parse_args_with_getopt(struct scrcpy_cli_args *args, int argc, char *argv[],
LOGE("SDK mouse mode requires video playback. Try --mouse=uhid.");
return false;
}
if (opts->gamepad_input_mode == SC_GAMEPAD_INPUT_MODE_UHID_OR_AOA) {
if (opts->gamepad_input_mode == SC_GAMEPAD_INPUT_MODE_AUTO) {
// UHID does not work on all devices (with old Android
// versions), so it cannot be enabled by default
opts->gamepad_input_mode = otg ? SC_GAMEPAD_INPUT_MODE_AOA
: SC_GAMEPAD_INPUT_MODE_DISABLED;
} else if (opts->gamepad_input_mode
== SC_GAMEPAD_INPUT_MODE_UHID_OR_AOA) {
opts->gamepad_input_mode = otg ? SC_GAMEPAD_INPUT_MODE_AOA
: SC_GAMEPAD_INPUT_MODE_UHID;
}
@@ -2868,7 +2889,6 @@ parse_args_with_getopt(struct scrcpy_cli_args *args, int argc, char *argv[],
if (gmode != SC_GAMEPAD_INPUT_MODE_AOA
&& gmode != SC_GAMEPAD_INPUT_MODE_DISABLED) {
LOGE("In OTG mode, --gamepad only supports aoa or disabled.");
return false;
}
if (kmode == SC_KEYBOARD_INPUT_MODE_DISABLED

View File

@@ -4,7 +4,7 @@
#include "util/log.h"
//#define SC_CLOCK_DEBUG // uncomment to debug
#define SC_CLOCK_NDEBUG // comment to debug
#define SC_CLOCK_RANGE 32
@@ -21,12 +21,10 @@ sc_clock_update(struct sc_clock *clock, sc_tick system, sc_tick stream) {
}
sc_tick offset = system - stream;
unsigned clock_weight = clock->range - 1;
unsigned value_weight = SC_CLOCK_RANGE - clock->range + 1;
clock->offset = (clock->offset * clock_weight + offset * value_weight)
/ SC_CLOCK_RANGE;
clock->offset = ((clock->range - 1) * clock->offset + offset)
/ clock->range;
#ifdef SC_CLOCK_DEBUG
#ifndef SC_CLOCK_NDEBUG
LOGD("Clock estimation: pts + %" PRItick, clock->offset);
#endif
}

View File

@@ -8,7 +8,7 @@
#include <libavutil/version.h>
#include <SDL2/SDL_version.h>
#ifndef _WIN32
#ifndef __WIN32
# define PRIu64_ PRIu64
# define SC_PRIsizet "zu"
#else

View File

@@ -116,7 +116,7 @@ sc_controller_push_msg(struct sc_controller *controller,
LOG_OOM();
}
}
// Otherwise, the msg is discarded
// Otherwise (if the queue is full), the msg is discarded
sc_mutex_unlock(&controller->mutex);

View File

@@ -8,6 +8,8 @@
#include "util/log.h"
#define SC_BUFFERING_NDEBUG // comment to debug
/** Downcast frame_sink to sc_delay_buffer */
#define DOWNCAST(SINK) container_of(SINK, struct sc_delay_buffer, frame_sink)
@@ -78,7 +80,7 @@ run_buffering(void *data) {
goto stopped;
}
#ifdef SC_BUFFERING_DEBUG
#ifndef SC_BUFFERING_NDEBUG
LOGD("Buffering: %" PRItick ";%" PRItick ";%" PRItick,
pts, dframe.push_date, sc_tick_now());
#endif
@@ -132,7 +134,6 @@ sc_delay_buffer_frame_sink_open(struct sc_frame_sink *sink,
sc_clock_init(&db->clock);
sc_vecdeque_init(&db->queue);
db->stopped = false;
if (!sc_frame_source_sinks_open(&db->frame_source, ctx)) {
goto error_destroy_wait_cond;
@@ -205,7 +206,7 @@ sc_delay_buffer_frame_sink_push(struct sc_frame_sink *sink,
return false;
}
#ifdef SC_BUFFERING_DEBUG
#ifndef SC_BUFFERING_NDEBUG
dframe.push_date = sc_tick_now();
#endif

View File

@@ -12,14 +12,12 @@
#include "util/tick.h"
#include "util/vecdeque.h"
//#define SC_BUFFERING_DEBUG // uncomment to debug
// forward declarations
typedef struct AVFrame AVFrame;
struct sc_delayed_frame {
AVFrame *frame;
#ifdef SC_BUFFERING_DEBUG
#ifndef NDEBUG
sc_tick push_date;
#endif
};

View File

@@ -38,7 +38,7 @@ sc_post_to_main_thread(sc_runnable_fn run, void *userdata) {
LOGD("Could not post runnable to main thread (filtered)");
} else {
assert(ret < 0);
LOGW("Could not post runnable to main thread: %s", SDL_GetError());
LOGW("Coud not post to main thread: %s", SDL_GetError());
}
return false;
}

View File

@@ -6,17 +6,17 @@
#include "util/binary.h"
#include "util/log.h"
// 2x2 bytes for left stick (X, Y)
// 2x2 bytes for right stick (Z, Rz)
// 2x2 bytes for L2/R2 triggers
// 2 bytes for left stick (X, Y)
// 2 bytes for right stick (Z, Rz)
// 2 bytes for L2/R2 triggers
// 2 bytes for buttons + padding,
// 1 byte for hat switch (dpad) + padding
#define SC_HID_GAMEPAD_EVENT_SIZE 15
// The ->buttons field stores the state for all buttons, but only some of them
// (the 16 LSB) must be transmitted "as is". The DPAD (hat switch) buttons are
// stored locally in the MSB of this field, but not transmitted as is: they are
// transformed to generate another specific byte.
// (the 16 LSB) must be transmitted "as is". The DPAD (hat switch) are stored
// locally in the MSB, but not transmitted as is: they are transformed to
// generate another specific byte.
#define SC_HID_BUTTONS_MASK 0xFFFF
// outside SC_HID_BUTTONS_MASK
@@ -26,7 +26,7 @@
#define SC_GAMEPAD_BUTTONS_BIT_DPAD_RIGHT UINT32_C(0x80000)
/**
* Gamepad descriptor manually crafted to transmit the input reports.
* Gamepad descriptor manually crafted to transmit the events.
*
* The HID specification is available here:
* <https://www.usb.org/document-library/device-class-definition-hid-111>
@@ -117,6 +117,7 @@ static const uint8_t SC_HID_GAMEPAD_REPORT_DESC[] = {
// Input (Data, Variable, Null State): 4-bit value
0x81, 0x42,
// End Collection
0xC0,
@@ -125,7 +126,7 @@ static const uint8_t SC_HID_GAMEPAD_REPORT_DESC[] = {
};
/**
* A gamepad HID input report is 15 bytes long:
* A gamepad HID event is 15 bytes long:
* - bytes 0-3: left stick state
* - bytes 4-7: right stick state
* - bytes 8-11: L2/R2 triggers state
@@ -134,28 +135,28 @@ static const uint8_t SC_HID_GAMEPAD_REPORT_DESC[] = {
*
* +---------------+
* byte 0: |. . . . . . . .|
* | | left stick x (0-65535, little-endian)
* | | left stick x state (0-65535)
* byte 1: |. . . . . . . .|
* +---------------+
* byte 2: |. . . . . . . .|
* | | left stick y (0-65535, little-endian)
* | | left stick y state (0-65535)
* byte 3: |. . . . . . . .|
* +---------------+
* byte 4: |. . . . . . . .|
* | | right stick x (0-65535, little-endian)
* | | right stick x state (0-65535)
* byte 5: |. . . . . . . .|
* +---------------+
* byte 6: |. . . . . . . .|
* | | right stick y (0-65535, little-endian)
* | | right stick y state (0-65535)
* byte 7: |. . . . . . . .|
* +---------------+
* byte 8: |. . . . . . . .|
* | | L2 trigger (0-32767, little-endian)
* byte 9: |0 . . . . . . .|
* | | L2 trigger state (0-65535)
* byte 9: |. . . . . . . .|
* +---------------+
* byte 10: |. . . . . . . .|
* | | R2 trigger (0-32767, little-endian)
* byte 11: |0 . . . . . . .|
* | | R2 trigger state (0-65535)
* byte 11: |. . . . . . . .|
* +---------------+
*
* ,--------------- SC_GAMEPAD_BUTTON_RIGHT_SHOULDER

View File

@@ -125,7 +125,7 @@ static const uint8_t SC_HID_KEYBOARD_REPORT_DESC[] = {
};
/**
* A keyboard HID input report is 8 bytes long:
* A keyboard HID event is 8 bytes long:
*
* - byte 0: modifiers (1 flag per modifier key, 8 possible modifier keys)
* - byte 1: reserved (always 0)
@@ -335,7 +335,7 @@ sc_hid_keyboard_generate_input_from_mods(struct sc_hid_input *hid_input,
void sc_hid_keyboard_generate_open(struct sc_hid_open *hid_open) {
hid_open->hid_id = SC_HID_ID_KEYBOARD;
hid_open->name = NULL; // No name specified after "scrcpy"
hid_open->name = "Keyboard";
hid_open->report_desc = SC_HID_KEYBOARD_REPORT_DESC;
hid_open->report_desc_size = sizeof(SC_HID_KEYBOARD_REPORT_DESC);
}

View File

@@ -14,7 +14,7 @@
* <https://www.usb.org/document-library/hid-usage-tables-15>
* §4 Generic Desktop Page (0x01) (p32)
*/
static const uint8_t SC_HID_MOUSE_REPORT_DESC[] = {
const uint8_t SC_HID_MOUSE_REPORT_DESC[] = {
// Usage Page (Generic Desktop)
0x05, 0x01,
// Usage (Mouse)
@@ -81,7 +81,7 @@ static const uint8_t SC_HID_MOUSE_REPORT_DESC[] = {
};
/**
* A mouse HID input report is 4 bytes long:
* A mouse HID event is 4 bytes long:
*
* - byte 0: buttons state
* - byte 1: relative x motion (signed byte from -127 to 127)
@@ -190,7 +190,7 @@ sc_hid_mouse_generate_input_from_scroll(struct sc_hid_input *hid_input,
void sc_hid_mouse_generate_open(struct sc_hid_open *hid_open) {
hid_open->hid_id = SC_HID_ID_MOUSE;
hid_open->name = NULL; // No name specified after "scrcpy"
hid_open->name = "Mouse";
hid_open->report_desc = SC_HID_MOUSE_REPORT_DESC;
hid_open->report_desc_size = sizeof(SC_HID_MOUSE_REPORT_DESC);
}

View File

@@ -516,7 +516,7 @@ sc_gamepad_device_event_type_from_sdl_type(uint32_t type) {
static inline enum sc_gamepad_axis
sc_gamepad_axis_from_sdl(uint8_t axis) {
if (axis <= SDL_CONTROLLER_AXIS_TRIGGERRIGHT) {
// SC_GAMEPAD_AXIS_* constants are initialized from
// SDL_GAMEPAD_AXIS_* constants are initialized from
// SDL_CONTROLLER_AXIS_*
return axis;
}

View File

@@ -402,7 +402,7 @@ sc_input_manager_process_key(struct sc_input_manager *im,
bool paused = im->screen->paused;
bool video = im->screen->video;
SDL_Keycode sdl_keycode = event->keysym.sym;
SDL_Keycode keycode = event->keysym.sym;
uint16_t mod = event->keysym.mod;
bool down = event->type == SDL_KEYDOWN;
bool ctrl = event->keysym.mod & KMOD_CTRL;
@@ -414,21 +414,21 @@ sc_input_manager_process_key(struct sc_input_manager *im,
// The second condition is necessary to ignore the release of the modifier
// key (because in this case mod is 0).
bool is_shortcut = is_shortcut_mod(im, mod)
|| is_shortcut_key(im, sdl_keycode);
|| is_shortcut_key(im, keycode);
if (down && !repeat) {
if (sdl_keycode == im->last_keycode && mod == im->last_mod) {
if (keycode == im->last_keycode && mod == im->last_mod) {
++im->key_repeat;
} else {
im->key_repeat = 0;
im->last_keycode = sdl_keycode;
im->last_keycode = keycode;
im->last_mod = mod;
}
}
if (is_shortcut) {
enum sc_action action = down ? SC_ACTION_DOWN : SC_ACTION_UP;
switch (sdl_keycode) {
switch (keycode) {
case SDLK_h:
if (im->kp && !shift && !repeat && !paused) {
action_home(im, action);
@@ -587,7 +587,7 @@ sc_input_manager_process_key(struct sc_input_manager *im,
}
uint64_t ack_to_wait = SC_SEQUENCE_INVALID;
bool is_ctrl_v = ctrl && !shift && sdl_keycode == SDLK_v && down && !repeat;
bool is_ctrl_v = ctrl && !shift && keycode == SDLK_v && down && !repeat;
if (im->clipboard_autosync && is_ctrl_v) {
if (im->legacy_paste) {
// inject the text as input events
@@ -615,20 +615,10 @@ sc_input_manager_process_key(struct sc_input_manager *im,
}
}
enum sc_keycode keycode = sc_keycode_from_sdl(sdl_keycode);
if (keycode == SC_KEYCODE_UNKNOWN) {
return;
}
enum sc_scancode scancode = sc_scancode_from_sdl(event->keysym.scancode);
if (scancode == SC_SCANCODE_UNKNOWN) {
return;
}
struct sc_key_event evt = {
.action = sc_action_from_sdl_keyboard_type(event->type),
.keycode = keycode,
.scancode = scancode,
.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),
};
@@ -751,10 +741,6 @@ sc_input_manager_process_mouse_button(struct sc_input_manager *im,
bool down = event->type == SDL_MOUSEBUTTONDOWN;
enum sc_mouse_button button = sc_mouse_button_from_sdl(event->button);
if (button == SC_MOUSE_BUTTON_UNKNOWN) {
return;
}
if (!down) {
// Mark the button as released
im->mouse_buttons_state &= ~button;
@@ -843,7 +829,7 @@ sc_input_manager_process_mouse_button(struct sc_input_manager *im,
struct sc_mouse_click_event evt = {
.position = sc_input_manager_get_position(im, event->x, event->y),
.action = sc_action_from_sdl_mousebutton_type(event->type),
.button = button,
.button = sc_mouse_button_from_sdl(event->button),
.pointer_id = use_finger ? SC_POINTER_ID_GENERIC_FINGER
: SC_POINTER_ID_MOUSE,
.buttons_state = im->mouse_buttons_state,
@@ -964,15 +950,10 @@ sc_input_manager_process_gamepad_device(struct sc_input_manager *im,
static void
sc_input_manager_process_gamepad_axis(struct sc_input_manager *im,
const SDL_ControllerAxisEvent *event) {
enum sc_gamepad_axis axis = sc_gamepad_axis_from_sdl(event->axis);
if (axis == SC_GAMEPAD_AXIS_UNKNOWN) {
return;
}
const SDL_ControllerAxisEvent *event) {
struct sc_gamepad_axis_event evt = {
.gamepad_id = event->which,
.axis = axis,
.axis = sc_gamepad_axis_from_sdl(event->axis),
.value = event->value,
};
im->gp->ops->process_gamepad_axis(im->gp, &evt);
@@ -981,15 +962,10 @@ sc_input_manager_process_gamepad_axis(struct sc_input_manager *im,
static void
sc_input_manager_process_gamepad_button(struct sc_input_manager *im,
const SDL_ControllerButtonEvent *event) {
enum sc_gamepad_button button = sc_gamepad_button_from_sdl(event->button);
if (button == SC_GAMEPAD_BUTTON_UNKNOWN) {
return;
}
struct sc_gamepad_button_event evt = {
.gamepad_id = event->which,
.action = sc_action_from_sdl_controllerbutton_type(event->type),
.button = button,
.button = sc_gamepad_button_from_sdl(event->button),
};
im->gp->ops->process_gamepad_button(im->gp, &evt);
}

View File

@@ -23,7 +23,7 @@ const struct scrcpy_options scrcpy_options_default = {
.record_format = SC_RECORD_FORMAT_AUTO,
.keyboard_input_mode = SC_KEYBOARD_INPUT_MODE_AUTO,
.mouse_input_mode = SC_MOUSE_INPUT_MODE_AUTO,
.gamepad_input_mode = SC_GAMEPAD_INPUT_MODE_DISABLED,
.gamepad_input_mode = SC_GAMEPAD_INPUT_MODE_AUTO,
.mouse_bindings = {
.pri = {
.right_click = SC_MOUSE_BINDING_AUTO,
@@ -49,7 +49,7 @@ const struct scrcpy_options scrcpy_options_default = {
.max_size = 0,
.video_bit_rate = 0,
.audio_bit_rate = 0,
.max_fps = NULL,
.max_fps = 0,
.lock_video_orientation = SC_LOCK_VIDEO_ORIENTATION_UNLOCKED,
.display_orientation = SC_ORIENTATION_0,
.record_orientation = SC_ORIENTATION_0,

View File

@@ -159,8 +159,9 @@ enum sc_mouse_input_mode {
};
enum sc_gamepad_input_mode {
SC_GAMEPAD_INPUT_MODE_DISABLED,
SC_GAMEPAD_INPUT_MODE_AUTO,
SC_GAMEPAD_INPUT_MODE_UHID_OR_AOA, // normal vs otg mode
SC_GAMEPAD_INPUT_MODE_DISABLED,
SC_GAMEPAD_INPUT_MODE_UHID,
SC_GAMEPAD_INPUT_MODE_AOA,
};
@@ -250,7 +251,7 @@ struct scrcpy_options {
uint16_t max_size;
uint32_t video_bit_rate;
uint32_t audio_bit_rate;
const char *max_fps; // float to be parsed by the server
uint16_t max_fps;
enum sc_lock_video_orientation lock_video_orientation;
enum sc_orientation display_orientation;
enum sc_orientation record_orientation;

View File

@@ -9,7 +9,6 @@
#include "events.h"
#include "util/log.h"
#include "util/str.h"
#include "util/thread.h"
struct sc_uhid_output_task_data {
struct sc_uhid_devices *uhid_devices;
@@ -44,8 +43,6 @@ sc_receiver_destroy(struct sc_receiver *receiver) {
static void
task_set_clipboard(void *userdata) {
assert(sc_thread_get_id() == SC_MAIN_THREAD_ID);
char *text = userdata;
char *current = SDL_GetClipboardText();
@@ -53,18 +50,17 @@ task_set_clipboard(void *userdata) {
SDL_free(current);
if (same) {
LOGD("Computer clipboard unchanged");
} else {
LOGI("Device clipboard copied");
SDL_SetClipboardText(text);
free(text);
return;
}
LOGI("Device clipboard copied");
SDL_SetClipboardText(text);
free(text);
}
static void
task_uhid_output(void *userdata) {
assert(sc_thread_get_id() == SC_MAIN_THREAD_ID);
struct sc_uhid_output_task_data *data = userdata;
sc_uhid_devices_process_hid_output(data->uhid_devices, data->id, data->data,
@@ -121,6 +117,11 @@ process_msg(struct sc_receiver *receiver, struct sc_device_msg *msg) {
}
}
// This is a programming error to receive this message if there is
// no uhid_devices instance
assert(receiver->uhid_devices);
// Also check at runtime (do not trust the server)
if (!receiver->uhid_devices) {
LOGE("Received unexpected HID output message");
sc_device_msg_destroy(msg);

View File

@@ -65,8 +65,8 @@ struct scrcpy {
struct sc_aoa aoa;
// sequence/ack helper to synchronize clipboard and Ctrl+v via HID
struct sc_acksync acksync;
#endif
struct sc_uhid_devices uhid_devices;
#endif
union {
struct sc_keyboard_sdk keyboard_sdk;
struct sc_keyboard_uhid keyboard_uhid;
@@ -136,10 +136,6 @@ sdl_set_hints(const char *render_driver) {
if (!SDL_SetHint(SDL_HINT_VIDEO_MINIMIZE_ON_FOCUS_LOSS, "0")) {
LOGW("Could not disable minimize on focus loss");
}
if (!SDL_SetHint(SDL_HINT_JOYSTICK_ALLOW_BACKGROUND_EVENTS, "1")) {
LOGW("Could not allow joystick background events");
}
}
static void
@@ -189,12 +185,11 @@ event_loop(struct scrcpy *s) {
case SDL_QUIT:
LOGD("User requested to quit");
return SCRCPY_EXIT_SUCCESS;
case SC_EVENT_RUN_ON_MAIN_THREAD: {
case SC_EVENT_RUN_ON_MAIN_THREAD:
sc_runnable_fn run = event.user.data1;
void *userdata = event.user.data2;
run(userdata);
break;
}
default:
if (!sc_screen_handle_event(&s->screen, &event)) {
return SCRCPY_EXIT_FAILURE;
@@ -729,6 +724,7 @@ aoa_complete:
#endif
struct sc_keyboard_uhid *uhid_keyboard = NULL;
struct sc_gamepad_uhid *uhid_gamepad = NULL;
if (options->keyboard_input_mode == SC_KEYBOARD_INPUT_MODE_SDK) {
sc_keyboard_sdk_init(&s->keyboard_sdk, &s->controller,
@@ -760,11 +756,12 @@ aoa_complete:
if (options->gamepad_input_mode == SC_GAMEPAD_INPUT_MODE_UHID) {
sc_gamepad_uhid_init(&s->gamepad_uhid, &s->controller);
gp = &s->gamepad_uhid.gamepad_processor;
uhid_gamepad = &s->gamepad_uhid;
}
struct sc_uhid_devices *uhid_devices = NULL;
if (uhid_keyboard) {
sc_uhid_devices_init(&s->uhid_devices, uhid_keyboard);
if (uhid_keyboard || uhid_gamepad) {
sc_uhid_devices_init(&s->uhid_devices, uhid_keyboard, uhid_gamepad);
uhid_devices = &s->uhid_devices;
}
@@ -901,8 +898,8 @@ aoa_complete:
timeout_started = true;
}
if (options->control
&& options->gamepad_input_mode != SC_GAMEPAD_INPUT_MODE_DISABLED) {
bool use_gamepads = true;
if (use_gamepads) {
init_sdl_gamepads();
}

View File

@@ -299,12 +299,6 @@ sc_screen_frame_sink_open(struct sc_frame_sink *sink,
struct sc_screen *screen = DOWNCAST(sink);
if (ctx->width <= 0 || ctx->width > 0xFFFF
|| ctx->height <= 0 || ctx->height > 0xFFFF) {
LOGE("Invalid video size: %dx%d", ctx->width, ctx->height);
return false;
}
assert(ctx->width > 0 && ctx->width <= 0xFFFF);
assert(ctx->height > 0 && ctx->height <= 0xFFFF);
// screen->frame_size is never used before the event is pushed, and the
@@ -315,6 +309,7 @@ sc_screen_frame_sink_open(struct sc_frame_sink *sink,
// Post the event on the UI thread (the texture must be created from there)
bool ok = sc_push_event(SC_EVENT_SCREEN_INIT_SIZE);
if (!ok) {
LOGW("Could not post init size event: %s", SDL_GetError());
return false;
}
@@ -356,6 +351,7 @@ sc_screen_frame_sink_push(struct sc_frame_sink *sink, const AVFrame *frame) {
// Post the event on the UI thread
bool ok = sc_push_event(SC_EVENT_NEW_FRAME);
if (!ok) {
LOGW("Could not post new frame event: %s", SDL_GetError());
return false;
}
}

View File

@@ -218,21 +218,6 @@ sc_server_get_audio_source_name(enum sc_audio_source audio_source) {
}
}
static bool
validate_string(const char *s) {
// The parameters values are passed as command line arguments to adb, so
// they must either be properly escaped, or they must not contain any
// special shell characters.
// Since they are not properly escaped on Windows anyway (see
// sys/win/process.c), just forbid special shell characters.
if (strpbrk(s, " ;'\"*$?&`#\\|<>[]{}()!~\r\n")) {
LOGE("Invalid server param: [%s]", s);
return false;
}
return true;
}
static sc_pid
execute_server(struct sc_server *server,
const struct sc_server_params *params) {
@@ -275,11 +260,6 @@ execute_server(struct sc_server *server,
} \
cmd[count++] = p; \
} while(0)
#define VALIDATE_STRING(s) do { \
if (!validate_string(s)) { \
goto end; \
} \
} while(0)
ADD_PARAM("scid=%08x", params->scid);
ADD_PARAM("log_level=%s", log_level_to_server_string(params->log_level));
@@ -321,8 +301,7 @@ execute_server(struct sc_server *server,
ADD_PARAM("max_size=%" PRIu16, params->max_size);
}
if (params->max_fps) {
VALIDATE_STRING(params->max_fps);
ADD_PARAM("max_fps=%s", params->max_fps);
ADD_PARAM("max_fps=%" PRIu16, params->max_fps);
}
if (params->lock_video_orientation != SC_LOCK_VIDEO_ORIENTATION_UNLOCKED) {
ADD_PARAM("lock_video_orientation=%" PRIi8,
@@ -332,7 +311,6 @@ execute_server(struct sc_server *server,
ADD_PARAM("tunnel_forward=true");
}
if (params->crop) {
VALIDATE_STRING(params->crop);
ADD_PARAM("crop=%s", params->crop);
}
if (!params->control) {
@@ -343,11 +321,9 @@ execute_server(struct sc_server *server,
ADD_PARAM("display_id=%" PRIu32, params->display_id);
}
if (params->camera_id) {
VALIDATE_STRING(params->camera_id);
ADD_PARAM("camera_id=%s", params->camera_id);
}
if (params->camera_size) {
VALIDATE_STRING(params->camera_size);
ADD_PARAM("camera_size=%s", params->camera_size);
}
if (params->camera_facing != SC_CAMERA_FACING_ANY) {
@@ -355,7 +331,6 @@ execute_server(struct sc_server *server,
sc_server_get_camera_facing_name(params->camera_facing));
}
if (params->camera_ar) {
VALIDATE_STRING(params->camera_ar);
ADD_PARAM("camera_ar=%s", params->camera_ar);
}
if (params->camera_fps) {
@@ -371,19 +346,15 @@ execute_server(struct sc_server *server,
ADD_PARAM("stay_awake=true");
}
if (params->video_codec_options) {
VALIDATE_STRING(params->video_codec_options);
ADD_PARAM("video_codec_options=%s", params->video_codec_options);
}
if (params->audio_codec_options) {
VALIDATE_STRING(params->audio_codec_options);
ADD_PARAM("audio_codec_options=%s", params->audio_codec_options);
}
if (params->video_encoder) {
VALIDATE_STRING(params->video_encoder);
ADD_PARAM("video_encoder=%s", params->video_encoder);
}
if (params->audio_encoder) {
VALIDATE_STRING(params->audio_encoder);
ADD_PARAM("audio_encoder=%s", params->audio_encoder);
}
if (params->power_off_on_close) {
@@ -659,14 +630,6 @@ sc_server_connect_to(struct sc_server *server, struct sc_server_info *info) {
}
}
if (control_socket != SC_SOCKET_NONE) {
// Disable Nagle's algorithm for the control socket
// (it only impacts the sending side, so it is useless to set it
// for the other sockets)
bool ok = net_set_tcp_nodelay(control_socket, true);
(void) ok; // error already logged
}
// we don't need the adb tunnel anymore
sc_adb_tunnel_close(tunnel, &server->intr, serial,
server->device_socket_name);

View File

@@ -44,7 +44,7 @@ struct sc_server_params {
uint16_t max_size;
uint32_t video_bit_rate;
uint32_t audio_bit_rate;
const char *max_fps; // float to be parsed by the server
uint16_t max_fps;
int8_t lock_video_orientation;
bool control;
uint32_t display_id;

View File

@@ -3,6 +3,7 @@
#include "hid/hid_gamepad.h"
#include "input_events.h"
#include "util/log.h"
#include "util/str.h"
/** Downcast gamepad processor to sc_gamepad_uhid */
#define DOWNCAST(GP) container_of(GP, struct sc_gamepad_uhid, gamepad_processor)
@@ -106,6 +107,22 @@ sc_gamepad_processor_process_gamepad_button(struct sc_gamepad_processor *gp,
}
void
sc_gamepad_uhid_process_hid_output(struct sc_gamepad_uhid *gamepad,
uint16_t hid_id, const uint8_t *data,
size_t size) {
(void) gamepad;
char *hex = sc_str_to_hex_string(data, size);
if (hex) {
LOGI("==== HID output [%" PRIu16 "] %s", hid_id, hex);
free(hex);
} else {
LOGI("==== HID output [%" PRIu16 "]", hid_id);
}
// TODO
}
void
sc_gamepad_uhid_init(struct sc_gamepad_uhid *gamepad,
struct sc_controller *controller) {

View File

@@ -20,4 +20,9 @@ void
sc_gamepad_uhid_init(struct sc_gamepad_uhid *mouse,
struct sc_controller *controller);
void
sc_gamepad_uhid_process_hid_output(struct sc_gamepad_uhid *gamepad,
uint16_t hid_id, const uint8_t *data,
size_t size);
#endif

View File

@@ -4,12 +4,15 @@
#include <inttypes.h>
#include "uhid/keyboard_uhid.h"
#include "uhid/gamepad_uhid.h"
#include "util/log.h"
void
sc_uhid_devices_init(struct sc_uhid_devices *devices,
struct sc_keyboard_uhid *keyboard) {
struct sc_keyboard_uhid *keyboard,
struct sc_gamepad_uhid *gamepad) {
devices->keyboard = keyboard;
devices->gamepad = gamepad;
}
void
@@ -21,6 +24,13 @@ sc_uhid_devices_process_hid_output(struct sc_uhid_devices *devices, uint16_t id,
} else {
LOGW("Unexpected keyboard HID output without UHID keyboard");
}
} else if (id >= SC_HID_ID_GAMEPAD_FIRST && id <= SC_HID_ID_GAMEPAD_LAST) {
if (devices->gamepad) {
sc_gamepad_uhid_process_hid_output(devices->gamepad, id, data,
size);
} else {
LOGW("Unexpected gamepad HID output without UHID gamepad");
}
} else {
LOGW("HID output ignored for id %" PRIu16, id);
}

View File

@@ -14,11 +14,13 @@
struct sc_uhid_devices {
struct sc_keyboard_uhid *keyboard;
struct sc_gamepad_uhid *gamepad;
};
void
sc_uhid_devices_init(struct sc_uhid_devices *devices,
struct sc_keyboard_uhid *keyboard);
struct sc_keyboard_uhid *keyboard,
struct sc_gamepad_uhid *gamepad);
void
sc_uhid_devices_process_hid_output(struct sc_uhid_devices *devices, uint16_t id,

View File

@@ -51,7 +51,7 @@ sc_hid_open_log(const struct sc_hid_open *hid_open) {
static void
sc_hid_close_log(const struct sc_hid_close *hid_close) {
// HID close: [00]
LOGV("HID close: [%" PRIu16 "]", hid_close->hid_id);
LOGV("HD close: [%" PRIu16 "]", hid_close->hid_id);
}
bool
@@ -227,11 +227,8 @@ sc_aoa_push_input_with_ack_to_wait(struct sc_aoa *aoa,
}
sc_mutex_lock(&aoa->mutex);
bool pushed = false;
size_t size = sc_vecdeque_size(&aoa->queue);
if (size < SC_AOA_EVENT_QUEUE_LIMIT) {
bool full = sc_vecdeque_is_full(&aoa->queue);
if (!full) {
bool was_empty = sc_vecdeque_is_empty(&aoa->queue);
struct sc_aoa_event *aoa_event =
@@ -239,17 +236,16 @@ sc_aoa_push_input_with_ack_to_wait(struct sc_aoa *aoa,
aoa_event->type = SC_AOA_EVENT_TYPE_INPUT;
aoa_event->input.hid = *hid_input;
aoa_event->input.ack_to_wait = ack_to_wait;
pushed = true;
if (was_empty) {
sc_cond_signal(&aoa->event_cond);
}
}
// Otherwise, the event is discarded
// Otherwise (if the queue is full), the event is discarded
sc_mutex_unlock(&aoa->mutex);
return pushed;
return !full;
}
bool
@@ -293,7 +289,7 @@ sc_aoa_push_close(struct sc_aoa *aoa, const struct sc_hid_close *hid_close) {
sc_mutex_lock(&aoa->mutex);
bool was_empty = sc_vecdeque_is_empty(&aoa->queue);
// a CLOSE event is non-droppable, so push it to the queue even above the
// an OPEN event is non-droppable, so push it to the queue even above the
// SC_AOA_EVENT_QUEUE_LIMIT
struct sc_aoa_event *aoa_event = sc_vecdeque_push_hole(&aoa->queue);
if (!aoa_event) {

View File

@@ -22,7 +22,10 @@ sc_usb_on_disconnected(struct sc_usb *usb, void *userdata) {
(void) usb;
(void) userdata;
sc_push_event(SC_EVENT_USB_DEVICE_DISCONNECTED);
bool ok = sc_push_event(SC_EVENT_USB_DEVICE_DISCONNECTED);
if (!ok) {
LOGE("Could not post USB disconnection event: %s", SDL_GetError());
}
}
static enum scrcpy_exit_code
@@ -58,10 +61,6 @@ scrcpy_otg(struct scrcpy_options *options) {
LOGW("Could not enable linear filtering");
}
if (!SDL_SetHint(SDL_HINT_JOYSTICK_ALLOW_BACKGROUND_EVENTS, "1")) {
LOGW("Could not allow joystick background events");
}
// Minimal SDL initialization
if (SDL_Init(SDL_INIT_EVENTS)) {
LOGE("Could not initialize SDL: %s", SDL_GetError());

View File

@@ -264,14 +264,9 @@ sc_screen_otg_process_gamepad_axis(struct sc_screen_otg *screen,
assert(screen->gamepad);
struct sc_gamepad_processor *gp = &screen->gamepad->gamepad_processor;
enum sc_gamepad_axis axis = sc_gamepad_axis_from_sdl(event->axis);
if (axis == SC_GAMEPAD_AXIS_UNKNOWN) {
return;
}
struct sc_gamepad_axis_event evt = {
.gamepad_id = event->which,
.axis = axis,
.axis = sc_gamepad_axis_from_sdl(event->axis),
.value = event->value,
};
gp->ops->process_gamepad_axis(gp, &evt);
@@ -283,15 +278,10 @@ sc_screen_otg_process_gamepad_button(struct sc_screen_otg *screen,
assert(screen->gamepad);
struct sc_gamepad_processor *gp = &screen->gamepad->gamepad_processor;
enum sc_gamepad_button button = sc_gamepad_button_from_sdl(event->button);
if (button == SC_GAMEPAD_BUTTON_UNKNOWN) {
return;
}
struct sc_gamepad_button_event evt = {
.gamepad_id = event->which,
.action = sc_action_from_sdl_controllerbutton_type(event->type),
.button = button,
.button = sc_gamepad_button_from_sdl(event->button),
};
gp->ops->process_gamepad_button(gp, &evt);
}

View File

@@ -44,7 +44,7 @@ sc_write64be(uint8_t *buf, uint64_t value) {
static inline void
sc_write64le(uint8_t *buf, uint64_t value) {
sc_write32le(buf, (uint32_t) value);
sc_write32le(&buf[4], value >> 32);
sc_write32le(buf, value >> 32);
}
static inline uint16_t

View File

@@ -15,7 +15,6 @@
# include <sys/types.h>
# include <sys/socket.h>
# include <netinet/in.h>
# include <netinet/tcp.h>
# include <arpa/inet.h>
# include <unistd.h>
# include <fcntl.h>
@@ -274,22 +273,6 @@ net_close(sc_socket socket) {
#endif
}
bool
net_set_tcp_nodelay(sc_socket socket, bool tcp_nodelay) {
sc_raw_socket raw_sock = unwrap(socket);
int value = tcp_nodelay ? 1 : 0;
int ret = setsockopt(raw_sock, IPPROTO_TCP, TCP_NODELAY,
(const void *) &value, sizeof(value));
if (ret == -1) {
net_perror("setsockopt(TCP_NODELAY)");
return false;
}
assert(ret == 0);
return true;
}
bool
net_parse_ipv4(const char *s, uint32_t *ipv4) {
struct in_addr addr;

View File

@@ -67,10 +67,6 @@ net_interrupt(sc_socket socket);
bool
net_close(sc_socket socket);
// Disable Nagle's algorithm (if tcp_nodelay is true)
bool
net_set_tcp_nodelay(sc_socket socket, bool tcp_nodelay);
/**
* Parse `ip` "xxx.xxx.xxx.xxx" to an IPv4 host representation
*/

View File

@@ -42,44 +42,6 @@ static void test_write64be(void) {
assert(buf[7] == 0xEF);
}
static void test_write16le(void) {
uint16_t val = 0xABCD;
uint8_t buf[2];
sc_write16le(buf, val);
assert(buf[0] == 0xCD);
assert(buf[1] == 0xAB);
}
static void test_write32le(void) {
uint32_t val = 0xABCD1234;
uint8_t buf[4];
sc_write32le(buf, val);
assert(buf[0] == 0x34);
assert(buf[1] == 0x12);
assert(buf[2] == 0xCD);
assert(buf[3] == 0xAB);
}
static void test_write64le(void) {
uint64_t val = 0xABCD1234567890EF;
uint8_t buf[8];
sc_write64le(buf, val);
assert(buf[0] == 0xEF);
assert(buf[1] == 0x90);
assert(buf[2] == 0x78);
assert(buf[3] == 0x56);
assert(buf[4] == 0x34);
assert(buf[5] == 0x12);
assert(buf[6] == 0xCD);
assert(buf[7] == 0xAB);
}
static void test_read16be(void) {
uint8_t buf[2] = {0xAB, 0xCD};
@@ -146,10 +108,6 @@ int main(int argc, char *argv[]) {
test_read32be();
test_read64be();
test_write16le();
test_write32le();
test_write64le();
test_float_to_u16fp();
test_float_to_i16fp();
return 0;

View File

@@ -78,7 +78,7 @@ static void test_options(void) {
assert(opts->video_bit_rate == 5000000);
assert(!strcmp(opts->crop, "100:200:300:400"));
assert(opts->fullscreen);
assert(!strcmp(opts->max_fps, "30"));
assert(opts->max_fps == 30);
assert(opts->max_size == 1024);
assert(opts->lock_video_orientation == 2);
assert(opts->port_range.first == 1234);

View File

@@ -233,10 +233,10 @@ install` must be run as root)._
#### Option 2: Use prebuilt server
- [`scrcpy-server-v2.7`][direct-scrcpy-server]
<sub>SHA-256: `a23c5659f36c260f105c022d27bcb3eafffa26070e7baa9eda66d01377a1adba`</sub>
- [`scrcpy-server-v2.6.1`][direct-scrcpy-server]
<sub>SHA-256: `ca7ab50b2e25a0e5af7599c30383e365983fa5b808e65ce2e1c1bba5bfe8dc3b`</sub>
[direct-scrcpy-server]: https://github.com/Genymobile/scrcpy/releases/download/v2.7/scrcpy-server-v2.7
[direct-scrcpy-server]: https://github.com/Genymobile/scrcpy/releases/download/v2.6.1/scrcpy-server-v2.6.1
Download the prebuilt server somewhere, and specify its path during the Meson
configuration:

View File

@@ -1,58 +0,0 @@
# Gamepad
Several gamepad input modes are available:
- `--gamepad=disabled` (default)
- `--gamepad=uhid` (or `-G`): simulates physical HID gamepads using the UHID
kernel module on the device
- `--gamepad=aoa`: simulates physical HID gamepads using the AOAv2 protocol
## Physical gamepad simulation
Two modes allow to simulate physical HID gamepads on the device, one for each
physical gamepad plugged into the computer.
### UHID
This mode simulates physical HID gamepads using the [UHID] kernel module on the
device.
[UHID]: https://kernel.org/doc/Documentation/hid/uhid.txt
To enable UHID gamepads, use:
```bash
scrcpy --gamepad=uhid
scrcpy -G # short version
```
Note: UHID may not work on old Android versions due to permission errors.
### AOA
This mode simulates physical HID gamepads using the [AOAv2] protocol.
[AOAv2]: https://source.android.com/devices/accessories/aoa2#hid-support
To enable AOA gamepads, use:
```bash
scrcpy --gamepad=aoa
```
Contrary to the other mode, it works at the USB level directly (so it only works
over USB).
It does not use the scrcpy server, and does not require `adb` (USB debugging).
Therefore, it is possible to control the device (but not mirror) even with USB
debugging disabled (see [OTG](otg.md)).
Note: For some reason, in this mode, Android detects multiple physical gamepads
as a single misbehaving one. Use UHID if you need multiple gamepads.
Note: On Windows, it may only work in [OTG mode](otg.md), not while mirroring
(it is not possible to open a USB device if it is already open by another
process like the _adb daemon_).

View File

@@ -53,8 +53,6 @@ scrcpy --mouse=uhid
scrcpy -M # short version
```
Note: UHID may not work on old Android versions due to permission errors.
### AOA

View File

@@ -6,18 +6,16 @@ was a [physical keyboard] and/or a [physical mouse] connected to the Android
device (see [keyboard](keyboard.md) and [mouse](mouse.md)).
[physical keyboard]: keyboard.md#physical-keyboard-simulation
[physical mouse]: mouse.md#physical-mouse-simulation
[physical mouse]: physical-keyboard-simulation
A special mode (OTG) allows to control the device using AOA
[keyboard](keyboard.md#aoa), [mouse](mouse.md#aoa) and
[gamepad](gamepad.md#aoa), without using _adb_ at all (so USB debugging is not
necessary). In this mode, video and audio are disabled, and `--keyboard=aoa` and
`--mouse=aoa` are implicitly set. However, gamepads are disabled by default, so
`--gamepad=aoa` (or `-G` in OTG mode) must be explicitly set.
[keyboard](keyboard.md#aoa) and [mouse](mouse.md#aoa), without using _adb_ at
all (so USB debugging is not necessary). In this mode, video and audio are
disabled, and `--keyboard=aoa and `--mouse=aoa` are implicitly set.
Therefore, it is possible to run _scrcpy_ with only physical keyboard, mouse and
gamepad simulation, as if the computer keyboard, mouse and gamepads were plugged
directly to the device via an OTG cable.
Therefore, it is possible to run _scrcpy_ with only physical keyboard and mouse
simulation, as if the computer keyboard and mouse were plugged directly to the
device via an OTG cable.
To enable OTG mode:
@@ -34,13 +32,6 @@ scrcpy --otg --keyboard=disabled
scrcpy --otg --mouse=disabled
```
and to enable gamepads:
```bash
scrcpy --otg --gamepad=aoa
scrcpy --otg -G # short version
```
It only works if the device is connected over USB.
## OTG issues on Windows
@@ -59,9 +50,9 @@ is enabled, then OTG mode is not necessary.
Instead, disable video and audio, and select UHID (or AOA):
```bash
scrcpy --no-video --no-audio --keyboard=uhid --mouse=uhid --gamepad=uhid
scrcpy --no-video --no-audio -KMG # short version
scrcpy --no-video --no-audio --keyboard=aoa --mouse=aoa --gamepad=aoa
scrcpy --no-video --no-audio --keyboard=uhid --mouse=uhid
scrcpy --no-video --no-audio -KM # short version
scrcpy --no-video --no-audio --keyboard=aoa --mouse=aoa
```
One benefit of UHID is that it also works wirelessly.

View File

@@ -4,14 +4,14 @@
Download the [latest release]:
- [`scrcpy-win64-v2.7.zip`][direct-win64] (64-bit)
<sub>SHA-256: `5910bc18d5a16f42d84185ddc7e16a4cee6a6f5f33451559c1a1d6d0099bd5f5`</sub>
- [`scrcpy-win32-v2.7.zip`][direct-win32] (32-bit)
<sub>SHA-256: `ef4daf89d500f33d78b830625536ecb18481429dd94433e7634c824292059d06`</sub>
- [`scrcpy-win64-v2.6.1.zip`][direct-win64] (64-bit)
<sub>SHA-256: `041fc3abf8578ddcead5a8c4a8be8960b7c4d45b21d3370ee2683605e86a728c`</sub>
- [`scrcpy-win32-v2.6.1.zip`][direct-win32] (32-bit)
<sub>SHA-256: `17a5d4d17230b4c90fad45af6395efda9aea287a03c04e6b4ecc9ceb8134ea04`</sub>
[latest release]: https://github.com/Genymobile/scrcpy/releases/latest
[direct-win64]: https://github.com/Genymobile/scrcpy/releases/download/v2.7/scrcpy-win64-v2.7.zip
[direct-win32]: https://github.com/Genymobile/scrcpy/releases/download/v2.7/scrcpy-win32-v2.7.zip
[direct-win64]: https://github.com/Genymobile/scrcpy/releases/download/v2.6.1/scrcpy-win64-v2.6.1.zip
[direct-win32]: https://github.com/Genymobile/scrcpy/releases/download/v2.6.1/scrcpy-win32-v2.6.1.zip
and extract it.

View File

@@ -2,8 +2,8 @@
set -e
BUILDDIR=build-auto
PREBUILT_SERVER_URL=https://github.com/Genymobile/scrcpy/releases/download/v2.7/scrcpy-server-v2.7
PREBUILT_SERVER_SHA256=a23c5659f36c260f105c022d27bcb3eafffa26070e7baa9eda66d01377a1adba
PREBUILT_SERVER_URL=https://github.com/Genymobile/scrcpy/releases/download/v2.6.1/scrcpy-server-v2.6.1
PREBUILT_SERVER_SHA256=ca7ab50b2e25a0e5af7599c30383e365983fa5b808e65ce2e1c1bba5bfe8dc3b
echo "[scrcpy] Downloading prebuilt server..."
wget "$PREBUILT_SERVER_URL" -O scrcpy-server

View File

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

View File

@@ -7,8 +7,8 @@ android {
applicationId "com.genymobile.scrcpy"
minSdkVersion 21
targetSdkVersion 34
versionCode 20700
versionName "2.7"
versionCode 20601
versionName "2.6.1"
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
}
buildTypes {

View File

@@ -12,7 +12,7 @@
set -e
SCRCPY_DEBUG=false
SCRCPY_VERSION_NAME=2.7
SCRCPY_VERSION_NAME=2.6.1
PLATFORM=${ANDROID_PLATFORM:-34}
BUILD_TOOLS=${ANDROID_BUILD_TOOLS:-34.0.0}

View File

@@ -29,7 +29,7 @@ public class Options {
private boolean audioDup;
private int videoBitRate = 8000000;
private int audioBitRate = 128000;
private float maxFps;
private int maxFps;
private int lockVideoOrientation = -1;
private boolean tunnelForward;
private Rect crop;
@@ -113,7 +113,7 @@ public class Options {
return audioBitRate;
}
public float getMaxFps() {
public int getMaxFps() {
return maxFps;
}
@@ -321,7 +321,7 @@ public class Options {
options.audioBitRate = Integer.parseInt(value);
break;
case "max_fps":
options.maxFps = parseFloat("max_fps", value);
options.maxFps = Integer.parseInt(value);
break;
case "lock_video_orientation":
options.lockVideoOrientation = Integer.parseInt(value);
@@ -456,14 +456,8 @@ public class Options {
}
int width = Integer.parseInt(tokens[0]);
int height = Integer.parseInt(tokens[1]);
if (width <= 0 || height <= 0) {
throw new IllegalArgumentException("Invalid crop size: " + width + "x" + height);
}
int x = Integer.parseInt(tokens[2]);
int y = Integer.parseInt(tokens[3]);
if (x < 0 || y < 0) {
throw new IllegalArgumentException("Invalid crop offset: " + x + ":" + y);
}
return new Rect(x, y, x + width, y + height);
}
@@ -493,12 +487,4 @@ public class Options {
float floatAr = Float.parseFloat(tokens[0]);
return CameraAspectRatio.fromFloat(floatAr);
}
private static float parseFloat(String key, String value) {
try {
return Float.parseFloat(value);
} catch (NumberFormatException e) {
throw new IllegalArgumentException("Invalid float value for " + key + ": \"" + value + "\"");
}
}
}

View File

@@ -56,11 +56,8 @@ public class AudioDirectCapture implements AudioCapture {
builder.setAudioSource(audioSource);
builder.setAudioFormat(AudioConfig.createAudioFormat());
int minBufferSize = AudioRecord.getMinBufferSize(SAMPLE_RATE, CHANNEL_CONFIG, ENCODING);
if (minBufferSize > 0) {
// This buffer size does not impact latency
builder.setBufferSizeInBytes(8 * minBufferSize);
}
// This buffer size does not impact latency
builder.setBufferSizeInBytes(8 * minBufferSize);
return builder.build();
}

View File

@@ -1,14 +1,14 @@
package com.genymobile.scrcpy.audio;
import com.genymobile.scrcpy.AsyncProcessor;
import com.genymobile.scrcpy.device.ConfigurationException;
import com.genymobile.scrcpy.device.Streamer;
import com.genymobile.scrcpy.util.Codec;
import com.genymobile.scrcpy.util.CodecOption;
import com.genymobile.scrcpy.util.CodecUtils;
import com.genymobile.scrcpy.device.ConfigurationException;
import com.genymobile.scrcpy.util.IO;
import com.genymobile.scrcpy.util.Ln;
import com.genymobile.scrcpy.util.LogUtils;
import com.genymobile.scrcpy.device.Streamer;
import android.annotation.TargetApi;
import android.media.MediaCodec;
@@ -287,13 +287,7 @@ public final class AudioEncoder implements AsyncProcessor {
if (encoderName != null) {
Ln.d("Creating audio encoder by name: '" + encoderName + "'");
try {
MediaCodec mediaCodec = MediaCodec.createByCodecName(encoderName);
String mimeType = Codec.getMimeType(mediaCodec);
if (!codec.getMimeType().equals(mimeType)) {
Ln.e("Audio encoder type for \"" + encoderName + "\" (" + mimeType + ") does not match codec type (" + codec.getMimeType() + ")");
throw new ConfigurationException("Incorrect encoder type: " + encoderName);
}
return mediaCodec;
return MediaCodec.createByCodecName(encoderName);
} catch (IllegalArgumentException e) {
Ln.e("Audio encoder '" + encoderName + "' for " + codec.getName() + " not found\n" + LogUtils.buildAudioEncoderListMessage());
throw new ConfigurationException("Unknown encoder: " + encoderName);

View File

@@ -82,7 +82,8 @@ public class ControlMessageReader {
}
private String parseString() throws IOException {
return parseString(4);
byte[] data = parseByteArray(4);
return new String(data, StandardCharsets.UTF_8);
}
private byte[] parseByteArray(int sizeBytes) throws IOException {

View File

@@ -173,12 +173,17 @@ public final class UhidManager {
ByteBuffer buf = ByteBuffer.allocate(280 + reportDesc.length).order(ByteOrder.nativeOrder());
buf.putInt(UHID_CREATE2);
String actualName = name.isEmpty() ? "scrcpy" : "scrcpy: " + name;
byte[] utf8Name = actualName.getBytes(StandardCharsets.UTF_8);
int len = StringUtils.getUtf8TruncationIndex(utf8Name, 127);
assert len <= 127;
final String prefix = "scrcpy: ";
if (name.isEmpty()) {
name = "(no name)";
}
byte[] utf8Name = name.getBytes(StandardCharsets.UTF_8);
int len = StringUtils.getUtf8TruncationIndex(utf8Name, 127 - prefix.length());
int nameLen = prefix.length() + len;
assert nameLen <= 127;
buf.put(prefix.getBytes(StandardCharsets.US_ASCII));
buf.put(utf8Name, 0, len);
buf.put(empty, 0, 256 - len);
buf.put(empty, 0, 256 - nameLen);
buf.putShort((short) reportDesc.length);
buf.putShort(BUS_VIRTUAL);

View File

@@ -1,7 +1,5 @@
package com.genymobile.scrcpy.util;
import android.media.MediaCodec;
public interface Codec {
enum Type {
@@ -16,9 +14,4 @@ public interface Codec {
String getName();
String getMimeType();
static String getMimeType(MediaCodec codec) {
String[] types = codec.getCodecInfo().getSupportedTypes();
return types.length > 0 ? types[0] : null;
}
}

View File

@@ -1,15 +1,15 @@
package com.genymobile.scrcpy.video;
import com.genymobile.scrcpy.AsyncProcessor;
import com.genymobile.scrcpy.device.ConfigurationException;
import com.genymobile.scrcpy.device.Size;
import com.genymobile.scrcpy.device.Streamer;
import com.genymobile.scrcpy.util.Codec;
import com.genymobile.scrcpy.util.CodecOption;
import com.genymobile.scrcpy.util.CodecUtils;
import com.genymobile.scrcpy.device.ConfigurationException;
import com.genymobile.scrcpy.util.IO;
import com.genymobile.scrcpy.util.Ln;
import com.genymobile.scrcpy.util.LogUtils;
import com.genymobile.scrcpy.device.Size;
import com.genymobile.scrcpy.device.Streamer;
import android.media.MediaCodec;
import android.media.MediaCodecInfo;
@@ -39,7 +39,7 @@ public class SurfaceEncoder implements AsyncProcessor {
private final String encoderName;
private final List<CodecOption> codecOptions;
private final int videoBitRate;
private final float maxFps;
private final int maxFps;
private final boolean downsizeOnError;
private boolean firstFrameSent;
@@ -48,8 +48,8 @@ public class SurfaceEncoder implements AsyncProcessor {
private Thread thread;
private final AtomicBoolean stopped = new AtomicBoolean();
public SurfaceEncoder(SurfaceCapture capture, Streamer streamer, int videoBitRate, float maxFps, List<CodecOption> codecOptions,
String encoderName, boolean downsizeOnError) {
public SurfaceEncoder(SurfaceCapture capture, Streamer streamer, int videoBitRate, int maxFps, List<CodecOption> codecOptions, String encoderName,
boolean downsizeOnError) {
this.capture = capture;
this.streamer = streamer;
this.videoBitRate = videoBitRate;
@@ -205,13 +205,7 @@ public class SurfaceEncoder implements AsyncProcessor {
if (encoderName != null) {
Ln.d("Creating encoder by name: '" + encoderName + "'");
try {
MediaCodec mediaCodec = MediaCodec.createByCodecName(encoderName);
String mimeType = Codec.getMimeType(mediaCodec);
if (!codec.getMimeType().equals(mimeType)) {
Ln.e("Video encoder type for \"" + encoderName + "\" (" + mimeType + ") does not match codec type (" + codec.getMimeType() + ")");
throw new ConfigurationException("Incorrect encoder type: " + encoderName);
}
return mediaCodec;
return MediaCodec.createByCodecName(encoderName);
} catch (IllegalArgumentException e) {
Ln.e("Video encoder '" + encoderName + "' for " + codec.getName() + " not found\n" + LogUtils.buildVideoEncoderListMessage());
throw new ConfigurationException("Unknown encoder: " + encoderName);
@@ -231,7 +225,7 @@ public class SurfaceEncoder implements AsyncProcessor {
}
}
private static MediaFormat createFormat(String videoMimeType, int bitRate, float maxFps, List<CodecOption> codecOptions) {
private static MediaFormat createFormat(String videoMimeType, int bitRate, int maxFps, List<CodecOption> codecOptions) {
MediaFormat format = new MediaFormat();
format.setString(MediaFormat.KEY_MIME, videoMimeType);
format.setInteger(MediaFormat.KEY_BIT_RATE, bitRate);