mirror of
https://github.com/Genymobile/scrcpy.git
synced 2025-04-20 03:25:03 +00:00
Sets the policy for how the display should show IME.
Possible values are "local", "fallback_display" and "hide".
This commit is contained in:
parent
c27d116a66
commit
b37aa747e2
15 changed files with 223 additions and 9 deletions
|
@ -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
|
||||
|
|
|
@ -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]'
|
||||
|
|
10
app/scrcpy.1
10
app/scrcpy.1
|
@ -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).
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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;
|
||||
};
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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");
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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
|
||||
```
|
||||
|
|
|
@ -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)) {
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Reference in a new issue