How scrcpy Handles Multi-Display Configurations: A Deep Dive into Android Screen Mirroring
scrcpy supports multi-display configurations by allowing users to mirror existing logical displays via --display_id or create isolated virtual displays via --new-display, with dedicated code paths in the server component handling input injection, power management, and geometry changes per display.
The Genymobile/scrcpy project provides a robust implementation for Android display streaming that extends beyond simple screen mirroring. Understanding how the codebase handles multiple displays reveals the architectural decisions that enable developers to target specific screens, create virtual environments, and manage power states across heterogeneous display topologies.
Selecting and Mirroring Existing Displays
When targeting a specific logical display, scrcpy parses the --display_id=<id> command-line argument in server/src/main/java/com/genymobile/scrcpy/Options.java. The parser stores this value in Options.displayId, which defaults to 0 (the primary display) if unspecified.
During video source initialization, the DeviceStreamer creates a VideoSource.DISPLAY instance that passes the chosen displayId to DisplayManager.getDisplayInfo(displayId). This wrapper method, located in server/src/main/java/com/genymobile/scrcpy/wrappers/DisplayManager.java, queries the Android framework's hidden DisplayManagerGlobal API to retrieve the display's size, rotation, and DPI.
All subsequent video capture operations and input injections reference this specific displayId, ensuring the stream remains isolated to the target screen even when the device contains multiple physical or logical displays.
Creating Virtual Displays with Custom Parameters
For scenarios requiring an isolated display environment, scrcpy implements virtual display creation through the --new-display=<size>/<dpi> option. The parser in Options.java instantiates a NewDisplay object (defined in server/src/main/java/com/genymobile/scrcpy/device/NewDisplay.java) to hold the optional width, height, and density parameters.
When this option is present, NewDisplayCapture.startNew() (located in server/src/main/java/com/genymobile/scrcpy/video/NewDisplayCapture.java) invokes DisplayManager.createNewVirtualDisplay(). This wrapper method calls the hidden Android API android.hardware.display.DisplayManager#createVirtualDisplay, creating a surface-backed virtual display that exists independently of the device's physical screens.
The virtual display serves as the video frame source, allowing applications to run in an isolated environment while scrcpy streams the content to the host machine.
Handling Display Geometry Changes
Virtual displays in scrcpy support dynamic geometry changes through the VirtualDisplayListener interface. In NewDisplayCapture.java, the constructor registers a listener that monitors size changes via displaySizeMonitor.start().
When the virtual display's dimensions change (either programmatically or through system events), the listener recomputes the transformation matrices stored in eventTransform and displayTransform. These matrices ensure that touch input coordinates map correctly to the new display geometry and that the video stream maintains proper aspect ratio and orientation.
This mechanism allows scrcpy to adapt to rotating displays or resolution changes without requiring a full reconnection.
Input Injection for Non-Primary Displays
Input event routing to secondary displays requires specific handling in server/src/main/java/com/genymobile/scrcpy/device/Device.java. The injectEvent() method first validates display support via supportsInputEvents(displayId).
For non-zero displayId values (indicating a secondary logical display), the method calls InputManager.setDisplayId(inputEvent, displayId) before injection. This hidden API method attaches the target display identifier to the input event, ensuring the Android framework routes the touch, key, or motion event to the correct display rather than the default primary screen.
The method then injects the event using either synchronous or asynchronous mode based on the INJECT_MODE parameter.
Power Management Across Multiple Physical Displays
On Android 10+ (API level 29 and above), scrcpy implements coordinated power management across all physical displays. The setDisplayPower() method in Device.java handles display power state changes (on/off) by enumerating all physical display connections.
The method obtains the list of physical display IDs through SurfaceControl.getPhysicalDisplayIds() on Android 10-13, or DisplayControl.getPhysicalDisplayIds() on Android 14+. It then iterates over each physical display ID, applying the requested power mode to each screen individually.
This ensures that when scrcpy requests a display power change, all physical displays on the device respond consistently, preventing scenarios where the primary display powers off while secondary displays remain active.
Application Launch and Rotation Control
scrcpy provides per-display application launching through Device.startApp(). When launching an activity on a specific display, the method constructs an ActivityOptions bundle and calls setLaunchDisplayId(displayId) (available on Android 8+). This system API directive instructs the Android Activity Manager to start the application on the specified logical display rather than the default display.
For display rotation, Device.rotateDevice() differentiates between the primary display (ID 0) and secondary displays. The method forwards rotation requests to the appropriate manager—either WindowManager for the primary display or DisplayManager for secondary logical displays—ensuring that rotation commands affect the intended target screen.
Summary
- scrcpy supports multi-display configurations through two primary mechanisms: mirroring existing logical displays via
--display_idand creating isolated virtual displays via--new-display. - The Options.java parser handles command-line arguments, storing display preferences in
displayIdandNewDisplayobjects for downstream consumption. - DisplayManager wrappers interface with hidden Android APIs to query display info, create virtual displays, and manage display geometry changes dynamically.
- Device.java centralizes per-display operations including input injection via
InputManager.setDisplayId(), power management across multiple physical displays, application launching withsetLaunchDisplayId(), and rotation control. - Virtual displays automatically handle geometry changes through
VirtualDisplayListenercallbacks that recompute transformation matrices for accurate input mapping.
Frequently Asked Questions
Can scrcpy mirror a secondary display while the primary display remains off?
Yes. You can use --display_id=1 (or the appropriate display ID) to target a secondary logical display while using --turn-screen-off or power commands that affect only the targeted display. On Android 10+, the setDisplayPower() method in Device.java iterates through all physical displays, but you can control individual logical displays separately through the display ID parameter.
How does scrcpy handle input events when using a virtual display?
When using --new-display, scrcpy creates a virtual display with a unique display ID. The Device.injectEvent() method automatically routes input events to this display by calling InputManager.setDisplayId(inputEvent, displayId) before injection. This ensures touch and keyboard events reach the virtual display rather than the physical primary screen, even though the virtual display has no physical touch layer.
What happens to the video stream if the target display changes resolution?
If the target display is a virtual display created via --new-display, the NewDisplayCapture class registers a VirtualDisplayListener that monitors size changes. When the resolution changes, the listener updates the eventTransform and displayTransform matrices to maintain correct coordinate mapping and aspect ratio. The video stream continues without requiring reconnection, though the client may need to handle the new resolution dimensions.
Can I launch an application directly onto a secondary display using scrcpy?
Yes. The Device.startApp() method in the scrcpy server constructs an ActivityOptions bundle and calls setLaunchDisplayId(displayId) (available on Android 8.0+). When you specify a display ID via --display_id, subsequent app launches via the start app feature will target that specific logical display, allowing you to open applications directly on secondary screens or virtual displays.
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 →