#include "Frameless.h" #include "Theme.h" #include "Utilities.h" #include #include #include #include static inline void setShadow(HWND hwnd) { const MARGINS shadow = {1, 0, 0, 0}; typedef HRESULT(WINAPI * DwmExtendFrameIntoClientAreaPtr)(HWND hWnd, const MARGINS *pMarInset); HMODULE module = LoadLibrary(L"dwmapi.dll"); if (module) { DwmExtendFrameIntoClientAreaPtr dwm_extendframe_into_client_area_; dwm_extendframe_into_client_area_ = reinterpret_cast(GetProcAddress(module, "DwmExtendFrameIntoClientArea")); if (dwm_extendframe_into_client_area_) { dwm_extendframe_into_client_area_(hwnd, &shadow); } } } enum WINDOWCOMPOSITIONATTRIB { WCA_UNDEFINED = 0, WCA_NCRENDERING_ENABLED = 1, WCA_NCRENDERING_POLICY = 2, WCA_TRANSITIONS_FORCEDISABLED = 3, WCA_ALLOW_NCPAINT = 4, WCA_CAPTION_BUTTON_BOUNDS = 5, WCA_NONCLIENT_RTL_LAYOUT = 6, WCA_FORCE_ICONIC_REPRESENTATION = 7, WCA_EXTENDED_FRAME_BOUNDS = 8, WCA_HAS_ICONIC_BITMAP = 9, WCA_THEME_ATTRIBUTES = 10, WCA_NCRENDERING_EXILED = 11, WCA_NCADORNMENTINFO = 12, WCA_EXCLUDED_FROM_LIVEPREVIEW = 13, WCA_VIDEO_OVERLAY_ACTIVE = 14, WCA_FORCE_ACTIVEWINDOW_APPEARANCE = 15, WCA_DISALLOW_PEEK = 16, WCA_CLOAK = 17, WCA_CLOAKED = 18, WCA_ACCENT_POLICY = 19, WCA_FREEZE_REPRESENTATION = 20, WCA_EVER_UNCLOAKED = 21, WCA_VISUAL_OWNER = 22, WCA_HOLOGRAPHIC = 23, WCA_EXCLUDED_FROM_DDA = 24, WCA_PASSIVEUPDATEMODE = 25, WCA_USEDARKMODECOLORS = 26, WCA_CORNER_STYLE = 27, WCA_PART_COLOR = 28, WCA_DISABLE_MOVESIZE_FEEDBACK = 29, WCA_LAST = 30 }; enum _DWM_SYSTEMBACKDROP_TYPE { _DWMSBT_AUTO, // [Default] Let DWM automatically decide the system-drawn backdrop for this // window. _DWMSBT_NONE, // [Disable] Do not draw any system backdrop. _DWMSBT_MAINWINDOW, // [Mica] Draw the backdrop material effect corresponding to a // long-lived window. _DWMSBT_TRANSIENTWINDOW, // [Acrylic] Draw the backdrop material effect corresponding to a // transient window. _DWMSBT_TABBEDWINDOW, // [Mica Alt] Draw the backdrop material effect corresponding to a // window with a tabbed title bar. }; enum ACCENT_STATE { ACCENT_DISABLED = 0, ACCENT_ENABLE_GRADIENT = 1, ACCENT_ENABLE_TRANSPARENTGRADIENT = 2, ACCENT_ENABLE_BLURBEHIND = 3, // Traditional DWM blur ACCENT_ENABLE_ACRYLICBLURBEHIND = 4, // RS4 1803 ACCENT_ENABLE_HOST_BACKDROP = 5, // RS5 1809 ACCENT_INVALID_STATE = 6 // Using this value will remove the window background }; enum ACCENT_FLAG { ACCENT_NONE = 0, ACCENT_ENABLE_ACRYLIC = 1, ACCENT_ENABLE_ACRYLIC_WITH_LUMINOSITY = 482, }; struct WINDOWCOMPOSITIONATTRIBDATA { WINDOWCOMPOSITIONATTRIB Attrib; PVOID pvData; SIZE_T cbData; }; using PWINDOWCOMPOSITIONATTRIBDATA = WINDOWCOMPOSITIONATTRIBDATA *; struct ACCENT_POLICY { DWORD dwAccentState; DWORD dwAccentFlags; DWORD dwGradientColor; // #AABBGGRR DWORD dwAnimationId; }; using PACCENT_POLICY = ACCENT_POLICY *; typedef HRESULT(WINAPI *DwmSetWindowAttributeFunc)(HWND hwnd, DWORD dwAttribute, LPCVOID pvAttribute, DWORD cbAttribute); typedef HRESULT(WINAPI *DwmExtendFrameIntoClientAreaFunc)(HWND hwnd, const MARGINS *pMarInset); typedef HRESULT(WINAPI *DwmIsCompositionEnabledFunc)(BOOL *pfEnabled); typedef HRESULT(WINAPI *DwmEnableBlurBehindWindowFunc)(HWND hWnd, const DWM_BLURBEHIND *pBlurBehind); typedef BOOL(WINAPI *SetWindowCompositionAttributeFunc)(HWND hwnd, const WINDOWCOMPOSITIONATTRIBDATA *); static DwmSetWindowAttributeFunc pDwmSetWindowAttribute = nullptr; static DwmExtendFrameIntoClientAreaFunc pDwmExtendFrameIntoClientArea = nullptr; static DwmIsCompositionEnabledFunc pDwmIsCompositionEnabled = nullptr; static DwmEnableBlurBehindWindowFunc pDwmEnableBlurBehindWindow = nullptr; static SetWindowCompositionAttributeFunc pSetWindowCompositionAttribute = nullptr; static RTL_OSVERSIONINFOW GetRealOSVersionImpl() { HMODULE hMod = ::GetModuleHandleW(L"ntdll.dll"); using RtlGetVersionPtr = NTSTATUS(WINAPI *)(PRTL_OSVERSIONINFOW); auto pRtlGetVersion = reinterpret_cast(::GetProcAddress(hMod, "RtlGetVersion")); RTL_OSVERSIONINFOW rovi{}; rovi.dwOSVersionInfoSize = sizeof(rovi); pRtlGetVersion(&rovi); return rovi; } RTL_OSVERSIONINFOW GetRealOSVersion() { static const auto result = GetRealOSVersionImpl(); return result; } static inline bool isWin7Only() { RTL_OSVERSIONINFOW rovi = GetRealOSVersion(); return rovi.dwMajorVersion = 7; } static inline bool isWin11OrGreater() { RTL_OSVERSIONINFOW rovi = GetRealOSVersion(); return (rovi.dwMajorVersion > 10) || (rovi.dwMajorVersion == 10 && rovi.dwMinorVersion >= 0 && rovi.dwBuildNumber >= 22000); } static bool containsCursorToItem(QQuickItem *item) { auto window = item->window(); if ((window == nullptr) || !item || !item->isVisible()) { return false; } auto point = window->mapFromGlobal(QCursor::pos()); auto rect = QRectF(item->mapToItem(window->contentItem(), QPointF(0, 0)), item->size()); if (rect.contains(point)) { return true; } return false; } static inline bool initializeFunctionPointers() { HMODULE module = LoadLibraryW(L"dwmapi.dll"); if (module) { if (!pDwmSetWindowAttribute) { pDwmSetWindowAttribute = reinterpret_cast(GetProcAddress(module, "DwmSetWindowAttribute")); if (!pDwmSetWindowAttribute) { return false; } } if (!pDwmExtendFrameIntoClientArea) { pDwmExtendFrameIntoClientArea = reinterpret_cast( GetProcAddress(module, "DwmExtendFrameIntoClientArea")); if (!pDwmExtendFrameIntoClientArea) { return false; } } if (!pDwmIsCompositionEnabled) { pDwmIsCompositionEnabled = reinterpret_cast(::GetProcAddress(module, "DwmIsCompositionEnabled")); if (!pDwmIsCompositionEnabled) { return false; } } if (!pDwmEnableBlurBehindWindow) { pDwmEnableBlurBehindWindow = reinterpret_cast(GetProcAddress(module, "DwmEnableBlurBehindWindow")); if (!pDwmEnableBlurBehindWindow) { return false; } } if (!pSetWindowCompositionAttribute) { HMODULE user32 = LoadLibraryW(L"user32.dll"); if (!user32) { return false; } pSetWindowCompositionAttribute = reinterpret_cast( GetProcAddress(user32, "SetWindowCompositionAttribute")); if (!pSetWindowCompositionAttribute) { return false; } } } return true; } static inline bool isWin1122H2OrGreater() { RTL_OSVERSIONINFOW rovi = GetRealOSVersion(); return (rovi.dwMajorVersion > 10) || (rovi.dwMajorVersion == 10 && rovi.dwMinorVersion >= 0 && rovi.dwBuildNumber >= 22621); } static inline bool setWindowDarkMode(HWND hwnd, const BOOL enable) { if (!initializeFunctionPointers()) { return false; } return bool(pDwmSetWindowAttribute(hwnd, 20, &enable, sizeof(BOOL))); } static inline bool isCompositionEnabled() { if (initializeFunctionPointers()) { BOOL composition_enabled = false; pDwmIsCompositionEnabled(&composition_enabled); return composition_enabled; } return false; } static inline bool isWin8OrGreater() { RTL_OSVERSIONINFOW rovi = GetRealOSVersion(); return (rovi.dwMajorVersion > 6) || (rovi.dwMajorVersion == 6 && rovi.dwMinorVersion >= 2); } static inline bool setWindowEffect(HWND hwnd, const QString &key, const bool &enable) { static constexpr const MARGINS extendedMargins = {-1, -1, -1, -1}; if (key == QStringLiteral("mica")) { if (!isWin11OrGreater() || !initializeFunctionPointers()) { return false; } if (enable) { pDwmExtendFrameIntoClientArea(hwnd, &extendedMargins); if (isWin1122H2OrGreater()) { const DWORD backdropType = _DWMSBT_MAINWINDOW; pDwmSetWindowAttribute(hwnd, 38, &backdropType, sizeof(backdropType)); } else { const BOOL enable = TRUE; pDwmSetWindowAttribute(hwnd, 1029, &enable, sizeof(enable)); } } else { if (isWin1122H2OrGreater()) { const DWORD backdropType = _DWMSBT_AUTO; pDwmSetWindowAttribute(hwnd, 38, &backdropType, sizeof(backdropType)); } else { const BOOL enable = FALSE; pDwmSetWindowAttribute(hwnd, 1029, &enable, sizeof(enable)); } } BOOL isDark = Theme::instance()->dark(); setWindowDarkMode(hwnd, isDark); return true; } if (key == QStringLiteral("mica-alt")) { if (!isWin1122H2OrGreater() || !initializeFunctionPointers()) { return false; } if (enable) { pDwmExtendFrameIntoClientArea(hwnd, &extendedMargins); const DWORD backdropType = _DWMSBT_TABBEDWINDOW; pDwmSetWindowAttribute(hwnd, 38, &backdropType, sizeof(backdropType)); } else { const DWORD backdropType = _DWMSBT_AUTO; pDwmSetWindowAttribute(hwnd, 38, &backdropType, sizeof(backdropType)); } BOOL isDark = Theme::instance()->dark(); setWindowDarkMode(hwnd, isDark); return true; } if (key == QStringLiteral("acrylic")) { if (!isWin11OrGreater() || !initializeFunctionPointers()) { return false; } if (enable) { pDwmExtendFrameIntoClientArea(hwnd, &extendedMargins); DWORD system_backdrop_type = _DWMSBT_TRANSIENTWINDOW; pDwmSetWindowAttribute(hwnd, 38, &system_backdrop_type, sizeof(DWORD)); } else { const DWORD backdropType = _DWMSBT_AUTO; pDwmSetWindowAttribute(hwnd, 38, &backdropType, sizeof(backdropType)); } BOOL isDark = Theme::instance()->dark(); setWindowDarkMode(hwnd, isDark); return true; } if (key == QStringLiteral("dwm-blur")) { if ((isWin7Only() && !isCompositionEnabled()) || !initializeFunctionPointers()) { return false; } BOOL isDark = Theme::instance()->dark(); setWindowDarkMode(hwnd, isDark && enable); if (enable) { if (isWin8OrGreater()) { ACCENT_POLICY policy{}; policy.dwAccentState = ACCENT_ENABLE_BLURBEHIND; policy.dwAccentFlags = ACCENT_NONE; WINDOWCOMPOSITIONATTRIBDATA wcad{}; wcad.Attrib = WCA_ACCENT_POLICY; wcad.pvData = &policy; wcad.cbData = sizeof(policy); pSetWindowCompositionAttribute(hwnd, &wcad); } else { DWM_BLURBEHIND bb{}; bb.fEnable = TRUE; bb.dwFlags = DWM_BB_ENABLE; pDwmEnableBlurBehindWindow(hwnd, &bb); } } else { if (isWin8OrGreater()) { ACCENT_POLICY policy{}; policy.dwAccentState = ACCENT_DISABLED; policy.dwAccentFlags = ACCENT_NONE; WINDOWCOMPOSITIONATTRIBDATA wcad{}; wcad.Attrib = WCA_ACCENT_POLICY; wcad.pvData = &policy; wcad.cbData = sizeof(policy); pSetWindowCompositionAttribute(hwnd, &wcad); } else { DWM_BLURBEHIND bb{}; bb.fEnable = FALSE; bb.dwFlags = DWM_BB_ENABLE; pDwmEnableBlurBehindWindow(hwnd, &bb); } } return true; } return false; } Frameless::Frameless(QQuickItem *parent) : QQuickItem{parent} { m_isWindows11OrGreater = Utilities::instance()->isWindows11OrGreater(); m_effect = "normal"; } QQuickItem *Frameless::appBar() const { return m_appBar; } void Frameless::setAppBar(QQuickItem *appBar) { if (m_appBar != appBar) { m_appBar = appBar; emit appBarChanged(); } } QQuickItem *Frameless::maximizeButton() const { return m_maximizeButton; } void Frameless::setMaximizeButton(QQuickItem *button) { if (m_maximizeButton != button) { m_maximizeButton = button; emit maximizeButtonChanged(); } } QQuickItem *Frameless::minimizedButton() const { return m_minimizedButton; } void Frameless::setMinimizedButton(QQuickItem *button) { if (m_minimizedButton != button) { m_minimizedButton = button; emit minimizedButtonChanged(); } } QQuickItem *Frameless::closeButton() const { return m_closeButton; } void Frameless::setCloseButton(QQuickItem *button) { if (m_closeButton != button) { m_closeButton = button; emit closeButtonChanged(); } } bool Frameless::fixSize() const { return m_fixSize; } void Frameless::setFixSize(bool fix) { if (m_fixSize != fix) { m_fixSize = fix; emit fixSizeChanged(); } } bool Frameless::topmost() const { return m_topmost; } void Frameless::setTopmost(bool topmost) { if (m_topmost != topmost) { m_topmost = topmost; emit topmostChanged(); } } bool Frameless::disabled() const { return m_disabled; } void Frameless::setDisabled(bool disabled) { if (m_disabled != disabled) { m_disabled = disabled; emit disabledChanged(); } } void Frameless::setHitTestVisible(QQuickItem *item) { if (!m_hitTestList.contains(item)) { m_hitTestList.append(item); } } void Frameless::showMaximized() { #ifdef Q_OS_WIN HWND hwnd = reinterpret_cast(window()->winId()); ::ShowWindow(hwnd, 3); #else window()->setVisibility(QQuickWindow::Maximized); #endif } void Frameless::showMinimized() { #ifdef Q_OS_WIN HWND hwnd = reinterpret_cast(window()->winId()); ::ShowWindow(hwnd, 2); #else window()->setVisibility(QQuickWindow::Minimized); #endif } void Frameless::showNormal() { window()->setVisibility(QQuickWindow::Windowed); } void Frameless::onDestruction() { QGuiApplication::instance()->removeNativeEventFilter(this); } void Frameless::componentComplete() { if (m_disabled) { return; } int w = window()->width(); int h = window()->height(); m_current = window()->winId(); window()->setFlags((window()->flags()) | Qt::CustomizeWindowHint | Qt::WindowMinimizeButtonHint | Qt::WindowCloseButtonHint); if (!m_fixSize) { window()->setFlag(Qt::WindowMaximizeButtonHint); } window()->installEventFilter(this); QGuiApplication::instance()->installNativeEventFilter(this); if (m_maximizeButton) { setHitTestVisible(m_maximizeButton); } if (m_minimizedButton) { setHitTestVisible(m_minimizedButton); } if (m_closeButton) { setHitTestVisible(m_closeButton); } #ifdef Q_OS_WIN #if (QT_VERSION == QT_VERSION_CHECK(6, 5, 3)) qWarning() << "Qt's own frameless bug, currently only exist in 6.5.3, please use other versions"; #endif HWND hwnd = reinterpret_cast(window()->winId()); DWORD style = ::GetWindowLongPtr(hwnd, GWL_STYLE); #if (QT_VERSION == QT_VERSION_CHECK(6, 7, 2)) style &= ~(WS_MINIMIZEBOX | WS_MAXIMIZEBOX | WS_SYSMENU); #endif if (m_fixSize) { ::SetWindowLongPtr(hwnd, GWL_STYLE, style | WS_THICKFRAME | WS_CAPTION); ; for (int i = 0; i <= QGuiApplication::screens().count() - 1; ++i) { connect(QGuiApplication::screens().at(i), &QScreen::logicalDotsPerInchChanged, this, [=] { SetWindowPos(hwnd, nullptr, 0, 0, 0, 0, SWP_NOZORDER | SWP_NOOWNERZORDER | SWP_NOMOVE | SWP_FRAMECHANGED); }); } } else { ::SetWindowLongPtr(hwnd, GWL_STYLE, style | WS_MAXIMIZEBOX | WS_THICKFRAME | WS_CAPTION); } SetWindowPos(hwnd, nullptr, 0, 0, 0, 0, SWP_NOZORDER | SWP_NOOWNERZORDER | SWP_NOMOVE | SWP_NOSIZE | SWP_FRAMECHANGED); connect(window(), &QQuickWindow::screenChanged, this, [hwnd] { ::SetWindowPos(hwnd, nullptr, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE | SWP_NOZORDER | SWP_FRAMECHANGED | SWP_NOOWNERZORDER); ::RedrawWindow(hwnd, nullptr, nullptr, RDW_INVALIDATE | RDW_UPDATENOW); }); if (!window()->property("_hideShadow").toBool()) { setShadow(hwnd); } if (isWin11OrGreater()) { availableEffects({"mica", "mica-alt", "acrylic", "dwm-blur", "normal"}); } else if (isWin7Only()) { availableEffects({"dwm-blur", "normal"}); } if (!m_effect.isEmpty()) { effective(setWindowEffect(hwnd, m_effect, true)); if (effective()) { m_currentEffect = effect(); } } connect(this, &Frameless::effectChanged, this, [hwnd, this] { if (effect() == m_currentEffect) { return; } if (effective()) { setWindowEffect(hwnd, m_currentEffect, false); } effective(setWindowEffect(hwnd, effect(), true)); if (effective()) { m_currentEffect = effect(); } else { m_effect = "normal"; m_currentEffect = "normal"; } }); connect(Theme::instance(), &Theme::blurBehindWindowEnabledChanged, this, [this] { if (Theme::instance()->blurBehindWindowEnabled()) { effect("normal"); } }); connect(Theme::instance(), &Theme::darkChanged, this, [hwnd, this] { if (effective() && !m_currentEffect.isEmpty() && m_currentEffect != "normal") { setWindowDarkMode(hwnd, Theme::instance()->dark()); } }); #endif auto appBarHeight = m_appBar->height(); h = qRound(h + appBarHeight); if (m_fixSize) { window()->setMaximumSize(QSize(w, h)); window()->setMinimumSize(QSize(w, h)); } else { window()->setMinimumHeight(window()->minimumHeight() + appBarHeight); window()->setMaximumHeight(window()->maximumHeight() + appBarHeight); } window()->resize(QSize(w, h)); connect(this, &Frameless::topmostChanged, this, [this] { setWindowTopmost(topmost()); }); setWindowTopmost(topmost()); } bool Frameless::eventFilter(QObject *obj, QEvent *event) { #ifndef Q_OS_WIN switch (ev->type()) { case QEvent::MouseButtonPress: if (_edges != 0) { QMouseEvent *event = static_cast(ev); if (event->button() == Qt::LeftButton) { _updateCursor(_edges); window()->startSystemResize(Qt::Edges(_edges)); } } else { if (_hitAppBar()) { qint64 clickTimer = QDateTime::currentMSecsSinceEpoch(); qint64 offset = clickTimer - this->_clickTimer; this->_clickTimer = clickTimer; if (offset < 300) { if (_isMaximized()) { showNormal(); } else { showMaximized(); } } else { window()->startSystemMove(); } } } break; case QEvent::MouseButtonRelease: _edges = 0; break; case QEvent::MouseMove: { if (_isMaximized() || _isFullScreen()) { break; } if (_fixSize) { break; } QMouseEvent *event = static_cast(ev); QPoint p = #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) event->pos(); #else event->position().toPoint(); #endif if (p.x() >= _margins && p.x() <= (window()->width() - _margins) && p.y() >= _margins && p.y() <= (window()->height() - _margins)) { if (_edges != 0) { _edges = 0; _updateCursor(_edges); } break; } _edges = 0; if (p.x() < _margins) { _edges |= Qt::LeftEdge; } if (p.x() > (window()->width() - _margins)) { _edges |= Qt::RightEdge; } if (p.y() < _margins) { _edges |= Qt::TopEdge; } if (p.y() > (window()->height() - _margins)) { _edges |= Qt::BottomEdge; } _updateCursor(_edges); break; } default: break; } #endif return QObject::eventFilter(obj, event); } bool Frameless::nativeEventFilter(const QByteArray &eventType, void *message, qintptr *result) { #ifdef Q_OS_WIN if ((eventType != "windows_generic_MSG") || !message) { return false; } const auto msg = static_cast(message); auto hwnd = msg->hwnd; if (!hwnd) { return false; } const quint64 wid = reinterpret_cast(hwnd); if (wid != m_current) { return false; } const auto uMsg = msg->message; const auto wParam = msg->wParam; const auto lParam = msg->lParam; if (uMsg == WM_WINDOWPOSCHANGING) { auto *wp = reinterpret_cast(lParam); if (wp != nullptr && (wp->flags & SWP_NOSIZE) == 0) { wp->flags |= SWP_NOCOPYBITS; *result = static_cast(::DefWindowProcW(hwnd, uMsg, wParam, lParam)); return true; } return false; } else if (uMsg == WM_NCCALCSIZE && wParam == TRUE) { bool isMaximum = ::IsZoomed(hwnd); if (isMaximum) { window()->setProperty("__margins", 7); } else { window()->setProperty("__margins", 0); } setMaximizeHovered(false); *result = WVR_REDRAW; return true; } else if (uMsg == WM_NCHITTEST) { if (m_isWindows11OrGreater) { if (hitMaximizeButton()) { if (*result == HTNOWHERE) { *result = HTZOOM; } setMaximizeHovered(true); return true; } setMaximizeHovered(false); setMaximizePressed(false); } *result = 0; POINT nativeGlobalPos{GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam)}; POINT nativeLocalPos = nativeGlobalPos; ::ScreenToClient(hwnd, &nativeLocalPos); RECT clientRect{0, 0, 0, 0}; ::GetClientRect(hwnd, &clientRect); auto clientWidth = clientRect.right - clientRect.left; auto clientHeight = clientRect.bottom - clientRect.top; bool left = nativeLocalPos.x < m_margins; bool right = nativeLocalPos.x > clientWidth - m_margins; bool top = nativeLocalPos.y < m_margins; bool bottom = nativeLocalPos.y > clientHeight - m_margins; *result = 0; if (!m_fixSize && !isFullScreen() && !isMaximized()) { if (left && top) { *result = HTTOPLEFT; } else if (left && bottom) { *result = HTBOTTOMLEFT; } else if (right && top) { *result = HTTOPRIGHT; } else if (right && bottom) { *result = HTBOTTOMRIGHT; } else if (left) { *result = HTLEFT; } else if (right) { *result = HTRIGHT; } else if (top) { *result = HTTOP; } else if (bottom) { *result = HTBOTTOM; } } if (0 != *result) { return true; } if (hitAppBar()) { *result = HTCAPTION; return true; } *result = HTCLIENT; return true; } else if (uMsg == WM_NCPAINT) { *result = FALSE; return false; } else if (uMsg == WM_NCACTIVATE) { *result = TRUE; return true; } else if (m_isWindows11OrGreater && (uMsg == WM_NCLBUTTONDBLCLK || uMsg == WM_NCLBUTTONDOWN)) { if (hitMaximizeButton()) { QMouseEvent event = QMouseEvent(QEvent::MouseButtonPress, QPoint(), QPoint(), Qt::LeftButton, Qt::LeftButton, Qt::NoModifier); QGuiApplication::sendEvent(m_maximizeButton, &event); setMaximizePressed(true); return true; } } else if (m_isWindows11OrGreater && (uMsg == WM_NCLBUTTONUP || uMsg == WM_NCRBUTTONUP)) { if (hitMaximizeButton()) { QMouseEvent event = QMouseEvent(QEvent::MouseButtonRelease, QPoint(), QPoint(), Qt::LeftButton, Qt::LeftButton, Qt::NoModifier); QGuiApplication::sendEvent(m_maximizeButton, &event); setMaximizePressed(false); return true; } } else if (uMsg == WM_NCRBUTTONDOWN) { if (wParam == HTCAPTION) { auto pos = window()->position(); auto offset = window()->mapFromGlobal(QCursor::pos()); showSystemMenu(QPoint(pos.x() + offset.x(), pos.y() + offset.y())); } } else if (uMsg == WM_KEYDOWN || uMsg == WM_SYSKEYDOWN) { const bool altPressed = ((wParam == VK_MENU) || (::GetKeyState(VK_MENU) < 0)); const bool spacePressed = ((wParam == VK_SPACE) || (::GetKeyState(VK_SPACE) < 0)); if (altPressed && spacePressed) { auto pos = window()->position(); showSystemMenu(QPoint(pos.x(), qRound(pos.y() + m_appBar->height()))); } } else if (uMsg == WM_SYSCOMMAND) { if (wParam == SC_MINIMIZE) { if (window()->transientParent()) { auto _hwnd = reinterpret_cast(window()->transientParent()->winId()); ::ShowWindow(_hwnd, 2); } else { auto _hwnd = reinterpret_cast(window()->winId()); ::ShowWindow(_hwnd, 2); } return true; } return false; } return false; #else return false; #endif } bool Frameless::isFullScreen() { return window()->visibility() == QWindow::FullScreen; } bool Frameless::isMaximized() { return window()->visibility() == QWindow::Maximized; } void Frameless::setMaximizeHovered(bool val) { if (m_maximizeButton) { m_maximizeButton->setProperty("hover", val); } } void Frameless::setMaximizePressed(bool val) { if (m_maximizeButton) { m_maximizeButton->setProperty("down", val); } } void Frameless::setWindowTopmost(bool topmost) { #ifdef Q_OS_WIN HWND hwnd = reinterpret_cast(window()->winId()); if (topmost) { ::SetWindowPos(hwnd, HWND_TOPMOST, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE); } else { ::SetWindowPos(hwnd, HWND_NOTOPMOST, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE); } #else window()->setFlag(Qt::WindowStaysOnTopHint, topmost); #endif } bool Frameless::hitMaximizeButton() { if (containsCursorToItem(m_maximizeButton)) { return true; } return false; } bool Frameless::hitAppBar() { for (int i = 0; i <= m_hitTestList.size() - 1; ++i) { auto item = m_hitTestList.at(i); if (containsCursorToItem(item)) { return false; } } if ((m_appBar != nullptr) && containsCursorToItem(m_appBar)) { return true; } return false; } void Frameless::showSystemMenu(QPoint point) { #ifdef Q_OS_WIN QScreen *screen = window()->screen(); if (!screen) { screen = QGuiApplication::primaryScreen(); } if (!screen) { return; } const QPoint origin = screen->geometry().topLeft(); auto nativePos = QPointF(QPointF(point - origin) * window()->devicePixelRatio()).toPoint() + origin; HWND hwnd = reinterpret_cast(window()->winId()); auto hMenu = ::GetSystemMenu(hwnd, FALSE); if (isMaximized() || isFullScreen()) { ::EnableMenuItem(hMenu, SC_MOVE, MFS_DISABLED); ::EnableMenuItem(hMenu, SC_RESTORE, MFS_ENABLED); } else { ::EnableMenuItem(hMenu, SC_MOVE, MFS_ENABLED); ::EnableMenuItem(hMenu, SC_RESTORE, MFS_DISABLED); } if (!m_fixSize && !isMaximized() && !isFullScreen()) { ::EnableMenuItem(hMenu, SC_SIZE, MFS_ENABLED); ::EnableMenuItem(hMenu, SC_MAXIMIZE, MFS_ENABLED); } else { ::EnableMenuItem(hMenu, SC_SIZE, MFS_DISABLED); ::EnableMenuItem(hMenu, SC_MAXIMIZE, MFS_DISABLED); } const int result = ::TrackPopupMenu(hMenu, (TPM_RETURNCMD | (QGuiApplication::isRightToLeft() ? TPM_RIGHTALIGN : TPM_LEFTALIGN)), nativePos.x(), nativePos.y(), 0, hwnd, nullptr); if (result) { ::PostMessageW(hwnd, WM_SYSCOMMAND, result, 0); } #endif }