Compare commits

..

7 Commits

Author SHA1 Message Date
fdc58722b3 Adapt to display API changes
The method SurfaceControl.createDisplay() has been removed in AOSP.

Use DisplayManager to create a VirtualDisplay object instead.

Fixes #4646 <https://github.com/Genymobile/scrcpy/issues/4646>
Fixes #4656 <https://github.com/Genymobile/scrcpy/issues/4656>
PR #4657 <https://github.com/Genymobile/scrcpy/pull/4657>

Signed-off-by: Romain Vimont <rom@rom1v.com>
2024-02-09 18:31:20 +01:00
5a6b8310ca Add note about official website 2023-12-13 12:55:14 +01:00
c6ff78f414 Update links to v2.3.1 2023-12-02 12:39:05 +01:00
40f2560d98 Bump version to 2.3.1 2023-12-02 12:30:19 +01:00
26aa28c998 Merge branch 'master' into release 2023-12-02 12:29:31 +01:00
ef79fcbbd2 Fix AV1 demuxing
For AV1, the config packet must not be merged with the next non-config
packet.

This fixes the following error when passing --video-codec=av1:

> INFO: [FFmpeg] libdav1d 1.3.0
> ERROR: [FFmpeg] Unknown OBU type 0 of size 29393
> ERROR: [FFmpeg] Error parsing OBU data
> ERROR: Decoder 'video': could not send video packet: -1094995529

PR #4487 <https://github.com/Genymobile/scrcpy/pull/4487>
2023-12-02 12:20:01 +01:00
bd9292931e Mention exclusive_caps mode in v4l2 documentation
PR #4435 <https://github.com/Genymobile/scrcpy/pull/4435>

Signed-off-by: Romain Vimont <rom@rom1v.com>
2023-11-28 08:32:28 +01:00
17 changed files with 219 additions and 102 deletions

View File

@ -1,4 +1,8 @@
# scrcpy (v2.3) **This GitHub repo (<https://github.com/Genymobile/scrcpy>) is the only official
source for the project. Do not download releases from random websites, even if
their name contains `scrcpy`.**
# scrcpy (v2.3.1)
<img src="app/data/icon.svg" width="128" height="128" alt="scrcpy" align="right" /> <img src="app/data/icon.svg" width="128" height="128" alt="scrcpy" align="right" />

View File

@ -13,7 +13,7 @@ BEGIN
VALUE "LegalCopyright", "Romain Vimont, Genymobile" VALUE "LegalCopyright", "Romain Vimont, Genymobile"
VALUE "OriginalFilename", "scrcpy.exe" VALUE "OriginalFilename", "scrcpy.exe"
VALUE "ProductName", "scrcpy" VALUE "ProductName", "scrcpy"
VALUE "ProductVersion", "2.3" VALUE "ProductVersion", "2.3.1"
END END
END END
BLOCK "VarFileInfo" BLOCK "VarFileInfo"

View File

@ -227,8 +227,9 @@ run_demuxer(void *data) {
} }
// Config packets must be merged with the next non-config packet only for // Config packets must be merged with the next non-config packet only for
// video streams // H.26x
bool must_merge_config_packet = codec->type == AVMEDIA_TYPE_VIDEO; bool must_merge_config_packet = raw_codec_id == SC_CODEC_ID_H264
|| raw_codec_id == SC_CODEC_ID_H265;
struct sc_packet_merger merger; struct sc_packet_merger merger;

View File

