diff --git a/app/src/cli.c b/app/src/cli.c index 41de8ca5..6c79be4d 100644 --- a/app/src/cli.c +++ b/app/src/cli.c @@ -26,6 +26,11 @@ scrcpy_print_usage(const char *arg0) { " Unit suffixes are supported: 'K' (x1000) and 'M' (x1000000).\n" " Default is %d.\n" "\n" + " --char-inject-fallback\n" + " Fallback to the clipboard (copy and paste) when non-supported\n" + " non-ASCII characters are typed.\n" + " Would override the device clipboard content when it happens.\n" + "\n" " --codec-options key[:type]=value[,...]\n" " Set a list of comma-separated key:type=value options for the\n" " device encoder.\n" @@ -651,12 +656,15 @@ guess_record_format(const char *filename) { #define OPT_DISABLE_SCREENSAVER 1020 #define OPT_SHORTCUT_MOD 1021 #define OPT_NO_KEY_REPEAT 1022 +#define OPT_CHAR_INJECT_FALLBACK 1023 bool scrcpy_parse_args(struct scrcpy_cli_args *args, int argc, char *argv[]) { static const struct option long_options[] = { {"always-on-top", no_argument, NULL, OPT_ALWAYS_ON_TOP}, {"bit-rate", required_argument, NULL, 'b'}, + {"char-inject-fallback", no_argument, NULL, + OPT_CHAR_INJECT_FALLBACK}, {"codec-options", required_argument, NULL, OPT_CODEC_OPTIONS}, {"crop", required_argument, NULL, OPT_CROP}, {"disable-screensaver", no_argument, NULL, @@ -719,6 +727,9 @@ scrcpy_parse_args(struct scrcpy_cli_args *args, int argc, char *argv[]) { case OPT_CROP: opts->crop = optarg; break; + case OPT_CHAR_INJECT_FALLBACK: + opts->char_inject_fallback = true; + break; case OPT_DISPLAY_ID: if (!parse_display_id(optarg, &opts->display_id)) { return false; diff --git a/app/src/scrcpy.c b/app/src/scrcpy.c index 45068cbb..ad71567c 100644 --- a/app/src/scrcpy.c +++ b/app/src/scrcpy.c @@ -313,6 +313,7 @@ scrcpy(const struct scrcpy_options *options) { .bit_rate = options->bit_rate, .max_fps = options->max_fps, .lock_video_orientation = options->lock_video_orientation, + .char_inject_fallback = options->char_inject_fallback, .control = options->control, .display_id = options->display_id, .show_touches = options->show_touches, diff --git a/app/src/scrcpy.h b/app/src/scrcpy.h index 86a2b57b..22bb6722 100644 --- a/app/src/scrcpy.h +++ b/app/src/scrcpy.h @@ -69,6 +69,7 @@ struct scrcpy_options { bool fullscreen; bool always_on_top; bool control; + bool char_inject_fallback; bool display; bool turn_screen_off; bool render_expired_frames; @@ -112,6 +113,7 @@ struct scrcpy_options { .show_touches = false, \ .fullscreen = false, \ .always_on_top = false, \ + .char_inject_fallback = false, \ .control = true, \ .display = true, \ .turn_screen_off = false, \ diff --git a/app/src/server.c b/app/src/server.c index 422bbfa5..1bf71dd5 100644 --- a/app/src/server.c +++ b/app/src/server.c @@ -294,6 +294,7 @@ execute_server(struct server *server, const struct server_params *params) { params->show_touches ? "true" : "false", params->stay_awake ? "true" : "false", params->codec_options ? params->codec_options : "-", + params->char_inject_fallback ? "true" : "false", }; #ifdef SERVER_DEBUGGER LOGI("Server debugger waiting for a client on device port " diff --git a/app/src/server.h b/app/src/server.h index 254afe30..5dd21c66 100644 --- a/app/src/server.h +++ b/app/src/server.h @@ -53,6 +53,7 @@ struct server_params { uint32_t bit_rate; uint16_t max_fps; int8_t lock_video_orientation; + bool char_inject_fallback; bool control; uint16_t display_id; bool show_touches; diff --git a/server/src/main/java/com/genymobile/scrcpy/Controller.java b/server/src/main/java/com/genymobile/scrcpy/Controller.java index 79feefc1..d0934e10 100644 --- a/server/src/main/java/com/genymobile/scrcpy/Controller.java +++ b/server/src/main/java/com/genymobile/scrcpy/Controller.java @@ -21,6 +21,7 @@ public class Controller { private final Device device; private final DesktopConnection connection; private final DeviceMessageSender sender; + private final boolean charInjectFallback; private final KeyCharacterMap charMap = KeyCharacterMap.load(KeyCharacterMap.VIRTUAL_KEYBOARD); @@ -31,9 +32,10 @@ public class Controller { private boolean keepPowerModeOff; - public Controller(Device device, DesktopConnection connection) { + public Controller(Device device, DesktopConnection connection, boolean charInjectFallback) { this.device = device; this.connection = connection; + this.charInjectFallback = charInjectFallback; initPointers(); sender = new DeviceMessageSender(connection); } @@ -163,8 +165,19 @@ public class Controller { int successCount = 0; for (char c : text.toCharArray()) { if (!injectChar(c)) { - Ln.w("Could not inject char u+" + String.format("%04x", (int) c)); - continue; + if (this.charInjectFallback) { + // Needs to handle the full remaining substring than only the failed character, + // since the events may happen too fast. E.g. if we are to do it per character, + // the expected input "ABCD" may lead to the actual value of "DDDD". + String remaining = text.substring(successCount); + setClipboard(remaining, true); + successCount = text.length(); + break; + } + else { + Ln.w("Could not inject char u+" + String.format("%04x", (int) c)); + continue; + } } successCount++; } diff --git a/server/src/main/java/com/genymobile/scrcpy/Options.java b/server/src/main/java/com/genymobile/scrcpy/Options.java index 06312a37..b137b96c 100644 --- a/server/src/main/java/com/genymobile/scrcpy/Options.java +++ b/server/src/main/java/com/genymobile/scrcpy/Options.java @@ -16,6 +16,7 @@ public class Options { private boolean showTouches; private boolean stayAwake; private String codecOptions; + private boolean charInjectFallback; public Ln.Level getLogLevel() { return logLevel; @@ -120,4 +121,12 @@ public class Options { public void setCodecOptions(String codecOptions) { this.codecOptions = codecOptions; } + + public boolean getCharInjectFallback() { + return charInjectFallback; + } + + public void setCharInjectFallback(boolean charInjectFallback) { + this.charInjectFallback = charInjectFallback; + } } diff --git a/server/src/main/java/com/genymobile/scrcpy/Server.java b/server/src/main/java/com/genymobile/scrcpy/Server.java index 9b7f9de8..8c0b0f10 100644 --- a/server/src/main/java/com/genymobile/scrcpy/Server.java +++ b/server/src/main/java/com/genymobile/scrcpy/Server.java @@ -57,7 +57,7 @@ public final class Server { ScreenEncoder screenEncoder = new ScreenEncoder(options.getSendFrameMeta(), options.getBitRate(), options.getMaxFps(), codecOptions); if (options.getControl()) { - final Controller controller = new Controller(device, connection); + final Controller controller = new Controller(device, connection, options.getCharInjectFallback()); // asynchronous startController(controller); @@ -120,7 +120,7 @@ public final class Server { "The server version (" + BuildConfig.VERSION_NAME + ") does not match the client " + "(" + clientVersion + ")"); } - final int expectedParameters = 14; + final int expectedParameters = 15; if (args.length != expectedParameters) { throw new IllegalArgumentException("Expecting " + expectedParameters + " parameters"); } @@ -167,6 +167,9 @@ public final class Server { String codecOptions = args[13]; options.setCodecOptions(codecOptions); + boolean charInjectFallback = Boolean.parseBoolean(args[14]); + options.setCharInjectFallback(charInjectFallback); + return options; }