How scrcpy Handles Virtual Displays on Android: Server Architecture Explained
Scrcpy creates and manages Android virtual displays by wrapping the hidden DisplayManager APIs via reflection, capturing rendered frames through Surface objects, and coordinating input events with coordinate mapping.
The Genymobile/scrcpy project streams Android screens to desktops by rendering content into virtual displays. Understanding how scrcpy handles virtual displays on Android reveals the core mechanism behind its screen mirroring and new display creation features. The server-side implementation spans reflection-based system service wrappers, capture pipelines, and input controllers.
Three-Layer Architecture
Scrcpy's virtual display implementation is organized into three distinct layers:
- Display Abstraction: Reflection-based access to Android's hidden APIs via
DisplayManager.java - Capture Pipeline: Creation of virtual displays and attachment of Surfaces in
ScreenCapture.javaandNewDisplayCapture.java - Notification System: Delivery of display IDs and coordinate mappers through
VirtualDisplayListenerto theController
The DisplayManager Wrapper (Reflection Layer)
Since Android hides certain display APIs from the SDK, scrcpy uses reflection to access system-level functionality located in server/src/main/java/com/genymobile/scrcpy/wrappers/DisplayManager.java.
The wrapper exposes createVirtualDisplay for mirroring existing displays:
public VirtualDisplay createVirtualDisplay(
String name, int width, int height, int displayIdToMirror, Surface surface)
throws Exception {
Method method = getCreateVirtualDisplayMethod();
return (VirtualDisplay) method.invoke(
null, name, width, height, displayIdToMirror, surface);
}
For creating new logical displays (used with --new-display), the wrapper provides createNewVirtualDisplay which calls the public API with extended flags at lines 176-181:
virtualDisplay = ServiceManager.getDisplayManager()
.createNewVirtualDisplay("scrcpy",
displaySize.getWidth(), displaySize.getHeight(),
dpi, surface, flags);
Capture Pipeline: Mirroring vs. New Displays
Scrcpy implements two distinct capture strategies depending on whether you mirror an existing display or create a new one.
Mirroring with ScreenCapture
The ScreenCapture class in server/src/main/java/com/genymobile/scrcpy/video/ScreenCapture.java handles mirroring. It first attempts to create a virtual display via the DisplayManager wrapper:
virtualDisplay = ServiceManager.getDisplayManager()
.createVirtualDisplay("scrcpy", inputSize.getWidth(),
inputSize.getHeight(), displayId, surface);
If this throws an exception (common on older Android versions or restricted devices), scrcpy falls back to the SurfaceControl API to create a secure off-screen display at lines 33-38.
Creating New Displays with NewDisplayCapture
When users invoke scrcpy with --new-display, the NewDisplayCapture class in server/src/main/java/com/genymobile/scrcpy/video/NewDisplayCapture.java creates a standalone virtual display with rich feature flags:
int flags = VirtualDisplay.FLAG_PUBLIC
| VirtualDisplay.FLAG_PRESENTATION
| VirtualDisplay.FLAG_OWN_CONTENT_ONLY
| VirtualDisplay.FLAG_SUPPORTS_TOUCH
| VirtualDisplay.FLAG_ROTATES_WITH_CONTENT;
if (vdDestroyContent) flags |= VirtualDisplay.FLAG_DESTROY_CONTENT_ON_REMOVAL;
if (vdSystemDecorations) flags |= VirtualDisplay.FLAG_SHOULD_SHOW_SYSTEM_DECORATIONS;
virtualDisplay = ServiceManager.getDisplayManager()
.createNewVirtualDisplay("scrcpy",
displaySize.getWidth(), displaySize.getHeight(),
dpi, surface, flags);
Coordinate Mapping and Input Handling
Creating the virtual display is only half the task; scrcpy must ensure touch and mouse events map correctly to the display coordinates.
VirtualDisplayListener Interface
Both capture classes implement VirtualDisplayListener to notify the system when a display is ready. In ScreenCapture.java (lines 46-59), successful creation triggers:
vdListener.onNewVirtualDisplay(virtualDisplayId, positionMapper);
Controller Implementation
The Controller class in server/src/main/java/com/genymobile/scrcpy/control/Controller.java implements this listener, storing the display ID and position mapper for input translation:
public void onNewVirtualDisplay(int virtualDisplayId,
PositionMapper positionMapper) {
this.virtualDisplayId = virtualDisplayId;
this.positionMapper = positionMapper;
}
The PositionMapper converts input event coordinates from the virtual display space back to the original device coordinates, ensuring accurate touch injection.
Summary
- scrcpy creates virtual displays via reflection-based wrappers around Android's hidden DisplayManager APIs, located in
DisplayManager.java - The ScreenCapture class mirrors existing displays and falls back to SurfaceControl if the reflection method fails
- NewDisplayCapture creates standalone virtual displays with configurable flags when using the
--new-displayoption - The VirtualDisplayListener interface bridges the capture pipeline and input controller, delivering display IDs and coordinate mappers
- Controller stores the virtual display ID and uses
PositionMapperto translate input events correctly
Frequently Asked Questions
What is the difference between ScreenCapture and NewDisplayCapture in scrcpy?
ScreenCapture mirrors an existing physical or logical display on the Android device, while NewDisplayCapture creates a brand new virtual display that runs independently. ScreenCapture uses createVirtualDisplay with a source display ID, whereas NewDisplayCapture uses createNewVirtualDisplay with feature flags like touch support and system decorations.
Why does scrcpy use reflection to access DisplayManager APIs?
Android hides certain display creation methods from the public SDK for security reasons. Scrcpy uses reflection in DisplayManager.java to access these system-level APIs, allowing it to create virtual displays that can mirror existing screens or capture secure content that standard SDK methods cannot access.
How does scrcpy handle input events on virtual displays?
Scrcpy implements the VirtualDisplayListener interface to receive the virtual display ID and a PositionMapper object when a display is created. The Controller class stores these and uses them to translate desktop mouse and keyboard coordinates into the correct Android screen coordinates before injecting input events.
What happens if the virtual display creation fails in ScreenCapture?
If the initial createVirtualDisplay call fails (typically due to API restrictions), scrcpy falls back to using the SurfaceControl API at lines 33-38 of ScreenCapture.java. This creates a secure off-screen display that can capture content without the standard virtual display limitations, ensuring compatibility across different Android versions.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →