mirror of
https://github.com/Genymobile/scrcpy.git
synced 2025-07-13 20:41:40 +00:00
Merge ac219a49d6
into c5ed2cfc28
This commit is contained in:
commit
e3b42ba622
5 changed files with 311 additions and 222 deletions
|
@ -25,6 +25,7 @@ android {
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
testImplementation 'junit:junit:4.13.2'
|
testImplementation 'junit:junit:4.13.2'
|
||||||
|
implementation 'com.fasterxml.jackson.core:jackson-databind:2.19.0-rc2'
|
||||||
}
|
}
|
||||||
|
|
||||||
apply from: "$project.rootDir/config/android-checkstyle.gradle"
|
apply from: "$project.rootDir/config/android-checkstyle.gradle"
|
||||||
|
|
|
@ -79,6 +79,7 @@ public class Options {
|
||||||
private boolean sendFrameMeta = true; // send PTS so that the client may record properly
|
private boolean sendFrameMeta = true; // send PTS so that the client may record properly
|
||||||
private boolean sendDummyByte = true; // write a byte on start to detect connection issues
|
private boolean sendDummyByte = true; // write a byte on start to detect connection issues
|
||||||
private boolean sendCodecMeta = true; // write the codec metadata before the stream
|
private boolean sendCodecMeta = true; // write the codec metadata before the stream
|
||||||
|
private boolean netArgs = false;
|
||||||
|
|
||||||
public Ln.Level getLogLevel() {
|
public Ln.Level getLogLevel() {
|
||||||
return logLevel;
|
return logLevel;
|
||||||
|
@ -288,6 +289,223 @@ public class Options {
|
||||||
return sendCodecMeta;
|
return sendCodecMeta;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean getEnableNetworkArgs() {
|
||||||
|
return netArgs;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void parseKeyValue(String key, String value) {
|
||||||
|
switch (key) {
|
||||||
|
case "scid":
|
||||||
|
int scid = Integer.parseInt(value, 0x10);
|
||||||
|
if (scid < -1) {
|
||||||
|
throw new IllegalArgumentException("scid may not be negative (except -1 for 'none'): " + scid);
|
||||||
|
}
|
||||||
|
this.scid = scid;
|
||||||
|
break;
|
||||||
|
case "log_level":
|
||||||
|
this.logLevel = Ln.Level.valueOf(value.toUpperCase(Locale.ENGLISH));
|
||||||
|
break;
|
||||||
|
case "video":
|
||||||
|
this.video = Boolean.parseBoolean(value);
|
||||||
|
break;
|
||||||
|
case "audio":
|
||||||
|
this.audio = Boolean.parseBoolean(value);
|
||||||
|
break;
|
||||||
|
case "video_codec":
|
||||||
|
VideoCodec videoCodec = VideoCodec.findByName(value);
|
||||||
|
if (videoCodec == null) {
|
||||||
|
throw new IllegalArgumentException("Video codec " + value + " not supported");
|
||||||
|
}
|
||||||
|
this.videoCodec = videoCodec;
|
||||||
|
break;
|
||||||
|
case "audio_codec":
|
||||||
|
AudioCodec audioCodec = AudioCodec.findByName(value);
|
||||||
|
if (audioCodec == null) {
|
||||||
|
throw new IllegalArgumentException("Audio codec " + value + " not supported");
|
||||||
|
}
|
||||||
|
this.audioCodec = audioCodec;
|
||||||
|
break;
|
||||||
|
case "video_source":
|
||||||
|
VideoSource videoSource = VideoSource.findByName(value);
|
||||||
|
if (videoSource == null) {
|
||||||
|
throw new IllegalArgumentException("Video source " + value + " not supported");
|
||||||
|
}
|
||||||
|
this.videoSource = videoSource;
|
||||||
|
break;
|
||||||
|
case "audio_source":
|
||||||
|
AudioSource audioSource = AudioSource.findByName(value);
|
||||||
|
if (audioSource == null) {
|
||||||
|
throw new IllegalArgumentException("Audio source " + value + " not supported");
|
||||||
|
}
|
||||||
|
this.audioSource = audioSource;
|
||||||
|
break;
|
||||||
|
case "audio_dup":
|
||||||
|
this.audioDup = Boolean.parseBoolean(value);
|
||||||
|
break;
|
||||||
|
case "max_size":
|
||||||
|
this.maxSize = Integer.parseInt(value) & ~7; // multiple of 8
|
||||||
|
break;
|
||||||
|
case "video_bit_rate":
|
||||||
|
this.videoBitRate = Integer.parseInt(value);
|
||||||
|
break;
|
||||||
|
case "audio_bit_rate":
|
||||||
|
this.audioBitRate = Integer.parseInt(value);
|
||||||
|
break;
|
||||||
|
case "max_fps":
|
||||||
|
this.maxFps = parseFloat("max_fps", value);
|
||||||
|
break;
|
||||||
|
case "angle":
|
||||||
|
this.angle = parseFloat("angle", value);
|
||||||
|
break;
|
||||||
|
case "tunnel_forward":
|
||||||
|
this.tunnelForward = Boolean.parseBoolean(value);
|
||||||
|
break;
|
||||||
|
case "crop":
|
||||||
|
if (!value.isEmpty()) {
|
||||||
|
this.crop = parseCrop(value);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case "control":
|
||||||
|
this.control = Boolean.parseBoolean(value);
|
||||||
|
break;
|
||||||
|
case "display_id":
|
||||||
|
this.displayId = Integer.parseInt(value);
|
||||||
|
break;
|
||||||
|
case "show_touches":
|
||||||
|
this.showTouches = Boolean.parseBoolean(value);
|
||||||
|
break;
|
||||||
|
case "stay_awake":
|
||||||
|
this.stayAwake = Boolean.parseBoolean(value);
|
||||||
|
break;
|
||||||
|
case "screen_off_timeout":
|
||||||
|
this.screenOffTimeout = Integer.parseInt(value);
|
||||||
|
if (this.screenOffTimeout < -1) {
|
||||||
|
throw new IllegalArgumentException("Invalid screen off timeout: " + this.screenOffTimeout);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case "video_codec_options":
|
||||||
|
this.videoCodecOptions = CodecOption.parse(value);
|
||||||
|
break;
|
||||||
|
case "audio_codec_options":
|
||||||
|
this.audioCodecOptions = CodecOption.parse(value);
|
||||||
|
break;
|
||||||
|
case "video_encoder":
|
||||||
|
if (!value.isEmpty()) {
|
||||||
|
this.videoEncoder = value;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case "audio_encoder":
|
||||||
|
if (!value.isEmpty()) {
|
||||||
|
this.audioEncoder = value;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case "power_off_on_close":
|
||||||
|
this.powerOffScreenOnClose = Boolean.parseBoolean(value);
|
||||||
|
break;
|
||||||
|
case "clipboard_autosync":
|
||||||
|
this.clipboardAutosync = Boolean.parseBoolean(value);
|
||||||
|
break;
|
||||||
|
case "downsize_on_error":
|
||||||
|
this.downsizeOnError = Boolean.parseBoolean(value);
|
||||||
|
break;
|
||||||
|
case "cleanup":
|
||||||
|
this.cleanup = Boolean.parseBoolean(value);
|
||||||
|
break;
|
||||||
|
case "power_on":
|
||||||
|
this.powerOn = Boolean.parseBoolean(value);
|
||||||
|
break;
|
||||||
|
case "list_encoders":
|
||||||
|
this.listEncoders = Boolean.parseBoolean(value);
|
||||||
|
break;
|
||||||
|
case "list_displays":
|
||||||
|
this.listDisplays = Boolean.parseBoolean(value);
|
||||||
|
break;
|
||||||
|
case "list_cameras":
|
||||||
|
this.listCameras = Boolean.parseBoolean(value);
|
||||||
|
break;
|
||||||
|
case "list_camera_sizes":
|
||||||
|
this.listCameraSizes = Boolean.parseBoolean(value);
|
||||||
|
break;
|
||||||
|
case "list_apps":
|
||||||
|
this.listApps = Boolean.parseBoolean(value);
|
||||||
|
break;
|
||||||
|
case "camera_id":
|
||||||
|
if (!value.isEmpty()) {
|
||||||
|
this.cameraId = value;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case "camera_size":
|
||||||
|
if (!value.isEmpty()) {
|
||||||
|
this.cameraSize = parseSize(value);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case "camera_facing":
|
||||||
|
if (!value.isEmpty()) {
|
||||||
|
CameraFacing facing = CameraFacing.findByName(value);
|
||||||
|
if (facing == null) {
|
||||||
|
throw new IllegalArgumentException("Camera facing " + value + " not supported");
|
||||||
|
}
|
||||||
|
this.cameraFacing = facing;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case "camera_ar":
|
||||||
|
if (!value.isEmpty()) {
|
||||||
|
this.cameraAspectRatio = parseCameraAspectRatio(value);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case "camera_fps":
|
||||||
|
this.cameraFps = Integer.parseInt(value);
|
||||||
|
break;
|
||||||
|
case "camera_high_speed":
|
||||||
|
this.cameraHighSpeed = Boolean.parseBoolean(value);
|
||||||
|
break;
|
||||||
|
case "new_display":
|
||||||
|
this.newDisplay = parseNewDisplay(value);
|
||||||
|
break;
|
||||||
|
case "vd_destroy_content":
|
||||||
|
this.vdDestroyContent = Boolean.parseBoolean(value);
|
||||||
|
break;
|
||||||
|
case "vd_system_decorations":
|
||||||
|
this.vdSystemDecorations = Boolean.parseBoolean(value);
|
||||||
|
break;
|
||||||
|
case "capture_orientation":
|
||||||
|
Pair<Orientation.Lock, Orientation> pair = parseCaptureOrientation(value);
|
||||||
|
this.captureOrientationLock = pair.first;
|
||||||
|
this.captureOrientation = pair.second;
|
||||||
|
break;
|
||||||
|
case "display_ime_policy":
|
||||||
|
this.displayImePolicy = parseDisplayImePolicy(value);
|
||||||
|
break;
|
||||||
|
case "send_device_meta":
|
||||||
|
this.sendDeviceMeta = Boolean.parseBoolean(value);
|
||||||
|
break;
|
||||||
|
case "send_frame_meta":
|
||||||
|
this.sendFrameMeta = Boolean.parseBoolean(value);
|
||||||
|
break;
|
||||||
|
case "send_dummy_byte":
|
||||||
|
this.sendDummyByte = Boolean.parseBoolean(value);
|
||||||
|
break;
|
||||||
|
case "send_codec_meta":
|
||||||
|
this.sendCodecMeta = Boolean.parseBoolean(value);
|
||||||
|
break;
|
||||||
|
case "raw_stream":
|
||||||
|
boolean rawStream = Boolean.parseBoolean(value);
|
||||||
|
if (rawStream) {
|
||||||
|
this.sendDeviceMeta = false;
|
||||||
|
this.sendFrameMeta = false;
|
||||||
|
this.sendDummyByte = false;
|
||||||
|
this.sendCodecMeta = false;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case "net_args":
|
||||||
|
this.netArgs = Boolean.parseBoolean(value);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
Ln.w("Unknown server option: " + key);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@SuppressWarnings("MethodLength")
|
@SuppressWarnings("MethodLength")
|
||||||
public static Options parse(String... args) {
|
public static Options parse(String... args) {
|
||||||
if (args.length < 1) {
|
if (args.length < 1) {
|
||||||
|
@ -310,212 +528,7 @@ public class Options {
|
||||||
}
|
}
|
||||||
String key = arg.substring(0, equalIndex);
|
String key = arg.substring(0, equalIndex);
|
||||||
String value = arg.substring(equalIndex + 1);
|
String value = arg.substring(equalIndex + 1);
|
||||||
switch (key) {
|
options.parseKeyValue(key, value);
|
||||||
case "scid":
|
|
||||||
int scid = Integer.parseInt(value, 0x10);
|
|
||||||
if (scid < -1) {
|
|
||||||
throw new IllegalArgumentException("scid may not be negative (except -1 for 'none'): " + scid);
|
|
||||||
}
|
|
||||||
options.scid = scid;
|
|
||||||
break;
|
|
||||||
case "log_level":
|
|
||||||
options.logLevel = Ln.Level.valueOf(value.toUpperCase(Locale.ENGLISH));
|
|
||||||
break;
|
|
||||||
case "video":
|
|
||||||
options.video = Boolean.parseBoolean(value);
|
|
||||||
break;
|
|
||||||
case "audio":
|
|
||||||
options.audio = Boolean.parseBoolean(value);
|
|
||||||
break;
|
|
||||||
case "video_codec":
|
|
||||||
VideoCodec videoCodec = VideoCodec.findByName(value);
|
|
||||||
if (videoCodec == null) {
|
|
||||||
throw new IllegalArgumentException("Video codec " + value + " not supported");
|
|
||||||
}
|
|
||||||
options.videoCodec = videoCodec;
|
|
||||||
break;
|
|
||||||
case "audio_codec":
|
|
||||||
AudioCodec audioCodec = AudioCodec.findByName(value);
|
|
||||||
if (audioCodec == null) {
|
|
||||||
throw new IllegalArgumentException("Audio codec " + value + " not supported");
|
|
||||||
}
|
|
||||||
options.audioCodec = audioCodec;
|
|
||||||
break;
|
|
||||||
case "video_source":
|
|
||||||
VideoSource videoSource = VideoSource.findByName(value);
|
|
||||||
if (videoSource == null) {
|
|
||||||
throw new IllegalArgumentException("Video source " + value + " not supported");
|
|
||||||
}
|
|
||||||
options.videoSource = videoSource;
|
|
||||||
break;
|
|
||||||
case "audio_source":
|
|
||||||
AudioSource audioSource = AudioSource.findByName(value);
|
|
||||||
if (audioSource == null) {
|
|
||||||
throw new IllegalArgumentException("Audio source " + value + " not supported");
|
|
||||||
}
|
|
||||||
options.audioSource = audioSource;
|
|
||||||
break;
|
|
||||||
case "audio_dup":
|
|
||||||
options.audioDup = Boolean.parseBoolean(value);
|
|
||||||
break;
|
|
||||||
case "max_size":
|
|
||||||
options.maxSize = Integer.parseInt(value) & ~7; // multiple of 8
|
|
||||||
break;
|
|
||||||
case "video_bit_rate":
|
|
||||||
options.videoBitRate = Integer.parseInt(value);
|
|
||||||
break;
|
|
||||||
case "audio_bit_rate":
|
|
||||||
options.audioBitRate = Integer.parseInt(value);
|
|
||||||
break;
|
|
||||||
case "max_fps":
|
|
||||||
options.maxFps = parseFloat("max_fps", value);
|
|
||||||
break;
|
|
||||||
case "angle":
|
|
||||||
options.angle = parseFloat("angle", value);
|
|
||||||
break;
|
|
||||||
case "tunnel_forward":
|
|
||||||
options.tunnelForward = Boolean.parseBoolean(value);
|
|
||||||
break;
|
|
||||||
case "crop":
|
|
||||||
if (!value.isEmpty()) {
|
|
||||||
options.crop = parseCrop(value);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case "control":
|
|
||||||
options.control = Boolean.parseBoolean(value);
|
|
||||||
break;
|
|
||||||
case "display_id":
|
|
||||||
options.displayId = Integer.parseInt(value);
|
|
||||||
break;
|
|
||||||
case "show_touches":
|
|
||||||
options.showTouches = Boolean.parseBoolean(value);
|
|
||||||
break;
|
|
||||||
case "stay_awake":
|
|
||||||
options.stayAwake = Boolean.parseBoolean(value);
|
|
||||||
break;
|
|
||||||
case "screen_off_timeout":
|
|
||||||
options.screenOffTimeout = Integer.parseInt(value);
|
|
||||||
if (options.screenOffTimeout < -1) {
|
|
||||||
throw new IllegalArgumentException("Invalid screen off timeout: " + options.screenOffTimeout);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case "video_codec_options":
|
|
||||||
options.videoCodecOptions = CodecOption.parse(value);
|
|
||||||
break;
|
|
||||||
case "audio_codec_options":
|
|
||||||
options.audioCodecOptions = CodecOption.parse(value);
|
|
||||||
break;
|
|
||||||
case "video_encoder":
|
|
||||||
if (!value.isEmpty()) {
|
|
||||||
options.videoEncoder = value;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case "audio_encoder":
|
|
||||||
if (!value.isEmpty()) {
|
|
||||||
options.audioEncoder = value;
|
|
||||||
}
|
|
||||||
case "power_off_on_close":
|
|
||||||
options.powerOffScreenOnClose = Boolean.parseBoolean(value);
|
|
||||||
break;
|
|
||||||
case "clipboard_autosync":
|
|
||||||
options.clipboardAutosync = Boolean.parseBoolean(value);
|
|
||||||
break;
|
|
||||||
case "downsize_on_error":
|
|
||||||
options.downsizeOnError = Boolean.parseBoolean(value);
|
|
||||||
break;
|
|
||||||
case "cleanup":
|
|
||||||
options.cleanup = Boolean.parseBoolean(value);
|
|
||||||
break;
|
|
||||||
case "power_on":
|
|
||||||
options.powerOn = Boolean.parseBoolean(value);
|
|
||||||
break;
|
|
||||||
case "list_encoders":
|
|
||||||
options.listEncoders = Boolean.parseBoolean(value);
|
|
||||||
break;
|
|
||||||
case "list_displays":
|
|
||||||
options.listDisplays = Boolean.parseBoolean(value);
|
|
||||||
break;
|
|
||||||
case "list_cameras":
|
|
||||||
options.listCameras = Boolean.parseBoolean(value);
|
|
||||||
break;
|
|
||||||
case "list_camera_sizes":
|
|
||||||
options.listCameraSizes = Boolean.parseBoolean(value);
|
|
||||||
break;
|
|
||||||
case "list_apps":
|
|
||||||
options.listApps = Boolean.parseBoolean(value);
|
|
||||||
break;
|
|
||||||
case "camera_id":
|
|
||||||
if (!value.isEmpty()) {
|
|
||||||
options.cameraId = value;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case "camera_size":
|
|
||||||
if (!value.isEmpty()) {
|
|
||||||
options.cameraSize = parseSize(value);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case "camera_facing":
|
|
||||||
if (!value.isEmpty()) {
|
|
||||||
CameraFacing facing = CameraFacing.findByName(value);
|
|
||||||
if (facing == null) {
|
|
||||||
throw new IllegalArgumentException("Camera facing " + value + " not supported");
|
|
||||||
}
|
|
||||||
options.cameraFacing = facing;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case "camera_ar":
|
|
||||||
if (!value.isEmpty()) {
|
|
||||||
options.cameraAspectRatio = parseCameraAspectRatio(value);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case "camera_fps":
|
|
||||||
options.cameraFps = Integer.parseInt(value);
|
|
||||||
break;
|
|
||||||
case "camera_high_speed":
|
|
||||||
options.cameraHighSpeed = Boolean.parseBoolean(value);
|
|
||||||
break;
|
|
||||||
case "new_display":
|
|
||||||
options.newDisplay = parseNewDisplay(value);
|
|
||||||
break;
|
|
||||||
case "vd_destroy_content":
|
|
||||||
options.vdDestroyContent = Boolean.parseBoolean(value);
|
|
||||||
break;
|
|
||||||
case "vd_system_decorations":
|
|
||||||
options.vdSystemDecorations = Boolean.parseBoolean(value);
|
|
||||||
break;
|
|
||||||
case "capture_orientation":
|
|
||||||
Pair<Orientation.Lock, Orientation> pair = parseCaptureOrientation(value);
|
|
||||||
options.captureOrientationLock = pair.first;
|
|
||||||
options.captureOrientation = pair.second;
|
|
||||||
break;
|
|
||||||
case "display_ime_policy":
|
|
||||||
options.displayImePolicy = parseDisplayImePolicy(value);
|
|
||||||
break;
|
|
||||||
case "send_device_meta":
|
|
||||||
options.sendDeviceMeta = Boolean.parseBoolean(value);
|
|
||||||
break;
|
|
||||||
case "send_frame_meta":
|
|
||||||
options.sendFrameMeta = Boolean.parseBoolean(value);
|
|
||||||
break;
|
|
||||||
case "send_dummy_byte":
|
|
||||||
options.sendDummyByte = Boolean.parseBoolean(value);
|
|
||||||
break;
|
|
||||||
case "send_codec_meta":
|
|
||||||
options.sendCodecMeta = Boolean.parseBoolean(value);
|
|
||||||
break;
|
|
||||||
case "raw_stream":
|
|
||||||
boolean rawStream = Boolean.parseBoolean(value);
|
|
||||||
if (rawStream) {
|
|
||||||
options.sendDeviceMeta = false;
|
|
||||||
options.sendFrameMeta = false;
|
|
||||||
options.sendDummyByte = false;
|
|
||||||
options.sendCodecMeta = false;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
Ln.w("Unknown server option: " + key);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (options.newDisplay != null) {
|
if (options.newDisplay != null) {
|
||||||
|
@ -526,6 +539,18 @@ public class Options {
|
||||||
return options;
|
return options;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void parseAdditional(String... args) {
|
||||||
|
for (String arg : args) {
|
||||||
|
int equalIndex = arg.indexOf('=');
|
||||||
|
if (equalIndex == -1) {
|
||||||
|
throw new IllegalArgumentException("Invalid key=value pair: \"" + arg + "\"");
|
||||||
|
}
|
||||||
|
String key = arg.substring(0, equalIndex);
|
||||||
|
String value = arg.substring(equalIndex + 1);
|
||||||
|
parseKeyValue(key, value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private static Rect parseCrop(String crop) {
|
private static Rect parseCrop(String crop) {
|
||||||
// input format: "width:height:x:y"
|
// input format: "width:height:x:y"
|
||||||
String[] tokens = crop.split(":");
|
String[] tokens = crop.split(":");
|
||||||
|
|
|
@ -17,6 +17,7 @@ import com.genymobile.scrcpy.device.Streamer;
|
||||||
import com.genymobile.scrcpy.opengl.OpenGLRunner;
|
import com.genymobile.scrcpy.opengl.OpenGLRunner;
|
||||||
import com.genymobile.scrcpy.util.Ln;
|
import com.genymobile.scrcpy.util.Ln;
|
||||||
import com.genymobile.scrcpy.util.LogUtils;
|
import com.genymobile.scrcpy.util.LogUtils;
|
||||||
|
import com.genymobile.scrcpy.util.StringUtils;
|
||||||
import com.genymobile.scrcpy.video.CameraCapture;
|
import com.genymobile.scrcpy.video.CameraCapture;
|
||||||
import com.genymobile.scrcpy.video.NewDisplayCapture;
|
import com.genymobile.scrcpy.video.NewDisplayCapture;
|
||||||
import com.genymobile.scrcpy.video.ScreenCapture;
|
import com.genymobile.scrcpy.video.ScreenCapture;
|
||||||
|
@ -75,22 +76,6 @@ public final class Server {
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void scrcpy(Options options) throws IOException, ConfigurationException {
|
private static void scrcpy(Options options) throws IOException, ConfigurationException {
|
||||||
if (Build.VERSION.SDK_INT < AndroidVersions.API_31_ANDROID_12 && options.getVideoSource() == VideoSource.CAMERA) {
|
|
||||||
Ln.e("Camera mirroring is not supported before Android 12");
|
|
||||||
throw new ConfigurationException("Camera mirroring 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;
|
CleanUp cleanUp = null;
|
||||||
|
|
||||||
if (options.getCleanup()) {
|
if (options.getCleanup()) {
|
||||||
|
@ -110,6 +95,36 @@ public final class Server {
|
||||||
|
|
||||||
DesktopConnection connection = DesktopConnection.open(scid, tunnelForward, video, audio, control, sendDummyByte);
|
DesktopConnection connection = DesktopConnection.open(scid, tunnelForward, video, audio, control, sendDummyByte);
|
||||||
try {
|
try {
|
||||||
|
if (options.getEnableNetworkArgs()) {
|
||||||
|
Ln.d("Waiting for additional args (JSON) ...");
|
||||||
|
String additionalOptions = connection.receiveAdditionalOptions();
|
||||||
|
|
||||||
|
if (additionalOptions != null && !additionalOptions.isEmpty()) {
|
||||||
|
Ln.d("Received additional options: " + additionalOptions);
|
||||||
|
String args = StringUtils.jsonToArgs(additionalOptions);
|
||||||
|
Ln.d("Additional args: " + args);
|
||||||
|
options.parseAdditional(args.split(" "));
|
||||||
|
} else {
|
||||||
|
Ln.d("No additional args received.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Build.VERSION.SDK_INT < AndroidVersions.API_31_ANDROID_12 && options.getVideoSource() == VideoSource.CAMERA) {
|
||||||
|
Ln.e("Camera mirroring is not supported before Android 12");
|
||||||
|
throw new ConfigurationException("Camera mirroring 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");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (options.getSendDeviceMeta()) {
|
if (options.getSendDeviceMeta()) {
|
||||||
connection.sendDeviceMeta(Device.getDeviceName());
|
connection.sendDeviceMeta(Device.getDeviceName());
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
package com.genymobile.scrcpy.device;
|
package com.genymobile.scrcpy.device;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
import com.genymobile.scrcpy.control.ControlChannel;
|
import com.genymobile.scrcpy.control.ControlChannel;
|
||||||
import com.genymobile.scrcpy.util.IO;
|
import com.genymobile.scrcpy.util.IO;
|
||||||
import com.genymobile.scrcpy.util.StringUtils;
|
import com.genymobile.scrcpy.util.StringUtils;
|
||||||
|
@ -9,9 +10,11 @@ import android.net.LocalSocket;
|
||||||
import android.net.LocalSocketAddress;
|
import android.net.LocalSocketAddress;
|
||||||
|
|
||||||
import java.io.Closeable;
|
import java.io.Closeable;
|
||||||
|
import java.io.DataInputStream;
|
||||||
import java.io.FileDescriptor;
|
import java.io.FileDescriptor;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.nio.charset.StandardCharsets;
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
public final class DesktopConnection implements Closeable {
|
public final class DesktopConnection implements Closeable {
|
||||||
|
|
||||||
|
@ -164,6 +167,28 @@ public final class DesktopConnection implements Closeable {
|
||||||
IO.writeFully(fd, buffer, 0, buffer.length);
|
IO.writeFully(fd, buffer, 0, buffer.length);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public String receiveAdditionalOptions() throws IOException {
|
||||||
|
LocalSocket socket = getFirstSocket(); // or choose a specific one
|
||||||
|
DataInputStream input = new DataInputStream(socket.getInputStream());
|
||||||
|
|
||||||
|
// Read length prefix (4 bytes, big-endian)
|
||||||
|
int length = input.readInt(); // throws if the socket closes or data is invalid
|
||||||
|
|
||||||
|
if (length == 0) {
|
||||||
|
return null; // No additional options sent
|
||||||
|
}
|
||||||
|
|
||||||
|
if (length < 0 || length > 10 * 1024 * 1024) { // Limit to 10MB to avoid OOM
|
||||||
|
throw new IOException("Invalid JSON message length: " + length);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read the JSON payload
|
||||||
|
byte[] jsonBytes = new byte[length];
|
||||||
|
input.readFully(jsonBytes);
|
||||||
|
|
||||||
|
return new String(jsonBytes, StandardCharsets.UTF_8);
|
||||||
|
}
|
||||||
|
|
||||||
public FileDescriptor getVideoFd() {
|
public FileDescriptor getVideoFd() {
|
||||||
return videoFd;
|
return videoFd;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,10 @@
|
||||||
package com.genymobile.scrcpy.util;
|
package com.genymobile.scrcpy.util;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
public final class StringUtils {
|
public final class StringUtils {
|
||||||
private StringUtils() {
|
private StringUtils() {
|
||||||
// not instantiable
|
// not instantiable
|
||||||
|
@ -19,4 +24,22 @@ public final class StringUtils {
|
||||||
}
|
}
|
||||||
return len;
|
return len;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static String jsonToArgs(String json) throws IOException {
|
||||||
|
// Parse the JSON string into a map
|
||||||
|
ObjectMapper mapper = new ObjectMapper();
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
Map<String, Object> map = mapper.readValue(json, Map.class);
|
||||||
|
|
||||||
|
// Convert to key=value arguments
|
||||||
|
StringBuilder sb = new StringBuilder();
|
||||||
|
for (Map.Entry<String, Object> entry : map.entrySet()) {
|
||||||
|
if (sb.length() > 0) {
|
||||||
|
sb.append(' ');
|
||||||
|
}
|
||||||
|
sb.append(entry.getKey()).append('=').append(entry.getValue());
|
||||||
|
}
|
||||||
|
|
||||||
|
return sb.toString();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue