Add freeform windowing mode and unconditional flags for virtual display desktop mode

- Add VIRTUAL_DISPLAY_FLAG_SHOULD_SHOW_SYSTEM_DECORATIONS unconditionally
  for API 34+ alongside OWN_FOCUS to ensure desktop mode compatibility
- Add WindowContainerTransaction-based setDisplayWindowingMode() to
  WindowManager using reflection to set WINDOWING_MODE_FREEFORM on the
  virtual display's default task area after creation
- Call setDisplayWindowingMode after virtual display creation on API 34+

Co-authored-by: lmichaelwar <20604470+lmichaelwar@users.noreply.github.com>
This commit is contained in:
copilot-swe-agent[bot] 2026-03-13 20:58:50 +00:00
parent ba00ef0874
commit 7199e79ed4
2 changed files with 101 additions and 1 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;
@ -187,7 +188,8 @@ public class NewDisplayCapture extends SurfaceCapture {
| VIRTUAL_DISPLAY_FLAG_TOUCH_FEEDBACK_DISABLED;
if (Build.VERSION.SDK_INT >= AndroidVersions.API_34_ANDROID_14) {
flags |= VIRTUAL_DISPLAY_FLAG_OWN_FOCUS
| VIRTUAL_DISPLAY_FLAG_DEVICE_DISPLAY_GROUP;
| VIRTUAL_DISPLAY_FLAG_DEVICE_DISPLAY_GROUP
| VIRTUAL_DISPLAY_FLAG_SHOULD_SHOW_SYSTEM_DECORATIONS;
}
}
virtualDisplay = ServiceManager.getDisplayManager()
@ -199,6 +201,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,9 @@ 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;
private final IInterface manager;
private Method getRotationMethod;
@ -264,4 +271,91 @@ 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) {
IBinder organizerBinder = new Binder();
Object organizerProxy = null;
Object daoController = null;
try {
// Get the IWindowOrganizerController
Class<?> serviceManagerClass = Class.forName("android.os.ServiceManager");
Method getServiceMethod = serviceManagerClass.getDeclaredMethod("getService", String.class);
IBinder wocBinder = (IBinder) getServiceMethod.invoke(null, "window_organizer");
if (wocBinder == null) {
Ln.w("window_organizer service not available");
return;
}
Class<?> iWocStubClass = Class.forName("android.window.IWindowOrganizerController$Stub");
Object windowOrganizerController = iWocStubClass.getDeclaredMethod("asInterface", IBinder.class).invoke(null, wocBinder);
// Get the IDisplayAreaOrganizerController
daoController = windowOrganizerController.getClass().getMethod("getDisplayAreaOrganizerController").invoke(windowOrganizerController);
// Create a no-op IDisplayAreaOrganizer proxy for registration
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 for FEATURE_DEFAULT_TASK_CONTAINER (1) to get display area tokens
int featureDefaultTaskContainer = 1;
Object parceledList = daoController.getClass()
.getMethod("registerOrganizer", idaoClass, int.class)
.invoke(daoController, organizerProxy, featureDefaultTaskContainer);
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);
}
}
}
}
}