diff --git a/app/scrcpy.1 b/app/scrcpy.1
index 1dfcab2b..7e856664 100644
--- a/app/scrcpy.1
+++ b/app/scrcpy.1
@@ -182,7 +182,7 @@ Possible values are "disabled", "sdk", "uhid" and "aoa":
- "uhid" simulates a physical HID keyboard using the Linux HID kernel module on the device.
- "aoa" simulates a physical HID keyboard using the AOAv2 protocol. It may only work over USB.
-For "uhid" and "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 "uhid" and "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 using the shortcut MOD+k (except in OTG mode), or by executing:
adb shell am start -a android.settings.HARD_KEYBOARD_SETTINGS
@@ -644,6 +644,10 @@ Copy computer clipboard to device, then paste (inject PASTE keycode, Android >=
.B MOD+Shift+v
Inject computer clipboard text as a sequence of key events
+.TP
+.B MOD+k
+Open keyboard settings on the device (for HID keyboard only)
+
.TP
.B MOD+i
Enable/disable FPS counter (print frames/second in logs)
diff --git a/app/src/cli.c b/app/src/cli.c
index 59cd5699..c1c68e92 100644
--- a/app/src/cli.c
+++ b/app/src/cli.c
@@ -377,8 +377,9 @@ static const struct sc_option options[] = {
"For \"uhid\" and \"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 "
- "android.settings.HARD_KEYBOARD_SETTINGS`.\n"
+ "settings page can be started directly using the shortcut "
+ "MOD+k (except in OTG mode) or by executing: `adb shell am "
+ "start -a android.settings.HARD_KEYBOARD_SETTINGS`.\n"
"This option is only available when a HID keyboard is enabled "
"(or a physical keyboard is connected).\n"
"Also see --mouse.",
@@ -965,6 +966,10 @@ static const struct sc_shortcut shortcuts[] = {
.shortcuts = { "MOD+Shift+v" },
.text = "Inject computer clipboard text as a sequence of key events",
},
+ {
+ .shortcuts = { "MOD+k" },
+ .text = "Open keyboard settings on the device (for HID keyboard only)",
+ },
{
.shortcuts = { "MOD+i" },
.text = "Enable/disable FPS counter (print frames/second in logs)",
diff --git a/app/src/control_msg.c b/app/src/control_msg.c
index 88575b4e..b3da5fe5 100644
--- a/app/src/control_msg.c
+++ b/app/src/control_msg.c
@@ -161,6 +161,7 @@ sc_control_msg_serialize(const struct sc_control_msg *msg, uint8_t *buf) {
case SC_CONTROL_MSG_TYPE_EXPAND_SETTINGS_PANEL:
case SC_CONTROL_MSG_TYPE_COLLAPSE_PANELS:
case SC_CONTROL_MSG_TYPE_ROTATE_DEVICE:
+ case SC_CONTROL_MSG_TYPE_OPEN_HARD_KEYBOARD_SETTINGS:
// no additional data
return 1;
default:
@@ -270,6 +271,9 @@ sc_control_msg_log(const struct sc_control_msg *msg) {
}
break;
}
+ case SC_CONTROL_MSG_TYPE_OPEN_HARD_KEYBOARD_SETTINGS:
+ LOG_CMSG("open hard keyboard settings");
+ break;
default:
LOG_CMSG("unknown type: %u", (unsigned) msg->type);
break;
diff --git a/app/src/control_msg.h b/app/src/control_msg.h
index 550168c2..cd1340ef 100644
--- a/app/src/control_msg.h
+++ b/app/src/control_msg.h
@@ -40,6 +40,7 @@ enum sc_control_msg_type {
SC_CONTROL_MSG_TYPE_ROTATE_DEVICE,
SC_CONTROL_MSG_TYPE_UHID_CREATE,
SC_CONTROL_MSG_TYPE_UHID_INPUT,
+ SC_CONTROL_MSG_TYPE_OPEN_HARD_KEYBOARD_SETTINGS,
};
enum sc_screen_power_mode {
diff --git a/app/src/input_manager.c b/app/src/input_manager.c
index 7186186f..f26c4164 100644
--- a/app/src/input_manager.c
+++ b/app/src/input_manager.c
@@ -318,6 +318,18 @@ rotate_device(struct sc_input_manager *im) {
}
}
+static void
+open_hard_keyboard_settings(struct sc_input_manager *im) {
+ assert(im->controller);
+
+ struct sc_control_msg msg;
+ msg.type = SC_CONTROL_MSG_TYPE_OPEN_HARD_KEYBOARD_SETTINGS;
+
+ if (!sc_controller_push_msg(im->controller, &msg)) {
+ LOGW("Could not request opening hard keyboard settings");
+ }
+}
+
static void
apply_orientation_transform(struct sc_input_manager *im,
enum sc_orientation transform) {
@@ -550,6 +562,13 @@ sc_input_manager_process_key(struct sc_input_manager *im,
rotate_device(im);
}
return;
+ case SDLK_k:
+ if (control && !shift && !repeat && down
+ && im->kp && im->kp->hid) {
+ // Only if the current keyboard is hid
+ open_hard_keyboard_settings(im);
+ }
+ return;
}
return;
diff --git a/app/src/keyboard_sdk.c b/app/src/keyboard_sdk.c
index 726f65a9..00b7f92a 100644
--- a/app/src/keyboard_sdk.c
+++ b/app/src/keyboard_sdk.c
@@ -340,5 +340,6 @@ sc_keyboard_sdk_init(struct sc_keyboard_sdk *kb,
// Key injection and clipboard synchronization are serialized
kb->key_processor.async_paste = false;
+ kb->key_processor.hid = false;
kb->key_processor.ops = &ops;
}
diff --git a/app/src/trait/key_processor.h b/app/src/trait/key_processor.h
index 8c51b11d..96374413 100644
--- a/app/src/trait/key_processor.h
+++ b/app/src/trait/key_processor.h
@@ -23,6 +23,13 @@ struct sc_key_processor {
*/
bool async_paste;
+ /**
+ * Set by the implementation to indicate that the keyboard is HID. In
+ * practice, it is used to react on a shortcut to open the hard keyboard
+ * settings only if the keyboard is HID.
+ */
+ bool hid;
+
const struct sc_key_processor_ops *ops;
};
diff --git a/app/src/uhid/keyboard_uhid.c b/app/src/uhid/keyboard_uhid.c
index f537bc29..515a3fd9 100644
--- a/app/src/uhid/keyboard_uhid.c
+++ b/app/src/uhid/keyboard_uhid.c
@@ -137,6 +137,7 @@ sc_keyboard_uhid_init(struct sc_keyboard_uhid *kb,
// 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.hid = true;
kb->key_processor.ops = &ops;
static const struct sc_uhid_receiver_ops uhid_receiver_ops = {
diff --git a/app/src/usb/keyboard_aoa.c b/app/src/usb/keyboard_aoa.c
index b69d6cd8..736c97b0 100644
--- a/app/src/usb/keyboard_aoa.c
+++ b/app/src/usb/keyboard_aoa.c
@@ -94,6 +94,7 @@ sc_keyboard_aoa_init(struct sc_keyboard_aoa *kb, struct sc_aoa *aoa) {
// events are sent over AOA, so it must wait for clipboard synchronization
// to be acknowledged by the device before injecting Ctrl+v.
kb->key_processor.async_paste = true;
+ kb->key_processor.hid = true;
kb->key_processor.ops = &ops;
return true;
diff --git a/app/tests/test_control_msg_serialize.c b/app/tests/test_control_msg_serialize.c
index 0ab61153..7a978f2b 100644
--- a/app/tests/test_control_msg_serialize.c
+++ b/app/tests/test_control_msg_serialize.c
@@ -370,6 +370,21 @@ static void test_serialize_uhid_input(void) {
assert(!memcmp(buf, expected, sizeof(expected)));
}
+static void test_serialize_open_hard_keyboard(void) {
+ struct sc_control_msg msg = {
+ .type = SC_CONTROL_MSG_TYPE_OPEN_HARD_KEYBOARD_SETTINGS,
+ };
+
+ uint8_t buf[SC_CONTROL_MSG_MAX_SIZE];
+ size_t size = sc_control_msg_serialize(&msg, buf);
+ assert(size == 1);
+
+ const uint8_t expected[] = {
+ SC_CONTROL_MSG_TYPE_OPEN_HARD_KEYBOARD_SETTINGS,
+ };
+ assert(!memcmp(buf, expected, sizeof(expected)));
+}
+
int main(int argc, char *argv[]) {
(void) argc;
(void) argv;
@@ -390,5 +405,6 @@ int main(int argc, char *argv[]) {
test_serialize_rotate_device();
test_serialize_uhid_create();
test_serialize_uhid_input();
+ test_serialize_open_hard_keyboard();
return 0;
}
diff --git a/doc/shortcuts.md b/doc/shortcuts.md
index 21bccbd9..8c402855 100644
--- a/doc/shortcuts.md
+++ b/doc/shortcuts.md
@@ -48,6 +48,7 @@ _[Super] is typically the Windows or Cmd key._
| Cut to clipboard⁵ | MOD+x
| Synchronize clipboards and paste⁵ | MOD+v
| Inject computer clipboard text | MOD+Shift+v
+ | Open keyboard settings (HID keyboard only) | MOD+k
| Enable/disable FPS counter (on stdout) | MOD+i
| Pinch-to-zoom/rotate | Ctrl+_click-and-move_
| Tilt (slide vertically with 2 fingers) | Shift+_click-and-move_
diff --git a/server/src/main/java/com/genymobile/scrcpy/ControlMessage.java b/server/src/main/java/com/genymobile/scrcpy/ControlMessage.java
index 74bf5610..bcbacb4b 100644
--- a/server/src/main/java/com/genymobile/scrcpy/ControlMessage.java
+++ b/server/src/main/java/com/genymobile/scrcpy/ControlMessage.java
@@ -19,6 +19,7 @@ public final class ControlMessage {
public static final int TYPE_ROTATE_DEVICE = 11;
public static final int TYPE_UHID_CREATE = 12;
public static final int TYPE_UHID_INPUT = 13;
+ public static final int TYPE_OPEN_HARD_KEYBOARD_SETTINGS = 14;
public static final long SEQUENCE_INVALID = 0;
diff --git a/server/src/main/java/com/genymobile/scrcpy/ControlMessageReader.java b/server/src/main/java/com/genymobile/scrcpy/ControlMessageReader.java
index 24aa73c0..1761d228 100644
--- a/server/src/main/java/com/genymobile/scrcpy/ControlMessageReader.java
+++ b/server/src/main/java/com/genymobile/scrcpy/ControlMessageReader.java
@@ -86,6 +86,7 @@ public class ControlMessageReader {
case ControlMessage.TYPE_EXPAND_SETTINGS_PANEL:
case ControlMessage.TYPE_COLLAPSE_PANELS:
case ControlMessage.TYPE_ROTATE_DEVICE:
+ case ControlMessage.TYPE_OPEN_HARD_KEYBOARD_SETTINGS:
msg = ControlMessage.createEmpty(type);
break;
case ControlMessage.TYPE_UHID_CREATE:
diff --git a/server/src/main/java/com/genymobile/scrcpy/Controller.java b/server/src/main/java/com/genymobile/scrcpy/Controller.java
index fd320d3f..87faf8ba 100644
--- a/server/src/main/java/com/genymobile/scrcpy/Controller.java
+++ b/server/src/main/java/com/genymobile/scrcpy/Controller.java
@@ -1,7 +1,9 @@
package com.genymobile.scrcpy;
import com.genymobile.scrcpy.wrappers.InputManager;
+import com.genymobile.scrcpy.wrappers.ServiceManager;
+import android.content.Intent;
import android.os.Build;
import android.os.SystemClock;
import android.view.InputDevice;
@@ -208,6 +210,9 @@ public class Controller implements AsyncProcessor {
case ControlMessage.TYPE_UHID_INPUT:
getUhidManager().writeInput(msg.getId(), msg.getData());
break;
+ case ControlMessage.TYPE_OPEN_HARD_KEYBOARD_SETTINGS:
+ openHardKeyboardSettings();
+ break;
default:
// do nothing
}
@@ -446,4 +451,9 @@ public class Controller implements AsyncProcessor {
return ok;
}
+
+ private void openHardKeyboardSettings() {
+ Intent intent = new Intent("android.settings.HARD_KEYBOARD_SETTINGS");
+ ServiceManager.getActivityManager().startActivity(intent);
+ }
}
diff --git a/server/src/test/java/com/genymobile/scrcpy/ControlMessageReaderTest.java b/server/src/test/java/com/genymobile/scrcpy/ControlMessageReaderTest.java
index 7cc67c3e..0c8086f7 100644
--- a/server/src/test/java/com/genymobile/scrcpy/ControlMessageReaderTest.java
+++ b/server/src/test/java/com/genymobile/scrcpy/ControlMessageReaderTest.java
@@ -366,6 +366,22 @@ public class ControlMessageReaderTest {
Assert.assertArrayEquals(data, event.getData());
}
+ @Test
+ public void testParseOpenHardKeyboardSettings() throws IOException {
+ ControlMessageReader reader = new ControlMessageReader();
+
+ ByteArrayOutputStream bos = new ByteArrayOutputStream();
+ DataOutputStream dos = new DataOutputStream(bos);
+ dos.writeByte(ControlMessage.TYPE_OPEN_HARD_KEYBOARD_SETTINGS);
+
+ byte[] packet = bos.toByteArray();
+
+ reader.readFrom(new ByteArrayInputStream(packet));
+ ControlMessage event = reader.next();
+
+ Assert.assertEquals(ControlMessage.TYPE_OPEN_HARD_KEYBOARD_SETTINGS, event.getType());
+ }
+
@Test
public void testMultiEvents() throws IOException {
ControlMessageReader reader = new ControlMessageReader();