// This file is part of Desktop App Toolkit, // a set of libraries for developing nice desktop applications. // // For license and copyright information please follow this link: // https://github.com/desktop-app/legal/blob/master/LEGAL // #include "ui/platform/win/ui_window_win.h" #include "ui/inactive_press.h" #include "ui/platform/win/ui_window_title_win.h" #include "ui/platform/win/ui_windows_direct_manipulation.h" #include "ui/platform/ui_platform_utility.h" #include "ui/widgets/rp_window.h" #include "base/platform/win/base_windows_safe_library.h" #include "base/platform/base_platform_info.h" #include "base/integration.h" #include "base/invoke_queued.h" #include "base/debug_log.h" #include "styles/palette.h" #include "styles/style_widgets.h" #include #include #include #include #include #include #include #include #include #include Q_DECLARE_METATYPE(QMargins); namespace Ui::Platform { namespace { constexpr auto kDWMWCP_ROUND = DWORD(2); constexpr auto kDWMWCP_DONOTROUND = DWORD(1); constexpr auto kDWMWA_WINDOW_CORNER_PREFERENCE = DWORD(33); constexpr auto kDWMWA_CAPTION_COLOR = DWORD(35); constexpr auto kDWMWA_TEXT_COLOR = DWORD(36); UINT(__stdcall *GetDpiForWindow)(_In_ HWND hwnd); int(__stdcall *GetSystemMetricsForDpi)( _In_ int nIndex, _In_ UINT dpi); [[nodiscard]] bool GetDpiForWindowSupported() { static const auto Result = [&] { #define LOAD_SYMBOL(lib, name) base::Platform::LoadMethod(lib, #name, name) const auto user32 = base::Platform::SafeLoadLibrary(L"User32.dll"); return LOAD_SYMBOL(user32, GetDpiForWindow); #undef LOAD_SYMBOL }(); return Result; } [[nodiscard]] bool GetSystemMetricsForDpiSupported() { static const auto Result = [&] { #define LOAD_SYMBOL(lib, name) base::Platform::LoadMethod(lib, #name, name) const auto user32 = base::Platform::SafeLoadLibrary(L"User32.dll"); return LOAD_SYMBOL(user32, GetSystemMetricsForDpi); #undef LOAD_SYMBOL }(); return Result; } [[nodiscard]] bool IsCompositionEnabled() { auto result = BOOL(FALSE); const auto success = (DwmIsCompositionEnabled(&result) == S_OK); return success && result; } [[nodiscard]] HWND FindTaskbarWindow(LPRECT rcMon = nullptr) { HWND hTaskbar = nullptr; RECT rcTaskbar, rcMatch; while ((hTaskbar = FindWindowEx( nullptr, hTaskbar, L"Shell_TrayWnd", nullptr)) != nullptr) { if (!rcMon) { break; // OK, return first found } if (GetWindowRect(hTaskbar, &rcTaskbar) && IntersectRect(&rcMatch, &rcTaskbar, rcMon)) { break; // OK, taskbar match monitor } } return hTaskbar; } [[nodiscard]] bool IsTaskbarAutoHidden( LPRECT rcMon = nullptr, PUINT pEdge = nullptr) { HWND hTaskbar = FindTaskbarWindow(rcMon); if (!hTaskbar) { if (pEdge) { *pEdge = (UINT)-1; } return false; } APPBARDATA state = {sizeof(state), hTaskbar}; APPBARDATA pos = {sizeof(pos), hTaskbar}; LRESULT lState = SHAppBarMessage(ABM_GETSTATE, &state); bool bAutoHidden = (lState & ABS_AUTOHIDE); if (SHAppBarMessage(ABM_GETTASKBARPOS, &pos)) { if (pEdge) { *pEdge = pos.uEdge; } } else { DEBUG_LOG(("Failed to get taskbar pos")); if (pEdge) { *pEdge = ABE_BOTTOM; } } return bAutoHidden; } void FixAeroSnap(HWND handle) { SetWindowLongPtr( handle, GWL_STYLE, GetWindowLongPtr(handle, GWL_STYLE) | WS_CAPTION | WS_THICKFRAME); } [[nodiscard]] HWND ResolveWindowHandle(not_null widget) { if (!::Platform::IsWindows8OrGreater()) { widget->setWindowFlag(Qt::FramelessWindowHint); } const auto result = GetWindowHandle(widget); if (!::Platform::IsWindows8OrGreater()) { FixAeroSnap(result); } return result; } [[nodiscard]] Qt::KeyboardModifiers LookupModifiers() { const auto check = [](int key) { return (GetKeyState(key) & 0x8000) != 0; }; auto result = Qt::KeyboardModifiers(); if (check(VK_SHIFT)) { result |= Qt::ShiftModifier; } // NB AltGr key (i.e., VK_RMENU on some keyboard layout) is not handled. if (check(VK_RMENU) || check(VK_MENU)) { result |= Qt::AltModifier; } if (check(VK_CONTROL)) { result |= Qt::ControlModifier; } if (check(VK_LWIN) || check(VK_RWIN)) { result |= Qt::MetaModifier; } return result; } } // namespace class WindowHelper::NativeFilter final : public QAbstractNativeEventFilter { public: void registerWindow(HWND handle, not_null helper); void unregisterWindow(HWND handle); bool nativeEventFilter( const QByteArray &eventType, void *message, long *result) override; private: base::flat_map> _windowByHandle; }; void WindowHelper::NativeFilter::registerWindow( HWND handle, not_null helper) { _windowByHandle.emplace(handle, helper); } void WindowHelper::NativeFilter::unregisterWindow(HWND handle) { _windowByHandle.remove(handle); } bool WindowHelper::NativeFilter::nativeEventFilter( const QByteArray &eventType, void *message, long *result) { auto filtered = false; const auto msg = static_cast(message); const auto i = _windowByHandle.find(msg->hwnd); if (i != end(_windowByHandle)) { base::Integration::Instance().enterFromEventLoop([&] { filtered = i->second->handleNativeEvent( msg->message, msg->wParam, msg->lParam, reinterpret_cast(result)); }); } return filtered; } WindowHelper::WindowHelper(not_null window) : BasicWindowHelper(window) , _handle(ResolveWindowHandle(window)) , _title(Ui::CreateChild(window.get())) , _body(Ui::CreateChild(window.get())) , _dpi(GetDpiForWindowSupported() ? GetDpiForWindow(_handle) : 0) { Expects(_handle != nullptr); init(); } WindowHelper::~WindowHelper() { GetNativeFilter()->unregisterWindow(_handle); } void WindowHelper::initInWindow(not_null window) { _title->initInWindow(window); } not_null WindowHelper::body() { return _body; } QMargins WindowHelper::frameMargins() { return _title->isHidden() ? BasicWindowHelper::nativeFrameMargins() : QMargins{ 0, _title->height(), 0, 0 }; } int WindowHelper::additionalContentPadding() const { return _title->isHidden() ? 0 : _title->additionalPadding(); } rpl::producer WindowHelper::additionalContentPaddingValue() const { return rpl::combine( _title->shownValue(), _title->additionalPaddingValue() ) | rpl::map([](bool shown, int padding) { return shown ? padding : 0; }) | rpl::distinct_until_changed(); } void WindowHelper::setTitle(const QString &title) { _title->setText(title); window()->setWindowTitle(title); } void WindowHelper::setTitleStyle(const style::WindowTitle &st) { _title->setStyle(st); updateWindowFrameColors(); } void WindowHelper::setNativeFrame(bool enabled) { if (!::Platform::IsWindows8OrGreater()) { window()->windowHandle()->setFlag(Qt::FramelessWindowHint, !enabled); if (!enabled) { FixAeroSnap(_handle); } } _title->setVisible(!enabled); if (enabled || ::Platform::IsWindows11OrGreater()) { _shadow.reset(); } else { _shadow.emplace(window(), st::windowShadowFg->c); _shadow->setResizeEnabled(!fixedSize()); initialShadowUpdate(); } updateCornersRounding(); updateMargins(); updateWindowFrameColors(); fixMaximizedWindow(); SetWindowPos( _handle, 0, 0, 0, 0, 0, SWP_FRAMECHANGED | SWP_NOMOVE | SWP_NOSIZE | SWP_NOZORDER | SWP_NOACTIVATE); } void WindowHelper::initialShadowUpdate() { if (!_shadow) { return; } using Change = WindowShadow::Change; const auto noShadowStates = (Qt::WindowMinimized | Qt::WindowMaximized); if ((window()->windowState() & noShadowStates) || window()->isHidden()) { _shadow->update(Change::Hidden); } else { _shadow->update(Change::Moved | Change::Resized | Change::Shown); } } void WindowHelper::updateCornersRounding() { if (!::Platform::IsWindows11OrGreater()) { return; } const auto preference = (_isFullScreen || _isMaximizedAndTranslucent) ? kDWMWCP_DONOTROUND : kDWMWCP_ROUND; DwmSetWindowAttribute( _handle, kDWMWA_WINDOW_CORNER_PREFERENCE, &preference, sizeof(preference)); } void WindowHelper::setMinimumSize(QSize size) { window()->setMinimumSize(size.width(), titleHeight() + size.height()); } void WindowHelper::setFixedSize(QSize size) { window()->setFixedSize(size.width(), titleHeight() + size.height()); _title->setResizeEnabled(false); if (_shadow) { _shadow->setResizeEnabled(false); } } void WindowHelper::setGeometry(QRect rect) { window()->setGeometry(rect.marginsAdded({ 0, titleHeight(), 0, 0 })); } void WindowHelper::showFullScreen() { if (!_isFullScreen) { _isFullScreen = true; updateMargins(); updateCornersRounding(); } window()->showFullScreen(); } void WindowHelper::showNormal() { window()->showNormal(); if (_isFullScreen) { _isFullScreen = false; updateMargins(); updateCornersRounding(); } } auto WindowHelper::hitTestRequests() const -> rpl::producer> { return _hitTestRequests.events(); } rpl::producer WindowHelper::systemButtonOver() const { return _systemButtonOver.events(); } rpl::producer WindowHelper::systemButtonDown() const { return _systemButtonDown.events(); } void WindowHelper::init() { _title->show(); GetNativeFilter()->registerWindow(_handle, this); style::PaletteChanged( ) | rpl::start_with_next([=] { if (_shadow) { _shadow->setColor(st::windowShadowFg->c); } updateWindowFrameColors(); Ui::ForceFullRepaint(window()); }, window()->lifetime()); rpl::combine( window()->sizeValue(), _title->heightValue(), _title->shownValue() ) | rpl::start_with_next([=]( QSize size, int titleHeight, bool titleShown) { _body->setGeometry( 0, titleShown ? titleHeight : 0, size.width(), size.height() - (titleShown ? titleHeight : 0)); }, _body->lifetime()); hitTestRequests( ) | rpl::filter([=](not_null request) { return ::Platform::IsWindows11OrGreater(); }) | rpl::start_with_next([=](not_null request) { request->result = [=] { const auto maximized = window()->isMaximized() || window()->isFullScreen(); const auto px = int(std::ceil( st::windowTitleHeight * window()->windowHandle()->devicePixelRatio() / 10.)); return (!maximized && (request->point.y() < px)) ? HitTestResult::Top : HitTestResult::Client; }(); }, window()->lifetime()); updateMargins(); if (!::Platform::IsWindows11OrGreater()) { _shadow.emplace(window(), st::windowShadowFg->c); } if (!::Platform::IsWindows8OrGreater()) { SetWindowTheme(_handle, L" ", L" "); QApplication::setStyle(QStyleFactory::create("Windows")); } updateWindowFrameColors(); const auto handleStateChanged = [=](Qt::WindowState state) { if (fixedSize() && (state & Qt::WindowMaximized)) { crl::on_main(window().get(), [=] { window()->setWindowState( window()->windowState() & ~Qt::WindowMaximized); }); } if (state != Qt::WindowMinimized) { const auto is = (state == Qt::WindowMaximized) && window()->testAttribute(Qt::WA_TranslucentBackground); if (_isMaximizedAndTranslucent != is) { _isMaximizedAndTranslucent = is; updateCornersRounding(); } } }; Ui::Connect( window()->windowHandle(), &QWindow::windowStateChanged, handleStateChanged); initialShadowUpdate(); updateCornersRounding(); auto dm = std::make_unique(window()); if (dm->valid()) { _directManipulation = std::move(dm); _directManipulation->events( ) | rpl::start_with_next([=](const DirectManipulationEvent &event) { handleDirectManipulationEvent(event); }, window()->lifetime()); } } void WindowHelper::handleDirectManipulationEvent( const DirectManipulationEvent &event) { using Type = DirectManipulationEventType; const auto send = [&](Qt::ScrollPhase phase) { if (const auto windowHandle = window()->windowHandle()) { auto global = POINT(); ::GetCursorPos(&global); auto local = global; ::ScreenToClient(_handle, &local); QWindowSystemInterface::handleWheelEvent( windowHandle, QPointF(local.x, local.y), QPointF(global.x, global.y), event.delta, event.delta, LookupModifiers(), phase, Qt::MouseEventSynthesizedBySystem); } }; switch (event.type) { case Type::ScrollStart: send(Qt::ScrollBegin); break; case Type::Scroll: send(Qt::ScrollUpdate); break; case Type::FlingStart: case Type::Fling: send(Qt::ScrollMomentum); break; case Type::ScrollStop: send(Qt::ScrollEnd); break; case Type::FlingStop: send(Qt::ScrollEnd); break; } } bool WindowHelper::handleNativeEvent( UINT msg, WPARAM wParam, LPARAM lParam, LRESULT *result) { if (handleSystemButtonEvent(msg, wParam, lParam, result)) { return true; } switch (msg) { case WM_ACTIVATE: { if (LOWORD(wParam) == WA_CLICKACTIVE) { Ui::MarkInactivePress(window(), true); } const auto active = (LOWORD(wParam) != WA_INACTIVE); if (_shadow) { if (active) { _shadow->update(WindowShadow::Change::Activate); } else { _shadow->update(WindowShadow::Change::Deactivate); } } updateWindowFrameColors(active); window()->update(); } return false; case WM_NCPAINT: { if (::Platform::IsWindows8OrGreater() || _title->isHidden()) { return false; } if (result) *result = 0; } return true; case WM_NCCALCSIZE: { if (_title->isHidden() || window()->isFullScreen() || !wParam) { return false; } const auto r = &((LPNCCALCSIZE_PARAMS)lParam)->rgrc[0]; const auto maximized = [&] { WINDOWPLACEMENT wp; wp.length = sizeof(WINDOWPLACEMENT); return GetWindowPlacement(_handle, &wp) && (wp.showCmd == SW_SHOWMAXIMIZED); }(); const auto addBorders = maximized || ::Platform::IsWindows11OrGreater(); if (addBorders) { const auto dpi = _dpi.current(); const auto borderWidth = (GetSystemMetricsForDpiSupported() && dpi) ? GetSystemMetricsForDpi(SM_CXSIZEFRAME, dpi) + GetSystemMetricsForDpi(SM_CXPADDEDBORDER, dpi) : GetSystemMetrics(SM_CXSIZEFRAME) + GetSystemMetrics(SM_CXPADDEDBORDER); r->left += borderWidth; r->right -= borderWidth; if (maximized) { r->top += borderWidth; } r->bottom -= borderWidth; } if (maximized) { const auto hMonitor = MonitorFromWindow( _handle, MONITOR_DEFAULTTONEAREST); MONITORINFO mi; mi.cbSize = sizeof(mi); UINT uEdge = (UINT)-1; if (GetMonitorInfo(hMonitor, &mi) && IsTaskbarAutoHidden(&mi.rcMonitor, &uEdge)) { switch (uEdge) { case ABE_LEFT: r->left += 1; break; case ABE_RIGHT: r->right -= 1; break; case ABE_TOP: r->top += 1; break; case ABE_BOTTOM: r->bottom -= 1; break; } } } if (result) *result = addBorders ? 0 : WVR_REDRAW; } return true; case WM_NCRBUTTONUP: { if (_title->isHidden()) { return false; } POINT p{ GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam) }; ScreenToClient(_handle, &p); const auto mapped = QPoint(p.x, p.y) / window()->windowHandle()->devicePixelRatio(); ShowWindowMenu(window(), mapped); if (result) *result = 0; } return true; case WM_NCACTIVATE: { if (_title->isHidden()) { return false; } if (IsCompositionEnabled()) { const auto res = DefWindowProc(_handle, msg, wParam, -1); if (result) *result = res; } else { // Thanks https://github.com/melak47/BorderlessWindow if (result) *result = 1; } } return true; case WM_WINDOWPOSCHANGING: case WM_WINDOWPOSCHANGED: { auto placement = WINDOWPLACEMENT{ .length = sizeof(WINDOWPLACEMENT), }; if (!GetWindowPlacement(_handle, &placement)) { LOG(("System Error: GetWindowPlacement failed.")); return false; } _title->refreshAdditionalPaddings(_handle, placement); if (_shadow) { if (placement.showCmd == SW_SHOWMAXIMIZED || placement.showCmd == SW_SHOWMINIMIZED) { _shadow->update(WindowShadow::Change::Hidden); } else { _shadow->update( WindowShadow::Change::Moved | WindowShadow::Change::Resized, (WINDOWPOS*)lParam); } } } return false; case WM_SIZE: { if (wParam == SIZE_MAXIMIZED || wParam == SIZE_RESTORED || wParam == SIZE_MINIMIZED) { const auto now = window()->windowState(); if (wParam != SIZE_RESTORED || (now != Qt::WindowNoState && now != Qt::WindowFullScreen)) { Qt::WindowState state = Qt::WindowNoState; if (wParam == SIZE_MAXIMIZED) { state = Qt::WindowMaximized; } else if (wParam == SIZE_MINIMIZED) { state = Qt::WindowMinimized; } window()->windowHandle()->windowStateChanged(state); } updateMargins(); _title->refreshAdditionalPaddings(_handle); if (_shadow) { const auto changes = (wParam == SIZE_MINIMIZED || wParam == SIZE_MAXIMIZED) ? WindowShadow::Change::Hidden : (WindowShadow::Change::Resized | WindowShadow::Change::Shown); _shadow->update(changes); } } } return false; case WM_SHOWWINDOW: { if (_shadow) { const auto style = GetWindowLongPtr(_handle, GWL_STYLE); const auto changes = WindowShadow::Change::Resized | ((wParam && !(style & (WS_MAXIMIZE | WS_MINIMIZE))) ? WindowShadow::Change::Shown : WindowShadow::Change::Hidden); _shadow->update(changes); } } return false; case WM_MOVE: { _title->refreshAdditionalPaddings(_handle); if (_shadow) { _shadow->update(WindowShadow::Change::Moved); } } return false; case WM_NCHITTEST: { if (!result || _title->isHidden()) { return false; } POINT p{ GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam) }; ScreenToClient(_handle, &p); const auto ratio = window()->windowHandle()->devicePixelRatio(); const auto mapped = QPoint( int(std::floor(p.x / ratio)), int(std::floor(p.y / ratio))); *result = [&]() -> LRESULT { if (!window()->rect().contains(mapped)) { return DefWindowProc(_handle, msg, wParam, lParam); } auto request = HitTestRequest{ .point = mapped, }; _hitTestRequests.fire(&request); switch (const auto result = request.result) { case HitTestResult::Client: return HTCLIENT; case HitTestResult::Caption: return HTCAPTION; case HitTestResult::Top: return HTTOP; case HitTestResult::TopRight: return HTTOPRIGHT; case HitTestResult::Right: return HTRIGHT; case HitTestResult::BottomRight: return HTBOTTOMRIGHT; case HitTestResult::Bottom: return HTBOTTOM; case HitTestResult::BottomLeft: return HTBOTTOMLEFT; case HitTestResult::Left: return HTLEFT; case HitTestResult::TopLeft: return HTTOPLEFT; case HitTestResult::Minimize: case HitTestResult::MaximizeRestore: case HitTestResult::Close: return systemButtonHitTest(result); case HitTestResult::None: default: return DefWindowProc(_handle, msg, wParam, lParam); }; }(); _systemButtonOver.fire(systemButtonHitTest(*result)); } return true; case WM_DPICHANGED: { _dpi = LOWORD(wParam); InvokeQueued(_title, [=] { _title->refreshAdditionalPaddings(_handle); }); } return false; case DM_POINTERHITTEST: { if (_directManipulation) { _directManipulation->handlePointerHitTest(wParam); return true; } } return false; } return false; } bool WindowHelper::fixedSize() const { return window()->minimumSize() == window()->maximumSize(); } bool WindowHelper::handleSystemButtonEvent( UINT msg, WPARAM wParam, LPARAM lParam, LRESULT *result) { if (_title->isHidden()) { return false; } const auto testResult = LOWORD(wParam); const auto sysButtons = { HTMINBUTTON, HTMAXBUTTON, HTCLOSE }; const auto overSysButton = ranges::contains(sysButtons, testResult); switch (msg) { case WM_NCLBUTTONDBLCLK: case WM_NCMBUTTONDBLCLK: case WM_NCRBUTTONDBLCLK: case WM_NCXBUTTONDBLCLK: { if (!overSysButton || fixedSize()) { return false; } // Ignore double clicks on system buttons. if (result) *result = 0; } return true; case WM_NCLBUTTONDOWN: case WM_NCLBUTTONUP: _systemButtonDown.fire((msg == WM_NCLBUTTONDOWN) ? systemButtonHitTest(testResult) : HitTestResult::None); if (overSysButton) { if (result) *result = 0; } return overSysButton; case WM_NCMBUTTONDOWN: case WM_NCMBUTTONUP: case WM_NCRBUTTONDOWN: case WM_NCRBUTTONUP: case WM_NCXBUTTONDOWN: case WM_NCXBUTTONUP: if (!overSysButton) { return false; } if (result) *result = 0; return true; case WM_NCMOUSEHOVER: case WM_NCMOUSEMOVE: _systemButtonOver.fire(systemButtonHitTest(testResult)); if (overSysButton) { if (result) *result = 0; } return overSysButton; case WM_NCMOUSELEAVE: _systemButtonOver.fire(HitTestResult::None); return false; } return false; } int WindowHelper::systemButtonHitTest(HitTestResult result) const { if (!SemiNativeSystemButtonProcessing()) { return HTCLIENT; } switch (result) { case HitTestResult::Minimize: return HTMINBUTTON; case HitTestResult::MaximizeRestore: return HTMAXBUTTON; case HitTestResult::Close: return HTCLOSE; } return HTTRANSPARENT; } HitTestResult WindowHelper::systemButtonHitTest(int result) const { if (!SemiNativeSystemButtonProcessing()) { return HitTestResult::None; } switch (result) { case HTMINBUTTON: return HitTestResult::Minimize; case HTMAXBUTTON: return HitTestResult::MaximizeRestore; case HTCLOSE: return HitTestResult::Close; } return HitTestResult::None; } int WindowHelper::titleHeight() const { return _title->isHidden() ? 0 : _title->height(); } void WindowHelper::updateWindowFrameColors() { updateWindowFrameColors(window()->isActiveWindow()); } void WindowHelper::updateWindowFrameColors(bool active) { if (!::Platform::IsWindows11OrGreater()) { return; } const auto bg = active ? _title->st()->bgActive->c : _title->st()->bg->c; COLORREF bgRef = RGB(bg.red(), bg.green(), bg.blue()); DwmSetWindowAttribute( _handle, kDWMWA_CAPTION_COLOR, &bgRef, sizeof(COLORREF)); const auto fg = active ? _title->st()->fgActive->c : _title->st()->fg->c; COLORREF fgRef = RGB(fg.red(), fg.green(), fg.blue()); DwmSetWindowAttribute( _handle, kDWMWA_TEXT_COLOR, &fgRef, sizeof(COLORREF)); } void WindowHelper::updateMargins() { if (::Platform::IsWindows11OrGreater() || _updatingMargins) return; _updatingMargins = true; const auto guard = gsl::finally([&] { _updatingMargins = false; }); RECT r{}; const auto style = GetWindowLongPtr(_handle, GWL_STYLE); const auto styleEx = GetWindowLongPtr(_handle, GWL_EXSTYLE); AdjustWindowRectEx(&a, style, false, styleEx); auto margins = QMargins(r.left, r.top, -r.right, -r.bottom); if (style & WS_MAXIMIZE) { RECT w, m; GetWindowRect(_handle , &w); m = w; HMONITOR hMonitor = MonitorFromRect(&w, MONITOR_DEFAULTTONEAREST); if (hMonitor) { MONITORINFO mi; mi.cbSize = sizeof(mi); GetMonitorInfo(hMonitor, &mi); m = mi.rcWork; } _marginsDelta = QMargins( w.left - m.left, w.top - m.top, m.right - w.right, m.bottom - w.bottom); margins.setLeft(margins.left() - _marginsDelta.left()); margins.setRight(margins.right() - _marginsDelta.right()); margins.setBottom(margins.bottom() - _marginsDelta.bottom()); margins.setTop(margins.top() - _marginsDelta.top()); } else if (!_marginsDelta.isNull()) { RECT w; GetWindowRect(_handle, &w); SetWindowPos( _handle, 0, 0, 0, w.right - w.left - _marginsDelta.left() - _marginsDelta.right(), w.bottom - w.top - _marginsDelta.top() - _marginsDelta.bottom(), (SWP_NOMOVE | SWP_NOSENDCHANGING | SWP_NOZORDER | SWP_NOACTIVATE | SWP_NOREPOSITION)); _marginsDelta = QMargins(); } if (_isFullScreen || _title->isHidden()) { margins = QMargins(); if (_title->isHidden()) { _marginsDelta = QMargins(); } } if (const auto native = QGuiApplication::platformNativeInterface()) { native->setWindowProperty( window()->windowHandle()->handle(), "WindowsCustomMargins", QVariant::fromValue(margins)); } } void WindowHelper::fixMaximizedWindow() { if (::Platform::IsWindows11OrGreater()) return; const auto style = GetWindowLongPtr(_handle, GWL_STYLE); if (style & WS_MAXIMIZE) { auto w = RECT(); GetWindowRect(_handle, &w); if (const auto hMonitor = MonitorFromRect(&w, MONITOR_DEFAULTTONEAREST)) { MONITORINFO mi; mi.cbSize = sizeof(mi); GetMonitorInfo(hMonitor, &mi); const auto m = mi.rcWork; SetWindowPos(_handle, 0, 0, 0, m.right - m.left - _marginsDelta.left() - _marginsDelta.right(), m.bottom - m.top - _marginsDelta.top() - _marginsDelta.bottom(), SWP_NOMOVE | SWP_NOSENDCHANGING | SWP_NOZORDER | SWP_NOACTIVATE | SWP_NOREPOSITION); } } } not_null WindowHelper::GetNativeFilter() { Expects(QCoreApplication::instance() != nullptr); static const auto GlobalFilter = [&] { const auto application = QCoreApplication::instance(); const auto filter = Ui::CreateChild(application); application->installNativeEventFilter(filter); return filter; }(); return GlobalFilter; } HWND GetWindowHandle(not_null widget) { const auto toplevel = widget->window(); toplevel->createWinId(); return GetWindowHandle(toplevel->windowHandle()); } HWND GetWindowHandle(not_null window) { return reinterpret_cast(window->winId()); } void SendWMPaintForce(not_null widget) { const auto toplevel = widget->window(); toplevel->createWinId(); SendWMPaintForce(toplevel->windowHandle()); } void SendWMPaintForce(not_null window) { ::InvalidateRect(GetWindowHandle(window), nullptr, FALSE); } std::unique_ptr CreateSpecialWindowHelper( not_null window) { return std::make_unique(window); } bool NativeWindowFrameSupported() { return true; } } // namespace Ui::Platform