This commit is contained in:
L Michael War 2026-04-18 07:15:02 +08:00 committed by GitHub
commit 7299bd6c4f
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 113 additions and 0 deletions

View file

@ -13,6 +13,7 @@ import com.genymobile.scrcpy.opengl.OpenGLRunner;
import com.genymobile.scrcpy.util.AffineMatrix;
import com.genymobile.scrcpy.util.Ln;
import com.genymobile.scrcpy.wrappers.ServiceManager;
import com.genymobile.scrcpy.wrappers.WindowManager;
import android.graphics.Rect;
import android.hardware.display.VirtualDisplay;
@ -199,6 +200,10 @@ public class NewDisplayCapture extends SurfaceCapture {
ServiceManager.getWindowManager().setDisplayImePolicy(virtualDisplayId, displayImePolicy);
}
if (Build.VERSION.SDK_INT >= AndroidVersions.API_34_ANDROID_14) {
ServiceManager.getWindowManager().setDisplayWindowingMode(virtualDisplayId, WindowManager.WINDOWING_MODE_FREEFORM);
}
displaySizeMonitor.start(virtualDisplayId, this::invalidate);
} catch (Exception e) {
Ln.e("Could not create display", e);

View file

@ -4,11 +4,15 @@ import com.genymobile.scrcpy.AndroidVersions;
import com.genymobile.scrcpy.util.Ln;
import android.annotation.TargetApi;
import android.os.Binder;
import android.os.Build;
import android.os.IBinder;
import android.os.IInterface;
import android.view.IDisplayWindowListener;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.List;
public final class WindowManager {
@ -18,6 +22,12 @@ public final class WindowManager {
public static final int DISPLAY_IME_POLICY_FALLBACK_DISPLAY = 1;
public static final int DISPLAY_IME_POLICY_HIDE = 2;
// android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM
public static final int WINDOWING_MODE_FREEFORM = 5;
// android.window.DisplayAreaOrganizer.FEATURE_DEFAULT_TASK_CONTAINER
private static final int FEATURE_DEFAULT_TASK_CONTAINER = 1;
private final IInterface manager;
private Method getRotationMethod;
@ -264,4 +274,102 @@ public final class WindowManager {
Ln.e("Could not invoke method", e);
}
}
/**
* Set the windowing mode of a display's default task area via WindowContainerTransaction.
*
* This is required for Android desktop mode to operate in freeform windowing mode on virtual displays.
*/
@TargetApi(AndroidVersions.API_34_ANDROID_14)
@SuppressWarnings("unchecked")
public void setDisplayWindowingMode(int displayId, int windowingMode) {
// A Binder token with the correct AIDL interface descriptor, so that any system server
// callbacks during registration are properly identified and silently handled
IBinder organizerBinder = new Binder() {
{
attachInterface(null, "android.window.IDisplayAreaOrganizer");
}
@Override
protected boolean onTransact(int code, android.os.Parcel data, android.os.Parcel reply, int flags)
throws android.os.RemoteException {
if (super.onTransact(code, data, reply, flags)) {
return true;
}
// Silently accept any organizer callback transactions
return true;
}
};
Object organizerProxy = null;
Object daoController = null;
try {
// Get the IWindowOrganizerController via ServiceManager
IInterface windowOrganizerController = ServiceManager.getService("window_organizer",
"android.window.IWindowOrganizerController");
if (windowOrganizerController == null) {
Ln.w("window_organizer service not available");
return;
}
// Get the IDisplayAreaOrganizerController
daoController = windowOrganizerController.getClass().getMethod("getDisplayAreaOrganizerController").invoke(windowOrganizerController);
// Create a no-op IDisplayAreaOrganizer proxy for registration; callbacks are ignored since we unregister immediately
Class<?> idaoClass = Class.forName("android.window.IDisplayAreaOrganizer");
organizerProxy = Proxy.newProxyInstance(
ClassLoader.getSystemClassLoader(),
new Class[]{idaoClass},
(proxy, method, args) -> {
if ("asBinder".equals(method.getName())) {
return organizerBinder;
}
return null;
}
);
// Register the organizer to get display area tokens
Object parceledList = daoController.getClass()
.getMethod("registerOrganizer", idaoClass, int.class)
.invoke(daoController, organizerProxy, FEATURE_DEFAULT_TASK_CONTAINER);
List<Object> displayAreaInfos = (List<Object>) parceledList.getClass().getMethod("getList").invoke(parceledList);
// Find the display area token matching our virtual display
Object targetToken = null;
for (Object info : displayAreaInfos) {
Object displayAreaInfo = info.getClass().getMethod("getDisplayAreaInfo").invoke(info);
int daDisplayId = displayAreaInfo.getClass().getDeclaredField("displayId").getInt(displayAreaInfo);
if (daDisplayId == displayId) {
targetToken = displayAreaInfo.getClass().getDeclaredField("token").get(displayAreaInfo);
break;
}
}
if (targetToken != null) {
// Create a WindowContainerTransaction to set the windowing mode
Class<?> wctClass = Class.forName("android.window.WindowContainerTransaction");
Object wct = wctClass.getDeclaredConstructor().newInstance();
Class<?> tokenClass = Class.forName("android.window.WindowContainerToken");
wctClass.getMethod("setWindowingMode", tokenClass, int.class).invoke(wct, targetToken, windowingMode);
// Apply the transaction
windowOrganizerController.getClass().getMethod("applyTransaction", wctClass).invoke(windowOrganizerController, wct);
} else {
Ln.w("Could not find display area for display " + displayId);
}
} catch (Exception e) {
Ln.w("Could not set windowing mode for display " + displayId, e);
} finally {
// Always unregister the organizer
if (organizerProxy != null && daoController != null) {
try {
Class<?> idaoClass = Class.forName("android.window.IDisplayAreaOrganizer");
daoController.getClass().getMethod("unregisterOrganizer", idaoClass).invoke(daoController, organizerProxy);
} catch (Exception e) {
Ln.w("Could not unregister display area organizer", e);
}
}
}
}
}