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 through Device.rotateDevice(), which freezes or thaws the Android orientation state.
  • Capture orientation (--capture-orientation) locks the video stream orientation on the server side using SurfaceControl.setDisplayProjection, independent of physical device rotation.
  • Client orientation (--orientation, --display-orientation) applies final transformations in screen.c before rendering, with sc_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:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →