mirror of
https://github.com/Genymobile/scrcpy.git
synced 2026-04-21 01:33:36 +00:00
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:
parent
ba00ef0874
commit
7199e79ed4
2 changed files with 101 additions and 1 deletions
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue