fix(server): RDP 会话中候选窗缩放跟随宿主应用实际渲染比例

This commit is contained in:
Jia Sui committed 2026-09-11 22:21:57 +08:00
1 parent 62b4688aa1
commit 753312f32c
4 files changed
+103 -6

No files matched your search

+18
View File
@@ -110,6 +110,24 @@ FLOAT GetScaleForPoint(POINT pt)
return ScaleFromMonitor(MonitorFromPoint(pt, MONITOR_DEFAULTTONEAREST));
}
ResolvedCandidateScale ResolveCandidateScaleForCaret(POINT caret)
{
// SM_REMOTESESSION is only set inside a real remote session; console and
// local logons never see it, so the local path is byte-for-byte unchanged.
// GetDpiForWindow returns 0 for an invalid/absent foreground HWND, which
// drops to the monitor path instead of trusting a bogus 0 scale.
if (GetSystemMetrics(SM_REMOTESESSION))
{
const UINT dpi = GetDpiForWindow(GetForegroundWindow());
if (dpi != 0)
{
return {static_cast<FLOAT>(dpi) / static_cast<FLOAT>(USER_DEFAULT_SCREEN_DPI),
CandidateScaleSource::RdpForeground};
}
}
return {GetScaleForPoint(caret), CandidateScaleSource::Monitor};
}
HalfScreenDipLimits QueryHalfScreenDipLimitsForPoint(POINT pt)
{
HalfScreenDipLimits limits{};
+28
View File
@@ -10,6 +10,34 @@ FLOAT GetForegroundWindowScale();
// Prefer the monitor that contains the caret / composition anchor. Foreground
// HWND can disagree with the caret on Office extended-display setups.
FLOAT GetScaleForPoint(POINT pt);
// Where the candidate scale came from — diagnostic logging must be able to
// tell the RDP foreground path apart from the plain monitor path.
enum class CandidateScaleSource
{
Monitor, // caret's monitor DPI (local sessions, and RDP fallback)
RdpForeground, // foreground window DPI inside an RDP session
};
struct ResolvedCandidateScale
{
FLOAT scale = 0.0f;
CandidateScaleSource source = CandidateScaleSource::Monitor;
};
// Candidate window scale authority. RDP syncs the client's display scaling
// into the session's monitor DPI metadata (150% client -> 144), while the
// focused application in the session usually still renders at 96 DPI, so the
// monitor metadata is unreliable there and GetDpiForWindow(foreground) is the
// best proxy for what the user actually sees:
// - DPI-unaware / System Aware host -> virtualized 96, matches the app;
// - PMv2 and correctly following -> current monitor DPI, matches the app;
// - PMv2 but not following (pathological) -> mismatch, cannot be detected
// via public APIs; documented as a known residue, not probed.
// Local sessions keep the caret-monitor convention (mixed-DPI multi-monitor
// setups depend on it). Falls back to the monitor path when the foreground
// window is gone (lock screen, focus switch) so behavior matches pre-fix.
ResolvedCandidateScale ResolveCandidateScaleForCaret(POINT caret);
// Candidate placement uses that monitor's work area to avoid taskbars/app bars.
MonitorCoordinates GetMonitorCoordinatesFromPoint(POINT pt);
+50 -5
View File
@@ -67,6 +67,23 @@ int AlignHostPixels(int value)
return ((value + kBucket - 1) / kBucket) * kBucket;
}
// Recompute half-screen DIP limits against `scale` when it differs from the
// monitor-derived scale, so clamping and window sizing stay on one scale
// (mirrors the webview path's rasterization-scale composition).
HalfScreenDipLimits ApplyScaleToHalfScreenLimits(POINT pt, FLOAT scale)
{
HalfScreenDipLimits limits = QueryHalfScreenDipLimitsForPoint(pt);
if (scale > 0.0f)
{
const double monitorWidthPx = static_cast<double>((std::max)(1, limits.monitor.right - limits.monitor.left));
const double monitorHeightPx = static_cast<double>((std::max)(1, limits.monitor.bottom - limits.monitor.top));
limits.scale = scale;
limits.maxWidthDip = (monitorWidthPx * 0.5) / static_cast<double>(scale);
limits.maxHeightDip = (monitorHeightPx * 0.5) / static_cast<double>(scale);
}
return limits;
}
D2D1_COLOR_F ParseCssColor(const std::string &text, D2D1_COLOR_F fallback)
{
std::string value = TrimCopy(text);
@@ -848,14 +865,23 @@ void CandidatePresenter::RestoreHostAfterMenu()
impl_->hostExpandedForMenu = false;
}
void CandidatePresenter::PlaceAndShow(POINT caret, float widthDip, float heightDip, float cardLeftDip, float cardTopDip)
void CandidatePresenter::PlaceAndShow(POINT caret, float widthDip, float heightDip, float cardLeftDip, float cardTopDip,
const ResolvedCandidateScale &resolved)
{
const HalfScreenDipLimits limits = QueryHalfScreenDipLimitsForPoint(caret);
FLOAT scale = limits.scale > 0.0f ? limits.scale : GetScaleForPoint(caret);
FLOAT scale = resolved.scale;
CandidateScaleSource scaleSource = resolved.source;
if (scale <= 0.0f)
{
const ResolvedCandidateScale now = ResolveCandidateScaleForCaret(caret);
scale = now.scale;
scaleSource = now.source;
}
if (scale <= 0.0f)
{
scale = 1.0f;
scaleSource = CandidateScaleSource::Monitor;
}
const HalfScreenDipLimits limits = ApplyScaleToHalfScreenLimits(caret, scale);
widthDip = static_cast<float>(ClampWidthDipToHalfScreen(widthDip, limits));
heightDip = static_cast<float>(ClampHeightDipToHalfScreen(heightDip, limits));
lastLayoutWidthDip_ = widthDip;
@@ -910,9 +936,24 @@ void CandidatePresenter::PlaceAndShow(POINT caret, float widthDip, float heightD
y = monitor.top + 2 - cardTopPx;
}
SetWindowPos(hwnd_, HWND_TOPMOST, x, y, widthPx, heightPx, SWP_NOACTIVATE | SWP_SHOWWINDOW);
// Rendering must use the same scale the window was just sized with: the ui
// override replaces GetDpiForWindow (144 inside RDP) so DIPs map exactly
// onto the pixels computed above. Must land before EnsureForComposition —
// a freshly created target reads it through DpiForHwnd().
const FLOAT dpiOverride = scale * 96.0f;
impl_->window->SetDpiOverride(dpiOverride);
impl_->resources.SetDpiOverride(dpiOverride);
impl_->resources.EnsureForComposition(hwnd_);
Present();
SetCandidateHostCloaked(false);
// Acceptance trace for the RDP candidate-scale fix: which scale authority
// won, and the system's own (potentially diverging) DPI values. Coordinates
// and DPI only — never user input, per the diagnostic logging red lines.
CAND_DIAG_LOGF(L"candidate-d2d place source={} scale={:.3f} hwnd_dpi={} system_dpi={} remote={} caret=({},{}) "
L"size_px=({},{})",
scaleSource == CandidateScaleSource::RdpForeground ? L"rdp-foreground" : L"monitor", scale,
GetDpiForWindow(hwnd_), GetDpiForSystem(), GetSystemMetrics(SM_REMOTESESSION) ? 1 : 0, caret.x,
caret.y, widthPx, heightPx);
}
void CandidatePresenter::ShowFromGlobalState()
@@ -970,7 +1011,11 @@ void CandidatePresenter::ShowFromGlobalState(POINT caret)
}
FillItemsFromUi();
impl_->list->SetHoverEnabled(hoverArmed_);
const HalfScreenDipLimits limits = QueryHalfScreenDipLimitsForPoint(caret);
// Resolve the scale once per show and thread it through measure, clamping,
// window sizing and rendering DPI — a foreground window changing mid-show
// must not split measure (clamped at one scale) from placement (another).
const ResolvedCandidateScale scale = ResolveCandidateScaleForCaret(caret);
const HalfScreenDipLimits limits = ApplyScaleToHalfScreenLimits(caret, scale.scale);
const float maxW = limits.maxWidthDip > 1.0 ? static_cast<float>(limits.maxWidthDip) : 480.0f;
const float maxH = limits.maxHeightDip > 1.0 ? static_cast<float>(limits.maxHeightDip) : 640.0f;
impl_->root->InvalidateMeasure();
@@ -992,7 +1037,7 @@ void CandidatePresenter::ShowFromGlobalState(POINT caret)
cardLeftDip = card.x;
cardTopDip = card.y;
}
PlaceAndShow(caret, widthDip, heightDip, cardLeftDip, cardTopDip);
PlaceAndShow(caret, widthDip, heightDip, cardLeftDip, cardTopDip, scale);
::is_global_wnd_cand_shown = true;
}
+7 -1
View File
@@ -1,5 +1,7 @@
#pragma once
#include "utils/window_utils.h"
#include <memory>
#include <string>
#include <windows.h>
@@ -26,7 +28,11 @@ class CandidatePresenter
void RebuildScene();
void ApplySkin();
void FillItemsFromUi();
void PlaceAndShow(POINT caret, float widthDip, float heightDip, float cardLeftDip, float cardTopDip);
// `scale` carries the scale resolved once per show in ShowFromGlobalState
// so measure, clamping, sizing and rendering all share one source; an
// unset (scale == 0) value makes PlaceAndShow resolve it itself.
void PlaceAndShow(POINT caret, float widthDip, float heightDip, float cardLeftDip, float cardTopDip,
const ResolvedCandidateScale &scale = {});
void ArmHoverIfPointerMoved();
void CommitItem(size_t pageIndex);
void ShowItemContextMenu(size_t pageIndex, POINT clientPoint);