How Scrcpy Handles Screen Orientation and Rotation: A Deep Dive into the Source Code
Scrcpy manages screen orientation through three distinct layers: device-side rotation controlled by the Android WindowManager, capture orientation enforced by the server video pipeline, and client-side display orientation applied before rendering on the host.
Mirroring an Android display to a desktop requires precise handling of scrcpy screen orientation and rotation to ensure the video stream appears correctly regardless of how the physical device is held. The Genymobile/scrcpy repository implements this through a sophisticated pipeline that separates device rotation, capture orientation, and final display orientation. This article examines the source code to explain how these systems interact, from the Java server running on the Android device to the C client rendering on the desktop.
Understanding the Three Layers of Orientation
Scrcpy distinguishes between three orientation concepts to provide flexible control:
| Concept | Applied By | Purpose |
|---|---|---|
| Device rotation | Android WindowManager and Device helpers | Physical rotation of the Android screen (portrait ↔ landscape) |
| Capture orientation | Server video pipeline (--capture-orientation) |
Orientation of the raw video stream sent to the client |
| Client/display orientation | Desktop client (--orientation, --display-orientation) |
Final rotation applied before rendering or recording |
Device-Side Rotation Handling
The Android server queries and controls physical device rotation through reflection-based wrappers around the Android WindowManager service.
Querying Current Rotation
WindowManager.getRotation() obtains the current device rotation by invoking either getDefaultDisplayRotation (Android 12+) or the legacy getRotation method via reflection.
public int getRotation() {
try {
Method method = getGetRotationMethod();
return (int) method.invoke(manager);
} catch (ReflectiveOperationException e) {
Ln.e("Could not invoke method", e);
return 0;
}
}
Source: server/src/main/java/com/genymobile/scrcpy/wrappers/WindowManager.java
Freezing and Applying Rotation
When the user requests a forced rotation via scrcpy --rotate-device, the server calls Device.rotateDevice(int displayId):
public static void rotateDevice(int displayId) {
WindowManager wm = ServiceManager.getWindowManager();
boolean accelerometerRotation = !wm.isRotationFrozen(displayId);
int currentRotation = getCurrentRotation(displayId);
int newRotation = (currentRotation & 1) ^ 1; // toggle portrait↔landscape
Ln.i("Device rotation requested: " + (newRotation == 0 ? "portrait" : "landscape"));
wm.freezeRotation(displayId, newRotation);
if (accelerometerRotation) {
wm.thawRotation(displayId);
}
}
Source: server/src/main/java/com/genymobile/scrcpy/device/Device.java
The method toggles between portrait and landscape, freezes the rotation to the new value, and restores auto-rotation if it was previously enabled.
Mapping Android Rotation to Scrcpy's Internal Orientation
The Orientation enum normalizes Android's counter-clockwise rotation values to scrcpy's clockwise orientation system:
public enum Orientation {
Orient0("0"), Orient90("90"), Orient180("180"), Orient270("270"),
Flip0("flip0"), Flip90("flip90"), Flip180("flip180"), Flip270("flip270");
public static Orientation fromRotation(int ccwRotation) {
// Display rotation is expressed counter‑clockwise, orientation is clockwise
int cwRotation = (4 - ccwRotation) % 4;
return values()[cwRotation];
}
}
Source: server/src/main/java/com/genymobile/scrcpy/device/Orientation.java
The fromRotation method converts Android's 0-3 counter-clockwise values to the corresponding clockwise enum values used throughout the scrcpy pipeline.
Capture Orientation in the Server Video Pipeline
The --capture-orientation option controls the orientation of the raw video stream independent of the physical device rotation.
Parsing the Capture Orientation Option
Options.parseCaptureOrientation handles the @ prefix for locking orientation:
private static Pair<Orientation.Lock, Orientation> parseCaptureOrientation(String value) {
if (value.isEmpty()) throw new IllegalArgumentException("Empty capture orientation string");
Orientation.Lock lock;
if (value.charAt(0) == '@') {
value = value.substring(1);
lock = value.isEmpty() ? Orientation.Lock.LockedInitial : Orientation.Lock.LockedValue;
} else {
lock = Orientation.Lock.Unlocked;
}
return Pair.create(lock, Orientation.getByName(value));
}
Source: server/src/main/java/com/genymobile/scrcpy/Options.java
Applying the Transform
In NewDisplayCapture, the server handles the oriented display size while ensuring input coordinates remain unrotated:
// DisplayInfo gives the oriented size (so videoSize includes the display rotation)
// This additional display rotation must not be included in the input events transform
Source: server/src/main/java/com/genymobile/scrcpy/video/NewDisplayCapture.java
When a capture orientation is specified, the server applies a rotation matrix via SurfaceControl.setDisplayProjection but does not rotate the coordinate system used for touch input events.
Client-Side Orientation and Rendering
The desktop client applies final transformations to the video stream before display or recording.
Core Logic in screen.c
The get_oriented_size function in app/src/screen.c calculates dimensions after rotation:
enum sc_orientation get_oriented_size(struct sc_size size, enum sc_orientation orientation) {
if (sc_orientation_is_swap(orientation)) {
// swap width/height when the orientation swaps axes
// ...
}
// ...
}
Source: app/src/screen.c
Changing Orientation at Runtime
The sc_screen_set_orientation function updates the display orientation dynamically:
sc_screen_set_orientation(struct sc_screen *screen, enum sc_orientation orientation) {
if (orientation == screen->orientation) return;
get_oriented_size(screen->frame_size, orientation);
screen->orientation = orientation;
LOGI("Display orientation set to %s", sc_orientation_get_name(orientation));
}
Source: app/src/screen.c
Recording Orientation
The recorder handles orientation metadata for output files:
sc_recorder_set_orientation(AVStream *stream, enum sc_orientation orientation) {
// writes rotation metadata for MP4/Matroska containers
}
Source: app/src/recorder.c
Practical Code Examples
Rotate the Device to Landscape
Force the Android device to landscape mode:
scrcpy --rotate-device
This triggers Device.rotateDevice() which calls WindowManager.freezeRotation() to lock the orientation.
Lock Capture Orientation
Keep the video stream fixed regardless of device rotation:
scrcpy --capture-orientation=@90
The @ prefix creates a LockedValue lock, and the server applies a 90-degree rotation matrix via SurfaceControl.setDisplayProjection.
Change Display Orientation on the Host
Rotate the scrcpy window independently of the device:
scrcpy --orientation=180
The client applies this rotation in sc_screen_set_orientation() before rendering.
Record with Custom Orientation
Save video with specific rotation metadata:
scrcpy --record=screen.mp4 --record-orientation=flip270
The recorder writes the appropriate rotation tag into the MP4 container via sc_recorder_set_orientation().
Summary
- Device rotation is queried via
WindowManager.getRotation()and controlled throughDevice.rotateDevice(), which freezes or thaws the Android orientation state. - Capture orientation (
--capture-orientation) locks the video stream orientation on the server side usingSurfaceControl.setDisplayProjection, independent of physical device rotation. - Client orientation (
--orientation,--display-orientation) applies final transformations inscreen.cbefore rendering, withsc_screen_set_orientation()handling runtime changes. - Recording orientation is managed separately in
recorder.c, writing rotation metadata to the output file container.
Frequently Asked Questions
How does scrcpy handle device rotation when the phone is physically turned?
Scrcpy detects physical rotation through WindowManager.getRotation() in WindowManager.java, which queries the Android display service. When the device rotates and no capture orientation lock is active, the server restarts the video encoder to match the new dimensions. If the --capture-orientation flag is locked with the @ prefix, the server maintains the specified orientation regardless of physical rotation by applying a rotation matrix via SurfaceControl.setDisplayProjection.
What is the difference between --capture-orientation and --orientation?
--capture-orientation controls the video stream on the Android device before transmission. It determines how the screen content is captured and encoded, and can be locked to a specific value (e.g., @90) to prevent rotation when the device turns. --orientation controls how the client displays the received video on the desktop, applying a final rotation in screen.c before rendering without affecting the underlying video stream or the device itself.
How does scrcpy rotate the device screen programmatically?
When using scrcpy --rotate-device, the client sends a control message that triggers Device.rotateDevice() in Device.java. This method queries the current rotation via WindowManager, calculates the opposite orientation (toggling between portrait and landscape), and calls WindowManager.freezeRotation() to lock the device to the new orientation. If the device was previously in auto-rotation mode, thawRotation() is called afterward to restore automatic rotation behavior.
Where is the orientation metadata stored when recording video?
Recording orientation is handled in app/src/recorder.c via sc_recorder_set_orientation(). This function writes rotation metadata into the output file container (MP4 or Matroska) using the appropriate AVStream fields. The orientation value comes from the --record-orientation command-line option and is transmitted from the server to the client as part of the video stream metadata, ensuring the recorded file plays back with the correct rotation regardless of the display orientation used during capture.
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 →