@ -233,10 +233,10 @@ install` must be run as root)._
#### Option 2: Use prebuilt server #### Option 2: Use prebuilt server
- [`scrcpy-server-v2.3`][direct-scrcpy-server] - [`scrcpy-server-v2.3.1`][direct-scrcpy-server]
<sub>SHA-256: `8daed514d7796fca6987dc973e201bd15ba51d0f7258973dec92d9ded00dbd5f`</sub> <sub>SHA-256: `f6814822fc308a7a532f253485c9038183c6296a6c5df470a9e383b4f8e7605b`</sub>
[direct-scrcpy-server]: https://github.com/Genymobile/scrcpy/releases/download/v2.3/scrcpy-server-v2.3 [direct-scrcpy-server]: https://github.com/Genymobile/scrcpy/releases/download/v2.3.1/scrcpy-server-v2.3.1
Download the prebuilt server somewhere, and specify its path during the Meson Download the prebuilt server somewhere, and specify its path during the Meson
configuration: configuration:

View File

@ -21,6 +21,13 @@ This will create a new video device in `/dev/videoN`, where `N` is an integer
(more [options](https://github.com/umlaeute/v4l2loopback#options) are available (more [options](https://github.com/umlaeute/v4l2loopback#options) are available
to create several devices or devices with specific IDs). to create several devices or devices with specific IDs).
If you encounter problems detecting your device with Chrome/WebRTC, you can try
`exclusive_caps` mode:
```
sudo modprobe v4l2loopback exclusive_caps=1
```
To list the enabled devices: To list the enabled devices:
```bash ```bash

View File

@ -4,14 +4,14 @@
Download the [latest release]: Download the [latest release]:
- [`scrcpy-win64-v2.3.zip`][direct-win64] (64-bit) - [`scrcpy-win64-v2.3.1.zip`][direct-win64] (64-bit)
<sub>SHA-256: `a2fdd2733bd337261bb493e77d990078a23e7a40149dd0c0dc45725c929a715f`</sub> <sub>SHA-256: `f1f78ac98214078425804e524a1bed515b9d4b8a05b78d210a4ced2b910b262d`</sub>
- [`scrcpy-win32-v2.3.zip`][direct-win32] (32-bit) - [`scrcpy-win32-v2.3.1.zip`][direct-win32] (32-bit)
<sub>SHA-256: `dfdbb69a872d717aed5bcfe352e571564c357fdb7a9c172d69f450fdf5154a0a`</sub> <sub>SHA-256: `5dffc2d432e9b8b5b0e16f12e71428c37c70d9124cfbe7620df0b41b7efe91ff`</sub>
[latest release]: https://github.com/Genymobile/scrcpy/releases/latest [latest release]: https://github.com/Genymobile/scrcpy/releases/latest
[direct-win64]: https://github.com/Genymobile/scrcpy/releases/download/v2.3/scrcpy-win64-v2.3.zip [direct-win64]: https://github.com/Genymobile/scrcpy/releases/download/v2.3.1/scrcpy-win64-v2.3.1.zip
[direct-win32]: https://github.com/Genymobile/scrcpy/releases/download/v2.3/scrcpy-win32-v2.3.zip [direct-win32]: https://github.com/Genymobile/scrcpy/releases/download/v2.3.1/scrcpy-win32-v2.3.1.zip
and extract it. and extract it.

View File

@ -2,8 +2,8 @@
set -e set -e
BUILDDIR=build-auto BUILDDIR=build-auto
PREBUILT_SERVER_URL=https://github.com/Genymobile/scrcpy/releases/download/v2.3/scrcpy-server-v2.3 PREBUILT_SERVER_URL=https://github.com/Genymobile/scrcpy/releases/download/v2.3.1/scrcpy-server-v2.3.1
PREBUILT_SERVER_SHA256=8daed514d7796fca6987dc973e201bd15ba51d0f7258973dec92d9ded00dbd5f PREBUILT_SERVER_SHA256=f6814822fc308a7a532f253485c9038183c6296a6c5df470a9e383b4f8e7605b
echo "[scrcpy] Downloading prebuilt server..." echo "[scrcpy] Downloading prebuilt server..."
wget "$PREBUILT_SERVER_URL" -O scrcpy-server wget "$PREBUILT_SERVER_URL" -O scrcpy-server

View File

@ -1,5 +1,5 @@
project('scrcpy', 'c', project('scrcpy', 'c',
version: '2.3', version: '2.3.1',
meson_version: '>= 0.48', meson_version: '>= 0.48',
default_options: [ default_options: [
'c_std=c11', 'c_std=c11',

View File

@ -7,8 +7,8 @@ android {
applicationId "com.genymobile.scrcpy" applicationId "com.genymobile.scrcpy"
minSdkVersion 21 minSdkVersion 21
targetSdkVersion 34 targetSdkVersion 34
versionCode 20300 versionCode 20301
versionName "2.3" versionName "2.3.1"
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
} }
buildTypes { buildTypes {

View File

@ -12,7 +12,7 @@
set -e set -e
SCRCPY_DEBUG=false SCRCPY_DEBUG=false
SCRCPY_VERSION_NAME=2.3 SCRCPY_VERSION_NAME=2.3.1
PLATFORM=${ANDROID_PLATFORM:-34} PLATFORM=${ANDROID_PLATFORM:-34}
BUILD_TOOLS=${ANDROID_BUILD_TOOLS:-34.0.0} BUILD_TOOLS=${ANDROID_BUILD_TOOLS:-34.0.0}

View File

@ -0,0 +1,26 @@
/*
* Copyright (C) 2019 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package android.view;
/**
* {@hide}
*/
oneway interface IDisplayFoldListener
{
/** Called when the foldedness of a display changes */
void onDisplayFoldChanged(int displayId, boolean folded);
}

View File

@ -0,0 +1,25 @@
/* //device/java/android/android/hardware/ISensorListener.aidl
**
** Copyright 2008, The Android Open Source Project
**
** Licensed under the Apache License, Version 2.0 (the "License");
** you may not use this file except in compliance with the License.
** You may obtain a copy of the License at
**
** http://www.apache.org/licenses/LICENSE-2.0
**
** Unless required by applicable law or agreed to in writing, software
** distributed under the License is distributed on an "AS IS" BASIS,
** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
** See the License for the specific language governing permissions and
** limitations under the License.
*/
package android.view;
/**
* {@hide}
*/
interface IRotationWatcher {
oneway void onRotationChanged(int rotation);
}

View File

@ -2,7 +2,6 @@ package com.genymobile.scrcpy;
import com.genymobile.scrcpy.wrappers.ClipboardManager; import com.genymobile.scrcpy.wrappers.ClipboardManager;
import com.genymobile.scrcpy.wrappers.DisplayControl; import com.genymobile.scrcpy.wrappers.DisplayControl;
import com.genymobile.scrcpy.wrappers.DisplayManager;
import com.genymobile.scrcpy.wrappers.InputManager; import com.genymobile.scrcpy.wrappers.InputManager;
import com.genymobile.scrcpy.wrappers.ServiceManager; import com.genymobile.scrcpy.wrappers.ServiceManager;
import com.genymobile.scrcpy.wrappers.SurfaceControl; import com.genymobile.scrcpy.wrappers.SurfaceControl;
@ -11,10 +10,10 @@ import com.genymobile.scrcpy.wrappers.WindowManager;
import android.content.IOnPrimaryClipChangedListener; import android.content.IOnPrimaryClipChangedListener;
import android.graphics.Rect; import android.graphics.Rect;
import android.os.Build; import android.os.Build;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.IBinder; import android.os.IBinder;
import android.os.SystemClock; import android.os.SystemClock;
import android.view.IDisplayFoldListener;
import android.view.IRotationWatcher;
import android.view.InputDevice; import android.view.InputDevice;
import android.view.InputEvent; import android.view.InputEvent;
import android.view.KeyCharacterMap; import android.view.KeyCharacterMap;
@ -34,21 +33,26 @@ public final class Device {
public static final int LOCK_VIDEO_ORIENTATION_UNLOCKED = -1; public static final int LOCK_VIDEO_ORIENTATION_UNLOCKED = -1;
public static final int LOCK_VIDEO_ORIENTATION_INITIAL = -2; public static final int LOCK_VIDEO_ORIENTATION_INITIAL = -2;
public interface DisplayChangeListener { public interface RotationListener {
void onDisplayChanged(); void onRotationChanged(int rotation);
}
public interface FoldListener {
void onFoldChanged(int displayId, boolean folded);
} }
public interface ClipboardListener { public interface ClipboardListener {
void onClipboardTextChanged(String text); void onClipboardTextChanged(String text);
} }
private final Size deviceSize;
private final Rect crop; private final Rect crop;
private int maxSize; private int maxSize;
private final int lockVideoOrientation; private final int lockVideoOrientation;
private Size deviceSize;
private ScreenInfo screenInfo; private ScreenInfo screenInfo;
private DisplayChangeListener displayChangeListener; private RotationListener rotationListener;
private FoldListener foldListener;
private ClipboardListener clipboardListener; private ClipboardListener clipboardListener;
private final AtomicBoolean isSettingClipboard = new AtomicBoolean(); private final AtomicBoolean isSettingClipboard = new AtomicBoolean();
@ -82,26 +86,46 @@ public final class Device {
screenInfo = ScreenInfo.computeScreenInfo(displayInfo.getRotation(), deviceSize, crop, maxSize, lockVideoOrientation); screenInfo = ScreenInfo.computeScreenInfo(displayInfo.getRotation(), deviceSize, crop, maxSize, lockVideoOrientation);
layerStack = displayInfo.getLayerStack(); layerStack = displayInfo.getLayerStack();
HandlerThread displayListenerThread = new HandlerThread("DisplayListenerThread"); ServiceManager.getWindowManager().registerRotationWatcher(new IRotationWatcher.Stub() {
displayListenerThread.start();
Handler displayListenerHandler = new Handler(displayListenerThread.getLooper());
ServiceManager.getDisplayManager().registerDisplayListener(new DisplayManager.DisplayListener() {
@Override @Override
public void onDisplayChanged(int displayId) { public void onRotationChanged(int rotation) {
synchronized (Device.this) {
screenInfo = screenInfo.withDeviceRotation(rotation);
// notify
if (rotationListener != null) {
rotationListener.onRotationChanged(rotation);
}
}
}
}, displayId);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
ServiceManager.getWindowManager().registerDisplayFoldListener(new IDisplayFoldListener.Stub() {
@Override
public void onDisplayFoldChanged(int displayId, boolean folded) {
if (Device.this.displayId != displayId) { if (Device.this.displayId != displayId) {
// Ignore events related to other display ids
return; return;
} }
synchronized (Device.this) {
DisplayInfo displayInfo = ServiceManager.getDisplayManager().getDisplayInfo(displayId); DisplayInfo displayInfo = ServiceManager.getDisplayManager().getDisplayInfo(displayId);
deviceSize = displayInfo.getSize(); if (displayInfo == null) {
screenInfo = ScreenInfo.computeScreenInfo(displayInfo.getRotation(), deviceSize, crop, maxSize, lockVideoOrientation); Ln.e("Display " + displayId + " not found\n" + LogUtils.buildDisplayListMessage());
return;
}
if (displayChangeListener != null) { screenInfo = ScreenInfo.computeScreenInfo(displayInfo.getRotation(), displayInfo.getSize(), options.getCrop(),
displayChangeListener.onDisplayChanged(); options.getMaxSize(), options.getLockVideoOrientation());
// notify
if (foldListener != null) {
foldListener.onFoldChanged(displayId, folded);
} }
} }
}, displayListenerHandler); }
});
}
if (options.getControl() && options.getClipboardAutosync()) { if (options.getControl() && options.getClipboardAutosync()) {
// If control and autosync are enabled, synchronize Android clipboard to the computer automatically // If control and autosync are enabled, synchronize Android clipboard to the computer automatically
@ -140,6 +164,10 @@ public final class Device {
} }
} }
public int getDisplayId() {
return displayId;
}
public synchronized void setMaxSize(int newMaxSize) { public synchronized void setMaxSize(int newMaxSize) {
maxSize = newMaxSize; maxSize = newMaxSize;
screenInfo = ScreenInfo.computeScreenInfo(screenInfo.getReverseVideoRotation(), deviceSize, crop, newMaxSize, lockVideoOrientation); screenInfo = ScreenInfo.computeScreenInfo(screenInfo.getReverseVideoRotation(), deviceSize, crop, newMaxSize, lockVideoOrientation);
@ -230,8 +258,12 @@ public final class Device {
return ServiceManager.getPowerManager().isScreenOn(); return ServiceManager.getPowerManager().isScreenOn();
} }
public synchronized void setDisplayChangeListener(DisplayChangeListener displayChangeListener) { public synchronized void setRotationListener(RotationListener rotationListener) {
this.displayChangeListener = displayChangeListener; this.rotationListener = rotationListener;
}
public synchronized void setFoldListener(FoldListener foldlistener) {
this.foldListener = foldlistener;
} }
public synchronized void setClipboardListener(ClipboardListener clipboardListener) { public synchronized void setClipboardListener(ClipboardListener clipboardListener) {

View File

@ -1,16 +1,19 @@
package com.genymobile.scrcpy; package com.genymobile.scrcpy;
import com.genymobile.scrcpy.wrappers.ServiceManager;
import com.genymobile.scrcpy.wrappers.SurfaceControl; import com.genymobile.scrcpy.wrappers.SurfaceControl;
import android.graphics.Rect; import android.graphics.Rect;
import android.hardware.display.VirtualDisplay;
import android.os.Build; import android.os.Build;
import android.os.IBinder; import android.os.IBinder;
import android.view.Surface; import android.view.Surface;
public class ScreenCapture extends SurfaceCapture implements Device.DisplayChangeListener { public class ScreenCapture extends SurfaceCapture implements Device.RotationListener, Device.FoldListener {
private final Device device; private final Device device;
private IBinder display; private IBinder display;
private VirtualDisplay virtualDisplay;
public ScreenCapture(Device device) { public ScreenCapture(Device device) {
this.device = device; this.device = device;
@ -18,7 +21,8 @@ public class ScreenCapture extends SurfaceCapture implements Device.DisplayChang
@Override @Override
public void init() { public void init() {
device.setDisplayChangeListener(this); device.setRotationListener(this);
device.setFoldListener(this);
} }
@Override @Override
@ -33,14 +37,35 @@ public class ScreenCapture extends SurfaceCapture implements Device.DisplayChang
if (display != null) { if (display != null) {
SurfaceControl.destroyDisplay(display); SurfaceControl.destroyDisplay(display);
display = null;
} }
if (virtualDisplay != null) {
virtualDisplay.release();
virtualDisplay = null;
}
try {
display = createDisplay(); display = createDisplay();
setDisplaySurface(display, surface, videoRotation, contentRect, unlockedVideoRect, layerStack); setDisplaySurface(display, surface, videoRotation, contentRect, unlockedVideoRect, layerStack);
Ln.d("Display: using SurfaceControl API");
} catch (Exception surfaceControlException) {
Rect videoRect = screenInfo.getVideoSize().toRect();
try {
virtualDisplay = ServiceManager.getDisplayManager()
.createVirtualDisplay("scrcpy", videoRect.width(), videoRect.height(), device.getDisplayId(), surface);
Ln.d("Display: using DisplayManager API");
} catch (Exception displayManagerException) {
Ln.e("Could not create display using SurfaceControl", surfaceControlException);
Ln.e("Could not create display using DisplayManager", displayManagerException);
throw new AssertionError("Could not create display");
}
}
} }
@Override @Override
public void release() { public void release() {
device.setDisplayChangeListener(null); device.setRotationListener(null);
device.setFoldListener(null);
if (display != null) { if (display != null) {
SurfaceControl.destroyDisplay(display); SurfaceControl.destroyDisplay(display);
} }
@ -58,11 +83,16 @@ public class ScreenCapture extends SurfaceCapture implements Device.DisplayChang
} }
@Override @Override
public void onDisplayChanged() { public void onFoldChanged(int displayId, boolean folded) {
requestReset(); requestReset();
} }
private static IBinder createDisplay() { @Override
public void onRotationChanged(int rotation) {
requestReset();
}
private static IBinder createDisplay() throws Exception {
// Since Android 12 (preview), secure displays could not be created with shell permissions anymore. // Since Android 12 (preview), secure displays could not be created with shell permissions anymore.
// On Android 12 preview, SDK_INT is still R (not S), but CODENAME is "S". // On Android 12 preview, SDK_INT is still R (not S), but CODENAME is "S".
boolean secure = Build.VERSION.SDK_INT < Build.VERSION_CODES.R || (Build.VERSION.SDK_INT == Build.VERSION_CODES.R && !"S".equals( boolean secure = Build.VERSION.SDK_INT < Build.VERSION_CODES.R || (Build.VERSION.SDK_INT == Build.VERSION_CODES.R && !"S".equals(

View File

@ -5,21 +5,18 @@ import com.genymobile.scrcpy.DisplayInfo;
import com.genymobile.scrcpy.Ln; import com.genymobile.scrcpy.Ln;
import com.genymobile.scrcpy.Size; import com.genymobile.scrcpy.Size;
import android.os.Build; import android.hardware.display.VirtualDisplay;
import android.os.Handler;
import android.view.Display; import android.view.Display;
import android.view.Surface;
import java.lang.reflect.Field; import java.lang.reflect.Field;
import java.lang.reflect.Proxy; import java.lang.reflect.Method;
import java.util.regex.Matcher; import java.util.regex.Matcher;
import java.util.regex.Pattern; import java.util.regex.Pattern;
public final class DisplayManager { public final class DisplayManager {
// Constant copied from AOSP framework_base: core/java/android/hardware/display/DisplayManager.java
private static final long EVENT_FLAG_DISPLAY_CHANGED = 1L << 2;
private final Object manager; // instance of hidden class android.hardware.display.DisplayManagerGlobal private final Object manager; // instance of hidden class android.hardware.display.DisplayManagerGlobal
private Method createVirtualDisplayMethod;
public DisplayManager(Object manager) { public DisplayManager(Object manager) {
this.manager = manager; this.manager = manager;
@ -102,46 +99,16 @@ public final class DisplayManager {
} }
} }
public void registerDisplayListener(DisplayListener listener, Handler handler) { private Method getCreateVirtualDisplayMethod() throws NoSuchMethodException {
try { if (createVirtualDisplayMethod == null) {
Class<?> displayListenerClass = Class.forName("android.hardware.display.DisplayManager$DisplayListener"); createVirtualDisplayMethod = android.hardware.display.DisplayManager.class
Object displayListenerProxy = Proxy.newProxyInstance(ClassLoader.getSystemClassLoader(), .getMethod("createVirtualDisplay", String.class, int.class, int.class, int.class, Surface.class);
new Class[]{displayListenerClass},
(proxy, method, args) -> {
if ("onDisplayChanged".equals(method.getName())) {
listener.onDisplayChanged((int) args[0]);
}
return null;
});
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) {
try {
manager.getClass()
.getMethod("registerDisplayListener", displayListenerClass, Handler.class, long.class)
.invoke(manager, displayListenerProxy, handler, EVENT_FLAG_DISPLAY_CHANGED);
return;
} catch (NoSuchMethodException e) {
// fall-through
} }
return createVirtualDisplayMethod;
} }
manager.getClass() public VirtualDisplay createVirtualDisplay(String name, int width, int height, int displayIdToMirror, Surface surface) throws Exception {
.getMethod("registerDisplayListener", displayListenerClass, Handler.class) Method method = getCreateVirtualDisplayMethod();
.invoke(manager, displayListenerProxy, handler); return (VirtualDisplay) method.invoke(null, name, width, height, displayIdToMirror, surface);
} catch (Exception e) {
// Screen size won't be updated, not a fatal error
Ln.e("Could not register display listener", e);
}
}
// Partially copied from AOSP framework_base: core/java/android/hardware/display/DisplayManager.java
public interface DisplayListener {
/**
* Called whenever the properties of a logical {@link android.view.Display},
* such as size and density, have changed.
*
* @param displayId The id of the logical display that changed.
*/
void onDisplayChanged(int displayId);
} }
} }

View File

@ -78,12 +78,8 @@ public final class SurfaceControl {
} }
} }
public static IBinder createDisplay(String name, boolean secure) { public static IBinder createDisplay(String name, boolean secure) throws Exception {
try {
return (IBinder) CLASS.getMethod("createDisplay", String.class, boolean.class).invoke(null, name, secure); return (IBinder) CLASS.getMethod("createDisplay", String.class, boolean.class).invoke(null, name, secure);
} catch (Exception e) {
throw new AssertionError(e);
}
} }
private static Method getGetBuiltInDisplayMethod() throws NoSuchMethodException { private static Method getGetBuiltInDisplayMethod() throws NoSuchMethodException {

View File

@ -2,7 +2,10 @@ package com.genymobile.scrcpy.wrappers;
import com.genymobile.scrcpy.Ln; import com.genymobile.scrcpy.Ln;
import android.annotation.TargetApi;
import android.os.IInterface; import android.os.IInterface;
import android.view.IDisplayFoldListener;
import android.view.IRotationWatcher;
import java.lang.reflect.InvocationTargetException; import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method; import java.lang.reflect.Method;
@ -91,4 +94,30 @@ public final class WindowManager {
Ln.e("Could not invoke method", e); Ln.e("Could not invoke method", e);
} }
} }
public void registerRotationWatcher(IRotationWatcher rotationWatcher, int displayId) {
try {
Class<?> cls = manager.getClass();
try {
// display parameter added since this commit:
// https://android.googlesource.com/platform/frameworks/base/+/35fa3c26adcb5f6577849fd0df5228b1f67cf2c6%5E%21/#F1
cls.getMethod("watchRotation", IRotationWatcher.class, int.class).invoke(manager, rotationWatcher, displayId);
} catch (NoSuchMethodException e) {
// old version
cls.getMethod("watchRotation", IRotationWatcher.class).invoke(manager, rotationWatcher);
}
} catch (Exception e) {
Ln.e("Could not register rotation watcher", e);
}
}
@TargetApi(29)
public void registerDisplayFoldListener(IDisplayFoldListener foldListener) {
try {
Class<?> cls = manager.getClass();
cls.getMethod("registerDisplayFoldListener", IDisplayFoldListener.class).invoke(manager, foldListener);
} catch (Exception e) {
Ln.e("Could not register display fold listener", e);
}
}
} }