Compare commits

...

16 Commits

Author SHA1 Message Date
0e2d084751 Update links to 3.1 2024-12-09 22:42:34 +01:00
754f4fc6fe Bump version to 3.1 2024-12-09 22:29:51 +01:00
aca6d30af5 Include dav1d in releases
Scrcpy supports AV1, but no decoder was provided in binary releases.

Include dav1d:
 - <https://www.videolan.org/projects/dav1d.html>
 - <https://code.videolan.org/videolan/dav1d>

Fixes #4744 <https://github.com/Genymobile/scrcpy/issues/4744>
PR #5644 <https://github.com/Genymobile/scrcpy/pull/5644>
2024-12-09 19:07:18 +01:00
f2018e026c Remove broken macOS flags
Due to a typo (a space was missing before the second '-L'), the
resulting LDFLAGS value was broken:

    "-L/opt/homebrew/opt/zlib/lib-L/opt/homebrew/opt/libiconv/lib"

This proves that the flag was useless. Remove it.

Refs #5517 comment <https://github.com/Genymobile/scrcpy/pull/5517#issuecomment-2495522201>
PR #5644 <https://github.com/Genymobile/scrcpy/pull/5644>
2024-12-09 19:07:03 +01:00
a507b4f559 Fix DisplayControl classpath
Use the full system server classpath to load DisplayControl, so that
turning the screen off on Android 14+ does not crash on certain devices.

Refs #4544 comment <https://github.com/Genymobile/scrcpy/issues/4544#issuecomment-2526999714>
Fixes #4544 <https://github.com/Genymobile/scrcpy/issues/4544>
Fixes #5274 <https://github.com/Genymobile/scrcpy/issues/5274>

Signed-off-by: Romain Vimont <rom@rom1v.com>
2024-12-09 10:29:20 +01:00
a9aadc95df Start cleanup process with setsid()
Reimplement c59a3c3169 using Os.setsid().

Refs #5613 comment <https://github.com/Genymobile/scrcpy/pull/5613#issuecomment-2527045669>

Suggested-by: Simon Chan <1330321+yume-chan@users.noreply.github.com>
2024-12-09 09:33:08 +01:00
28b5bfb90e Revert "Start cleanup process with setsid or nohup"
This reverts commit c59a3c3169.

The next commit will use Os.setsid() instead.
2024-12-09 09:29:29 +01:00
65256d7cc7 Upgrade SDL (2.30.10) 2024-12-08 18:17:07 +01:00
328bb74f80 Log gamepad added/removed
Add a log when a gamepad is added or removed.

PR #5623 <https://github.com/Genymobile/scrcpy/pull/5623>
2024-12-08 18:16:57 +01:00
7418fd0662 Use Xbox 360 gamepad name
Some games do not work without a known gamepad name.

Fixes #5362 <https://github.com/Genymobile/scrcpy/issues/5362>
Refs #5623 comment <https://github.com/Genymobile/scrcpy/pull/5623#issuecomment-2525685323>
PR #5623 <https://github.com/Genymobile/scrcpy/pull/5623>

Signed-off-by: Romain Vimont <rom@rom1v.com>
2024-12-08 17:01:03 +01:00
0a09518a49 Use Xbox 360 gamepad USB ids
Use the vendorId and productId of an Xbox 360 controller for better
support (the HID gamepad protocol used in scrcpy is similar to that of
the Xbox 360 controller).

Fixes #5362 <https://github.com/Genymobile/scrcpy/issues/5362>
PR #5623 <https://github.com/Genymobile/scrcpy/pull/5623>
2024-12-08 11:00:18 +01:00
27a5934a1d Define UHID vendorId and productId from the client
Let the client choose the USB ids, that it transmits in UHID_CREATE
requests.

PR #5623 <https://github.com/Genymobile/scrcpy/pull/5623>
2024-12-08 11:00:18 +01:00
86a68fac6c Fix gamepad axis initial values
By default, initialize axis to 0, which is represented by 0x8000 as a
16-bit unsigned value.

PR #5623 <https://github.com/Genymobile/scrcpy/pull/5623>
2024-12-08 11:00:18 +01:00
1786f28e6f Fix gamepad HID descriptor
Use Z and Rz for L2/R2, which are more widely supported than
Brake/Accelerator.

The right stick must then be bound to Rx and Ry.

Fixes #5362 <https://github.com/Genymobile/scrcpy/issues/5362>
PR #5623 <https://github.com/Genymobile/scrcpy/pull/5623>
2024-12-08 11:00:18 +01:00
9cf4d52721 Fix HID gamepad comments
PR #5623 <https://github.com/Genymobile/scrcpy/pull/5623>
2024-12-08 11:00:18 +01:00
4bd1c5981d Split gamepad device added/removed events
Use two separate callbacks for gamepad device added and gamepad device
removed.

It looks cleaner.

PR #5623 <https://github.com/Genymobile/scrcpy/pull/5623>
2024-12-08 11:00:18 +01:00
38 changed files with 292 additions and 179 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 (v3.0.2)
# scrcpy (v3.1)
<img src="app/data/icon.svg" width="128" height="128" alt="scrcpy" align="right" />

68
app/deps/dav1d.sh Executable file
View File

@ -0,0 +1,68 @@
#!/usr/bin/env bash
set -ex
DEPS_DIR=$(dirname ${BASH_SOURCE[0]})
cd "$DEPS_DIR"
. common
process_args "$@"
VERSION=1.5.0
FILENAME=dav1d-$VERSION.tar.gz
PROJECT_DIR=dav1d-$VERSION
SHA256SUM=78b15d9954b513ea92d27f39362535ded2243e1b0924fde39f37a31ebed5f76b
cd "$SOURCES_DIR"
if [[ -d "$PROJECT_DIR" ]]
then
echo "$PWD/$PROJECT_DIR" found
else
get_file "https://code.videolan.org/videolan/dav1d/-/archive/$VERSION/$FILENAME" "$FILENAME" "$SHA256SUM"
tar xf "$FILENAME" # First level directory is "$PROJECT_DIR"
fi
mkdir -p "$BUILD_DIR/$PROJECT_DIR"
cd "$BUILD_DIR/$PROJECT_DIR"
if [[ -d "$DIRNAME" ]]
then
echo "'$PWD/$DIRNAME' already exists, not reconfigured"
cd "$DIRNAME"
else
mkdir "$DIRNAME"
cd "$DIRNAME"
conf=(
--prefix="$INSTALL_DIR/$DIRNAME"
--libdir=lib
-Denable_tests=false
-Denable_tools=false
# Always build dav1d statically
--default-library=static
)
if [[ "$BUILD_TYPE" == cross ]]
then
case "$HOST" in
win32)
conf+=(
--cross-file="$SOURCES_DIR/$PROJECT_DIR/package/crossfiles/i686-w64-mingw32.meson"
)
;;
win64)
conf+=(
--cross-file="$SOURCES_DIR/$PROJECT_DIR/package/crossfiles/x86_64-w64-mingw32.meson"
)
;;
*)
echo "Unsupported host: $HOST" >&2
exit 1
esac
fi
meson setup . "$SOURCES_DIR/$PROJECT_DIR" "${conf[@]}"
fi
ninja
ninja install

View File

@ -40,16 +40,14 @@ else
export LDFLAGS='-static-libgcc -static'
elif [[ "$HOST" == "macos" ]]
then
export LDFLAGS="$LDFLAGS -L/opt/homebrew/opt/zlib/lib"
export CPPFLAGS="$CPPFLAGS -I/opt/homebrew/opt/zlib/include"
export LDFLAGS="$LDFLAGS-L/opt/homebrew/opt/libiconv/lib"
export CPPFLAGS="$CPPFLAGS -I/opt/homebrew/opt/libiconv/include"
export PKG_CONFIG_PATH="/opt/homebrew/opt/zlib/lib/pkgconfig"
fi
export PKG_CONFIG_PATH="$INSTALL_DIR/$DIRNAME/lib/pkgconfig:$PKG_CONFIG_PATH"
conf=(
--prefix="$INSTALL_DIR/$DIRNAME"
--pkg-config-flags="--static"
--extra-cflags="-O2 -fPIC"
--disable-programs
--disable-doc
@ -62,9 +60,11 @@ else
--disable-vaapi
--disable-vdpau
--enable-swresample
--enable-libdav1d
--enable-decoder=h264
--enable-decoder=hevc
--enable-decoder=av1
--enable-decoder=libdav1d
--enable-decoder=pcm_s16le
--enable-decoder=opus
--enable-decoder=aac

View File

@ -5,10 +5,10 @@ cd "$DEPS_DIR"
. common
process_args "$@"
VERSION=2.30.9
VERSION=2.30.10
FILENAME=SDL-$VERSION.tar.gz
PROJECT_DIR=SDL-release-$VERSION
SHA256SUM=682a055004081e37d81a7d4ce546c3ee3ef2e0e6a675ed2651e430ccd14eb407
SHA256SUM=35a8b9c4f3635d85762b904ac60ca4e0806bff89faeb269caafbe80860d67168
cd "$SOURCES_DIR"

View File

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

View File

@ -152,8 +152,10 @@ sc_control_msg_serialize(const struct sc_control_msg *msg, uint8_t *buf) {
return 2;
case SC_CONTROL_MSG_TYPE_UHID_CREATE:
sc_write16be(&buf[1], msg->uhid_create.id);
sc_write16be(&buf[3], msg->uhid_create.vendor_id);
sc_write16be(&buf[5], msg->uhid_create.product_id);
size_t index = 3;
size_t index = 7;
index += write_string_tiny(&buf[index], msg->uhid_create.name, 127);
sc_write16be(&buf[index], msg->uhid_create.report_desc_size);
@ -278,9 +280,13 @@ sc_control_msg_log(const struct sc_control_msg *msg) {
// Quote only if name is not null
const char *name = msg->uhid_create.name;
const char *quote = name ? "\"" : "";
LOG_CMSG("UHID create [%" PRIu16 "] name=%s%s%s "
"report_desc_size=%" PRIu16, msg->uhid_create.id,
quote, name, quote, msg->uhid_create.report_desc_size);
LOG_CMSG("UHID create [%" PRIu16 "] %04" PRIx16 ":%04" PRIx16
" name=%s%s%s report_desc_size=%" PRIu16,
msg->uhid_create.id,
msg->uhid_create.vendor_id,
msg->uhid_create.product_id,
quote, name, quote,
msg->uhid_create.report_desc_size);
break;
}
case SC_CONTROL_MSG_TYPE_UHID_INPUT: {

View File

@ -94,6 +94,8 @@ struct sc_control_msg {
} set_display_power;
struct {
uint16_t id;
uint16_t vendor_id;
uint16_t product_id;
const char *name; // pointer to static data
uint16_t report_desc_size;
const uint8_t *report_desc; // pointer to static data

View File

@ -15,7 +15,6 @@ struct sc_hid_input {
struct sc_hid_open {
uint16_t hid_id;
const char *name; // pointer to static memory
const uint8_t *report_desc; // pointer to static memory
size_t report_desc_size;
};

View File

@ -52,10 +52,10 @@ static const uint8_t SC_HID_GAMEPAD_REPORT_DESC[] = {
0x09, 0x30,
// Usage (Y) Left stick y
0x09, 0x31,
// Usage (Z) Right stick x
0x09, 0x32,
// Usage (Rz) Right stick y
0x09, 0x35,
// Usage (Rx) Right stick x
0x09, 0x33,
// Usage (Ry) Right stick y
0x09, 0x34,
// Logical Minimum (0)
0x15, 0x00,
// Logical Maximum (65535)
@ -65,15 +65,15 @@ static const uint8_t SC_HID_GAMEPAD_REPORT_DESC[] = {
0x75, 0x10,
// Report Count (4)
0x95, 0x04,
// Input (Data, Variable, Absolute): 4 bytes (X, Y, Z, Rz)
// Input (Data, Variable, Absolute): 4x2 bytes (X, Y, Z, Rz)
0x81, 0x02,
// Usage Page (Simulation Controls)
0x05, 0x02,
// Usage (Brake)
0x09, 0xC5,
// Usage (Accelerator)
0x09, 0xC4,
// Usage Page (Generic Desktop)
0x05, 0x01,
// Usage (Z)
0x09, 0x32,
// Usage (Rz)
0x09, 0x35,
// Logical Minimum (0)
0x15, 0x00,
// Logical Maximum (32767)
@ -82,7 +82,7 @@ static const uint8_t SC_HID_GAMEPAD_REPORT_DESC[] = {
0x75, 0x10,
// Report Count (2)
0x95, 0x02,
// Input (Data, Variable, Absolute): 2 bytes (L2, R2)
// Input (Data, Variable, Absolute): 2x2 bytes (L2, R2)
0x81, 0x02,
// Usage Page (Buttons)
@ -182,7 +182,7 @@ static const uint8_t SC_HID_GAMEPAD_REPORT_DESC[] = {
* `------------- SC_GAMEPAD_BUTTON_RIGHT_STICK
*
* +---------------+
* byte 14: |0 0 0 . . . . .| hat switch (dpad) position (0-8)
* byte 14: |0 0 0 0 . . . .| hat switch (dpad) position (0-8)
* +---------------+
* 9 possible positions and their values:
* 8 1 2
@ -191,16 +191,19 @@ static const uint8_t SC_HID_GAMEPAD_REPORT_DESC[] = {
* (8 is top-left, 1 is top, 2 is top-right, etc.)
*/
// [-32768 to 32767] -> [0 to 65535]
#define AXIS_RESCALE(V) (uint16_t) (((int32_t) V) + 0x8000)
static void
sc_hid_gamepad_slot_init(struct sc_hid_gamepad_slot *slot,
uint32_t gamepad_id) {
assert(gamepad_id != SC_GAMEPAD_ID_INVALID);
slot->gamepad_id = gamepad_id;
slot->buttons = 0;
slot->axis_left_x = 0;
slot->axis_left_y = 0;
slot->axis_right_x = 0;
slot->axis_right_y = 0;
slot->axis_left_x = AXIS_RESCALE(0);
slot->axis_left_y = AXIS_RESCALE(0);
slot->axis_right_x = AXIS_RESCALE(0);
slot->axis_right_y = AXIS_RESCALE(0);
slot->axis_left_trigger = 0;
slot->axis_right_trigger = 0;
}
@ -243,14 +246,8 @@ sc_hid_gamepad_generate_open(struct sc_hid_gamepad *hid,
sc_hid_gamepad_slot_init(&hid->slots[slot_idx], gamepad_id);
SDL_GameController* game_controller =
SDL_GameControllerFromInstanceID(gamepad_id);
assert(game_controller);
const char *name = SDL_GameControllerName(game_controller);
uint16_t hid_id = sc_hid_gamepad_slot_get_id(slot_idx);
hid_open->hid_id = hid_id;
hid_open->name = name;
hid_open->report_desc = SC_HID_GAMEPAD_REPORT_DESC;
hid_open->report_desc_size = sizeof(SC_HID_GAMEPAD_REPORT_DESC);
@ -423,8 +420,6 @@ sc_hid_gamepad_generate_input_from_axis(struct sc_hid_gamepad *hid,
struct sc_hid_gamepad_slot *slot = &hid->slots[slot_idx];
// [-32768 to 32767] -> [0 to 65535]
#define AXIS_RESCALE(V) (uint16_t) (((int32_t) V) + 0x8000)
switch (event->axis) {
case SC_GAMEPAD_AXIS_LEFTX:
slot->axis_left_x = AXIS_RESCALE(event->value);

View File

@ -335,7 +335,6 @@ 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->report_desc = SC_HID_KEYBOARD_REPORT_DESC;
hid_open->report_desc_size = sizeof(SC_HID_KEYBOARD_REPORT_DESC);
}

View File

@ -190,7 +190,6 @@ 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->report_desc = SC_HID_MOUSE_REPORT_DESC;
hid_open->report_desc_size = sizeof(SC_HID_MOUSE_REPORT_DESC);
}

View File

@ -412,18 +412,12 @@ struct sc_touch_event {
float pressure;
};
enum sc_gamepad_device_event_type {
SC_GAMEPAD_DEVICE_ADDED,
SC_GAMEPAD_DEVICE_REMOVED,
};
// As documented in <https://wiki.libsdl.org/SDL2/SDL_JoystickID>:
// The ID value starts at 0 and increments from there. The value -1 is an
// invalid ID.
#define SC_GAMEPAD_ID_INVALID UINT32_C(-1)
struct sc_gamepad_device_event {
enum sc_gamepad_device_event_type type;
uint32_t gamepad_id;
};
@ -503,16 +497,6 @@ sc_mouse_buttons_state_from_sdl(uint32_t buttons_state) {
return buttons_state;
}
static inline enum sc_gamepad_device_event_type
sc_gamepad_device_event_type_from_sdl_type(uint32_t type) {
assert(type == SDL_CONTROLLERDEVICEADDED
|| type == SDL_CONTROLLERDEVICEREMOVED);
if (type == SDL_CONTROLLERDEVICEADDED) {
return SC_GAMEPAD_DEVICE_ADDED;
}
return SC_GAMEPAD_DEVICE_REMOVED;
}
static inline enum sc_gamepad_axis
sc_gamepad_axis_from_sdl(uint8_t axis) {
if (axis <= SDL_CONTROLLER_AXIS_TRIGGERRIGHT) {

View File

@ -908,7 +908,6 @@ sc_input_manager_process_mouse_wheel(struct sc_input_manager *im,
static void
sc_input_manager_process_gamepad_device(struct sc_input_manager *im,
const SDL_ControllerDeviceEvent *event) {
SDL_JoystickID id;
if (event->type == SDL_CONTROLLERDEVICEADDED) {
SDL_GameController *gc = SDL_GameControllerOpen(event->which);
if (!gc) {
@ -923,9 +922,12 @@ sc_input_manager_process_gamepad_device(struct sc_input_manager *im,
return;
}
id = SDL_JoystickInstanceID(joystick);
struct sc_gamepad_device_event evt = {
.gamepad_id = SDL_JoystickInstanceID(joystick),
};
im->gp->ops->process_gamepad_added(im->gp, &evt);
} else if (event->type == SDL_CONTROLLERDEVICEREMOVED) {
id = event->which;
SDL_JoystickID id = event->which;
SDL_GameController *gc = SDL_GameControllerFromInstanceID(id);
if (gc) {
@ -933,16 +935,15 @@ sc_input_manager_process_gamepad_device(struct sc_input_manager *im,
} else {
LOGW("Unknown gamepad device removed");
}
struct sc_gamepad_device_event evt = {
.gamepad_id = id,
};
im->gp->ops->process_gamepad_removed(im->gp, &evt);
} else {
// Nothing to do
return;
}
struct sc_gamepad_device_event evt = {
.type = sc_gamepad_device_event_type_from_sdl_type(event->type),
.gamepad_id = id,
};
im->gp->ops->process_gamepad_device(im->gp, &evt);
}
static void

View File

@ -20,13 +20,22 @@ struct sc_gamepad_processor {
struct sc_gamepad_processor_ops {
/**
* Process a gamepad device added or removed
* Process a gamepad device added event
*
* This function is mandatory.
*/
void
(*process_gamepad_device)(struct sc_gamepad_processor *gp,
const struct sc_gamepad_device_event *event);
(*process_gamepad_added)(struct sc_gamepad_processor *gp,
const struct sc_gamepad_device_event *event);
/**
* Process a gamepad device removed event
*
* This function is mandatory.
*/
void
(*process_gamepad_removed)(struct sc_gamepad_processor *gp,
const struct sc_gamepad_device_event *event);
/**
* Process a gamepad axis event

View File

@ -7,6 +7,11 @@
/** Downcast gamepad processor to sc_gamepad_uhid */
#define DOWNCAST(GP) container_of(GP, struct sc_gamepad_uhid, gamepad_processor)
// Xbox 360
#define SC_GAMEPAD_UHID_VENDOR_ID UINT16_C(0x045e)
#define SC_GAMEPAD_UHID_PRODUCT_ID UINT16_C(0x028e)
#define SC_GAMEPAD_UHID_NAME "Microsoft X-Box 360 Pad"
static void
sc_gamepad_uhid_send_input(struct sc_gamepad_uhid *gamepad,
const struct sc_hid_input *hid_input,
@ -30,7 +35,9 @@ sc_gamepad_uhid_send_open(struct sc_gamepad_uhid *gamepad,
struct sc_control_msg msg;
msg.type = SC_CONTROL_MSG_TYPE_UHID_CREATE;
msg.uhid_create.id = hid_open->hid_id;
msg.uhid_create.name = hid_open->name;
msg.uhid_create.vendor_id = SC_GAMEPAD_UHID_VENDOR_ID;
msg.uhid_create.product_id = SC_GAMEPAD_UHID_PRODUCT_ID;
msg.uhid_create.name = SC_GAMEPAD_UHID_NAME;
msg.uhid_create.report_desc = hid_open->report_desc;
msg.uhid_create.report_desc_size = hid_open->report_desc_size;
@ -52,29 +59,39 @@ sc_gamepad_uhid_send_close(struct sc_gamepad_uhid *gamepad,
}
static void
sc_gamepad_processor_process_gamepad_device(struct sc_gamepad_processor *gp,
sc_gamepad_processor_process_gamepad_added(struct sc_gamepad_processor *gp,
const struct sc_gamepad_device_event *event) {
struct sc_gamepad_uhid *gamepad = DOWNCAST(gp);
if (event->type == SC_GAMEPAD_DEVICE_ADDED) {
struct sc_hid_open hid_open;
if (!sc_hid_gamepad_generate_open(&gamepad->hid, &hid_open,
event->gamepad_id)) {
return;
}
sc_gamepad_uhid_send_open(gamepad, &hid_open);
} else {
assert(event->type == SC_GAMEPAD_DEVICE_REMOVED);
struct sc_hid_close hid_close;
if (!sc_hid_gamepad_generate_close(&gamepad->hid, &hid_close,
event->gamepad_id)) {
return;
}
sc_gamepad_uhid_send_close(gamepad, &hid_close);
struct sc_hid_open hid_open;
if (!sc_hid_gamepad_generate_open(&gamepad->hid, &hid_open,
event->gamepad_id)) {
return;
}
SDL_GameController* game_controller =
SDL_GameControllerFromInstanceID(event->gamepad_id);
assert(game_controller);
const char *name = SDL_GameControllerName(game_controller);
LOGI("Gamepad added: [%" PRIu32 "] %s", event->gamepad_id, name);
sc_gamepad_uhid_send_open(gamepad, &hid_open);
}
static void
sc_gamepad_processor_process_gamepad_removed(struct sc_gamepad_processor *gp,
const struct sc_gamepad_device_event *event) {
struct sc_gamepad_uhid *gamepad = DOWNCAST(gp);
struct sc_hid_close hid_close;
if (!sc_hid_gamepad_generate_close(&gamepad->hid, &hid_close,
event->gamepad_id)) {
return;
}
LOGI("Gamepad removed: [%" PRIu32 "]", event->gamepad_id);
sc_gamepad_uhid_send_close(gamepad, &hid_close);
}
static void
@ -114,7 +131,8 @@ sc_gamepad_uhid_init(struct sc_gamepad_uhid *gamepad,
gamepad->controller = controller;
static const struct sc_gamepad_processor_ops ops = {
.process_gamepad_device = sc_gamepad_processor_process_gamepad_device,
.process_gamepad_added = sc_gamepad_processor_process_gamepad_added,
.process_gamepad_removed = sc_gamepad_processor_process_gamepad_removed,
.process_gamepad_axis = sc_gamepad_processor_process_gamepad_axis,
.process_gamepad_button = sc_gamepad_processor_process_gamepad_button,
};

View File

@ -141,7 +141,9 @@ sc_keyboard_uhid_init(struct sc_keyboard_uhid *kb,
struct sc_control_msg msg;
msg.type = SC_CONTROL_MSG_TYPE_UHID_CREATE;
msg.uhid_create.id = SC_HID_ID_KEYBOARD;
msg.uhid_create.name = hid_open.name;
msg.uhid_create.vendor_id = 0;
msg.uhid_create.product_id = 0;
msg.uhid_create.name = NULL;
msg.uhid_create.report_desc = hid_open.report_desc;
msg.uhid_create.report_desc_size = hid_open.report_desc_size;
if (!sc_controller_push_msg(controller, &msg)) {

View File

@ -81,7 +81,9 @@ sc_mouse_uhid_init(struct sc_mouse_uhid *mouse,
struct sc_control_msg msg;
msg.type = SC_CONTROL_MSG_TYPE_UHID_CREATE;
msg.uhid_create.id = SC_HID_ID_MOUSE;
msg.uhid_create.name = hid_open.name;
msg.uhid_create.vendor_id = 0;
msg.uhid_create.product_id = 0;
msg.uhid_create.name = NULL;
msg.uhid_create.report_desc = hid_open.report_desc;
msg.uhid_create.report_desc_size = hid_open.report_desc_size;
if (!sc_controller_push_msg(controller, &msg)) {

View File

@ -7,33 +7,35 @@
#define DOWNCAST(GP) container_of(GP, struct sc_gamepad_aoa, gamepad_processor)
static void
sc_gamepad_processor_process_gamepad_device(struct sc_gamepad_processor *gp,
sc_gamepad_processor_process_gamepad_added(struct sc_gamepad_processor *gp,
const struct sc_gamepad_device_event *event) {
struct sc_gamepad_aoa *gamepad = DOWNCAST(gp);
if (event->type == SC_GAMEPAD_DEVICE_ADDED) {
struct sc_hid_open hid_open;
if (!sc_hid_gamepad_generate_open(&gamepad->hid, &hid_open,
event->gamepad_id)) {
return;
}
struct sc_hid_open hid_open;
if (!sc_hid_gamepad_generate_open(&gamepad->hid, &hid_open,
event->gamepad_id)) {
return;
}
// exit_on_error: false (a gamepad open failure should not exit scrcpy)
if (!sc_aoa_push_open(gamepad->aoa, &hid_open, false)) {
LOGW("Could not push AOA HID open (gamepad)");
}
} else {
assert(event->type == SC_GAMEPAD_DEVICE_REMOVED);
// exit_on_error: false (a gamepad open failure should not exit scrcpy)
if (!sc_aoa_push_open(gamepad->aoa, &hid_open, false)) {
LOGW("Could not push AOA HID open (gamepad)");
}
}
struct sc_hid_close hid_close;
if (!sc_hid_gamepad_generate_close(&gamepad->hid, &hid_close,
event->gamepad_id)) {
return;
}
static void
sc_gamepad_processor_process_gamepad_removed(struct sc_gamepad_processor *gp,
const struct sc_gamepad_device_event *event) {
struct sc_gamepad_aoa *gamepad = DOWNCAST(gp);
if (!sc_aoa_push_close(gamepad->aoa, &hid_close)) {
LOGW("Could not push AOA HID close (gamepad)");
}
struct sc_hid_close hid_close;
if (!sc_hid_gamepad_generate_close(&gamepad->hid, &hid_close,
event->gamepad_id)) {
return;
}
if (!sc_aoa_push_close(gamepad->aoa, &hid_close)) {
LOGW("Could not push AOA HID close (gamepad)");
}
}
@ -76,7 +78,8 @@ sc_gamepad_aoa_init(struct sc_gamepad_aoa *gamepad, struct sc_aoa *aoa) {
sc_hid_gamepad_init(&gamepad->hid);
static const struct sc_gamepad_processor_ops ops = {
.process_gamepad_device = sc_gamepad_processor_process_gamepad_device,
.process_gamepad_added = sc_gamepad_processor_process_gamepad_added,
.process_gamepad_removed = sc_gamepad_processor_process_gamepad_removed,
.process_gamepad_axis = sc_gamepad_processor_process_gamepad_axis,
.process_gamepad_button = sc_gamepad_processor_process_gamepad_button,
};

View File

@ -175,7 +175,6 @@ sc_screen_otg_process_gamepad_device(struct sc_screen_otg *screen,
assert(screen->gamepad);
struct sc_gamepad_processor *gp = &screen->gamepad->gamepad_processor;
SDL_JoystickID id;
if (event->type == SDL_CONTROLLERDEVICEADDED) {
SDL_GameController *gc = SDL_GameControllerOpen(event->which);
if (!gc) {
@ -190,9 +189,12 @@ sc_screen_otg_process_gamepad_device(struct sc_screen_otg *screen,
return;
}
id = SDL_JoystickInstanceID(joystick);
struct sc_gamepad_device_event evt = {
.gamepad_id = SDL_JoystickInstanceID(joystick),
};
gp->ops->process_gamepad_added(gp, &evt);
} else if (event->type == SDL_CONTROLLERDEVICEREMOVED) {
id = event->which;
SDL_JoystickID id = event->which;
SDL_GameController *gc = SDL_GameControllerFromInstanceID(id);
if (gc) {
@ -200,16 +202,12 @@ sc_screen_otg_process_gamepad_device(struct sc_screen_otg *screen,
} else {
LOGW("Unknown gamepad device removed");
}
} else {
// Nothing to do
return;
}
struct sc_gamepad_device_event evt = {
.type = sc_gamepad_device_event_type_from_sdl_type(event->type),
.gamepad_id = id,
};
gp->ops->process_gamepad_device(gp, &evt);
struct sc_gamepad_device_event evt = {
.gamepad_id = id,
};
gp->ops->process_gamepad_removed(gp, &evt);
}
}
static void

View File

@ -329,6 +329,8 @@ static void test_serialize_uhid_create(void) {
.type = SC_CONTROL_MSG_TYPE_UHID_CREATE,
.uhid_create = {
.id = 42,
.vendor_id = 0x1234,
.product_id = 0x5678,
.name = "ABC",
.report_desc_size = sizeof(report_desc),
.report_desc = report_desc,
@ -337,11 +339,13 @@ static void test_serialize_uhid_create(void) {
uint8_t buf[SC_CONTROL_MSG_MAX_SIZE];
size_t size = sc_control_msg_serialize(&msg, buf);
assert(size == 20);
assert(size == 24);
const uint8_t expected[] = {
SC_CONTROL_MSG_TYPE_UHID_CREATE,
0, 42, // id
0x12, 0x34, // vendor id
0x56, 0x78, // product id
3, // name size
65, 66, 67, // "ABC"
0, 11, // report desc size

View File

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

View File

@ -6,11 +6,11 @@
Download a static build of the [latest release]:
- [`scrcpy-linux-x86_64-v3.0.2.tar.gz`][direct-linux-x86_64] (x86_64)
<sub>SHA-256: `20b69dcd379bb7d7208bf1e4858cf04162fc856697be0e6c03863d7b3c1e734a`</sub>
- [`scrcpy-linux-x86_64-v3.1.tar.gz`][direct-linux-x86_64] (x86_64)
<sub>SHA-256: `37dba54092ed9ec6b2f8f95432f61b8ea124aec9f1e9f2b3d22d4b10bb04c59a`</sub>
[latest release]: https://github.com/Genymobile/scrcpy/releases/latest
[direct-linux-x86_64]: https://github.com/Genymobile/scrcpy/releases/download/v3.0.2/scrcpy-linux-x86_64-v3.0.2.tar.gz
[direct-linux-x86_64]: https://github.com/Genymobile/scrcpy/releases/download/v3.1/scrcpy-linux-x86_64-v3.1.tar.gz
and extract it.

View File

@ -6,15 +6,15 @@
Download a static build of the [latest release]:
- [`scrcpy-macos-aarch64-v3.0.2.tar.gz`][direct-macos-aarch64] (aarch64)
<sub>SHA-256: `811ba2f4e856146bdd161e24c3490d78efbec2339ca783fac791d041c0aecfb6`</sub>
- [`scrcpy-macos-aarch64-v3.1.tar.gz`][direct-macos-aarch64] (aarch64)
<sub>SHA-256: `478618d940421e5f57942f5479d493ecbb38210682937a200f712aee5f235daf`</sub>
- [`scrcpy-macos-x86_64-v3.0.2.tar.gz`][direct-macos-x86_64] (x86_64)
<sub>SHA-256: `8effff54dca3a3e46eaaec242771a13a7f81af2e18670b3d0d8ed6b461bb4f79`</sub>
- [`scrcpy-macos-x86_64-v3.1.tar.gz`][direct-macos-x86_64] (x86_64)
<sub>SHA-256: `acde98e29c273710ffa469371dbca4a728a44c41c380381f8a54e5b5301b9e87`</sub>
[latest release]: https://github.com/Genymobile/scrcpy/releases/latest
[direct-macos-aarch64]: https://github.com/Genymobile/scrcpy/releases/download/v3.0.2/scrcpy-macos-aarch64-v3.0.2.tar.gz
[direct-macos-x86_64]: https://github.com/Genymobile/scrcpy/releases/download/v3.0.2/scrcpy-macos-x86_64-v3.0.2.tar.gz
[direct-macos-aarch64]: https://github.com/Genymobile/scrcpy/releases/download/v3.1/scrcpy-macos-aarch64-v3.1.tar.gz
[direct-macos-x86_64]: https://github.com/Genymobile/scrcpy/releases/download/v3.1/scrcpy-macos-x86_64-v3.1.tar.gz
and extract it.

View File

@ -6,14 +6,14 @@
Download the [latest release]:
- [`scrcpy-win64-v3.0.2.zip`][direct-win64] (64-bit)
<sub>SHA-256: `f0de59f5d46127c87cd822d39d6665e016b86db4cd048101b262f6adb6766832`</sub>
- [`scrcpy-win32-v3.0.2.zip`][direct-win32] (32-bit)
<sub>SHA-256: `8db8d4984d642012c55802de71f507f8ff9f68a8cfed456d7a1982d47e065f64`</sub>
- [`scrcpy-win64-v3.1.zip`][direct-win64] (64-bit)
<sub>SHA-256: `0c05ea395d95cfe36bee974eeb435a3db87ea5594ff738370d5dc3068a9538ca`</sub>
- [`scrcpy-win32-v3.1.zip`][direct-win32] (32-bit)
<sub>SHA-256: `2b4674ef76719680ac5a9b482d1943bdde3fa25821ad2e98f3c40c347d00d560`</sub>
[latest release]: https://github.com/Genymobile/scrcpy/releases/latest
[direct-win64]: https://github.com/Genymobile/scrcpy/releases/download/v3.0.2/scrcpy-win64-v3.0.2.zip
[direct-win32]: https://github.com/Genymobile/scrcpy/releases/download/v3.0.2/scrcpy-win32-v3.0.2.zip
[direct-win64]: https://github.com/Genymobile/scrcpy/releases/download/v3.1/scrcpy-win64-v3.1.zip
[direct-win32]: https://github.com/Genymobile/scrcpy/releases/download/v3.1/scrcpy-win32-v3.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/v3.0.2/scrcpy-server-v3.0.2
PREBUILT_SERVER_SHA256=e19fe024bfa3367809494407ad6ca809a6f6e77dac95e99f85ba75144e0ba35d
PREBUILT_SERVER_URL=https://github.com/Genymobile/scrcpy/releases/download/v3.1/scrcpy-server-v3.1
PREBUILT_SERVER_SHA256=958f0944a62f23b1f33a16e9eb14844c1a04b882ca175a738c16d23cb22b86c0
echo "[scrcpy] Downloading prebuilt server..."
wget "$PREBUILT_SERVER_URL" -O scrcpy-server

View File

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

View File

@ -15,6 +15,7 @@ LINUX_BUILD_DIR="$WORK_DIR/build-linux-$ARCH"
app/deps/adb_linux.sh
app/deps/sdl.sh linux native static
app/deps/dav1d.sh linux native static
app/deps/ffmpeg.sh linux native static
app/deps/libusb.sh linux native static

View File

@ -15,6 +15,7 @@ MACOS_BUILD_DIR="$WORK_DIR/build-macos-$ARCH"
app/deps/adb_macos.sh
app/deps/sdl.sh macos native static
app/deps/dav1d.sh macos native static
app/deps/ffmpeg.sh macos native static
app/deps/libusb.sh macos native static

View File

@ -22,6 +22,7 @@ WINXX_BUILD_DIR="$WORK_DIR/build-$WINXX"
app/deps/adb_windows.sh
app/deps/sdl.sh $WINXX cross shared
app/deps/dav1d.sh $WINXX cross shared
app/deps/ffmpeg.sh $WINXX cross shared
app/deps/libusb.sh $WINXX cross shared

View File

@ -7,8 +7,8 @@ android {
applicationId "com.genymobile.scrcpy"
minSdkVersion 21
targetSdkVersion 35
versionCode 30002
versionName "3.0.2"
versionCode 30100
versionName "3.1"
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
}
buildTypes {

View File

@ -12,7 +12,7 @@
set -e
SCRCPY_DEBUG=false
SCRCPY_VERSION_NAME=3.0.2
SCRCPY_VERSION_NAME=3.1
PLATFORM=${ANDROID_PLATFORM:-35}
BUILD_TOOLS=${ANDROID_BUILD_TOOLS:-35.0.0}

View File

@ -6,12 +6,12 @@ import com.genymobile.scrcpy.util.Settings;
import com.genymobile.scrcpy.util.SettingsException;
import android.os.BatteryManager;
import android.system.ErrnoException;
import android.system.Os;
import java.io.File;
import java.io.IOException;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.List;
/**
* Handle the cleanup of scrcpy, even if the main process is killed.
@ -109,22 +109,16 @@ public final class CleanUp {
private void run(int displayId, int restoreStayOn, boolean disableShowTouches, boolean powerOffScreen, int restoreScreenOffTimeout)
throws IOException {
List<String> cmd = new ArrayList<>();
if (new File("/system/bin/setsid").exists()) {
cmd.add("/system/bin/setsid");
} else if (new File("/system/bin/nohup").exists()) {
cmd.add("/system/bin/nohup");
}
cmd.add("app_process");
cmd.add("/");
cmd.add(CleanUp.class.getName());
cmd.add(String.valueOf(displayId));
cmd.add(String.valueOf(restoreStayOn));
cmd.add(String.valueOf(disableShowTouches));
cmd.add(String.valueOf(powerOffScreen));
cmd.add(String.valueOf(restoreScreenOffTimeout));
String[] cmd = {
"app_process",
"/",
CleanUp.class.getName(),
String.valueOf(displayId),
String.valueOf(restoreStayOn),
String.valueOf(disableShowTouches),
String.valueOf(powerOffScreen),
String.valueOf(restoreScreenOffTimeout),
};
ProcessBuilder builder = new ProcessBuilder(cmd);
builder.environment().put("CLASSPATH", Server.SERVER_PATH);
@ -171,6 +165,12 @@ public final class CleanUp {
}
public static void main(String... args) {
try {
// Start a new session to avoid being terminated along with the server process on some devices
Os.setsid();
} catch (ErrnoException e) {
Ln.e("setsid() failed", e);
}
unlinkSelf();
int displayId = Integer.parseInt(args[0]);

View File

@ -51,6 +51,8 @@ public final class ControlMessage {
private int id;
private byte[] data;
private boolean on;
private int vendorId;
private int productId;
private ControlMessage() {
}
@ -131,10 +133,12 @@ public final class ControlMessage {
return msg;
}
public static ControlMessage createUhidCreate(int id, String name, byte[] reportDesc) {
public static ControlMessage createUhidCreate(int id, int vendorId, int productId, String name, byte[] reportDesc) {
ControlMessage msg = new ControlMessage();
msg.type = TYPE_UHID_CREATE;
msg.id = id;
msg.vendorId = vendorId;
msg.productId = productId;
msg.text = name;
msg.data = reportDesc;
return msg;
@ -237,4 +241,12 @@ public final class ControlMessage {
public boolean getOn() {
return on;
}
public int getVendorId() {
return vendorId;
}
public int getProductId() {
return productId;
}
}

View File

@ -142,9 +142,11 @@ public class ControlMessageReader {
private ControlMessage parseUhidCreate() throws IOException {
int id = dis.readUnsignedShort();
int vendorId = dis.readUnsignedShort();
int productId = dis.readUnsignedShort();
String name = parseString(1);
byte[] data = parseByteArray(2);
return ControlMessage.createUhidCreate(id, name, data);
return ControlMessage.createUhidCreate(id, vendorId, productId, name, data);
}
private ControlMessage parseUhidInput() throws IOException {

View File

@ -290,7 +290,7 @@ public class Controller implements AsyncProcessor, VirtualDisplayListener {
Device.rotateDevice(getActionDisplayId());
break;
case ControlMessage.TYPE_UHID_CREATE:
getUhidManager().open(msg.getId(), msg.getText(), msg.getData());
getUhidManager().open(msg.getId(), msg.getVendorId(), msg.getProductId(), msg.getText(), msg.getData());
break;
case ControlMessage.TYPE_UHID_INPUT:
getUhidManager().writeInput(msg.getId(), msg.getData());

View File

@ -48,7 +48,7 @@ public final class UhidManager {
}
}
public void open(int id, String name, byte[] reportDesc) throws IOException {
public void open(int id, int vendorId, int productId, String name, byte[] reportDesc) throws IOException {
try {
FileDescriptor fd = Os.open("/dev/uhid", OsConstants.O_RDWR, 0);
try {
@ -58,7 +58,7 @@ public final class UhidManager {
close(old);
}
byte[] req = buildUhidCreate2Req(name, reportDesc);
byte[] req = buildUhidCreate2Req(vendorId, productId, name, reportDesc);
Os.write(fd, req, 0, req.length);
registerUhidListener(id, fd);
@ -148,7 +148,7 @@ public final class UhidManager {
}
}
private static byte[] buildUhidCreate2Req(String name, byte[] reportDesc) {
private static byte[] buildUhidCreate2Req(int vendorId, int productId, String name, byte[] reportDesc) {
/*
* struct uhid_event {
* uint32_t type;
@ -174,7 +174,7 @@ 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;
String actualName = name.isEmpty() ? "scrcpy" : name;
byte[] utf8Name = actualName.getBytes(StandardCharsets.UTF_8);
int len = StringUtils.getUtf8TruncationIndex(utf8Name, 127);
assert len <= 127;
@ -183,8 +183,8 @@ public final class UhidManager {
buf.putShort((short) reportDesc.length);
buf.putShort(BUS_VIRTUAL);
buf.putInt(0); // vendor id
buf.putInt(0); // product id
buf.putInt(vendorId);
buf.putInt(productId);
buf.putInt(0); // version
buf.putInt(0); // country;
buf.put(reportDesc);

View File

@ -6,6 +6,7 @@ import com.genymobile.scrcpy.util.Ln;
import android.annotation.SuppressLint;
import android.annotation.TargetApi;
import android.os.IBinder;
import android.system.Os;
import java.lang.reflect.Method;
@ -21,7 +22,9 @@ public final class DisplayControl {
Class<?> classLoaderFactoryClass = Class.forName("com.android.internal.os.ClassLoaderFactory");
Method createClassLoaderMethod = classLoaderFactoryClass.getDeclaredMethod("createClassLoader", String.class, String.class, String.class,
ClassLoader.class, int.class, boolean.class, String.class);
ClassLoader classLoader = (ClassLoader) createClassLoaderMethod.invoke(null, "/system/framework/services.jar", null, null,
String systemServerClasspath = Os.getenv("SYSTEMSERVERCLASSPATH");
ClassLoader classLoader = (ClassLoader) createClassLoaderMethod.invoke(null, systemServerClasspath, null, null,
ClassLoader.getSystemClassLoader(), 0, true, null);
displayControlClass = classLoader.loadClass("com.android.server.display.DisplayControl");

View File

@ -322,6 +322,8 @@ public class ControlMessageReaderTest {
DataOutputStream dos = new DataOutputStream(bos);
dos.writeByte(ControlMessage.TYPE_UHID_CREATE);
dos.writeShort(42); // id
dos.writeShort(0x1234); // vendorId
dos.writeShort(0x5678); // productId
dos.writeByte(3); // name size
dos.write("ABC".getBytes(StandardCharsets.US_ASCII));
byte[] data = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11};
@ -335,6 +337,8 @@ public class ControlMessageReaderTest {
ControlMessage event = reader.read();
Assert.assertEquals(ControlMessage.TYPE_UHID_CREATE, event.getType());
Assert.assertEquals(42, event.getId());
Assert.assertEquals(0x1234, event.getVendorId());
Assert.assertEquals(0x5678, event.getProductId());
Assert.assertEquals("ABC", event.getText());
Assert.assertArrayEquals(data, event.getData());