全屏缩放默认不再置顶 (#1216)

* feat: 全屏缩放时默认不再置顶,添加置顶选项

* fix: 修复消息弹窗会使窗口取消置顶的问题

* fix: 修复消息弹窗弹出动画 bug
This commit is contained in:
Xu 2025-07-28 18:26:42 +08:00 committed by GitHub
commit d1076bbb95
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
24 changed files with 179 additions and 101 deletions

View file

@ -7,7 +7,7 @@ namespace Magpie {
// 根据需要在交换链和 DirectComposition 两种呈现方式间切换。交换链可以触发
// DirectFlip/IndependentFlip 以最小化延迟DirectComposition 在调整尺寸
// 时闪烁更少,这个呈现器旨在结合两者的优势。
class AdaptivePresenter : public PresenterBase {
class AdaptivePresenter final : public PresenterBase {
protected:
bool _Initialize(HWND hwndAttach) noexcept override;

View file

@ -5,7 +5,7 @@
namespace Magpie {
class CompSwapchainPresenter : public PresenterBase {
class CompSwapchainPresenter final : public PresenterBase {
protected:
bool _Initialize(HWND hwndAttach) noexcept override;

View file

@ -36,46 +36,6 @@ static std::string LogEffects(const std::vector<EffectOption>& effects) noexcept
return result;
}
bool ScalingOptions::Prepare() noexcept {
if (screenshotsDir.empty()) {
Logger::Get().Error("screenshotsDir 为空");
return false;
}
if (!showToast) {
Logger::Get().Error("showToast 为空");
return false;
}
if (!showError) {
Logger::Get().Error("showError 为空");
return false;
}
if (!save) {
Logger::Get().Error("save 为空");
return false;
}
if (IsWindowedMode()) {
if (Is3DGameMode()) {
return false;
}
// Desktop Duplication 不支持窗口模式缩放
if (captureMethod == CaptureMethod::DesktopDuplication) {
captureMethod = CaptureMethod::GraphicsCapture;
}
}
// GDI 和 DwmSharedSurface 不支持捕获标题栏
if (captureMethod == CaptureMethod::GDI || captureMethod == CaptureMethod::DwmSharedSurface) {
IsCaptureTitleBar(false);
}
return true;
}
void ScalingOptions::Log() const noexcept {
Logger::Get().Info(fmt::format(R"(缩放选项
IsWindowedMode: {}

View file

@ -51,9 +51,7 @@ ScalingRuntime::~ScalingRuntime() {
}
bool ScalingRuntime::Start(HWND hwndSrc, ScalingOptions&& options) {
if (!options.Prepare()) {
return false;
}
assert(!options.screenshotsDir.empty() && options.showToast && options.showError && options.save);
_Dispatcher().TryEnqueue([this, hwndSrc, options(std::move(options))]() mutable {
// 初始化时视为处于缩放状态

View file

@ -47,6 +47,29 @@ static void LogRects(const RECT& srcRect, const RECT& rendererRect, const RECT&
}
ScalingError ScalingWindow::_StartImpl(HWND hwndSrc) noexcept {
Logger::Get().Info(fmt::format("缩放开始\n\t程序版本: {}\n\tOS 版本: {}\n\t管理员: {}",
#ifdef MP_VERSION_TAG
STRING(MP_VERSION_TAG),
#else
"dev",
#endif
Win32Helper::GetOSVersion().ToString<char>(),
Win32Helper::IsProcessElevated() ? "" : ""
));
if (_options.IsWindowedMode()) {
if (_options.Is3DGameMode()) {
return ScalingError::Windowed3DGameMode;
} else if (_options.captureMethod == CaptureMethod::DesktopDuplication) {
return ScalingError::WindowedDesktopDuplication;
}
}
if (FindWindow(CommonSharedConstants::SCALING_WINDOW_CLASS_NAME, nullptr)) {
Logger::Get().Error("已存在缩放窗口");
return ScalingError::ScalingFailedGeneral;
}
InitMessage();
_runtimeError = ScalingError::NoError;
@ -58,21 +81,6 @@ ScalingError ScalingWindow::_StartImpl(HWND hwndSrc) noexcept {
_areResizeHelperWindowsVisible = false;
_isSrcRepositioning = false;
if (FindWindow(CommonSharedConstants::SCALING_WINDOW_CLASS_NAME, nullptr)) {
Logger::Get().Error("已存在缩放窗口");
return ScalingError::ScalingFailedGeneral;
}
Logger::Get().Info(fmt::format("缩放开始\n\t程序版本: {}\n\tOS 版本: {}\n\t管理员: {}",
#ifdef MP_VERSION_TAG
STRING(MP_VERSION_TAG),
#else
"dev",
#endif
Win32Helper::GetOSVersion().ToString<char>(),
Win32Helper::IsProcessElevated() ? "" : ""
));
if (ScalingError error = _srcTracker.Set(hwndSrc, _options); error != ScalingError::NoError) {
Logger::Get().Error("初始化 SrcTracker 失败");
return error;
@ -82,7 +90,7 @@ ScalingError ScalingWindow::_StartImpl(HWND hwndSrc) noexcept {
if (_options.IsWindowedMode()) {
Logger::Get().Info("已最大化的窗口不支持窗口模式缩放");
return ScalingError::BannedInWindowedMode;
} else if (!_options.IsAllowScalingMaximized()) {
} else if (!_options.RealIsAllowScalingMaximized()) {
Logger::Get().Info("源窗口已最大化");
return ScalingError::Maximized;
}
@ -1190,7 +1198,7 @@ void ScalingWindow::_Show() noexcept {
}
// 模拟独占全屏
if (_options.IsSimulateExclusiveFullscreen()) {
if (_options.RealIsSimulateExclusiveFullscreen()) {
// 延迟 1s 以避免干扰游戏的初始化,见 #495
([]()->winrt::fire_and_forget {
const uint32_t runId = RunId();
@ -1819,7 +1827,7 @@ winrt::fire_and_forget ScalingWindow::_UpdateFocusStateAsync(
}
if (_srcTracker.IsOwnedWindowFocused() ||
(!onSrcOwnedWindowFocusedChanged && !_options.IsWindowedMode()))
(_options.RealIsKeepOnTop() && !onSrcOwnedWindowFocusedChanged))
{
if (!onShow) {
const uint32_t runId = RunId();
@ -1844,8 +1852,8 @@ winrt::fire_and_forget ScalingWindow::_UpdateFocusStateAsync(
const HWND hwndPrev = GetWindow(_srcTracker.Handle(), GW_HWNDPREV);
SetWindowPos(Handle(), hwndPrev,
0, 0, 0, 0, SWP_NOACTIVATE | SWP_NOMOVE | SWP_NOSIZE);
} else if (!onSrcOwnedWindowFocusedChanged && !_options.IsWindowedMode()) {
// 源窗口位于前台时将缩放窗口置顶,这使不支持 MPO 的显卡更容易激活 DirectFlip
} else if (_options.RealIsKeepOnTop() && !onSrcOwnedWindowFocusedChanged) {
// 源窗口位于前台时将缩放窗口置顶
if (_srcTracker.IsFocused()) {
if (!_options.IsDebugMode()) {
SetWindowPos(Handle(), HWND_TOPMOST,

View file

@ -7,7 +7,7 @@ namespace Magpie {
class CursorManager;
class ScalingWindow : public WindowBaseT<ScalingWindow> {
class ScalingWindow final : public WindowBaseT<ScalingWindow> {
using base_type = WindowBaseT<ScalingWindow>;
friend base_type;

View file

@ -435,8 +435,10 @@ ScalingError SrcTracker::_CalcSrcRect(const ScalingOptions& options, LONG border
// DWM 绘制,前者无需裁剪,后者不能裁剪。
_srcRect = _windowRect;
} else {
const bool isCaptureTitleBar = options.RealIsCaptureTitleBar();
// UWP 窗口都是 NoTitleBar 类型,但可能使用子窗口作为“客户区”
if (_windowKind == SrcWindowKind::NoTitleBar && !options.IsCaptureTitleBar() && GetClientRectOfUWP(_hWnd, _srcRect)) {
if (_windowKind == SrcWindowKind::NoTitleBar && !isCaptureTitleBar && GetClientRectOfUWP(_hWnd, _srcRect)) {
_srcRect.top = std::max(_srcRect.top, _windowFrameRect.top + borderThicknessInFrame);
} else {
_srcRect.left = _windowFrameRect.left + borderThicknessInFrame;
@ -444,7 +446,7 @@ ScalingError SrcTracker::_CalcSrcRect(const ScalingOptions& options, LONG border
_srcRect.right = _windowFrameRect.right - borderThicknessInFrame;
_srcRect.bottom = _windowFrameRect.bottom - borderThicknessInFrame;
if (!options.IsCaptureTitleBar() || _windowKind == SrcWindowKind::OnlyThickFrame) {
if (!isCaptureTitleBar || _windowKind == SrcWindowKind::OnlyThickFrame) {
RECT clientRect;
if (!Win32Helper::GetClientScreenRect(_hWnd, clientRect)) {
Logger::Get().Error("GetClientScreenRect 失败");

View file

@ -56,11 +56,12 @@ struct ScalingFlags {
static constexpr uint32_t AllowScalingMaximized = 1 << 15;
static constexpr uint32_t EnableStatisticsForDynamicDetection = 1 << 16;
// 只影响缩放行为Magpie.Core 不负责启动 TouchHelper.exe
static constexpr uint32_t IsTouchSupportEnabled = 1 << 17;
static constexpr uint32_t TouchSupportEnabled = 1 << 17;
static constexpr uint32_t InlineParams = 1 << 18;
static constexpr uint32_t IsFP16Disabled = 1 << 19;
static constexpr uint32_t FP16Disabled = 1 << 19;
static constexpr uint32_t BenchmarkMode = 1 << 20;
static constexpr uint32_t DeveloperMode = 1 << 21;
static constexpr uint32_t KeepOnTop = 1 << 22;
};
enum class ScalingType {
@ -127,6 +128,8 @@ enum class ScalingError {
TouchSupport,
// 3D 游戏模式下不支持窗口模式缩放
Windowed3DGameMode,
// Desktop Duplication 不支持窗口模式缩放
WindowedDesktopDuplication,
// 通用的不支持缩放错误
InvalidSourceWindow,
// 因窗口已最大化或全屏而无法缩放,可通过更改设置强制缩放
@ -157,24 +160,22 @@ struct ScalingOptions {
DEFINE_FLAG_ACCESSOR(IsDeveloperMode, ScalingFlags::DeveloperMode, flags)
DEFINE_FLAG_ACCESSOR(IsDebugMode, ScalingFlags::DebugMode, flags)
DEFINE_FLAG_ACCESSOR(IsBenchmarkMode, ScalingFlags::BenchmarkMode, flags)
DEFINE_FLAG_ACCESSOR(IsFP16Disabled, ScalingFlags::IsFP16Disabled, flags)
DEFINE_FLAG_ACCESSOR(IsFP16Disabled, ScalingFlags::FP16Disabled, flags)
DEFINE_FLAG_ACCESSOR(IsEffectCacheDisabled, ScalingFlags::DisableEffectCache, flags)
DEFINE_FLAG_ACCESSOR(IsFontCacheDisabled, ScalingFlags::DisableFontCache, flags)
DEFINE_FLAG_ACCESSOR(IsSaveEffectSources, ScalingFlags::SaveEffectSources, flags)
DEFINE_FLAG_ACCESSOR(IsWarningsAreErrors, ScalingFlags::WarningsAreErrors, flags)
DEFINE_FLAG_ACCESSOR(IsStatisticsForDynamicDetectionEnabled, ScalingFlags::EnableStatisticsForDynamicDetection, flags)
DEFINE_FLAG_ACCESSOR(IsInlineParams, ScalingFlags::InlineParams, flags)
DEFINE_FLAG_ACCESSOR(IsTouchSupportEnabled, ScalingFlags::IsTouchSupportEnabled, flags)
DEFINE_FLAG_ACCESSOR(IsTouchSupportEnabled, ScalingFlags::TouchSupportEnabled, flags)
DEFINE_FLAG_ACCESSOR(IsAllowScalingMaximized, ScalingFlags::AllowScalingMaximized, flags)
DEFINE_FLAG_ACCESSOR(IsKeepOnTop, ScalingFlags::KeepOnTop, flags)
DEFINE_FLAG_ACCESSOR(IsSimulateExclusiveFullscreen, ScalingFlags::SimulateExclusiveFullscreen, flags)
DEFINE_FLAG_ACCESSOR(Is3DGameMode, ScalingFlags::Is3DGameMode, flags)
DEFINE_FLAG_ACCESSOR(IsCaptureTitleBar, ScalingFlags::CaptureTitleBar, flags)
DEFINE_FLAG_ACCESSOR(IsAdjustCursorSpeed, ScalingFlags::AdjustCursorSpeed, flags)
DEFINE_FLAG_ACCESSOR(IsDirectFlipDisabled, ScalingFlags::DisableDirectFlip, flags)
bool Prepare() noexcept;
void Log() const noexcept;
std::vector<EffectOption> effects;
uint32_t flags = ScalingFlags::AdjustCursorSpeed;
Cropping cropping{};
@ -197,6 +198,26 @@ struct ScalingOptions {
void (*showToast)(HWND hwndTarget, std::wstring_view msg) noexcept = nullptr;
void (*showError)(HWND hwndTarget, ScalingError error) noexcept = nullptr;
void (*save)(const ScalingOptions& options, HWND hwndScaling) noexcept = nullptr;
void Log() const noexcept;
bool RealIsCaptureTitleBar() const noexcept {
// GDI 和 DwmSharedSurface 不支持捕获标题栏
return IsCaptureTitleBar() &&
captureMethod != CaptureMethod::GDI && captureMethod != CaptureMethod::DwmSharedSurface;
}
bool RealIsAllowScalingMaximized() const noexcept {
return IsAllowScalingMaximized() && !IsWindowedMode();
}
bool RealIsKeepOnTop() const noexcept {
return IsKeepOnTop() && !IsWindowedMode();
}
bool RealIsSimulateExclusiveFullscreen() const noexcept {
return IsSimulateExclusiveFullscreen() && !IsWindowedMode();
}
};
}

View file

@ -608,6 +608,8 @@ bool AppSettings::_Save(const _AppSettingsData& data) noexcept {
writer.Double(data._minFrameRate);
writer.Key("disableFP16");
writer.Bool(data._isFP16Disabled);
writer.Key("keepOnTop");
writer.Bool(data._isKeepOnTop);
ScalingModesService::Get().Export(writer);
@ -808,6 +810,7 @@ void AppSettings::_LoadSettings(const rapidjson::GenericObject<true, rapidjson::
JsonHelper::ReadBool(root, "enableStatisticsForDynamicDetection", _isStatisticsForDynamicDetectionEnabled);
JsonHelper::ReadFloat(root, "minFrameRate", _minFrameRate);
JsonHelper::ReadBool(root, "disableFP16", _isFP16Disabled);
JsonHelper::ReadBool(root, "keepOnTop", _isKeepOnTop);
[[maybe_unused]] bool result = ScalingModesService::Get().Import(root, true);
assert(result);

View file

@ -75,6 +75,7 @@ struct _AppSettingsData {
bool _isCheckForPreviewUpdates = false;
bool _isStatisticsForDynamicDetectionEnabled = false;
bool _isFP16Disabled = false;
bool _isKeepOnTop = false;
};
class AppSettings : private _AppSettingsData {
@ -206,6 +207,15 @@ public:
SaveAsync();
}
bool IsKeepOnTop() const noexcept {
return _isKeepOnTop;
}
void IsKeepOnTop(bool value) noexcept {
_isKeepOnTop = value;
SaveAsync();
}
bool IsAllowScalingMaximized() const noexcept {
return _isAllowScalingMaximized;
}

View file

@ -111,7 +111,7 @@
</local:SettingsCard>
<local:SettingsExpander x:Uid="Home_Toolbar_InitialState">
<local:SettingsExpander.HeaderIcon>
<FontIcon Glyph="&#xE840;" />
<FontIcon Glyph="&#xE890;" />
</local:SettingsExpander.HeaderIcon>
<local:SettingsExpander.Content>
<TextBlock Foreground="{ThemeResource TextFillColorSecondaryBrush}"
@ -193,12 +193,12 @@
<ToggleSwitch x:Uid="ToggleSwitch"
IsOn="{x:Bind ViewModel.IsAllowScalingMaximized, Mode=TwoWay}" />
</local:SettingsCard>
<local:SettingsCard x:Uid="Home_Advanced_InlineParams">
<local:SettingsCard x:Uid="Home_Advanced_KeepOnTop">
<local:SettingsCard.HeaderIcon>
<FontIcon Glyph="&#xE9E9;" />
<FontIcon Glyph="&#xE718;" />
</local:SettingsCard.HeaderIcon>
<ToggleSwitch x:Uid="ToggleSwitch"
IsOn="{x:Bind ViewModel.IsInlineParams, Mode=TwoWay}" />
IsOn="{x:Bind ViewModel.IsKeepOnTop, Mode=TwoWay}" />
</local:SettingsCard>
<local:SettingsCard x:Uid="Home_Advanced_SimulateExclusiveFullscreen">
<local:SettingsCard.HeaderIcon>
@ -211,6 +211,13 @@
IsClosable="False"
IsOpen="{x:Bind ViewModel.IsSimulateExclusiveFullscreen, Mode=OneWay}"
Severity="Warning" />
<local:SettingsCard x:Uid="Home_Advanced_InlineParams">
<local:SettingsCard.HeaderIcon>
<FontIcon Glyph="&#xE9E9;" />
</local:SettingsCard.HeaderIcon>
<ToggleSwitch x:Uid="ToggleSwitch"
IsOn="{x:Bind ViewModel.IsInlineParams, Mode=TwoWay}" />
</local:SettingsCard>
<local:SettingsCard x:Uid="Home_Advanced_MinFrameRate"
IsWrapEnabled="True">
<local:SettingsCard.HeaderIcon>

View file

@ -314,26 +314,33 @@ bool HomeViewModel::IsAllowScalingMaximized() const noexcept {
}
void HomeViewModel::IsAllowScalingMaximized(bool value) {
AppSettings::Get().IsAllowScalingMaximized(value);
AppSettings& settings = AppSettings::Get();
if (settings.IsAllowScalingMaximized() == value) {
return;
}
settings.IsAllowScalingMaximized(value);
RaisePropertyChanged(L"IsAllowScalingMaximized");
if (value) {
ScalingService::Get().CheckForeground();
}
}
bool HomeViewModel::IsInlineParams() const noexcept {
return AppSettings::Get().IsInlineParams();
bool HomeViewModel::IsKeepOnTop() const noexcept {
return AppSettings::Get().IsKeepOnTop();
}
void HomeViewModel::IsInlineParams(bool value) {
void HomeViewModel::IsKeepOnTop(bool value) {
AppSettings& settings = AppSettings::Get();
if (settings.IsInlineParams() == value) {
if (settings.IsKeepOnTop() == value) {
return;
}
settings.IsInlineParams(value);
RaisePropertyChanged(L"IsInlineParams");
settings.IsKeepOnTop(value);
RaisePropertyChanged(L"IsKeepOnTop");
}
bool HomeViewModel::IsSimulateExclusiveFullscreen() const noexcept {
@ -351,6 +358,21 @@ void HomeViewModel::IsSimulateExclusiveFullscreen(bool value) {
RaisePropertyChanged(L"IsSimulateExclusiveFullscreen");
}
bool HomeViewModel::IsInlineParams() const noexcept {
return AppSettings::Get().IsInlineParams();
}
void HomeViewModel::IsInlineParams(bool value) {
AppSettings& settings = AppSettings::Get();
if (settings.IsInlineParams() == value) {
return;
}
settings.IsInlineParams(value);
RaisePropertyChanged(L"IsInlineParams");
}
static constexpr std::array MIN_FRAME_RATE_OPTIONS{ 0,5,10,15,20,30,60 };
IVector<IInspectable> HomeViewModel::MinFrameRateOptions() {

View file

@ -63,12 +63,15 @@ struct HomeViewModel : HomeViewModelT<HomeViewModel>, wil::notify_property_chang
bool IsAllowScalingMaximized() const noexcept;
void IsAllowScalingMaximized(bool value);
bool IsInlineParams() const noexcept;
void IsInlineParams(bool value);
bool IsKeepOnTop() const noexcept;
void IsKeepOnTop(bool value);
bool IsSimulateExclusiveFullscreen() const noexcept;
void IsSimulateExclusiveFullscreen(bool value);
bool IsInlineParams() const noexcept;
void IsInlineParams(bool value);
static IVector<IInspectable> MinFrameRateOptions();
int MinFrameRateIndex() const noexcept;

View file

@ -28,8 +28,9 @@ namespace Magpie {
Boolean IsShowTouchSupportInfoBar { get; };
Boolean IsAllowScalingMaximized;
Boolean IsInlineParams;
Boolean IsKeepOnTop;
Boolean IsSimulateExclusiveFullscreen;
Boolean IsInlineParams;
static IVector<IInspectable> MinFrameRateOptions { get; };
Int32 MinFrameRateIndex;

View file

@ -4,7 +4,7 @@
namespace Magpie {
class MainWindow : public XamlWindowT<MainWindow, winrt::com_ptr<winrt::Magpie::implementation::RootPage>> {
class MainWindow final : public XamlWindowT<MainWindow, winrt::com_ptr<winrt::Magpie::implementation::RootPage>> {
using base_type = XamlWindowT<MainWindow, winrt::com_ptr<winrt::Magpie::implementation::RootPage>>;
friend WindowBaseT<MainWindow>;
public:

View file

@ -184,6 +184,10 @@
ItemsSource="{x:Bind ViewModel.CaptureMethods, Mode=OneTime}"
SelectedIndex="{x:Bind ViewModel.CaptureMethod, Mode=TwoWay}" />
</local:SettingsCard>
<muxc:InfoBar x:Uid="Profile_General_DesktopDuplicationWarning"
IsClosable="False"
IsOpen="{x:Bind ViewModel.IsCaptureMethodDesktopDuplication, Mode=OneWay}"
Severity="Warning" />
<local:SettingsCard x:Name="AutoScaleSettingsCard"
x:Uid="Profile_General_AutoScale"
x:Load="{x:Bind ViewModel.IsNotDefaultProfile, Mode=OneTime}"

View file

@ -367,11 +367,16 @@ void ProfileViewModel::CaptureMethod(int value) {
_data->captureMethod = captureMethod;
RaisePropertyChanged(L"CaptureMethod");
RaisePropertyChanged(L"IsCaptureMethodDesktopDuplication");
RaisePropertyChanged(L"CanCaptureTitleBar");
AppSettings::Get().SaveAsync();
}
bool ProfileViewModel::IsCaptureMethodDesktopDuplication() const noexcept {
return _data->captureMethod == CaptureMethod::DesktopDuplication;
}
int ProfileViewModel::AutoScale() const noexcept {
return (int)_data->autoScale;
}

View file

@ -65,6 +65,8 @@ struct ProfileViewModel : ProfileViewModelT<ProfileViewModel>,
int CaptureMethod() const noexcept;
void CaptureMethod(int value);
bool IsCaptureMethodDesktopDuplication() const noexcept;
int AutoScale() const noexcept;
void AutoScale(int value);

View file

@ -28,6 +28,7 @@ namespace Magpie {
IVector<IInspectable> CaptureMethods { get; };
Int32 CaptureMethod;
Boolean IsCaptureMethodDesktopDuplication { get; };
Int32 AutoScale;
Boolean Is3DGameMode;

View file

@ -601,7 +601,7 @@
<value>Make effect parameters inline</value>
</data>
<data name="Home_Advanced_SimulateExclusiveFullscreen.Description" xml:space="preserve">
<value>Notifications and pop-ups from certain applications will be blocked</value>
<value>Applies only to fullscreen scaling. Suppresses certain app notifications and pop-ups when enabled</value>
</data>
<data name="Home_Advanced_SimulateExclusiveFullscreen.Header" xml:space="preserve">
<value>Simulate exclusive fullscreen when scaling</value>
@ -872,7 +872,7 @@
<value>Windowed scaling shortcut</value>
</data>
<data name="Home_Advanced_AllowScalingMaximized.Description" xml:space="preserve">
<value>Applies to fullscreen scaling only</value>
<value>Applies only to fullscreen scaling</value>
</data>
<data name="Home_Toolbar.Description" xml:space="preserve">
<value>The toolbar appears at the top of the scaled window, providing features like FPS display and screenshot capture. In windowed mode, it also allows dragging the scaled window.</value>
@ -1000,4 +1000,16 @@
<data name="Home_Advanced_DeveloperOptions_LocateLogs.Header" xml:space="preserve">
<value>Open logs location</value>
</data>
<data name="Home_Advanced_KeepOnTop.Description" xml:space="preserve">
<value>Applies only to fullscreen scaling. When enabled, the scaled window stays on top while in the foreground to minimize distractions, though it may obscure menus and pop-ups. It also helps reduce latency on GPUs without Multi-Plane Overlay support</value>
</data>
<data name="Home_Advanced_KeepOnTop.Header" xml:space="preserve">
<value>Keep scaled window on top</value>
</data>
<data name="Message_WindowedDesktopDuplication" xml:space="preserve">
<value>Windowed scaling is not supported with Desktop Duplication capture.</value>
</data>
<data name="Profile_General_DesktopDuplicationWarning.Title" xml:space="preserve">
<value>This capture method is incompatible with windowed scaling.</value>
</data>
</root>

View file

@ -601,7 +601,7 @@
<value>内联效果参数</value>
</data>
<data name="Home_Advanced_SimulateExclusiveFullscreen.Description" xml:space="preserve">
<value>可以阻止某些应用的通知和弹窗</value>
<value>仅适用于全屏模式缩放。启用后可以阻止某些应用的通知和弹窗</value>
</data>
<data name="Home_Advanced_SimulateExclusiveFullscreen.Header" xml:space="preserve">
<value>缩放时模拟独占全屏</value>
@ -1000,4 +1000,16 @@
<data name="Home_Advanced_DeveloperOptions_LocateLogs.Header" xml:space="preserve">
<value>打开日志位置</value>
</data>
<data name="Home_Advanced_KeepOnTop.Description" xml:space="preserve">
<value>仅适用于全屏模式缩放。启用后当缩放窗口位于前台时将置顶以减少干扰但可能会遮挡菜单和弹窗。如果显卡不支持多平面叠加MPO此选项也有助于降低延迟</value>
</data>
<data name="Home_Advanced_KeepOnTop.Header" xml:space="preserve">
<value>置顶缩放窗口</value>
</data>
<data name="Message_WindowedDesktopDuplication" xml:space="preserve">
<value>Desktop Duplication 捕获不支持窗口模式缩放。</value>
</data>
<data name="Profile_General_DesktopDuplicationWarning.Title" xml:space="preserve">
<value>此捕获方式和窗口模式缩放不兼容。</value>
</data>
</root>

View file

@ -167,6 +167,10 @@ static void ShowError(HWND hWnd, ScalingError error) noexcept {
key = L"Message_Windowed3DGameMode";
isFail = false;
break;
case ScalingError::WindowedDesktopDuplication:
key = L"Message_WindowedDesktopDuplication";
isFail = false;
break;
case ScalingError::InvalidSourceWindow:
key = L"Message_InvalidSourceWindow";
break;
@ -434,6 +438,7 @@ ScalingError ScalingService::_StartScaleImpl(HWND hWnd, const Profile& profile,
options.IsStatisticsForDynamicDetectionEnabled(settings.IsStatisticsForDynamicDetectionEnabled());
options.IsInlineParams(settings.IsInlineParams());
options.IsFP16Disabled(settings.IsFP16Disabled());
options.IsKeepOnTop(settings.IsKeepOnTop());
if (options.maxFrameRate) {
// 最小帧数不能大于最大帧数

View file

@ -1,7 +1,4 @@
#pragma once
#include <winrt/Magpie.h>
#include <variant>
#include "SmallVector.h"
namespace Magpie {

View file

@ -50,8 +50,12 @@ void ToastPage::InitializeComponent() {
static bool TrySetOwnder(HWND hwndToast, HWND hwndTarget) noexcept {
// 如果源窗口挂起SetWindowLongPtr 会卡住
return !Win32Helper::IsWindowHung(hwndTarget) &&
(SetWindowLongPtr(hwndToast, GWLP_HWNDPARENT, (LONG_PTR)hwndTarget) || GetLastError() == 0);
if (!Win32Helper::IsWindowHung(hwndTarget)) {
return false;
}
SetLastError(0);
return SetWindowLongPtr(hwndToast, GWLP_HWNDPARENT, (LONG_PTR)hwndTarget) || GetLastError() == 0;
}
static void UpdateToastPosition(HWND hwndToast, const RECT& frameRect, bool updateZOrder) noexcept {
@ -101,6 +105,8 @@ fire_and_forget ToastPage::ShowMessageOnWindow(std::wstring title, std::wstring
MUXC::TeachingTip oldTeachingTip = MessageTeachingTip();
if (oldTeachingTip) {
UnloadObject(oldTeachingTip);
// 确保卸载完成,防止弹出动画 bug
co_await resume_foreground(dispatcher, CoreDispatcherPriority::Low);
} else {
oldTeachingTip = std::move(_oldTeachingTip);
}
@ -112,9 +118,8 @@ fire_and_forget ToastPage::ShowMessageOnWindow(std::wstring title, std::wstring
// 更改所有者关系使弹窗始终在 hwndTarget 上方。如果失败,改为定期将弹窗置顶,如果 hwndTarget
// 的 IL 更高或是 UWP 窗口就会发生这种情况。
SetLastError(0);
const bool isOwned = TrySetOwnder(_hwndToast, hwndTarget);
bool isTargetTopMost = GetWindowExStyle(_hwndToast) & WS_EX_TOPMOST;
bool isTargetTopMost = GetWindowExStyle(hwndTarget) & WS_EX_TOPMOST;
if (isOwned) {
// _hwndToast 的输入已被附加到了 hWnd 上,这是所有者窗口的默认行为,但我们不需要。
// 见 https://devblogs.microsoft.com/oldnewthing/20130412-00/?p=4683
@ -249,7 +254,7 @@ fire_and_forget ToastPage::ShowMessageOnWindow(std::wstring title, std::wstring
co_return;
}
isTargetTopMost = GetWindowExStyle(_hwndToast) & WS_EX_TOPMOST;
isTargetTopMost = GetWindowExStyle(hwndTarget) & WS_EX_TOPMOST;
if (isTargetTopMost || (!isOwned && GetForegroundWindow() == (HWND)hwndTarget)) {
// 如果 hwndTarget 位于前台,定期将弹窗置顶
SetWindowPos(_hwndToast, HWND_TOPMOST, 0, 0, 0, 0, SWP_NOACTIVATE | SWP_NOMOVE | SWP_NOSIZE);