diff --git a/server/build.gradle b/server/build.gradle index 1f939a1..dbc8261 100644 --- a/server/build.gradle +++ b/server/build.gradle @@ -6,8 +6,8 @@ android { applicationId "com.genymobile.scrcpy" minSdkVersion 21 targetSdkVersion 31 - versionCode 12100 - versionName "1.21" + versionCode 12400 + versionName "1.24" testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" } buildTypes { diff --git a/server/build_without_gradle.sh b/server/build_without_gradle.sh old mode 100644 new mode 100755 index 0f86c29..c881e38 --- a/server/build_without_gradle.sh +++ b/server/build_without_gradle.sh @@ -12,10 +12,9 @@ set -e SCRCPY_DEBUG=false -SCRCPY_VERSION_NAME=1.21 +SCRCPY_VERSION_NAME=1.24 -PLATFORM_VERSION=31 -PLATFORM=${ANDROID_PLATFORM:-$PLATFORM_VERSION} +PLATFORM=${ANDROID_PLATFORM:-31} BUILD_TOOLS=${ANDROID_BUILD_TOOLS:-31.0.0} BUILD_DIR="$(realpath ${BUILD_DIR:-build_manual})" @@ -57,7 +56,7 @@ javac -bootclasspath "$ANDROID_JAR" -cp "$CLASSES_DIR" -d "$CLASSES_DIR" \ echo "Dexing..." cd "$CLASSES_DIR" -if [[ $PLATFORM_VERSION -lt 31 ]] +if [[ $PLATFORM -lt 31 ]] then # use dx "$ANDROID_HOME/build-tools/$BUILD_TOOLS/dx" --dex \ diff --git a/server/config/android-checkstyle.gradle b/server/config/android-checkstyle.gradle deleted file mode 100644 index f998530..0000000 --- a/server/config/android-checkstyle.gradle +++ /dev/null @@ -1,28 +0,0 @@ -apply plugin: 'checkstyle' -check.dependsOn 'checkstyle' - -checkstyle { - toolVersion = '6.19' -} - -task checkstyle(type: Checkstyle) { - description = "Check Java style with Checkstyle" - configFile = rootProject.file("config/checkstyle/checkstyle.xml") - source = javaSources() - classpath = files() - ignoreFailures = true -} - -def javaSources() { - def files = [] - android.sourceSets.each { sourceSet -> - sourceSet.java.each { javaSource -> - javaSource.getSrcDirs().each { - if (it.exists()) { - files.add(it) - } - } - } - } - return files -} diff --git a/server/src/main/java/com/genymobile/scrcpy/ControlMessage.java b/server/src/main/java/com/genymobile/scrcpy/ControlMessage.java index 63ba0fa..99eb805 100644 --- a/server/src/main/java/com/genymobile/scrcpy/ControlMessage.java +++ b/server/src/main/java/com/genymobile/scrcpy/ControlMessage.java @@ -71,12 +71,13 @@ public final class ControlMessage { return msg; } - public static ControlMessage createInjectScrollEvent(Position position, int hScroll, int vScroll) { + public static ControlMessage createInjectScrollEvent(Position position, int hScroll, int vScroll, int buttons) { ControlMessage msg = new ControlMessage(); msg.type = TYPE_INJECT_SCROLL_EVENT; msg.position = position; msg.hScroll = hScroll; msg.vScroll = vScroll; + msg.buttons = buttons; return msg; } diff --git a/server/src/main/java/com/genymobile/scrcpy/ControlMessageReader.java b/server/src/main/java/com/genymobile/scrcpy/ControlMessageReader.java index f09ed26..24dc5e5 100644 --- a/server/src/main/java/com/genymobile/scrcpy/ControlMessageReader.java +++ b/server/src/main/java/com/genymobile/scrcpy/ControlMessageReader.java @@ -10,7 +10,7 @@ public class ControlMessageReader { static final int INJECT_KEYCODE_PAYLOAD_LENGTH = 13; static final int INJECT_TOUCH_EVENT_PAYLOAD_LENGTH = 27; - static final int INJECT_SCROLL_EVENT_PAYLOAD_LENGTH = 20; + static final int INJECT_SCROLL_EVENT_PAYLOAD_LENGTH = 24; static final int BACK_OR_SCREEN_ON_LENGTH = 1; static final int SET_SCREEN_POWER_MODE_PAYLOAD_LENGTH = 1; static final int GET_CLIPBOARD_LENGTH = 1; @@ -154,7 +154,8 @@ public class ControlMessageReader { Position position = readPosition(buffer); int hScroll = buffer.getInt(); int vScroll = buffer.getInt(); - return ControlMessage.createInjectScrollEvent(position, hScroll, vScroll); + int buttons = buffer.getInt(); + return ControlMessage.createInjectScrollEvent(position, hScroll, vScroll, buttons); } private ControlMessage parseBackOrScreenOnEvent() { diff --git a/server/src/main/java/com/genymobile/scrcpy/Controller.java b/server/src/main/java/com/genymobile/scrcpy/Controller.java index 9246004..913371e 100644 --- a/server/src/main/java/com/genymobile/scrcpy/Controller.java +++ b/server/src/main/java/com/genymobile/scrcpy/Controller.java @@ -22,6 +22,7 @@ public class Controller { private final DesktopConnection connection; private final DeviceMessageSender sender; private final boolean clipboardAutosync; + private final boolean powerOn; private final KeyCharacterMap charMap = KeyCharacterMap.load(KeyCharacterMap.VIRTUAL_KEYBOARD); @@ -32,10 +33,11 @@ public class Controller { private boolean keepPowerModeOff; - public Controller(Device device, DesktopConnection connection, boolean clipboardAutosync) { + public Controller(Device device, DesktopConnection connection, boolean clipboardAutosync, boolean powerOn) { this.device = device; this.connection = connection; this.clipboardAutosync = clipboardAutosync; + this.powerOn = powerOn; initPointers(); sender = new DeviceMessageSender(connection); } @@ -56,7 +58,7 @@ public class Controller { public void control() throws IOException { // on start, power on the device - if (!Device.isScreenOn()) { + if (powerOn && !Device.isScreenOn()) { device.pressReleaseKeycode(KeyEvent.KEYCODE_POWER, Device.INJECT_MODE_ASYNC); // dirty hack @@ -98,7 +100,7 @@ public class Controller { break; case ControlMessage.TYPE_INJECT_SCROLL_EVENT: if (device.supportsInputEvents()) { - injectScroll(msg.getPosition(), msg.getHScroll(), msg.getVScroll()); + injectScroll(msg.getPosition(), msg.getHScroll(), msg.getVScroll(), msg.getButtons()); } break; case ControlMessage.TYPE_BACK_OR_SCREEN_ON: @@ -221,7 +223,7 @@ public class Controller { return device.injectEvent(event, Device.INJECT_MODE_ASYNC); } - private boolean injectScroll(Position position, int hScroll, int vScroll) { + private boolean injectScroll(Position position, int hScroll, int vScroll, int buttons) { long now = SystemClock.uptimeMillis(); Point point = device.getPhysicalPoint(position); if (point == null) { @@ -239,7 +241,7 @@ public class Controller { coords.setAxisValue(MotionEvent.AXIS_VSCROLL, vScroll); MotionEvent event = MotionEvent - .obtain(lastTouchDown, now, MotionEvent.ACTION_SCROLL, 1, pointerProperties, pointerCoords, 0, 0, 1f, 1f, DEFAULT_DEVICE_ID, 0, + .obtain(lastTouchDown, now, MotionEvent.ACTION_SCROLL, 1, pointerProperties, pointerCoords, 0, buttons, 1f, 1f, DEFAULT_DEVICE_ID, 0, InputDevice.SOURCE_MOUSE, 0); return device.injectEvent(event, Device.INJECT_MODE_ASYNC); } diff --git a/server/src/main/java/com/genymobile/scrcpy/DesktopConnection.java b/server/src/main/java/com/genymobile/scrcpy/DesktopConnection.java index 0ec4304..78728d8 100644 --- a/server/src/main/java/com/genymobile/scrcpy/DesktopConnection.java +++ b/server/src/main/java/com/genymobile/scrcpy/DesktopConnection.java @@ -30,8 +30,13 @@ public final class DesktopConnection implements Closeable { private DesktopConnection(LocalSocket videoSocket, LocalSocket controlSocket) throws IOException { this.videoSocket = videoSocket; this.controlSocket = controlSocket; - controlInputStream = controlSocket.getInputStream(); - controlOutputStream = controlSocket.getOutputStream(); + if (controlSocket != null) { + controlInputStream = controlSocket.getInputStream(); + controlOutputStream = controlSocket.getOutputStream(); + } else { + controlInputStream = null; + controlOutputStream = null; + } videoFd = videoSocket.getFileDescriptor(); } @@ -41,50 +46,55 @@ public final class DesktopConnection implements Closeable { return localSocket; } - public static DesktopConnection open(Device device, boolean tunnelForward) throws IOException { + public static DesktopConnection open(boolean tunnelForward, boolean control, boolean sendDummyByte) throws IOException { LocalSocket videoSocket; - LocalSocket controlSocket; + LocalSocket controlSocket = null; if (tunnelForward) { LocalServerSocket localServerSocket = new LocalServerSocket(SOCKET_NAME); try { videoSocket = localServerSocket.accept(); - // send one byte so the client may read() to detect a connection error - videoSocket.getOutputStream().write(0); - try { - controlSocket = localServerSocket.accept(); - } catch (IOException | RuntimeException e) { - videoSocket.close(); - throw e; + if (sendDummyByte) { + // send one byte so the client may read() to detect a connection error + videoSocket.getOutputStream().write(0); + } + if (control) { + try { + controlSocket = localServerSocket.accept(); + } catch (IOException | RuntimeException e) { + videoSocket.close(); + throw e; + } } } finally { localServerSocket.close(); } } else { videoSocket = connect(SOCKET_NAME); - try { - controlSocket = connect(SOCKET_NAME); - } catch (IOException | RuntimeException e) { - videoSocket.close(); - throw e; + if (control) { + try { + controlSocket = connect(SOCKET_NAME); + } catch (IOException | RuntimeException e) { + videoSocket.close(); + throw e; + } } } - DesktopConnection connection = new DesktopConnection(videoSocket, controlSocket); - Size videoSize = device.getScreenInfo().getVideoSize(); - connection.send(Device.getDeviceName(), videoSize.getWidth(), videoSize.getHeight()); - return connection; + return new DesktopConnection(videoSocket, controlSocket); } public void close() throws IOException { videoSocket.shutdownInput(); videoSocket.shutdownOutput(); videoSocket.close(); - controlSocket.shutdownInput(); - controlSocket.shutdownOutput(); - controlSocket.close(); + if (controlSocket != null) { + controlSocket.shutdownInput(); + controlSocket.shutdownOutput(); + controlSocket.close(); + } } - private void send(String deviceName, int width, int height) throws IOException { + public void sendDeviceMeta(String deviceName, int width, int height) throws IOException { byte[] buffer = new byte[DEVICE_NAME_FIELD_LENGTH + 4]; byte[] deviceNameBytes = deviceName.getBytes(StandardCharsets.UTF_8); diff --git a/server/src/main/java/com/genymobile/scrcpy/Device.java b/server/src/main/java/com/genymobile/scrcpy/Device.java index ba833a0..763a7fa 100644 --- a/server/src/main/java/com/genymobile/scrcpy/Device.java +++ b/server/src/main/java/com/genymobile/scrcpy/Device.java @@ -42,6 +42,11 @@ public final class Device { void onClipboardTextChanged(String text); } + private final Size deviceSize; + private final Rect crop; + private int maxSize; + private final int lockVideoOrientation; + private ScreenInfo screenInfo; private RotationListener rotationListener; private ClipboardListener clipboardListener; @@ -69,7 +74,12 @@ public final class Device { int displayInfoFlags = displayInfo.getFlags(); - screenInfo = ScreenInfo.computeScreenInfo(displayInfo, options.getCrop(), options.getMaxSize(), options.getLockVideoOrientation()); + deviceSize = displayInfo.getSize(); + crop = options.getCrop(); + maxSize = options.getMaxSize(); + lockVideoOrientation = options.getLockVideoOrientation(); + + screenInfo = ScreenInfo.computeScreenInfo(displayInfo.getRotation(), deviceSize, crop, maxSize, lockVideoOrientation); layerStack = displayInfo.getLayerStack(); SERVICE_MANAGER.getWindowManager().registerRotationWatcher(new IRotationWatcher.Stub() { @@ -123,6 +133,11 @@ public final class Device { } } + public synchronized void setMaxSize(int newMaxSize) { + maxSize = newMaxSize; + screenInfo = ScreenInfo.computeScreenInfo(screenInfo.getReverseVideoRotation(), deviceSize, crop, newMaxSize, lockVideoOrientation); + } + public synchronized ScreenInfo getScreenInfo() { return screenInfo; } diff --git a/server/src/main/java/com/genymobile/scrcpy/Options.java b/server/src/main/java/com/genymobile/scrcpy/Options.java index 1ac1717..d1607c2 100644 --- a/server/src/main/java/com/genymobile/scrcpy/Options.java +++ b/server/src/main/java/com/genymobile/scrcpy/Options.java @@ -12,7 +12,6 @@ public class Options { private int lockVideoOrientation = -1; private boolean tunnelForward; private Rect crop; - private boolean sendFrameMeta = true; // send PTS so that the client may record properly private boolean control = true; private int displayId; private boolean showTouches; @@ -21,6 +20,14 @@ public class Options { private String encoderName; private boolean powerOffScreenOnClose; private boolean clipboardAutosync = true; + private boolean downsizeOnError = true; + private boolean cleanup = true; + private boolean powerOn = true; + + // Options not used by the scrcpy client, but useful to use scrcpy-server directly + private boolean sendDeviceMeta = true; // send device name and size + 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 public Ln.Level getLogLevel() { return logLevel; @@ -78,14 +85,6 @@ public class Options { this.crop = crop; } - public boolean getSendFrameMeta() { - return sendFrameMeta; - } - - public void setSendFrameMeta(boolean sendFrameMeta) { - this.sendFrameMeta = sendFrameMeta; - } - public boolean getControl() { return control; } @@ -149,4 +148,52 @@ public class Options { public void setClipboardAutosync(boolean clipboardAutosync) { this.clipboardAutosync = clipboardAutosync; } + + public boolean getDownsizeOnError() { + return downsizeOnError; + } + + public void setDownsizeOnError(boolean downsizeOnError) { + this.downsizeOnError = downsizeOnError; + } + + public boolean getCleanup() { + return cleanup; + } + + public void setCleanup(boolean cleanup) { + this.cleanup = cleanup; + } + + public boolean getPowerOn() { + return powerOn; + } + + public void setPowerOn(boolean powerOn) { + this.powerOn = powerOn; + } + + public boolean getSendDeviceMeta() { + return sendDeviceMeta; + } + + public void setSendDeviceMeta(boolean sendDeviceMeta) { + this.sendDeviceMeta = sendDeviceMeta; + } + + public boolean getSendFrameMeta() { + return sendFrameMeta; + } + + public void setSendFrameMeta(boolean sendFrameMeta) { + this.sendFrameMeta = sendFrameMeta; + } + + public boolean getSendDummyByte() { + return sendDummyByte; + } + + public void setSendDummyByte(boolean sendDummyByte) { + this.sendDummyByte = sendDummyByte; + } } diff --git a/server/src/main/java/com/genymobile/scrcpy/ScreenEncoder.java b/server/src/main/java/com/genymobile/scrcpy/ScreenEncoder.java index f98c53d..e95896d 100644 --- a/server/src/main/java/com/genymobile/scrcpy/ScreenEncoder.java +++ b/server/src/main/java/com/genymobile/scrcpy/ScreenEncoder.java @@ -25,24 +25,33 @@ public class ScreenEncoder implements Device.RotationListener { private static final int REPEAT_FRAME_DELAY_US = 100_000; // repeat after 100ms private static final String KEY_MAX_FPS_TO_ENCODER = "max-fps-to-encoder"; - private static final int NO_PTS = -1; + // Keep the values in descending order + private static final int[] MAX_SIZE_FALLBACK = {2560, 1920, 1600, 1280, 1024, 800}; + + private static final long PACKET_FLAG_CONFIG = 1L << 63; + private static final long PACKET_FLAG_KEY_FRAME = 1L << 62; private final AtomicBoolean rotationChanged = new AtomicBoolean(); private final ByteBuffer headerBuffer = ByteBuffer.allocate(12); - private String encoderName; - private List codecOptions; - private int bitRate; - private int maxFps; - private boolean sendFrameMeta; + private final String encoderName; + private final List codecOptions; + private final int bitRate; + private final int maxFps; + private final boolean sendFrameMeta; + private final boolean downsizeOnError; private long ptsOrigin; - public ScreenEncoder(boolean sendFrameMeta, int bitRate, int maxFps, List codecOptions, String encoderName) { + private boolean firstFrameSent; + + public ScreenEncoder(boolean sendFrameMeta, int bitRate, int maxFps, List codecOptions, String encoderName, + boolean downsizeOnError) { this.sendFrameMeta = sendFrameMeta; this.bitRate = bitRate; this.maxFps = maxFps; this.codecOptions = codecOptions; this.encoderName = encoderName; + this.downsizeOnError = downsizeOnError; } @Override @@ -81,20 +90,41 @@ public class ScreenEncoder implements Device.RotationListener { Rect unlockedVideoRect = screenInfo.getUnlockedVideoSize().toRect(); int videoRotation = screenInfo.getVideoRotation(); int layerStack = device.getLayerStack(); - setSize(format, videoRect.width(), videoRect.height()); - configure(codec, format); - Surface surface = codec.createInputSurface(); - setDisplaySurface(display, surface, videoRotation, contentRect, unlockedVideoRect, layerStack); - codec.start(); + + Surface surface = null; try { + configure(codec, format); + surface = codec.createInputSurface(); + setDisplaySurface(display, surface, videoRotation, contentRect, unlockedVideoRect, layerStack); + codec.start(); + alive = encode(codec, fd); // do not call stop() on exception, it would trigger an IllegalStateException codec.stop(); + } catch (IllegalStateException | IllegalArgumentException e) { + Ln.e("Encoding error: " + e.getClass().getName() + ": " + e.getMessage()); + if (!downsizeOnError || firstFrameSent) { + // Fail immediately + throw e; + } + + int newMaxSize = chooseMaxSizeFallback(screenInfo.getVideoSize()); + if (newMaxSize == 0) { + // Definitively fail + throw e; + } + + // Retry with a smaller device size + Ln.i("Retrying with -m" + newMaxSize + "..."); + device.setMaxSize(newMaxSize); + alive = true; } finally { destroyDisplay(display); codec.release(); - surface.release(); + if (surface != null) { + surface.release(); + } } } while (alive); } finally { @@ -102,6 +132,18 @@ public class ScreenEncoder implements Device.RotationListener { } } + private static int chooseMaxSizeFallback(Size failedSize) { + int currentMaxSize = Math.max(failedSize.getWidth(), failedSize.getHeight()); + for (int value : MAX_SIZE_FALLBACK) { + if (value < currentMaxSize) { + // We found a smaller value to reduce the video size + return value; + } + } + // No fallback, fail definitively + return 0; + } + private boolean encode(MediaCodec codec, FileDescriptor fd) throws IOException { boolean eof = false; MediaCodec.BufferInfo bufferInfo = new MediaCodec.BufferInfo(); @@ -122,6 +164,10 @@ public class ScreenEncoder implements Device.RotationListener { } IO.writeFully(fd, codecBuffer); + if ((bufferInfo.flags & MediaCodec.BUFFER_FLAG_CODEC_CONFIG) == 0) { + // If this is not a config packet, then it contains a frame + firstFrameSent = true; + } } } finally { if (outputBufferId >= 0) { @@ -138,12 +184,15 @@ public class ScreenEncoder implements Device.RotationListener { long pts; if ((bufferInfo.flags & MediaCodec.BUFFER_FLAG_CODEC_CONFIG) != 0) { - pts = NO_PTS; // non-media data packet + pts = PACKET_FLAG_CONFIG; // non-media data packet } else { if (ptsOrigin == 0) { ptsOrigin = bufferInfo.presentationTimeUs; } pts = bufferInfo.presentationTimeUs - ptsOrigin; + if ((bufferInfo.flags & MediaCodec.BUFFER_FLAG_KEY_FRAME) != 0) { + pts |= PACKET_FLAG_KEY_FRAME; + } } headerBuffer.putLong(pts); diff --git a/server/src/main/java/com/genymobile/scrcpy/ScreenInfo.java b/server/src/main/java/com/genymobile/scrcpy/ScreenInfo.java index c27322e..8e5b401 100644 --- a/server/src/main/java/com/genymobile/scrcpy/ScreenInfo.java +++ b/server/src/main/java/com/genymobile/scrcpy/ScreenInfo.java @@ -80,15 +80,12 @@ public final class ScreenInfo { return new ScreenInfo(newContentRect, newUnlockedVideoSize, newDeviceRotation, lockedVideoOrientation); } - public static ScreenInfo computeScreenInfo(DisplayInfo displayInfo, Rect crop, int maxSize, int lockedVideoOrientation) { - int rotation = displayInfo.getRotation(); - + public static ScreenInfo computeScreenInfo(int rotation, Size deviceSize, Rect crop, int maxSize, int lockedVideoOrientation) { if (lockedVideoOrientation == Device.LOCK_VIDEO_ORIENTATION_INITIAL) { // The user requested to lock the video orientation to the current orientation lockedVideoOrientation = rotation; } - Size deviceSize = displayInfo.getSize(); Rect contentRect = new Rect(0, 0, deviceSize.getWidth(), deviceSize.getHeight()); if (crop != null) { if (rotation % 2 != 0) { // 180s preserve dimensions diff --git a/server/src/main/java/com/genymobile/scrcpy/Server.java b/server/src/main/java/com/genymobile/scrcpy/Server.java index fc31dad..1df9155 100644 --- a/server/src/main/java/com/genymobile/scrcpy/Server.java +++ b/server/src/main/java/com/genymobile/scrcpy/Server.java @@ -1,7 +1,6 @@ package com.genymobile.scrcpy; import android.graphics.Rect; -import android.media.MediaCodec; import android.media.MediaCodecInfo; import android.os.BatteryManager; import android.os.Build; @@ -12,7 +11,6 @@ import java.util.Locale; public final class Server { - private Server() { // not instantiable } @@ -20,6 +18,7 @@ public final class Server { private static void initAndCleanUp(Options options) { boolean mustDisableShowTouchesOnCleanUp = false; int restoreStayOn = -1; + boolean restoreNormalPowerMode = options.getControl(); // only restore power mode if control is enabled if (options.getShowTouches() || options.getStayAwake()) { Settings settings = Device.getSettings(); if (options.getShowTouches()) { @@ -51,10 +50,13 @@ public final class Server { } } - try { - CleanUp.configure(options.getDisplayId(), restoreStayOn, mustDisableShowTouchesOnCleanUp, true, options.getPowerOffScreenOnClose()); - } catch (IOException e) { - Ln.e("Could not configure cleanup", e); + if (options.getCleanup()) { + try { + CleanUp.configure(options.getDisplayId(), restoreStayOn, mustDisableShowTouchesOnCleanUp, restoreNormalPowerMode, + options.getPowerOffScreenOnClose()); + } catch (IOException e) { + Ln.e("Could not configure cleanup", e); + } } } @@ -66,15 +68,21 @@ public final class Server { Thread initThread = startInitThread(options); boolean tunnelForward = options.isTunnelForward(); + boolean control = options.getControl(); + boolean sendDummyByte = options.getSendDummyByte(); - try (DesktopConnection connection = DesktopConnection.open(device, tunnelForward)) { + try (DesktopConnection connection = DesktopConnection.open(tunnelForward, control, sendDummyByte)) { + if (options.getSendDeviceMeta()) { + Size videoSize = device.getScreenInfo().getVideoSize(); + connection.sendDeviceMeta(Device.getDeviceName(), videoSize.getWidth(), videoSize.getHeight()); + } ScreenEncoder screenEncoder = new ScreenEncoder(options.getSendFrameMeta(), options.getBitRate(), options.getMaxFps(), codecOptions, - options.getEncoderName()); + options.getEncoderName(), options.getDownsizeOnError()); Thread controllerThread = null; Thread deviceMessageSenderThread = null; - if (options.getControl()) { - final Controller controller = new Controller(device, connection, options.getClipboardAutosync()); + if (control) { + final Controller controller = new Controller(device, connection, options.getClipboardAutosync(), options.getPowerOn()); // asynchronous controllerThread = startController(controller); @@ -199,10 +207,6 @@ public final class Server { Rect crop = parseCrop(value); options.setCrop(crop); break; - case "send_frame_meta": - boolean sendFrameMeta = Boolean.parseBoolean(value); - options.setSendFrameMeta(sendFrameMeta); - break; case "control": boolean control = Boolean.parseBoolean(value); options.setControl(control); @@ -236,6 +240,38 @@ public final class Server { boolean clipboardAutosync = Boolean.parseBoolean(value); options.setClipboardAutosync(clipboardAutosync); break; + case "downsize_on_error": + boolean downsizeOnError = Boolean.parseBoolean(value); + options.setDownsizeOnError(downsizeOnError); + break; + case "cleanup": + boolean cleanup = Boolean.parseBoolean(value); + options.setCleanup(cleanup); + break; + case "power_on": + boolean powerOn = Boolean.parseBoolean(value); + options.setPowerOn(powerOn); + break; + case "send_device_meta": + boolean sendDeviceMeta = Boolean.parseBoolean(value); + options.setSendDeviceMeta(sendDeviceMeta); + break; + case "send_frame_meta": + boolean sendFrameMeta = Boolean.parseBoolean(value); + options.setSendFrameMeta(sendFrameMeta); + break; + case "send_dummy_byte": + boolean sendDummyByte = Boolean.parseBoolean(value); + options.setSendDummyByte(sendDummyByte); + break; + case "raw_video_stream": + boolean rawVideoStream = Boolean.parseBoolean(value); + if (rawVideoStream) { + options.setSendDeviceMeta(false); + options.setSendFrameMeta(false); + options.setSendDummyByte(false); + } + break; default: Ln.w("Unknown server option: " + key); break; @@ -262,16 +298,6 @@ public final class Server { } private static void suggestFix(Throwable e) { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { - if (e instanceof MediaCodec.CodecException) { - MediaCodec.CodecException mce = (MediaCodec.CodecException) e; - if (mce.getErrorCode() == 0xfffffc0e) { - Ln.e("The hardware encoder is not able to encode at the given definition."); - Ln.e("Try with a lower definition:"); - Ln.e(" scrcpy -m 1024"); - } - } - } if (e instanceof InvalidDisplayIdException) { InvalidDisplayIdException idie = (InvalidDisplayIdException) e; int[] displayIds = idie.getAvailableDisplayIds(); diff --git a/server/src/main/java/com/genymobile/scrcpy/wrappers/InputManager.java b/server/src/main/java/com/genymobile/scrcpy/wrappers/InputManager.java index e17b5a1..38e96d4 100644 --- a/server/src/main/java/com/genymobile/scrcpy/wrappers/InputManager.java +++ b/server/src/main/java/com/genymobile/scrcpy/wrappers/InputManager.java @@ -2,7 +2,6 @@ package com.genymobile.scrcpy.wrappers; import com.genymobile.scrcpy.Ln; -import android.os.IInterface; import android.view.InputEvent; import java.lang.reflect.InvocationTargetException; @@ -14,12 +13,12 @@ public final class InputManager { public static final int INJECT_INPUT_EVENT_MODE_WAIT_FOR_RESULT = 1; public static final int INJECT_INPUT_EVENT_MODE_WAIT_FOR_FINISH = 2; - private final IInterface manager; + private final android.hardware.input.InputManager manager; private Method injectInputEventMethod; private static Method setDisplayIdMethod; - public InputManager(IInterface manager) { + public InputManager(android.hardware.input.InputManager manager) { this.manager = manager; } diff --git a/server/src/main/java/com/genymobile/scrcpy/wrappers/ServiceManager.java b/server/src/main/java/com/genymobile/scrcpy/wrappers/ServiceManager.java index 6f4b9c0..ea2a078 100644 --- a/server/src/main/java/com/genymobile/scrcpy/wrappers/ServiceManager.java +++ b/server/src/main/java/com/genymobile/scrcpy/wrappers/ServiceManager.java @@ -4,6 +4,7 @@ import android.annotation.SuppressLint; import android.os.IBinder; import android.os.IInterface; +import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; @SuppressLint("PrivateApi,DiscouragedPrivateApi") @@ -56,7 +57,13 @@ public final class ServiceManager { public InputManager getInputManager() { if (inputManager == null) { - inputManager = new InputManager(getService("input", "android.hardware.input.IInputManager")); + try { + Method getInstanceMethod = android.hardware.input.InputManager.class.getDeclaredMethod("getInstance"); + android.hardware.input.InputManager im = (android.hardware.input.InputManager) getInstanceMethod.invoke(null); + inputManager = new InputManager(im); + } catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException e) { + throw new AssertionError(e); + } } return inputManager; } diff --git a/server/src/test/java/com/genymobile/scrcpy/ControlMessageReaderTest.java b/server/src/test/java/com/genymobile/scrcpy/ControlMessageReaderTest.java index 5e79d4f..2a4ffe7 100644 --- a/server/src/test/java/com/genymobile/scrcpy/ControlMessageReaderTest.java +++ b/server/src/test/java/com/genymobile/scrcpy/ControlMessageReaderTest.java @@ -128,6 +128,7 @@ public class ControlMessageReaderTest { dos.writeShort(1920); dos.writeInt(1); dos.writeInt(-1); + dos.writeInt(1); byte[] packet = bos.toByteArray(); @@ -144,6 +145,7 @@ public class ControlMessageReaderTest { Assert.assertEquals(1920, event.getPosition().getScreenSize().getHeight()); Assert.assertEquals(1, event.getHScroll()); Assert.assertEquals(-1, event.getVScroll()); + Assert.assertEquals(1, event.getButtons()); } @Test