Add fit-width-pan-Y viewer mode for portrait streams on landscape monitors#1867
Draft
REMvisual wants to merge 2 commits intomoonlight-stream:masterfrom
Draft
Add fit-width-pan-Y viewer mode for portrait streams on landscape monitors#1867REMvisual wants to merge 2 commits intomoonlight-stream:masterfrom
REMvisual wants to merge 2 commits intomoonlight-stream:masterfrom
Conversation
…itors When streaming a portrait remote desktop (e.g. 1440x2560) onto a landscape monitor, the default aspect-preserving scaler letterboxes the stream into a narrow centered column with large unused bars. This adds an opt-in viewer-side mode that scales the stream to fill the window width and pans vertically based on the local cursor's Y position - so the user sees a zoomed-in view of the remote and can move their cursor to scroll it. The mode is purely client-side: the host's resolution, capture, and protocol input semantics are unchanged. Coordinates sent to the host go through the existing scaleSourceToDestinationSurface->LiSendMousePositionEvent path - the new dst rect (with negative y and h > window_h) inverts correctly through the existing transform. Implementation: - New StreamingPreferences::fitWidthPanY (default off) with QSettings persistence - StreamUtils::scaleSourceToDestinationSurface now reads the pref. When the default path would letterbox left/right (stream taller than window), it instead extends dst vertically and offsets dst.y by the most recently computed pan amount. All 11 renderer call sites and the input/touch call sites pick this up with no per-call-site changes. - Pan offset is stored in a static QAtomicInt and updated by the absolute-mouse motion handler from the cursor's window-relative Y. This guarantees the inverse transform in mouse.cpp uses the same offset that the next render frame will display. - updatePointerRegionLock clamps dst to the window via SDL_IntersectRect so SDL_SetWindowMouseRect receives a valid in-window rect when fit-width is active and the unclamped dst extends offscreen. - Settings UI checkbox in the Input Settings group. Renderer compatibility: SDL (viewport-clipped), D3D11VA (NDC rasterizer-clipped), DXVA2, EGL, VAAPI, Vulkan/libplacebo, MMAL, CUDA, VDPAU all handle the extended dst correctly via natural clipping. DRM (kernel mode setting via configurePlane) is the one platform where strict hardware plane bounds may need a per-renderer clamp; not addressed in this change. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Motivation
When streaming a host with a portrait (vertical) display resolution (e.g. 1440×2560) onto a landscape client monitor (e.g. 1920×1080), the default aspect-preserving scaler letterboxes the stream into a narrow centered column with two large unused bars on the left and right. Most of the local screen is wasted.
This PR adds an opt-in viewer-side mode that:
The mode is purely client-side: the host's resolution, capture pipeline, and protocol input semantics are completely unchanged. Coordinates flow through the same
scaleSourceToDestinationSurface→LiSendMousePositionEventpath as today; the new dst rect (with negativeyandh > window_h) inverts correctly through the existing transform.Design
The implementation hinges on the fact that every renderer backend and the input/touch code already routes through one helper,
StreamUtils::scaleSourceToDestinationSurface. By making that helper read a new pref and produce a different dst rect when active, all 11 renderer backends and the input-coordinate transform pick up the new behavior with zero per-call-site changes.When the pref is on and the default math would have letterboxed left/right (i.e. stream is taller than window aspect), the helper:
dst.x = 0,dst.w = window_w(full window width)dst.h = ceil(window_w * src.h / src.w)(extends past window vertically — the zoomed height)dst.y -= panOffset(negative, off-screen top)The pan offset is stored in a
static QAtomicIntand updated bySdlInputHandler::handleMouseMotionEventfrom the cursor's window-relative Y. Writing it from the input handler before computing the inverse transform guarantees the renderer's next frame and this event'sLiSendMousePositionEventuse the same offset, keeping click coordinates consistent with what the user sees.Renderer compatibility
sdlvid)SDL_RenderSetViewportwith extended rect — SDL clips to renderer outputIDirect3DDevice9::StretchRectafter scalepl_render_imagewith crop extending past target — clips per libplacebo blit semanticsconfigurePlaneare strictCursor confinement (
updatePointerRegionLock) clamps dst to the window viaSDL_IntersectRect, soSDL_SetWindowMouseRectalways receives an in-window rect.Scope / non-goals
LiSendMouseMoveEventfor games) don't scale deltas in this PR — the feature is a desktop-streaming tool. Easy follow-up to scalexrel/yrelby1/zoomin fit-width mode.Files
app/settings/streamingpreferences.{h,cpp}— newfitWidthPanYbool, QSettings keyfitwidthpanyapp/streaming/streamutils.{h,cpp}— atomic pan-offset getter/setter, scaler reads prefapp/streaming/input/mouse.cpp— pan offset write before inverse transform;SDL_IntersectRecton confinementapp/gui/SettingsView.qml— checkbox in Input Settings groupTesting
Draft because I have not yet built and run this against a real portrait host. Will mark ready-for-review after a local smoke test on Windows (D3D11VA + SDL fallback). Happy to take review feedback in parallel.