Sets the policy for how the display should show IME.

Possible values are "local", "fallback_display" and "hide".
This commit is contained in:
chengjian.scj 2024-12-25 15:30:37 +08:00
parent c27d116a66
commit b37aa747e2
15 changed files with 223 additions and 9 deletions

View file

@ -23,6 +23,7 @@ _scrcpy() {
-d --select-usb
--disable-screensaver
--display-id=
--display-ime-policy=
--display-orientation=
-e --select-tcpip
-f --fullscreen
@ -148,6 +149,10 @@ _scrcpy() {
COMPREPLY=($(compgen -W '0 90 180 270 flip0 flip90 flip180 flip270' -- "$cur"))
return
;;
--display-ime-policy)
COMPREPLY=($(compgen -W 'local fallback_display hide' -- "$cur"))
return
;;
--record-orientation)
COMPREPLY=($(compgen -W '0 90 180 270' -- "$cur"))
return

View file

@ -30,6 +30,7 @@ arguments=(
{-d,--select-usb}'[Use USB device]'
'--disable-screensaver[Disable screensaver while scrcpy is running]'
'--display-id=[Specify the display id to mirror]'
'--display-ime-policy=[Sets the policy for how the display should show IME]'
'--display-orientation=[Set the initial display orientation]:orientation values:(0 90 180 270 flip0 flip90 flip180 flip270)'
{-e,--select-tcpip}'[Use TCP/IP device]'
{-f,--fullscreen}'[Start in fullscreen]'

View file

@ -169,6 +169,16 @@ Possible values are 0, 90, 180, 270, flip0, flip90, flip180 and flip270. The num
Default is 0.
.TP
.BI "\-\-display\-ime-policy " value
Sets the policy for how the display should show IME.
Possible values are "local", "fallback_display" and "hide".
- The "local" policy means that the IME should appear on the local display.
- The "fallback_display" policy means that the IME should appear on a fallback display. The fallback display is always DEFAULT DISPLAY.
- The "hide" policy means that the IME should be hidden.
.TP
.B \-e, \-\-select\-tcpip
Use TCP/IP device (if there is exactly one, like adb -e).

View file

@ -94,6 +94,7 @@ enum {
OPT_CAMERA_FPS,
OPT_CAMERA_HIGH_SPEED,
OPT_DISPLAY_ORIENTATION,
OPT_DISPLAY_IME_POLICY,
OPT_RECORD_ORIENTATION,
OPT_ORIENTATION,
OPT_KEYBOARD,
@ -366,6 +367,20 @@ static const struct sc_option options[] = {
" scrcpy --list-displays\n"
"Default is 0.",
},
{
.longopt_id = OPT_DISPLAY_IME_POLICY,
.longopt = "display-ime-policy",
.argdesc = "value",
.text = "Sets the policy for how the display should show IME.\n"
"Possible values are \"local\", \"fallback_display\" and "
"\"hide\".\n"
"The \"local\" policy means that the IME should appear on the "
"local display.\n"
"The \"fallback_display\" policy means that the IME should "
"appear on a fallback display. The fallback display is always "
"DEFAULT DISPLAY.\n"
"The \"hide\" policy means that the IME should be hidden.\n",
},
{
.longopt_id = OPT_DISPLAY_ORIENTATION,
.longopt = "display-orientation",
@ -1615,6 +1630,24 @@ parse_audio_output_buffer(const char *s, sc_tick *tick) {
return true;
}
static bool
parse_display_ime_policy(const char *optarg, enum sc_display_ime_policy *policy) {
if (!strcmp(optarg, "local")) {
*policy = SC_DISPLAY_IME_POLICY_LOCAL;
return true;
}
if (!strcmp(optarg, "fallback_display")) {
*policy = SC_DISPLAY_IME_POLICY_FALLBACK_DISPLAY;
return true;
}
if (!strcmp(optarg, "hide")) {
*policy = SC_DISPLAY_IME_POLICY_HIDE;
return true;
}
LOGE("Unsupported display IME policy: %s (expected local, fallback_display, hide)", optarg);
return false;
}
static bool
parse_orientation(const char *s, enum sc_orientation *orientation) {
if (!strcmp(s, "0")) {
@ -2723,6 +2756,11 @@ parse_args_with_getopt(struct scrcpy_cli_args *args, int argc, char *argv[],
case OPT_NO_VD_SYSTEM_DECORATIONS:
opts->vd_system_decorations = false;
break;
case OPT_DISPLAY_IME_POLICY:
if (!parse_display_ime_policy(optarg, &opts->display_ime_policy)) {
return false;
}
break;
default:
// getopt prints the error message on stderr
return false;
@ -2978,6 +3016,11 @@ parse_args_with_getopt(struct scrcpy_cli_args *args, int argc, char *argv[],
return false;
}
if (opts->display_ime_policy != SC_DISPLAY_IME_POLICY_UNDEFINDED) {
LOGE("--display-ime_policy is only available with --video-source=display");
return false;
}
if (opts->camera_id && opts->camera_facing != SC_CAMERA_FACING_ANY) {
LOGE("Cannot specify both --camera-id and --camera-facing");
return false;
@ -3014,9 +3057,17 @@ parse_args_with_getopt(struct scrcpy_cli_args *args, int argc, char *argv[],
return false;
}
if (opts->display_id != 0 && opts->new_display) {
LOGE("Cannot specify both --display-id and --new-display");
return false;
if (opts->display_id != 0) {
if (opts->new_display) {
LOGE("Cannot specify both --display-id and --new-display");
return false;
}
} else {
if (!opts->new_display
&& opts->display_ime_policy != SC_DISPLAY_IME_POLICY_UNDEFINDED) {
LOGE("--display-ime-policy not supported if display_id == 0 and new_display == NULL");
return false;
}
}
if (opts->audio && opts->audio_source == SC_AUDIO_SOURCE_AUTO) {
@ -3200,6 +3251,10 @@ parse_args_with_getopt(struct scrcpy_cli_args *args, int argc, char *argv[],
LOGE("OTG mode: could not select display");
return false;
}
if (opts->display_ime_policy != SC_DISPLAY_IME_POLICY_UNDEFINDED) {
LOGE("OTG mode: could not set the policy for how the display should show IME.");
return false;
}
if (v4l2) {
LOGE("OTG mode: could not sink to V4L2 device");
return false;

View file

@ -109,6 +109,7 @@ const struct scrcpy_options scrcpy_options_default = {
.audio_dup = false,
.new_display = NULL,
.start_app = NULL,
.display_ime_policy = SC_DISPLAY_IME_POLICY_UNDEFINDED,
.angle = NULL,
.vd_destroy_content = true,
.vd_system_decorations = true,

View file

@ -83,6 +83,13 @@ enum sc_orientation { // v v v
SC_ORIENTATION_FLIP_270, // 1 1 1
};
enum sc_display_ime_policy {
SC_DISPLAY_IME_POLICY_UNDEFINDED = -1,
SC_DISPLAY_IME_POLICY_LOCAL,
SC_DISPLAY_IME_POLICY_FALLBACK_DISPLAY,
SC_DISPLAY_IME_POLICY_HIDE,
};
enum sc_orientation_lock {
SC_ORIENTATION_UNLOCKED,
SC_ORIENTATION_LOCKED_VALUE, // lock to specified orientation
@ -309,6 +316,7 @@ struct scrcpy_options {
bool audio_dup;
const char *new_display; // [<width>x<height>][/<dpi>] parsed by the server
const char *start_app;
enum sc_display_ime_policy display_ime_policy;
bool vd_destroy_content;
bool vd_system_decorations;
};

View file

@ -436,6 +436,7 @@ scrcpy(struct scrcpy_options *options) {
.control = options->control,
.display_id = options->display_id,
.new_display = options->new_display,
.display_ime_policy = options->display_ime_policy,
.video = options->video,
.audio = options->audio,
.audio_dup = options->audio_dup,

View file

@ -376,6 +376,10 @@ execute_server(struct sc_server *server,
VALIDATE_STRING(params->new_display);
ADD_PARAM("new_display=%s", params->new_display);
}
if (params->display_ime_policy != SC_DISPLAY_IME_POLICY_UNDEFINDED) {
assert(params->display_ime_policy >= 0);
ADD_PARAM("display_ime_policy=%" PRIu32, params->display_ime_policy);
}
if (!params->vd_destroy_content) {
ADD_PARAM("vd_destroy_content=false");
}

View file

@ -50,6 +50,7 @@ struct sc_server_params {
bool control;
uint32_t display_id;
const char *new_display;
enum sc_display_ime_policy display_ime_policy;
bool video;
bool audio;
bool audio_dup;

View file

@ -61,3 +61,14 @@ To move them to the main display instead, use:
```
scrcpy --new-display --no-vd-destroy-content
```
## Display IME policy
By default, virtual display's IME appears on the DEFAULT display.
TO appears on the LOCAL display, use `--display-ime-policy=local`:
```bash
scrcpy --display-id=1 --display-ime-policy=local
scrcpy --new-display --display-ime-policy=local
```

View file

@ -4,6 +4,7 @@ import com.genymobile.scrcpy.device.Device;
import com.genymobile.scrcpy.util.Ln;
import com.genymobile.scrcpy.util.Settings;
import com.genymobile.scrcpy.util.SettingsException;
import com.genymobile.scrcpy.wrappers.ServiceManager;
import android.os.BatteryManager;
import android.system.ErrnoException;
@ -100,15 +101,27 @@ public final class CleanUp {
boolean powerOffScreen = options.getPowerOffScreenOnClose();
int displayId = options.getDisplayId();
int restoreDisplayImePolicy = -1;
if (displayId > 0) {
int displayImePolicy = options.getDisplayImePolicy();
if (displayImePolicy != -1) {
int currentValue = ServiceManager.getWindowManager().getDisplayImePolicy(displayId);
if (currentValue != displayImePolicy) {
ServiceManager.getWindowManager().setDisplayImePolicy(displayId, displayImePolicy);
restoreDisplayImePolicy = currentValue;
}
}
}
try {
run(displayId, restoreStayOn, disableShowTouches, powerOffScreen, restoreScreenOffTimeout);
run(displayId, restoreStayOn, disableShowTouches, powerOffScreen, restoreScreenOffTimeout, restoreDisplayImePolicy);
} catch (IOException e) {
Ln.e("Clean up I/O exception", e);
}
}
private void run(int displayId, int restoreStayOn, boolean disableShowTouches, boolean powerOffScreen, int restoreScreenOffTimeout)
throws IOException {
private void run(int displayId, int restoreStayOn, boolean disableShowTouches, boolean powerOffScreen, int restoreScreenOffTimeout,
int restoreDisplayImePolicy) throws IOException {
String[] cmd = {
"app_process",
"/",
@ -118,6 +131,7 @@ public final class CleanUp {
String.valueOf(disableShowTouches),
String.valueOf(powerOffScreen),
String.valueOf(restoreScreenOffTimeout),
String.valueOf(restoreDisplayImePolicy),
};
ProcessBuilder builder = new ProcessBuilder(cmd);
@ -178,6 +192,7 @@ public final class CleanUp {
boolean disableShowTouches = Boolean.parseBoolean(args[2]);
boolean powerOffScreen = Boolean.parseBoolean(args[3]);
int restoreScreenOffTimeout = Integer.parseInt(args[4]);
int restoreDisplayImePolicy = Integer.parseInt(args[5]);
// Dynamic option
boolean restoreDisplayPower = false;
@ -223,6 +238,11 @@ public final class CleanUp {
}
}
if (restoreDisplayImePolicy != -1) {
Ln.i("Restoring \"display IME policy\"");
ServiceManager.getWindowManager().setDisplayImePolicy(displayId, restoreDisplayImePolicy);
}
// Change the power of the main display when mirroring a virtual display
int targetDisplayId = displayId != Device.DISPLAY_ID_NONE ? displayId : 0;
if (Device.isScreenOn(targetDisplayId)) {

View file

@ -48,6 +48,7 @@ public class Options {
private boolean showTouches;
private boolean stayAwake;
private int screenOffTimeout = -1;
private int displayImePolicy = -1;
private List<CodecOption> videoCodecOptions;
private List<CodecOption> audioCodecOptions;
@ -194,6 +195,10 @@ public class Options {
return audioCodecOptions;
}
public int getDisplayImePolicy() {
return displayImePolicy;
}
public String getVideoEncoder() {
return videoEncoder;
}
@ -381,6 +386,14 @@ public class Options {
case "display_id":
options.displayId = Integer.parseInt(value);
break;
case "display_ime_policy":
if (!value.isEmpty()) {
options.displayImePolicy = Integer.parseInt(value);
if (options.displayImePolicy == -1) {
throw new IllegalArgumentException("Invalid display IME policy: " + options.displayImePolicy);
}
}
break;
case "show_touches":
options.showTouches = Boolean.parseBoolean(value);
break;

View file

@ -80,9 +80,15 @@ public final class Server {
throw new ConfigurationException("Camera mirroring is not supported");
}
if (Build.VERSION.SDK_INT < AndroidVersions.API_29_ANDROID_10 && options.getNewDisplay() != null) {
Ln.e("New virtual display is not supported before Android 10");
throw new ConfigurationException("New virtual display is not supported");
if (Build.VERSION.SDK_INT < AndroidVersions.API_29_ANDROID_10) {
if (options.getNewDisplay() != null) {
Ln.e("New virtual display is not supported before Android 10");
throw new ConfigurationException("New virtual display is not supported");
}
if (options.getDisplayImePolicy() != -1) {
Ln.e("Display IME policy is not supported before Android 10");
throw new ConfigurationException("Display IME policy is not supported");
}
}
CleanUp cleanUp = null;

View file

@ -49,6 +49,7 @@ public class NewDisplayCapture extends SurfaceCapture {
private Size mainDisplaySize;
private int mainDisplayDpi;
private int maxSize;
private int displayImePolicy;
private final Rect crop;
private final boolean captureOrientationLocked;
private final Orientation captureOrientation;
@ -68,6 +69,7 @@ public class NewDisplayCapture extends SurfaceCapture {
this.newDisplay = options.getNewDisplay();
assert newDisplay != null;
this.maxSize = options.getMaxSize();
this.displayImePolicy = options.getDisplayImePolicy();
this.crop = options.getCrop();
assert options.getCaptureOrientationLock() != null;
this.captureOrientationLocked = options.getCaptureOrientationLock() != Orientation.Lock.Unlocked;
@ -191,6 +193,10 @@ public class NewDisplayCapture extends SurfaceCapture {
virtualDisplayId = virtualDisplay.getDisplay().getDisplayId();
Ln.i("New display: " + displaySize.getWidth() + "x" + displaySize.getHeight() + "/" + dpi + " (id=" + virtualDisplayId + ")");
if (displayImePolicy != -1) {
ServiceManager.getWindowManager().setDisplayImePolicy(virtualDisplayId, displayImePolicy);
}
displaySizeMonitor.start(virtualDisplayId, this::invalidate);
} catch (Exception e) {
Ln.e("Could not create display", e);

View file

@ -10,6 +10,11 @@ import android.view.IDisplayWindowListener;
import java.lang.reflect.Method;
public final class WindowManager {
// see https://android.googlesource.com/platform/frameworks/base.git/+/refs/heads/main/core/java/android/view/WindowManager.java#692
public static final int DISPLAY_IME_POLICY_LOCAL = 0;
public static final int DISPLAY_IME_POLICY_FALLBACK_DISPLAY = 1;
public static final int DISPLAY_IME_POLICY_HIDE = 2;
private final IInterface manager;
private Method getRotationMethod;
@ -22,6 +27,13 @@ public final class WindowManager {
private Method thawDisplayRotationMethod;
private int thawDisplayRotationMethodVersion;
private Method getDisplayImePolicyMethod;
private boolean getDisplayImePolicyMethodNewVersion = true;
private Method setDisplayImePolicyMethod;
private boolean setDisplayImePolicyMethodNewVersion = true;
static WindowManager create() {
IInterface manager = ServiceManager.getService("window", "android.view.IWindowManager");
return new WindowManager(manager);
@ -198,4 +210,64 @@ public final class WindowManager {
Ln.e("Could not unregister display window listener", e);
}
}
@TargetApi(AndroidVersions.API_29_ANDROID_10)
private Method getGetDisplayImePolicyMethod() throws NoSuchMethodException {
if (getDisplayImePolicyMethod == null) {
try {
getDisplayImePolicyMethod = manager.getClass().getMethod("getDisplayImePolicy", int.class);
} catch (NoSuchMethodException e) {
getDisplayImePolicyMethod = manager.getClass().getMethod("shouldShowIme", int.class);
getDisplayImePolicyMethodNewVersion = false;
}
}
return getDisplayImePolicyMethod;
}
@TargetApi(AndroidVersions.API_29_ANDROID_10)
public int getDisplayImePolicy(int displayId) {
try {
Method method = getGetDisplayImePolicyMethod();
if (getDisplayImePolicyMethodNewVersion) {
return (int) method.invoke(manager, displayId);
} else {
boolean shouldShow = (boolean) method.invoke(manager, displayId);
return shouldShow ? DISPLAY_IME_POLICY_LOCAL : DISPLAY_IME_POLICY_FALLBACK_DISPLAY;
}
} catch (ReflectiveOperationException e) {
Ln.e("Could not invoke method", e);
return DISPLAY_IME_POLICY_FALLBACK_DISPLAY;
}
}
@TargetApi(AndroidVersions.API_29_ANDROID_10)
private Method getSetDisplayImePolicyMethod() throws NoSuchMethodException {
if (setDisplayImePolicyMethod == null) {
try {
setDisplayImePolicyMethod = manager.getClass().getMethod("setDisplayImePolicy", int.class, int.class);
} catch (NoSuchMethodException e) {
setDisplayImePolicyMethod = manager.getClass().getMethod("setShouldShowIme", int.class, boolean.class);
setDisplayImePolicyMethodNewVersion = false;
}
}
return setDisplayImePolicyMethod;
}
@TargetApi(AndroidVersions.API_29_ANDROID_10)
public void setDisplayImePolicy(int displayId, int imePolicy) {
try {
Method method = getSetDisplayImePolicyMethod();
if (setDisplayImePolicyMethodNewVersion) {
method.invoke(manager, displayId, imePolicy);
} else {
if (imePolicy != DISPLAY_IME_POLICY_HIDE) {
method.invoke(manager, displayId, imePolicy == DISPLAY_IME_POLICY_LOCAL);
} else {
Ln.w("DISPLAY_IME_POLICY_HIDE not supported on this device");
}
}
} catch (ReflectiveOperationException e) {
Ln.e("Could not invoke method", e);
}
}
}