Skip to content

Add flex display support (resizable virtual display) - #6772

Merged
rom1v merged 26 commits into
devfrom
flex-display
May 9, 2026
Merged

rom1v merged 26 commits into
devfrom
flex-display

Conversation

@rom1v

@rom1v rom1v commented Apr 16, 2026

Copy link
Copy Markdown
Collaborator
# Start Android Settings in a window
scrcpy --new-display=1024x768 --start-app=com.android.settings --flex-display

# -x is equivalent to --flex-display
scrcpy --new-display=1024x768 --start-app=com.android.settings -x

# By default, the display size/dpi is 1280x960/160
scrcpy --new-display --start-app=com.android.settings --flex-display

Use --keep-active to prevent the screen from turning off:

scrcpy --new-display --flex-display --keep-active

Increase the bit rate (and/or change the codec) to maintain good quality even with large windows:

scrcpy --new-display -x --video-codec=h265 -b16M

Demo

Here is Firefox for Android running in a "flex" virtual display, ran as follow:

scrcpy --new=/192 -x --start-app=org.mozilla.firefox --keep-active --no-vd-system-decorations
scrcpy-flex-display-2.mp4
previous video
scrcpy --new-display=800x600 -x --start-app=org.mozilla.firefox
scrcpy-flex-display.mp4

Download binaries

Here are binaries built by Github Actions (for flex-display.16): https://github.com/rom1v/scrcpy/actions/runs/25569279750

(download the artifact release-XXX where XXX is your target platform)

old versions

Preparation

To prepare compatibility between dynamic resizing and encoders constraints (minimum size, maximum size and alignment), several changes were merged:

Principles

The core of this feature (and the easy part) consists in a call to VirtualDisplay.resize().

"Resize display" requests between the client and the server must never accumulate. To achieve this:

  • requests are squashed on the client side, keeping only the latest value to send
  • requests are squashed on the server side, and the capture/encoding is reset only if the resulting size or rotation differs from the latest state
  • virtualDisplay.resize() is called from the same thread as the encoding process (otherwise Android would internally accumulate resize calls)

The difficult part is correctly handling resize events both on the client side and server sides.

In particular, a virtual display can be resized "on its own" (e.g., on app rotation, such as with Alt+r) or as the result of an asynchronous client resize request. Both cases trigger the same resize event (detected by DeviceMonitor) on the server side, but only independent resizes must reset the capture/encoding session.

On the client side, a window resize event triggers a resize request to the device, which (asynchronously) causes the frame size to change later, which in turn may trigger another client window resize…

To handle this properly, the cause of a capture reset is tracked (in particular "client resize" vs "independent resize", see DisplayPropertiesTracker) and transmitted over the wire as an additional flag in the session metadata introduced in #6159. When a new frame with a new size is received, the client can determine whether it must adapt the window size to match the frame. To avoid stuttering, the window must not be resized if the frame size change resulted from its own resize request, since it's asynchronous and additional resize requests may already be in flight.

On the client side, when --flex-display is enabled, the rendered frame is not scaled/centered in the window (see --render-fit). It is rendered 1:1 in the top-left corner (which may show black bars or cropping between the resize request and the actual resize, due to unavoidable asynchrony).

Glitches

During a display resize, the captured video stream may contain glitches. The issue arises because everything is asynchronous, involves multiple Android processes, and cannot be synchronized/atomic:

  • the call to virtualDisplay.resize()
  • the exact moment when the virtual display is actually resized
  • the display event notifying a display change
  • the call to virtualDisplay.setSurface()

In other words, resizing the display and assigning the MediaCodec Surface to the virtual display cannot be made atomic. As a result, the system may briefly render at the old size on the new surface, or at the new size on the old surface.

EDIT: also see comments below (#6772 (comment)).

Size and DPI

During a resize, the DPI is preserved. I think it's the correct thing to do.

It is possible to specify the initial size and DPI (e.g., --new-display=1920x1080/240). When not specified, the default size is 1280x960 and the default dpi is 160 (arbitrarily). Unlike "normal" mirroring mode, these values are not derived from the device display, as they are tied to the client machine.

In theory, they could be computed from the computer's display size and DPI, but this would add complexity and require initializing the SDL video module before starting the server (at least if we want to pass these data as parameter), which would slightly time-to-firstframe. I think a default size and DPI are good enough, as they can still be explicitly configured.

Unlike other PRs, there is no "render factor". The virtual display is rendered 1:1 without scaling, for better quality and simplicity.

PR History

  • flex-display.1: initial version
  • flex-display.2: rename --render-fit=natural to --render-fit=letterbox
  • flex-display.3: fixes after reviews
  • flex-display.4: change approach (see Add flex display support (resizable virtual display) #6772 (comment))
  • flex-display.5: minor refactors and rebase onto the latest dev
  • flex-display.6: fixes after review + rebase onto dev with --keep-active
  • flex-display.7: fix resize-to-fit and wrong timing logic
  • flex-display.8: fix rotation of non-flex displays
  • flex-display.9: fix resize behavior above maximum codec size
  • flex-display.10: rebase on Fix OpenGL runner shutdown deadlock #6794 to fix OpenGL graceful shutdown
  • flex-display.11: rebase and minor fixes
  • flex-display.12: allow --max-size with flex displays
  • flex-display.13: fix behavior for scale factor != 100%
  • flex-display.14: fix video constraints synchronization
  • flex-display.15: rebase on Add option to change the background color #6807 (dark background) + center unscaled display
  • flex-display.16: fix rotated virtual display size detection
  • flex-display.17: center for resize_to_fit
  • flex-display.18: minor technical changes after reviews

Supersedes #6350, #6351 and #6705.

Fixes #6632

@rom1v rom1v mentioned this pull request Apr 16, 2026
1 task
@rom1v

rom1v commented Apr 16, 2026

Copy link
Copy Markdown
Collaborator Author

Glitches

As a result, the system may briefly render at the old size on the new surface, or at the new size on the old surface.

It can be avoided almost entirely with:

diff --git a/server/src/main/java/com/genymobile/scrcpy/video/NewDisplayCapture.java b/server/src/main/java/com/genymobile/scrcpy/video/NewDisplayCapture.java
index ebe424f95..e2dca2b44 100644
--- a/server/src/main/java/com/genymobile/scrcpy/video/NewDisplayCapture.java
+++ b/server/src/main/java/com/genymobile/scrcpy/video/NewDisplayCapture.java
@@ -19,6 +19,7 @@ import com.genymobile.scrcpy.wrappers.ServiceManager;
 import android.graphics.Rect;
 import android.hardware.display.VirtualDisplay;
 import android.os.Build;
+import android.os.SystemClock;
 import android.view.Surface;
 
 import java.io.IOException;
@@ -265,6 +266,7 @@ public class NewDisplayCapture extends SurfaceCapture {
         if (virtualDisplay == null) {
             startNew(surface);
         } else {
+            SystemClock.sleep(300);
             virtualDisplay.setSurface(surface);
         }
 

But it's not a really good solution… Even if instead we wait explicitly for the display event before setting the surface, it still glitches.

@rom1v

rom1v commented Apr 16, 2026

Copy link
Copy Markdown
Collaborator Author

Oh, it's not necessarily a Surface issue (or at least not only): when the virtual display is resized, there is a fade-in/fade-out animation between the previous content (old size) and the new content (new size), so the old size is scaled and blended into the resized content for a few frames, causing a visual glitch.

It can be seen by recording and replaying in slow motion:

scrcpy --new-display=1024x768 --start-app=com.android.settings --flex-display --record=file.mp4

Then trigger one resize, then replay the file with VLC or mpv in slow motion (use [ to slow down and ] to speed up).

@Tech-Tac

Copy link
Copy Markdown
Contributor

That's great! I couldn't find any major issues, at least not ones caused by the apps or my phone not handling virtual displays well.

@Bizz91

Bizz91 commented Apr 18, 2026

Copy link
Copy Markdown

Can it also be used on main display too outside virtual display? Love it btw, Huge thanks.

@rom1v

rom1v commented Apr 18, 2026

Copy link
Copy Markdown
Collaborator Author

Can it also be used on main display too outside virtual display?

Nope, the main display cannot be resized that way (and we don't want to change the resolution of the physical display).

@Bizz91

Bizz91 commented Apr 18, 2026

Copy link
Copy Markdown

The black bars in landscape mode on main display on pc won't go away I guess right even at the same resolution as phone?

Comment thread app/src/controller.c Outdated
Comment thread server/src/main/java/com/genymobile/scrcpy/video/NewDisplayCapture.java Outdated
Comment thread server/src/main/java/com/genymobile/scrcpy/video/CaptureControl.java Outdated
Comment thread server/src/main/java/com/genymobile/scrcpy/display/DisplayPropertiesTracker.java Outdated
Comment thread app/src/screen.h
Comment thread server/src/main/java/com/genymobile/scrcpy/video/NewDisplayCapture.java Outdated
Comment thread server/src/main/java/com/genymobile/scrcpy/video/NewDisplayCapture.java Outdated
Comment thread server/src/main/java/com/genymobile/scrcpy/video/NewDisplayCapture.java Outdated
Comment thread server/src/main/java/com/genymobile/scrcpy/video/NewDisplayCapture.java Outdated
Comment thread server/src/main/java/com/genymobile/scrcpy/video/NewDisplayCapture.java Outdated
@anotheruserofgithub

anotheruserofgithub commented Apr 18, 2026

Copy link
Copy Markdown

I have a dumb question about glitches (#6772 (comment)), it's probably nonsensical: Could you intentionally drop frames between requesting a resize and setting the new surface? Is it equivalent to your tentative sleep? Would it lead to stuttering (instead of glitches) if it takes too much time? And would the next few frames still show the same issue (#6772 (comment))?

@anotheruserofgithub

anotheruserofgithub commented Apr 18, 2026

Copy link
Copy Markdown

By the way, the documentation of VirtualDisplay.setSurface() says:

It is still the caller's responsibility to destroy the surface after it has been detached. 

Thus maybe you should do this in NewDisplayCapture.start():

            Surface oldSurface = virtualDisplay.getSurface();
            virtualDisplay.setSurface(surface);
            if (oldSurface != null) {
                oldSurface.release();
            }

But I'm really not sure because I don't fully understand how and when virtual displays are (re)created.

[EDIT] The doc is not really clear because "detached" is when you set the surface to null, but what happens when you just change it? Is it released automatically?

@yume-chan

Copy link
Copy Markdown
Contributor

For physical displays, instead of changing physical display resolution, maybe it can change capture resolution instead. Would be useful when streaming over Internet, or reduce CPU/RAM usages when multiple instances are open (tiled, each one is small)

@rom1v

rom1v commented Apr 19, 2026

Copy link
Copy Markdown
Collaborator Author

@anotheruserofgithub I really appreciate the detailed feedback, it's super helpful. I fixed in a new version.

Thus maybe you should do this in NewDisplayCapture.start()

The surface is owned by SurfaceEncoder, it is already released there.

It seems forcing an OpenGL filter improves the result (not sure if it's just a side effect), but I think it is still not perfect:

diff --git a/server/src/main/java/com/genymobile/scrcpy/video/NewDisplayCapture.java b/server/src/main/java/com/genymobile/scrcpy/video/NewDisplayCapture.java
index 851be5579..8ad77fb7a 100644
--- a/server/src/main/java/com/genymobile/scrcpy/video/NewDisplayCapture.java
+++ b/server/src/main/java/com/genymobile/scrcpy/video/NewDisplayCapture.java
@@ -213,6 +213,10 @@ public class NewDisplayCapture extends SurfaceCapture {
         //                    = DISPLAY_FILTER_MATRIX⁻¹ * FILTER_MATRIX⁻¹
         //                    = displayRotationMatrix * eventTransform
         displayTransform = AffineMatrix.multiplyAll(displayRotationMatrix, eventTransform);
+        if (flexDisplay && displayTransform == null) {
+            // Force OpenGL rendering to avoid glitches on resize
+            displayTransform = AffineMatrix.IDENTITY;
+        }
     }
 
     public void startNew(Surface surface) {
@@ -282,6 +286,9 @@ public class NewDisplayCapture extends SurfaceCapture {
             glRunner.stopAndRelease();
             glRunner = null;
         }
+        if (virtualDisplay != null) {
+            virtualDisplay.setSurface(null);
+        }
     }
 
     @Override

@rom1v

rom1v commented Apr 19, 2026

Copy link
Copy Markdown
Collaborator Author

@yume-chan

For physical displays, instead of changing physical display resolution, maybe it can change capture resolution instead. Would be useful when streaming over Internet, or reduce CPU/RAM usages when multiple instances are open (tiled, each one is small)

That's an idea for a separate feature (although the quality is lower if you scale the capture because there is no mipmapping), but it won't allow to change the aspect ratio anyway. Not sure this needs to be dynamic (I don't know)?

Comment thread doc/window.md Outdated
@anotheruserofgithub

Copy link
Copy Markdown

when the virtual display is resized, there is a fade-in/fade-out animation between the previous content (old size) and the new content (new size)

Apparently you'd need to turn off window animation, either in Developer options or programmatically:

But I couldn't find a way to get the window shown on the virtual display (is there any?) and change its layout params.

@rom1v

rom1v commented Apr 19, 2026

Copy link
Copy Markdown
Collaborator Author

Apparently you'd need to turn off window animation, either in Developer options or programmatically

It can be turned off globally (and it improves the visual result):

diff
diff --git a/server/src/main/java/com/genymobile/scrcpy/wrappers/WindowManager.java b/server/src/main/java/com/genymobile/scrcpy/wrappers/WindowManager.java
index 7ba5cc06d..8d2c1de4c 100644
--- a/server/src/main/java/com/genymobile/scrcpy/wrappers/WindowManager.java
+++ b/server/src/main/java/com/genymobile/scrcpy/wrappers/WindowManager.java
@@ -6,6 +6,7 @@ import com.genymobile.scrcpy.util.Ln;
 import android.annotation.TargetApi;
 import android.os.Build;
 import android.os.IInterface;
+import android.provider.Settings;
 import android.view.IDisplayWindowListener;
 
 import java.lang.reflect.Method;
@@ -40,6 +41,7 @@ public final class WindowManager {
 
     private WindowManager(IInterface manager) {
         this.manager = manager;
+        disableAnimations();
     }
 
     private Method getGetRotationMethod() throws NoSuchMethodException {
@@ -264,4 +266,22 @@ public final class WindowManager {
             Ln.e("Could not invoke method", e);
         }
     }
+
+    public void disableAnimations() {
+        try {
+            Method getMethod = manager.getClass().getMethod("getAnimationScale", int.class);
+            // Settings.Global.WINDOW_ANIMATION_SCALE
+            Ln.i("  [0] = " + getMethod.invoke(manager, 0));
+            // Settings.Global.TRANSITION_ANIMATION_SCALE
+            Ln.i("  [1] = " + getMethod.invoke(manager, 1));
+            // Settings.Global.ANIMATOR_DURATION_SCALE
+            Ln.i("  [2] = " + getMethod.invoke(manager, 2));
+            Method method = manager.getClass().getMethod("setAnimationScale", int.class, float.class);
+            method.invoke(manager, 0, 0);
+            method.invoke(manager, 1, 0);
+            method.invoke(manager, 2, 0);
+        } catch (ReflectiveOperationException e) {
+            Ln.e("Could not invoke method", e);
+        }
+    }
 }

(the relevant value is at index 1, the "transition animation scale")

They can also be set via adb:

# get values
adb shell settings get global window_animation_scale
adb shell settings get global transition_animation_scale
adb shell settings get global animator_duration_scale

# disable
adb shell settings put global window_animation_scale 0
adb shell settings put global transition_animation_scale 0
adb shell settings put global animator_duration_scale 0

# restore
adb shell settings put global window_animation_scale 1
adb shell settings put global transition_animation_scale 1
adb shell settings put global animator_duration_scale 1

That does not resolve all glitches though.

@anotheruserofgithub

Copy link
Copy Markdown

That does not resolve all glitches though.

This other thing I saw was ROTATION_ANIMATION_JUMPCUT that can be set to WindowManager.LayoutParams.rotationAnimation but I don't know on which LayoutParams instance to do it and whether it's relevant for resizing flex VDs (as it seems to be specifically about rotation).

@rom1v rom1v changed the title Add flex display support (resizable virtual display) [Draft] Add flex display support (resizable virtual display) Apr 20, 2026
@rom1v

rom1v commented Apr 21, 2026

Copy link
Copy Markdown
Collaborator Author
  • virtualDisplay.resize() is called from the same thread as the encoding process (otherwise Android would internally accumulate resize calls)

I finally abandoned this approach: virtualDisplay.resize() itself is an asynchronous request, and multiple requests can accumulate faster than the display can actually update, so it did not make sense.

In the end, I implemented an event debouncer (based on timing, something I initially wanted to avoid) that applies a resize (by calling virtualDisplay.resize() only after a fixed delay (hardcoded to 300 ms), collapsing all resize events within that window into a single update.

In case you're wondering, the debouncer is implemented on the server side (not on the client side) for several reasons:

  • if the display size/rotation changes independently, any the pending client resizes must be canceled (this is not possible if the client resize events are sent after a delay)
  • what matters is the interval between actual resize() calls on the server side (if the client sends several resizes over wifi and the network lags, those requests could otherwise be applied in rapid succession once they arrive) (this is a minor concern in practice)
  • in the future, we may be able to avoid a time-based debouncer and instead trigger resizes precisely when the display is "ready" (whatever that means), which would inherently be a server-side concern

@anotheruserofgithub

anotheruserofgithub commented Apr 21, 2026

Copy link
Copy Markdown

I installed a Genymotion emulator and built this PR. I can resize the window a couple times but it always crashes with a buffer dequeue exception (expand to see the logs). This only happens with --flex-display.

Terminal output
$ ./run x -Vverbose --no-audio --flex-display --new-display=570x1230/220
scrcpy 3.3.4 <https://github.com/Genymobile/scrcpy>
INFO: ADB device found:
INFO:     --> (tcpip)  127.0.0.1:6555                  device  Phone
DEBUG: Device serial: 127.0.0.1:6555
DEBUG: Using SCRCPY_SERVER_PATH: x/server/scrcpy-server
x/server/scrcpy-server: 1 file pushed, 0 skipped. 337.3 MB/s (730578 bytes in 0.002s)
[server] INFO: Device: [Genymobile] Genymotion Phone (Android 15)
DEBUG: Server connected
DEBUG: Starting controller thread
DEBUG: Starting receiver thread
[server] DEBUG: Using video encoder: 'OMX.google.h264.encoder'
[server] DEBUG: Video codec size alignment requirement: 1px
[server] INFO: New display: 570x1230/220 (id=2)
INFO: Renderer: opengl
INFO: OpenGL version: 4.6 (Compatibility Profile) Mesa 25.2.8-0ubuntu0.24.04.1
INFO: Trilinear filtering enabled
DEBUG: Using icon from SCRCPY_ICON_DIR: app/data/scrcpy.png
DEBUG: Demuxer 'video': starting thread
VERBOSE: resize_display(512, 1104)
VERBOSE: input: resize display 512x1104
[server] VERBOSE: NewDisplayCapture: requestResize(512, 1104)
[server] VERBOSE: NewDisplayCapture: constrained size = 512x1104
INFO: Texture: 512x1104
[server] VERBOSE: DisplayMonitor: onDisplayConfigurationChanged(2)
[server] VERBOSE: DisplayMonitor: 570x1230 [rotation=0] -> 512x1104 [rotation=0]
VERBOSE: resize_display(513, 1104)
VERBOSE: input: resize display 513x1104
[server] VERBOSE: NewDisplayCapture: requestResize(513, 1104)
[server] VERBOSE: NewDisplayCapture: constrained size = 512x1101
VERBOSE: resize_display(506, 1094)
VERBOSE: input: resize display 506x1094
[server] VERBOSE: NewDisplayCapture: requestResize(506, 1094)
[server] VERBOSE: NewDisplayCapture: constrained size = 506x1094
VERBOSE: resize_display(498, 1081)
VERBOSE: input: resize display 498x1081
[server] VERBOSE: NewDisplayCapture: requestResize(498, 1081)
[server] VERBOSE: NewDisplayCapture: constrained size = 498x1081
VERBOSE: resize_display(496, 1077)
VERBOSE: input: resize display 496x1077
[server] VERBOSE: NewDisplayCapture: requestResize(496, 1077)
[server] VERBOSE: NewDisplayCapture: constrained size = 496x1077
[server] VERBOSE: DisplayMonitor: onDisplayConfigurationChanged(2)
[server] VERBOSE: DisplayMonitor: 512x1104 [rotation=0] -> 496x1077 [rotation=0]
[server] DEBUG: Screen streaming stopped
[server] DEBUG: Device message sender stopped
DEBUG: Demuxer 'video': end of frames
DEBUG: Receiver stopped
WARN: Device disconnected
[server] DEBUG: Controller stopped
[server] ERROR: Exception on thread Thread[video,5,main]
java.lang.IllegalStateException: Pending dequeue output buffer request cancelled

	at android.media.MediaCodec.native_dequeueOutputBuffer(Native Method)
	at android.media.MediaCodec.dequeueOutputBuffer(MediaCodec.java:4057)
	at com.genymobile.scrcpy.video.SurfaceEncoder.encode(SurfaceEncoder.java:171)
	at com.genymobile.scrcpy.video.SurfaceEncoder.streamCapture(SurfaceEncoder.java:136)
	at com.genymobile.scrcpy.video.SurfaceEncoder.lambda$start$0$com-genymobile-scrcpy-video-SurfaceEncoder(SurfaceEncoder.java:265)
	at com.genymobile.scrcpy.video.SurfaceEncoder$$ExternalSyntheticLambda0.run(D8$$SyntheticClass:0)
	at java.lang.Thread.run(Thread.java:1012)
DEBUG: Using icon from SCRCPY_ICON_DIR: app/data/disconnected.png
DEBUG: Controller stopped
Killed 
DEBUG: Server disconnected
DEBUG: Server terminated
DEBUG: Closing after device disconnection
DEBUG: Quit...

Side remark: Maybe the "disconnected" icon should be positioned at the center of the window regardless of the render-fit mode?

@rom1v

rom1v commented Apr 22, 2026

Copy link
Copy Markdown
Collaborator Author

I can resize the window a couple times but it always crashes with a buffer dequeue exception

Is it specific to the new version of this PR (flex-display.4), or did it also happen with flex-display.1?

@anotheruserofgithub

Copy link
Copy Markdown

Is it specific to the new version of this PR (flex-display.4), or did it also happen with flex-display.1?

It fails the same way with all versions, including flex-display.1. I just updated #6772 (comment) with verbose logs. Let me know if I can do something more to help.

@anotheruserofgithub

anotheruserofgithub commented Apr 22, 2026

Copy link
Copy Markdown

See SurfaceEncoder from #6350, that seems to be related. I can test the diff later if you want.

[EDIT] The try-catch avoids the crash (the exception message is "Pending dequeue output buffer request cancelled"), but then the issue is that the virtual display is not resized. It gets resized only when there are no such exceptions.

@rom1v

rom1v commented Apr 23, 2026

Copy link
Copy Markdown
Collaborator Author

I can resize the window a couple times but it always crashes with a buffer dequeue exception

I don't know if it's an emulator issue, or if it can happen on a real device. If it cannot happen on real devices, it's not a big deal I think.

You edited #6772 (comment) to include verbose logs, but the exception is not exactly the same as the initial one. Initially it included this error message:

java.lang.IllegalStateException: Pending dequeue output buffer request cancelled

Btw, can you reproduce with --min-size-alignment=16?

rom1v added 15 commits May 9, 2026 17:28
Add an option to configure how the rendering fits the window.

The default, `--render-fit=letterbox`, preserves the aspect ratio
and fits the window as best as possible, adding black bars at
the top/bottom or left/right if needed. This has been the only behavior
scrcpy supported so far.

Another mode, `--render-fit=unscaled`, renders the display without
scaling. This mode will be useful for virtual display resizing.

Refs #6772 comment <#6772 (review)>
PR #6772 <#6772>
Introduce `--flex-display` (or `-x`) to continuously resize the virtual
display to match the window.

Fixes #6632 <#6632>
PR #6772 <#6772>
The physical size of the virtual display does not change when the
display is rotated, although the reported display size does.

Refs #6772 comment <#6772 (review)>
PR #6772 <#6772>
Track resize requests caused by frame-size changes to avoid sending
incorrect "resize display" requests to the server.

PR #6772 <#6772>
Previously, the minor dimension was rounded to the nearest multiple of
the alignment requirement when constraining size. This could result in
the dimension being increased.

Change the behavior to always round down instead, ensuring the
constrained size never grows.

This fixes a conflict with the client-side "optimal window size"
computation, which never increases dimensions. With flex displays, the
old behavior could lead to feedback loop between window and display
resizing with mismatched dimensions:

 - window  resized to 2341x1317
 - display resized to 2340x1318
 - window  resized to 2338x1317
 - display resized to 2336x1318

PR #6772 <#6772>
Make the minimum codec size respect the provided `--min-size-alignment`
value.

Refs #6766 comment <#6766 (comment)>
PR #6772 <#6772>
The size must be constrained by the video capabilities, but unlike
fixed displays, the aspect ratio should not be preserved in order to use
the maximum available area.

Refs #6772 comment <#6772 (comment)>
PR #6772 <#6772>
Add an option to fit the window without preserving the aspect ratio.

PR #6772 <#6772>
The `--max-size` option behaves slightly differently depending on the
mode.

Refs #6772 comment <#6772 (comment)>
PR #6772 <#6772>
On a computer with a scale factor different from 1, physical and logical
sizes differ. For example, with a scale factor of 2, if the logical
(window) size is 800x600, the physical (renderer) size is 1600x1200.

They were not interpreted consistently in scrcpy.

To fix the confusion:
 - resize the flex display according to the logical size
   (SDL_EVENT_WINDOW_RESIZED);
 - scale rendering to match the physical area defined by the logical
   size;
 - no longer convert input event coordinates.

This was not an issue before flex displays because rendering scaled the
content to fit the window (`--render-fit=letterbox`), so the difference
in physical size had no effect.

Refs #6772 comment <#6772 (comment)>
PR #6772 <#6772>
Centering unscaled content should not cause blurring due to pixel
misalignment.

PR #6772 <#6772>
@rom1v

rom1v commented May 9, 2026

Copy link
Copy Markdown
Collaborator Author

Time to merge 🚀

Many thanks again to @anotheruserofgithub for the deep-dive review and thorough testing, which helped catch important issues. 👍

@rom1v
rom1v merged commit 1db8da5 into dev May 9, 2026
@anotheruserofgithub

Copy link
Copy Markdown

Great work! Thanks. :)

Two things left to do now that this is merged:

rom1v added a commit that referenced this pull request May 11, 2026
@rom1v

rom1v commented May 11, 2026

Copy link
Copy Markdown
Collaborator Author

@anotheruserofgithub

Update links in doc/develop.md

Done in d678988.

Correct the link to virtual-display.md

Done in the merge commit from master into dev: d678988.

@anotheruserofgithub anotheruserofgithub left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Some minor details I was about to post just when you merged, I thought they wouldn't be worth the trouble so I abandoned them, but since I already "revived" another closed PR (#6822 (review)), maybe you might want to consider these as well.

Comment thread app/src/demuxer.c
Comment thread app/src/demuxer.c
Comment thread doc/virtual_display.md
rom1v added a commit that referenced this pull request May 27, 2026
The `dpi` field is not synchronized, and the DPI to use is the one from
the latest `DisplayInfo`.

Refs #6772 comment <#6772 (comment)>
rom1v added a commit that referenced this pull request May 27, 2026
For consistency, name the "client resize" flag 'R' and add the "frame
header" arrow to make the schemas in `app/src/demuxer.c` and
`doc/develop.md` identical.

Refs #6772 comment <https://github.com/Genymobile/scrcpy/pull/6772/changes#r3305729151>
Refs #6772 comment <https://github.com/Genymobile/scrcpy/pull/6772/changes#r3305729425>
rom1v added a commit that referenced this pull request May 27, 2026
For consistency, name the "client resize" flag 'R' and add the "frame
header" arrow to make the schemas in `app/src/demuxer.c` and
`doc/develop.md` identical.

Refs #6772 comment <#6772 (comment)>
Refs #6772 comment <#6772 (comment)>
@perlicacute

Copy link
Copy Markdown

this is crazy good for app development

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

7 participants