mirror of
https://github.com/Genymobile/scrcpy.git
synced 2026-01-03 05:44:26 +01:00
Add UHID keyboard support
Use the following command:
scrcpy --keyboard=uhid
Co-authored-by: Romain Vimont <rom@rom1v.com>
Signed-off-by: Romain Vimont <rom@rom1v.com>
This commit is contained in:
committed by
Romain Vimont
parent
a6978cb059
commit
2fbbb2ac65
@@ -116,7 +116,7 @@ _scrcpy() {
|
||||
return
|
||||
;;
|
||||
--keyboard)
|
||||
COMPREPLY=($(compgen -W 'disabled sdk aoa' -- "$cur"))
|
||||
COMPREPLY=($(compgen -W 'disabled sdk aoa uhid' -- "$cur"))
|
||||
return
|
||||
;;
|
||||
--mouse)
|
||||
|
||||
@@ -34,7 +34,7 @@ arguments=(
|
||||
'--force-adb-forward[Do not attempt to use \"adb reverse\" to connect to the device]'
|
||||
'--forward-all-clicks[Forward clicks to device]'
|
||||
{-h,--help}'[Print the help]'
|
||||
'--keyboard[Set the keyboard input mode]:mode:(disabled sdk aoa)'
|
||||
'--keyboard[Set the keyboard input mode]:mode:(disabled sdk aoa uhid)'
|
||||
'--kill-adb-on-close[Kill adb when scrcpy terminates]'
|
||||
'--legacy-paste[Inject computer clipboard text as a sequence of key events on Ctrl+v]'
|
||||
'--list-camera-sizes[List the valid camera capture sizes]'
|
||||
|
||||
@@ -35,6 +35,7 @@ src = [
|
||||
'src/hid/hid_mouse.c',
|
||||
'src/trait/frame_source.c',
|
||||
'src/trait/packet_source.c',
|
||||
'src/uhid/keyboard_uhid.c',
|
||||
'src/util/acksync.c',
|
||||
'src/util/audiobuf.c',
|
||||
'src/util/average.c',
|
||||
|
||||
@@ -179,9 +179,10 @@ Possible values are "disabled", "sdk" and "aoa":
|
||||
|
||||
- "disabled" does not send keyboard inputs to the device.
|
||||
- "sdk" uses the Android system API to deliver keyboard events to applications.
|
||||
- "aoa" simulates a physical keyboard using the AOAv2 protocol. It may only work over USB.
|
||||
- "aoa" simulates a physical HID keyboard using the AOAv2 protocol. It may only work over USB.
|
||||
- "uhid" simulates a physical HID keyboard using the Linux HID kernel module on the device.
|
||||
|
||||
For "aoa", the keyboard layout must be configured (once and for all) on the device, via Settings -> System -> Languages and input -> Physical keyboard. This settings page can be started directly:
|
||||
For "aoa" and "uhid", the keyboard layout must be configured (once and for all) on the device, via Settings -> System -> Languages and input -> Physical keyboard. This settings page can be started directly:
|
||||
|
||||
adb shell am start -a android.settings.HARD_KEYBOARD_SETTINGS
|
||||
|
||||
|
||||
@@ -365,18 +365,21 @@ static const struct sc_option options[] = {
|
||||
.longopt = "keyboard",
|
||||
.argdesc = "mode",
|
||||
.text = "Select how to send keyboard inputs to the device.\n"
|
||||
"Possible values are \"disabled\", \"sdk\" and \"aoa\".\n"
|
||||
"Possible values are \"disabled\", \"sdk\", \"aoa\" and "
|
||||
"\"uhid\".\n"
|
||||
"\"disabled\" does not send keyboard inputs to the device.\n"
|
||||
"\"sdk\" uses the Android system API to deliver keyboard\n"
|
||||
"events to applications.\n"
|
||||
"\"aoa\" simulates a physical keyboard using the AOAv2\n"
|
||||
"\"aoa\" simulates a physical HID keyboard using the AOAv2\n"
|
||||
"protocol. It may only work over USB.\n"
|
||||
"For \"aoa\", the keyboard layout must be configured (once and "
|
||||
"for all) on the device, via Settings -> System -> Languages "
|
||||
"and input -> Physical keyboard. This settings page can be "
|
||||
"started directly: `adb shell am start -a "
|
||||
"\"uhid\" simulates a physical HID keyboard using the Linux "
|
||||
"UHID kernel module on the device."
|
||||
"For \"aoa\" and \"uhid\", the keyboard layout must be "
|
||||
"configured (once and for all) on the device, via Settings -> "
|
||||
"System -> Languages and input -> Physical keyboard. This "
|
||||
"settings page can be started directly: `adb shell am start -a "
|
||||
"android.settings.HARD_KEYBOARD_SETTINGS`.\n"
|
||||
"This option is only available when the HID keyboard is "
|
||||
"This option is only available when a HID keyboard is enabled "
|
||||
"enabled (or a physical keyboard is connected).\n"
|
||||
"Also see --mouse.",
|
||||
},
|
||||
@@ -1949,7 +1952,13 @@ parse_keyboard(const char *optarg, enum sc_keyboard_input_mode *mode) {
|
||||
#endif
|
||||
}
|
||||
|
||||
LOGE("Unsupported keyboard: %s (expected disabled, sdk or aoa)", optarg);
|
||||
if (!strcmp(optarg, "uhid")) {
|
||||
*mode = SC_KEYBOARD_INPUT_MODE_UHID;
|
||||
return true;
|
||||
}
|
||||
|
||||
LOGE("Unsupported keyboard: %s (expected disabled, sdk, aoa or uhid)",
|
||||
optarg);
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
@@ -146,6 +146,17 @@ sc_control_msg_serialize(const struct sc_control_msg *msg, uint8_t *buf) {
|
||||
case SC_CONTROL_MSG_TYPE_SET_SCREEN_POWER_MODE:
|
||||
buf[1] = msg->set_screen_power_mode.mode;
|
||||
return 2;
|
||||
case SC_CONTROL_MSG_TYPE_UHID_CREATE:
|
||||
sc_write16be(&buf[1], msg->uhid_create.id);
|
||||
sc_write16be(&buf[3], msg->uhid_create.report_desc_size);
|
||||
memcpy(&buf[5], msg->uhid_create.report_desc,
|
||||
msg->uhid_create.report_desc_size);
|
||||
return 5 + msg->uhid_create.report_desc_size;
|
||||
case SC_CONTROL_MSG_TYPE_UHID_INPUT:
|
||||
sc_write16be(&buf[1], msg->uhid_input.id);
|
||||
sc_write16be(&buf[3], msg->uhid_input.size);
|
||||
memcpy(&buf[5], msg->uhid_input.data, msg->uhid_input.size);
|
||||
return 5 + msg->uhid_input.size;
|
||||
case SC_CONTROL_MSG_TYPE_EXPAND_NOTIFICATION_PANEL:
|
||||
case SC_CONTROL_MSG_TYPE_EXPAND_SETTINGS_PANEL:
|
||||
case SC_CONTROL_MSG_TYPE_COLLAPSE_PANELS:
|
||||
@@ -242,6 +253,23 @@ sc_control_msg_log(const struct sc_control_msg *msg) {
|
||||
case SC_CONTROL_MSG_TYPE_ROTATE_DEVICE:
|
||||
LOG_CMSG("rotate device");
|
||||
break;
|
||||
case SC_CONTROL_MSG_TYPE_UHID_CREATE:
|
||||
LOG_CMSG("UHID create [%" PRIu16 "] report_desc_size=%" PRIu16,
|
||||
msg->uhid_create.id, msg->uhid_create.report_desc_size);
|
||||
break;
|
||||
case SC_CONTROL_MSG_TYPE_UHID_INPUT: {
|
||||
char *hex = sc_str_to_hex_string(msg->uhid_input.data,
|
||||
msg->uhid_input.size);
|
||||
if (hex) {
|
||||
LOG_CMSG("UHID input [%" PRIu16 "] %s",
|
||||
msg->uhid_input.id, hex);
|
||||
free(hex);
|
||||
} else {
|
||||
LOG_CMSG("UHID input [%" PRIu16 "] size=%" PRIu16,
|
||||
msg->uhid_input.id, msg->uhid_input.size);
|
||||
}
|
||||
break;
|
||||
}
|
||||
default:
|
||||
LOG_CMSG("unknown type: %u", (unsigned) msg->type);
|
||||
break;
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
#include "android/input.h"
|
||||
#include "android/keycodes.h"
|
||||
#include "coords.h"
|
||||
#include "hid/hid_event.h"
|
||||
|
||||
#define SC_CONTROL_MSG_MAX_SIZE (1 << 18) // 256k
|
||||
|
||||
@@ -37,6 +38,8 @@ enum sc_control_msg_type {
|
||||
SC_CONTROL_MSG_TYPE_SET_CLIPBOARD,
|
||||
SC_CONTROL_MSG_TYPE_SET_SCREEN_POWER_MODE,
|
||||
SC_CONTROL_MSG_TYPE_ROTATE_DEVICE,
|
||||
SC_CONTROL_MSG_TYPE_UHID_CREATE,
|
||||
SC_CONTROL_MSG_TYPE_UHID_INPUT,
|
||||
};
|
||||
|
||||
enum sc_screen_power_mode {
|
||||
@@ -92,6 +95,16 @@ struct sc_control_msg {
|
||||
struct {
|
||||
enum sc_screen_power_mode mode;
|
||||
} set_screen_power_mode;
|
||||
struct {
|
||||
uint16_t id;
|
||||
uint16_t report_desc_size;
|
||||
const uint8_t *report_desc; // pointer to static data
|
||||
} uhid_create;
|
||||
struct {
|
||||
uint16_t id;
|
||||
uint16_t size;
|
||||
uint8_t data[SC_HID_MAX_SIZE];
|
||||
} uhid_input;
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
@@ -144,6 +144,7 @@ enum sc_keyboard_input_mode {
|
||||
SC_KEYBOARD_INPUT_MODE_DISABLED,
|
||||
SC_KEYBOARD_INPUT_MODE_SDK,
|
||||
SC_KEYBOARD_INPUT_MODE_AOA,
|
||||
SC_KEYBOARD_INPUT_MODE_UHID,
|
||||
};
|
||||
|
||||
enum sc_mouse_input_mode {
|
||||
|
||||
@@ -25,6 +25,7 @@
|
||||
#include "recorder.h"
|
||||
#include "screen.h"
|
||||
#include "server.h"
|
||||
#include "uhid/keyboard_uhid.h"
|
||||
#ifdef HAVE_USB
|
||||
# include "usb/aoa_hid.h"
|
||||
# include "usb/keyboard_aoa.h"
|
||||
@@ -67,6 +68,7 @@ struct scrcpy {
|
||||
#ifdef HAVE_USB
|
||||
struct sc_keyboard_aoa keyboard_aoa;
|
||||
#endif
|
||||
struct sc_keyboard_uhid keyboard_uhid;
|
||||
};
|
||||
union {
|
||||
struct sc_mouse_sdk mouse_sdk;
|
||||
@@ -656,15 +658,22 @@ aoa_hid_end:
|
||||
assert(options->mouse_input_mode != SC_MOUSE_INPUT_MODE_AOA);
|
||||
#endif
|
||||
|
||||
// keyboard_input_mode may have been reset if HID mode failed
|
||||
// keyboard_input_mode may have been reset if AOA mode failed
|
||||
if (options->keyboard_input_mode == SC_KEYBOARD_INPUT_MODE_SDK) {
|
||||
sc_keyboard_sdk_init(&s->keyboard_sdk, &s->controller,
|
||||
options->key_inject_mode,
|
||||
options->forward_key_repeat);
|
||||
kp = &s->keyboard_sdk.key_processor;
|
||||
} else if (options->keyboard_input_mode
|
||||
== SC_KEYBOARD_INPUT_MODE_UHID) {
|
||||
bool ok = sc_keyboard_uhid_init(&s->keyboard_uhid, &s->controller);
|
||||
if (!ok) {
|
||||
goto end;
|
||||
}
|
||||
kp = &s->keyboard_uhid.key_processor;
|
||||
}
|
||||
|
||||
// mouse_input_mode may have been reset if HID mode failed
|
||||
// mouse_input_mode may have been reset if AOA mode failed
|
||||
if (options->mouse_input_mode == SC_MOUSE_INPUT_MODE_SDK) {
|
||||
sc_mouse_sdk_init(&s->mouse_sdk, &s->controller);
|
||||
mp = &s->mouse_sdk.mouse_processor;
|
||||
|
||||
72
app/src/uhid/keyboard_uhid.c
Normal file
72
app/src/uhid/keyboard_uhid.c
Normal file
@@ -0,0 +1,72 @@
|
||||
#include "keyboard_uhid.h"
|
||||
|
||||
#include "util/log.h"
|
||||
|
||||
/** Downcast key processor to keyboard_uhid */
|
||||
#define DOWNCAST(KP) container_of(KP, struct sc_keyboard_uhid, key_processor)
|
||||
|
||||
#define UHID_KEYBOARD_ID 1
|
||||
|
||||
static void
|
||||
sc_key_processor_process_key(struct sc_key_processor *kp,
|
||||
const struct sc_key_event *event,
|
||||
uint64_t ack_to_wait) {
|
||||
(void) ack_to_wait;
|
||||
|
||||
if (event->repeat) {
|
||||
// In USB HID protocol, key repeat is handled by the host (Android), so
|
||||
// just ignore key repeat here.
|
||||
return;
|
||||
}
|
||||
|
||||
struct sc_keyboard_uhid *kb = DOWNCAST(kp);
|
||||
|
||||
struct sc_hid_event hid_event;
|
||||
|
||||
// Not all keys are supported, just ignore unsupported keys
|
||||
if (sc_hid_keyboard_event_from_key(&kb->hid, &hid_event, event)) {
|
||||
struct sc_control_msg msg;
|
||||
msg.type = SC_CONTROL_MSG_TYPE_UHID_INPUT;
|
||||
msg.uhid_input.id = UHID_KEYBOARD_ID;
|
||||
|
||||
assert(hid_event.size <= SC_HID_MAX_SIZE);
|
||||
memcpy(msg.uhid_input.data, hid_event.data, hid_event.size);
|
||||
msg.uhid_input.size = hid_event.size;
|
||||
|
||||
if (!sc_controller_push_msg(kb->controller, &msg)) {
|
||||
LOGE("Could not send UHID_INPUT message (key)");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool
|
||||
sc_keyboard_uhid_init(struct sc_keyboard_uhid *kb,
|
||||
struct sc_controller *controller) {
|
||||
sc_hid_keyboard_init(&kb->hid);
|
||||
|
||||
kb->controller = controller;
|
||||
|
||||
static const struct sc_key_processor_ops ops = {
|
||||
.process_key = sc_key_processor_process_key,
|
||||
// Never forward text input via HID (all the keys are injected
|
||||
// separately)
|
||||
.process_text = NULL,
|
||||
};
|
||||
|
||||
// Clipboard synchronization is requested over the same control socket, so
|
||||
// there is no need for a specific synchronization mechanism
|
||||
kb->key_processor.async_paste = false;
|
||||
kb->key_processor.ops = &ops;
|
||||
|
||||
struct sc_control_msg msg;
|
||||
msg.type = SC_CONTROL_MSG_TYPE_UHID_CREATE;
|
||||
msg.uhid_create.id = UHID_KEYBOARD_ID;
|
||||
msg.uhid_create.report_desc = SC_HID_KEYBOARD_REPORT_DESC;
|
||||
msg.uhid_create.report_desc_size = SC_HID_KEYBOARD_REPORT_DESC_LEN;
|
||||
if (!sc_controller_push_msg(controller, &msg)) {
|
||||
LOGE("Could not send UHID_CREATE message (keyboard)");
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
23
app/src/uhid/keyboard_uhid.h
Normal file
23
app/src/uhid/keyboard_uhid.h
Normal file
@@ -0,0 +1,23 @@
|
||||
#ifndef SC_KEYBOARD_UHID_H
|
||||
#define SC_KEYBOARD_UHID_H
|
||||
|
||||
#include "common.h"
|
||||
|
||||
#include <stdbool.h>
|
||||
|
||||
#include "controller.h"
|
||||
#include "hid/hid_keyboard.h"
|
||||
#include "trait/key_processor.h"
|
||||
|
||||
struct sc_keyboard_uhid {
|
||||
struct sc_key_processor key_processor; // key processor trait
|
||||
|
||||
struct sc_hid_keyboard hid;
|
||||
struct sc_controller *controller;
|
||||
};
|
||||
|
||||
bool
|
||||
sc_keyboard_uhid_init(struct sc_keyboard_uhid *kb,
|
||||
struct sc_controller *controller);
|
||||
|
||||
#endif
|
||||
@@ -323,6 +323,53 @@ static void test_serialize_rotate_device(void) {
|
||||
assert(!memcmp(buf, expected, sizeof(expected)));
|
||||
}
|
||||
|
||||
static void test_serialize_uhid_create(void) {
|
||||
const uint8_t report_desc[] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11};
|
||||
struct sc_control_msg msg = {
|
||||
.type = SC_CONTROL_MSG_TYPE_UHID_CREATE,
|
||||
.uhid_create = {
|
||||
.id = 42,
|
||||
.report_desc_size = sizeof(report_desc),
|
||||
.report_desc = report_desc,
|
||||
},
|
||||
};
|
||||
|
||||
uint8_t buf[SC_CONTROL_MSG_MAX_SIZE];
|
||||
size_t size = sc_control_msg_serialize(&msg, buf);
|
||||
assert(size == 16);
|
||||
|
||||
const uint8_t expected[] = {
|
||||
SC_CONTROL_MSG_TYPE_UHID_CREATE,
|
||||
0, 42, // id
|
||||
0, 11, // size
|
||||
1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11,
|
||||
};
|
||||
assert(!memcmp(buf, expected, sizeof(expected)));
|
||||
}
|
||||
|
||||
static void test_serialize_uhid_input(void) {
|
||||
struct sc_control_msg msg = {
|
||||
.type = SC_CONTROL_MSG_TYPE_UHID_INPUT,
|
||||
.uhid_input = {
|
||||
.id = 42,
|
||||
.size = 5,
|
||||
.data = {1, 2, 3, 4, 5},
|
||||
},
|
||||
};
|
||||
|
||||
uint8_t buf[SC_CONTROL_MSG_MAX_SIZE];
|
||||
size_t size = sc_control_msg_serialize(&msg, buf);
|
||||
assert(size == 10);
|
||||
|
||||
const uint8_t expected[] = {
|
||||
SC_CONTROL_MSG_TYPE_UHID_INPUT,
|
||||
0, 42, // id
|
||||
0, 5, // size
|
||||
1, 2, 3, 4, 5,
|
||||
};
|
||||
assert(!memcmp(buf, expected, sizeof(expected)));
|
||||
}
|
||||
|
||||
int main(int argc, char *argv[]) {
|
||||
(void) argc;
|
||||
(void) argv;
|
||||
@@ -341,5 +388,7 @@ int main(int argc, char *argv[]) {
|
||||
test_serialize_set_clipboard_long();
|
||||
test_serialize_set_screen_power_mode();
|
||||
test_serialize_rotate_device();
|
||||
test_serialize_uhid_create();
|
||||
test_serialize_uhid_input();
|
||||
return 0;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user