From 2f4686a63ceb8eac046729cd01c5c83f6bd10a8f Mon Sep 17 00:00:00 2001 From: Yuhang Zhao <2546789017@qq.com> Date: Thu, 9 Apr 2020 13:13:01 +0800 Subject: [PATCH] Update. Signed-off-by: Yuhang Zhao <2546789017@qq.com> --- winnativeeventfilter.cpp | 654 +++++++++++++-------------------------- winnativeeventfilter.h | 10 +- 2 files changed, 214 insertions(+), 450 deletions(-) diff --git a/winnativeeventfilter.cpp b/winnativeeventfilter.cpp index e539b3d..5937c8c 100644 --- a/winnativeeventfilter.cpp +++ b/winnativeeventfilter.cpp @@ -25,12 +25,16 @@ #include "winnativeeventfilter.h" #include +#if (QT_VERSION >= QT_VERSION_CHECK(5, 14, 0)) #include +#else +#include +#endif #include #include #include -#include #include +#include #ifndef ABM_GETSTATE // Only available since Windows XP @@ -77,21 +81,6 @@ #define SM_CXPADDEDBORDER 92 #endif -#ifndef DWM_BB_ENABLE -// Only available since Windows Vista -#define DWM_BB_ENABLE 0x00000001 -#endif - -#ifndef WTNCA_NODRAWCAPTION -// Only available since Windows Vista -#define WTNCA_NODRAWCAPTION 0x00000001 -#endif - -#ifndef WTNCA_NODRAWICON -// Only available since Windows Vista -#define WTNCA_NODRAWICON 0x00000002 -#endif - #ifndef WM_NCUAHDRAWCAPTION // Not documented, only available since Windows Vista #define WM_NCUAHDRAWCAPTION 0x00AE @@ -114,65 +103,22 @@ #ifndef WNEF_GENERATE_WINAPI #define WNEF_GENERATE_WINAPI(funcName, resultType, ...) \ -using _WNEF_WINAPI_##funcName = resultType (WINAPI *) (__VA_ARGS__); \ -_WNEF_WINAPI_##funcName m_lp##funcName = nullptr; + using _WNEF_WINAPI_##funcName = resultType (WINAPI *) (__VA_ARGS__); \ + _WNEF_WINAPI_##funcName m_lp##funcName = nullptr; #endif #ifndef WNEF_RESOLVE_WINAPI #define WNEF_RESOLVE_WINAPI(libName, funcName) \ if (!m_lp##funcName) { \ - QLibrary library(QString::fromUtf8(#libName)); \ - m_lp##funcName = \ - reinterpret_cast<_WNEF_WINAPI_##funcName>(library.resolve(#funcName)); \ - if (!m_lp##funcName) { \ - qWarning().noquote() \ - << "Failed to resolve" << #funcName << "from" << #libName \ - << "-->" << library.errorString(); \ - } \ + QLibrary library(QString::fromUtf8(#libName)); \ + m_lp##funcName = \ + reinterpret_cast<_WNEF_WINAPI_##funcName>(library.resolve(#funcName)); \ + Q_ASSERT_X(m_lp##funcName, __FUNCTION__, qUtf8Printable(library.errorString()));\ } #endif -#ifndef WNEF_USE_WINAPI -#define WNEF_USE_WINAPI(funcName, ...) \ -if (m_lp##funcName) {\ -m_lp##funcName(__VA_ARGS__);\ -} else {\ -qWarning().noquote() << "Uninitialized function:" << #funcName;\ -} -#endif - namespace { -using WINDOWTHEMEATTRIBUTETYPE = enum _WINDOWTHEMEATTRIBUTETYPE { - WTA_NONCLIENT = 1 -}; - -using WINDOWCOMPOSITIONATTRIB = enum _WINDOWCOMPOSITIONATTRIB { - WCA_ACCENT_POLICY = 19 -}; - -using WINDOWCOMPOSITIONATTRIBDATA = struct _WINDOWCOMPOSITIONATTRIBDATA { - DWORD dwAttribute; - PVOID pvAttribute; - DWORD cbAttribute; -}; - -using ACCENT_STATE = enum _ACCENT_STATE { - ACCENT_DISABLED = 0, - ACCENT_ENABLE_GRADIENT = 1, - ACCENT_ENABLE_TRANSPARENTGRADIENT = 2, - ACCENT_ENABLE_BLURBEHIND = 3, - ACCENT_ENABLE_ACRYLICBLURBEHIND = 4, - ACCENT_INVALID_STATE = 5 -}; - -using ACCENT_POLICY = struct _ACCENT_POLICY { - ACCENT_STATE AccentState; - DWORD AccentFlags; - DWORD GradientColor; - DWORD AnimationId; -}; - using MONITOR_DPI_TYPE = enum _MONITOR_DPI_TYPE { MDT_EFFECTIVE_DPI = 0, MDT_ANGULAR_DPI = 1, @@ -180,24 +126,12 @@ using MONITOR_DPI_TYPE = enum _MONITOR_DPI_TYPE { MDT_DEFAULT = MDT_EFFECTIVE_DPI }; -using WTA_OPTIONS = struct _WTA_OPTIONS { - DWORD dwFlags; - DWORD dwMask; -}; - using DWMNCRENDERINGPOLICY = enum _DWMNCRENDERINGPOLICY { DWMNCRP_ENABLED = 2 }; using DWMWINDOWATTRIBUTE = enum _DWMWINDOWATTRIBUTE { DWMWA_NCRENDERING_POLICY = 2 }; -using DWM_BLURBEHIND = struct _DWM_BLURBEHIND { - DWORD dwFlags; - BOOL fEnable; - HRGN hRgnBlur; - BOOL fTransitionOnMaximized; -}; - using MARGINS = struct _MARGINS { int cxLeftWidth; int cxRightWidth; @@ -220,13 +154,6 @@ WNEF_GENERATE_WINAPI(GetDpiForSystem, UINT) WNEF_GENERATE_WINAPI(GetSystemMetricsForDpi, int, int, UINT) WNEF_GENERATE_WINAPI(GetDpiForMonitor, HRESULT, HMONITOR, MONITOR_DPI_TYPE, UINT *, UINT *) -WNEF_GENERATE_WINAPI(SetWindowCompositionAttribute, BOOL, HWND, const WINDOWCOMPOSITIONATTRIBDATA *) -WNEF_GENERATE_WINAPI(SetWindowThemeAttribute, HRESULT, HWND, - WINDOWTHEMEATTRIBUTETYPE, - PVOID, DWORD) -WNEF_GENERATE_WINAPI(IsThemeActive, BOOL) -WNEF_GENERATE_WINAPI(DwmEnableBlurBehindWindow, HRESULT, HWND, - const DWM_BLURBEHIND *) WNEF_GENERATE_WINAPI(DwmExtendFrameIntoClientArea, HRESULT, HWND, const MARGINS *) WNEF_GENERATE_WINAPI(DwmIsCompositionEnabled, HRESULT, BOOL *) WNEF_GENERATE_WINAPI(DwmSetWindowAttribute, HRESULT, HWND, DWORD, LPCVOID, DWORD) @@ -262,9 +189,6 @@ void WinNativeEventFilter::uninstall() { m_instance.reset(); } if (!m_framelessWindows.isEmpty()) { - for (auto &&window : qAsConst(m_framelessWindows)) { - refreshWindow(window); - } m_framelessWindows.clear(); } } @@ -294,7 +218,6 @@ void WinNativeEventFilter::addFramelessWindow(HWND window, void WinNativeEventFilter::removeFramelessWindow(HWND window) { if (window && m_framelessWindows.contains(window)) { m_framelessWindows.removeAll(window); - refreshWindow(window); } } @@ -372,6 +295,7 @@ bool WinNativeEventFilter::nativeEventFilter(const QByteArray &eventType, } if (m_framelessWindows.isEmpty()) { // Only top level windows can be frameless. + // Try to avoid this case because it will result in strange behavior, use addFramelessWindow if possible. const HWND parent = GetAncestor(msg->hwnd, GA_PARENT); if (parent && (parent != GetDesktopWindow())) { return false; @@ -382,87 +306,86 @@ bool WinNativeEventFilter::nativeEventFilter(const QByteArray &eventType, createUserData(msg->hwnd); const auto data = reinterpret_cast(GetWindowLongPtrW(msg->hwnd, GWLP_USERDATA)); - // Don't forget to init it if not inited, otherwise the window style will - // not be updated, but don't init it twice as well. - if (!data->inited) { - init(data); + if (!data->initialized) { + data->initialized = TRUE; + // The following two lines are necessary to remove the three system buttons (minimize, maximize and close), + // but they will make the Acrylic (available since Win10 1709) unusable. + SetWindowLongPtrW(msg->hwnd, GWL_EXSTYLE, WS_EX_APPWINDOW | WS_EX_LAYERED); + SetLayeredWindowAttributes(msg->hwnd, RGB(255, 0, 255), 0, LWA_COLORKEY); + // Make sure our window have it's frame shadow. + // The frame shadow is drawn by Desktop Window Manager (DWM), don't draw it yourself. + // The frame shadow will get lost if DWM composition is disabled, it's designed to be, + // don't force the window to draw a frame shadow in that case. + // According to MSDN, DWM composition is always enabled and can't be disabled since Win8. + handleDwmCompositionChanged(data); + // For debug purposes. + qDebug().noquote() << "Window handle:" << msg->hwnd; + qDebug().noquote() << "Window DPI:" << getDotsPerInchForWindow(msg->hwnd) + << "Window DPR:" + << getDevicePixelRatioForWindow(msg->hwnd); + qDebug().noquote() << "Window border width:" << borderWidth(msg->hwnd) + << "Window border height:" << borderHeight(msg->hwnd) + << "Window titlebar height:" + << titlebarHeight(msg->hwnd); } switch (msg->message) { - case WM_NCCREATE: { - // Work-around a long-existing Windows bug. - const auto userData = - reinterpret_cast(msg->lParam)->lpCreateParams; - SetWindowLongPtrW(msg->hwnd, GWLP_USERDATA, - reinterpret_cast(userData)); - refreshWindow(msg->hwnd); - break; - } case WM_NCCALCSIZE: { - // If wParam is TRUE, it specifies that the application should indicate - // which part of the client area contains valid information. The system - // copies the valid information to the specified area within the new - // client area. If wParam is FALSE, the application does not need to - // indicate the valid part of the client area. - if (static_cast(msg->wParam)) { - if (IsMaximized(msg->hwnd)) { - const HMONITOR monitor = - MonitorFromWindow(msg->hwnd, MONITOR_DEFAULTTONEAREST); - if (monitor) { - MONITORINFO monitorInfo; - SecureZeroMemory(&monitorInfo, sizeof(monitorInfo)); - monitorInfo.cbSize = sizeof(monitorInfo); - GetMonitorInfoW(monitor, &monitorInfo); - auto ¶ms = - *reinterpret_cast(msg->lParam); - params.rgrc[0] = monitorInfo.rcWork; - // If the client rectangle is the same as the monitor's - // rectangle, the shell assumes that the window has gone - // fullscreen, so it removes the topmost attribute from any - // auto-hide appbars, making them inaccessible. To avoid - // this, reduce the size of the client area by one pixel on - // a certain edge. The edge is chosen based on which side of - // the monitor is likely to contain an auto-hide appbar, so - // the missing client area is covered by it. - if (EqualRect(¶ms.rgrc[0], &monitorInfo.rcMonitor)) { - if (m_SHAppBarMessage) { - APPBARDATA abd; - SecureZeroMemory(&abd, sizeof(abd)); - abd.cbSize = sizeof(abd); - const UINT taskbarState = - m_SHAppBarMessage(ABM_GETSTATE, &abd); - if (taskbarState & ABS_AUTOHIDE) { - int edge = -1; - abd.hWnd = - FindWindowW(L"Shell_TrayWnd", nullptr); - if (abd.hWnd) { - const HMONITOR taskbarMonitor = - MonitorFromWindow( - abd.hWnd, MONITOR_DEFAULTTONEAREST); - if (taskbarMonitor && - (taskbarMonitor == monitor)) { - m_SHAppBarMessage(ABM_GETTASKBARPOS, - &abd); - edge = abd.uEdge; - } - } - if (edge == ABE_BOTTOM) { - params.rgrc[0].bottom--; - } else if (edge == ABE_LEFT) { - params.rgrc[0].left++; - } else if (edge == ABE_TOP) { - params.rgrc[0].top++; - } else if (edge == ABE_RIGHT) { - params.rgrc[0].right--; - } - } + // Sent when the size and position of a window's client area must be calculated. By processing this message, an application can control the content of the window's client area when the size or position of the window changes. + // If wParam is TRUE, lParam points to an NCCALCSIZE_PARAMS structure that contains information an application can use to calculate the new size and position of the client rectangle. + // If wParam is FALSE, lParam points to a RECT structure. On entry, the structure contains the proposed window rectangle for the window. On exit, the structure should contain the screen coordinates of the corresponding window client area. + auto &rect = static_cast(msg->wParam) ? (*reinterpret_cast(msg->lParam)).rgrc[0] : *reinterpret_cast(msg->lParam); + if (IsMaximized(msg->hwnd)) { + const HMONITOR windowMonitor = + MonitorFromWindow(msg->hwnd, MONITOR_DEFAULTTONEAREST); + MONITORINFO monitorInfo; + SecureZeroMemory(&monitorInfo, sizeof(monitorInfo)); + monitorInfo.cbSize = sizeof(monitorInfo); + GetMonitorInfoW(windowMonitor, &monitorInfo); + rect = monitorInfo.rcWork; + // If the client rectangle is the same as the monitor's + // rectangle, the shell assumes that the window has gone + // fullscreen, so it removes the topmost attribute from any + // auto-hide appbars, making them inaccessible. To avoid + // this, reduce the size of the client area by one pixel on + // a certain edge. The edge is chosen based on which side of + // the monitor is likely to contain an auto-hide appbar, so + // the missing client area is covered by it. + if (EqualRect(&monitorInfo.rcWork, &monitorInfo.rcMonitor)) { + APPBARDATA abd; + SecureZeroMemory(&abd, sizeof(abd)); + abd.cbSize = sizeof(abd); + const UINT taskbarState = + m_lpSHAppBarMessage(ABM_GETSTATE, &abd); + if (taskbarState & ABS_AUTOHIDE) { + int edge = -1; + abd.hWnd = + FindWindowW(L"Shell_TrayWnd", nullptr); + if (abd.hWnd) { + const HMONITOR taskbarMonitor = + MonitorFromWindow( + abd.hWnd, MONITOR_DEFAULTTOPRIMARY); + if (taskbarMonitor && + (taskbarMonitor == windowMonitor)) { + m_lpSHAppBarMessage(ABM_GETTASKBARPOS, + &abd); + edge = abd.uEdge; } } + if (edge == ABE_BOTTOM) { + rect.bottom--; + } else if (edge == ABE_LEFT) { + rect.left++; + } else if (edge == ABE_TOP) { + rect.top++; + } else if (edge == ABE_RIGHT) { + rect.right--; + } } } } + // If the wParam parameter is FALSE, the application should return zero. + // If wParam is TRUE and an application returns zero, the old client area is preserved and is aligned with the upper-left corner of the new client area. // "*result = 0" removes the window frame (including the titlebar). - // But the frame shadow is lost at the same time. We'll bring it - // back later. // Don't use "*result = WVR_REDRAW", although it can also remove // the window frame, it will cause child widgets have strange behaviors. // "*result = 0" means we have processed this message and let Windows @@ -473,13 +396,6 @@ bool WinNativeEventFilter::nativeEventFilter(const QByteArray &eventType, *result = 0; return true; } - case WM_DWMCOMPOSITIONCHANGED: { - // Bring the frame shadow back through DWM. - // Don't paint the shadow manually using QPainter or QGraphicsEffect. - handleDwmCompositionChanged(data); - *result = 0; - return true; - } case WM_NCUAHDRAWCAPTION: case WM_NCUAHDRAWFRAME: { // These undocumented messages are sent to draw themed window @@ -506,6 +422,11 @@ bool WinNativeEventFilter::nativeEventFilter(const QByteArray &eventType, return true; } case WM_NCHITTEST: { + if (data->windowData.mouseTransparent) { + // Mouse events will be passed to the parent window. + *result = HTTRANSPARENT; + return true; + } const auto getHTResult = [](HWND _hWnd, LPARAM _lParam, const WINDOW *_data) -> LRESULT { const auto isInSpecificAreas = [](int x, int y, @@ -552,36 +473,47 @@ bool WinNativeEventFilter::nativeEventFilter(const QByteArray &eventType, } return HTCLIENT; } - const bool isTop = isInsideWindow && (mouse.y < bh); - const bool isBottom = isInsideWindow && (mouse.y > (wh - bh)); - const bool isLeft = isInsideWindow && (mouse.x < bw); - const bool isRight = isInsideWindow && (mouse.x > (ww - bw)); - if (isTop) { + if (_data->windowData.fixedSize) { + if (isTitlebar) { + return HTCAPTION; + } else { + // Un-resizeable border. + return HTBORDER; + } + } else { + const bool isTop = isInsideWindow && (mouse.y < bh); + const bool isBottom = isInsideWindow && (mouse.y > (wh - bh)); + // Make the border wider to let the user easy to resize on corners. + const int factor = (isTop || isBottom) ? 2 : 1; + const bool isLeft = isInsideWindow && (mouse.x < (bw * factor)); + const bool isRight = isInsideWindow && (mouse.x > (ww - (bw * factor))); + if (isTop) { + if (isLeft) { + return HTTOPLEFT; + } + if (isRight) { + return HTTOPRIGHT; + } + return HTTOP; + } + if (isBottom) { + if (isLeft) { + return HTBOTTOMLEFT; + } + if (isRight) { + return HTBOTTOMRIGHT; + } + return HTBOTTOM; + } if (isLeft) { - return HTTOPLEFT; + return HTLEFT; } if (isRight) { - return HTTOPRIGHT; + return HTRIGHT; } - return HTTOP; - } - if (isBottom) { - if (isLeft) { - return HTBOTTOMLEFT; + if (isTitlebar) { + return HTCAPTION; } - if (isRight) { - return HTBOTTOMRIGHT; - } - return HTBOTTOM; - } - if (isLeft) { - return HTLEFT; - } - if (isRight) { - return HTRIGHT; - } - if (isTitlebar) { - return HTCAPTION; } return HTCLIENT; }; @@ -592,83 +524,78 @@ bool WinNativeEventFilter::nativeEventFilter(const QByteArray &eventType, // Don't cover the taskbar when maximized. const HMONITOR monitor = MonitorFromWindow(msg->hwnd, MONITOR_DEFAULTTONEAREST); - if (monitor) { - MONITORINFO monitorInfo; - SecureZeroMemory(&monitorInfo, sizeof(monitorInfo)); - monitorInfo.cbSize = sizeof(monitorInfo); - GetMonitorInfoW(monitor, &monitorInfo); - const RECT rcWorkArea = monitorInfo.rcWork; - const RECT rcMonitorArea = monitorInfo.rcMonitor; - auto &mmi = *reinterpret_cast(msg->lParam); - if (QOperatingSystemVersion::current() < - QOperatingSystemVersion::Windows8) { - // FIXME: Buggy on Windows 7: - // The origin of coordinates is the top left edge of the - // monitor's work area. Why? It should be the top left edge of - // the monitor's area. - mmi.ptMaxPosition.x = rcMonitorArea.left; - mmi.ptMaxPosition.y = rcMonitorArea.top; - } else { - // Works fine on Windows 8/8.1/10 - mmi.ptMaxPosition.x = - std::abs(rcWorkArea.left - rcMonitorArea.left); - mmi.ptMaxPosition.y = std::abs(rcWorkArea.top - rcMonitorArea.top); - } - if (data->windowData.maximumSize.isEmpty()) { - mmi.ptMaxSize.x = std::abs(rcWorkArea.right - rcWorkArea.left); - mmi.ptMaxSize.y = std::abs(rcWorkArea.bottom - rcWorkArea.top); - } else { - mmi.ptMaxSize.x = std::round(getDevicePixelRatioForWindow(msg->hwnd) * - data->windowData.maximumSize.width()); - mmi.ptMaxSize.y = std::round(getDevicePixelRatioForWindow(msg->hwnd) * - data->windowData.maximumSize.height()); - } - mmi.ptMaxTrackSize.x = mmi.ptMaxSize.x; - mmi.ptMaxTrackSize.y = mmi.ptMaxSize.y; - if (!data->windowData.minimumSize.isEmpty()) { - mmi.ptMinTrackSize.x = std::round(getDevicePixelRatioForWindow(msg->hwnd) * - data->windowData.minimumSize.width()); - mmi.ptMinTrackSize.y = std::round(getDevicePixelRatioForWindow(msg->hwnd) * - data->windowData.minimumSize.height()); - } - *result = 0; - return true; + MONITORINFO monitorInfo; + SecureZeroMemory(&monitorInfo, sizeof(monitorInfo)); + monitorInfo.cbSize = sizeof(monitorInfo); + GetMonitorInfoW(monitor, &monitorInfo); + const RECT rcWorkArea = monitorInfo.rcWork; + const RECT rcMonitorArea = monitorInfo.rcMonitor; + auto &mmi = *reinterpret_cast(msg->lParam); + if (QOperatingSystemVersion::current() < + QOperatingSystemVersion::Windows8) { + // FIXME: Buggy on Windows 7: + // The origin of coordinates is the top left edge of the + // monitor's work area. Why? It should be the top left edge of + // the monitor's area. + mmi.ptMaxPosition.x = rcMonitorArea.left; + mmi.ptMaxPosition.y = rcMonitorArea.top; + } else { + // Works fine on Windows 8/8.1/10 + mmi.ptMaxPosition.x = + std::abs(rcWorkArea.left - rcMonitorArea.left); + mmi.ptMaxPosition.y = std::abs(rcWorkArea.top - rcMonitorArea.top); } - break; + if (data->windowData.maximumSize.isEmpty()) { + mmi.ptMaxSize.x = std::abs(rcWorkArea.right - rcWorkArea.left); + mmi.ptMaxSize.y = std::abs(rcWorkArea.bottom - rcWorkArea.top); + } else { + mmi.ptMaxSize.x = std::round(getDevicePixelRatioForWindow(msg->hwnd) * + data->windowData.maximumSize.width()); + mmi.ptMaxSize.y = std::round(getDevicePixelRatioForWindow(msg->hwnd) * + data->windowData.maximumSize.height()); + } + mmi.ptMaxTrackSize.x = mmi.ptMaxSize.x; + mmi.ptMaxTrackSize.y = mmi.ptMaxSize.y; + if (!data->windowData.minimumSize.isEmpty()) { + mmi.ptMinTrackSize.x = std::round(getDevicePixelRatioForWindow(msg->hwnd) * + data->windowData.minimumSize.width()); + mmi.ptMinTrackSize.y = std::round(getDevicePixelRatioForWindow(msg->hwnd) * + data->windowData.minimumSize.height()); + } + *result = 0; + return true; } case WM_SETICON: case WM_SETTEXT: { // Disable painting while these messages are handled to prevent them // from drawing a window caption over the client area, but only when - // composition and theming are disabled. These messages don't paint + // composition is disabled. These messages don't paint // when composition is enabled and blocking WM_NCUAHDRAWCAPTION should // be enough to prevent painting when theming is enabled. - if (!data->dwmCompositionEnabled && !data->themeEnabled) { + if (!data->dwmCompositionEnabled) { const LONG_PTR oldStyle = GetWindowLongPtrW(msg->hwnd, GWL_STYLE); // Prevent Windows from drawing the default title bar by temporarily // toggling the WS_VISIBLE style. SetWindowLongPtrW(msg->hwnd, GWL_STYLE, oldStyle & ~WS_VISIBLE); - refreshWindow(msg->hwnd); const LRESULT ret = DefWindowProcW(msg->hwnd, msg->message, msg->wParam, msg->lParam); SetWindowLongPtrW(msg->hwnd, GWL_STYLE, oldStyle); - refreshWindow(msg->hwnd); *result = ret; return true; } break; } - case WM_THEMECHANGED: { - handleThemeChanged(data); + case WM_DWMCOMPOSITIONCHANGED: { + handleDwmCompositionChanged(data); break; } + case WM_THEMECHANGED: + case WM_WINDOWPOSCHANGING: case WM_WINDOWPOSCHANGED: { - // The client area of our window equals to it's non-client area now (we - // achieve this in WM_NCCALCSIZE), so the following line will repaint - // the whole window. - InvalidateRect(msg->hwnd, nullptr, TRUE); - // Don't return true here because it will block Qt's paint events - // and it'll result in a never updated window. + const HWND _hWnd = msg->hwnd; + QTimer::singleShot(50, [this, _hWnd](){ + redrawWindow(_hWnd); + }); break; } case WM_DPICHANGED: { @@ -690,7 +617,7 @@ bool WinNativeEventFilter::nativeEventFilter(const QByteArray &eventType, // We can intercept Qt's handling of this message and resize the window // ourself, but after reading Qt's source code, I found that Qt does // more than resizing, so it's safer to let Qt do the scaling. - QTimer::singleShot(50, [_hWnd]() { + QTimer::singleShot(50, [this, _hWnd]() { RECT rect = {0, 0, 0, 0}; GetWindowRect(_hWnd, &rect); const int x = rect.left; @@ -701,10 +628,10 @@ bool WinNativeEventFilter::nativeEventFilter(const QByteArray &eventType, // too obvious for the user and the experience is not good. MoveWindow(_hWnd, x, y, width + 1, height + 1, TRUE); // Re-paint the window after resizing. - refreshWindow(_hWnd); + redrawWindow(_hWnd); // Restore and repaint. MoveWindow(_hWnd, x, y, width, height, TRUE); - refreshWindow(_hWnd); + redrawWindow(_hWnd); }); break; } @@ -715,131 +642,24 @@ bool WinNativeEventFilter::nativeEventFilter(const QByteArray &eventType, return false; } -void WinNativeEventFilter::init(WINDOW *data) { - // Make sure we don't init the same window twice. - data->inited = TRUE; - // Make sure our window is a normal application window, we'll remove the - // window frame later in Win32 events, don't use WS_POPUP to do this. - SetWindowLongPtrW(data->hWnd, GWL_STYLE, - WS_OVERLAPPEDWINDOW | WS_CLIPCHILDREN | WS_CLIPSIBLINGS); - // Make our window a layered window to get better performance. It's also - // needed to remove the three system buttons (minimize, maximize and close) - // with the help of the next line. - SetWindowLongPtrW(data->hWnd, GWL_EXSTYLE, WS_EX_APPWINDOW | WS_EX_LAYERED); - SetLayeredWindowAttributes(data->hWnd, RGB(255, 0, 255), 0, LWA_COLORKEY); - // Make sure our window has the frame shadow. - // According to MSDN, SetWindowLong won't take effect unless we trigger a - // frame change event manually, we will do it inside the - // handleDwmCompositionChanged function, so it's not necessary to do it - // here. - handleDwmCompositionChanged(data); - handleThemeChanged(data); - // For debug purposes. - qDebug().noquote() << "Window handle:" << data->hWnd; - qDebug().noquote() << "Window DPI:" << getDotsPerInchForWindow(data->hWnd) - << "Window DPR:" - << getDevicePixelRatioForWindow(data->hWnd); - qDebug().noquote() << "Window border width:" << borderWidth(data->hWnd) - << "Window border height:" << borderHeight(data->hWnd) - << "Window titlebar height:" - << titlebarHeight(data->hWnd); -} - void WinNativeEventFilter::handleDwmCompositionChanged(WINDOW *data) { BOOL enabled = FALSE; - if (m_DwmIsCompositionEnabled) { - m_DwmIsCompositionEnabled(&enabled); - } + m_lpDwmIsCompositionEnabled(&enabled); data->dwmCompositionEnabled = enabled; - // We should not draw the frame shadow if DWM composition is disabled, in - // other words, a window should not have frame shadow when Windows Aero is - // not enabled. - // Note that, start from Win8, the DWM composition is always enabled and - // can't be disabled. if (enabled) { - if (m_DwmSetWindowAttribute) { - // The frame shadow is drawn on the non-client area and thus we have - // to make sure the non-client area rendering is enabled first. - const DWMNCRENDERINGPOLICY ncrp = DWMNCRP_ENABLED; - m_DwmSetWindowAttribute(data->hWnd, DWMWA_NCRENDERING_POLICY, &ncrp, - sizeof(ncrp)); - } - if (m_DwmExtendFrameIntoClientArea) { - // Negative margins have special meaning to - // DwmExtendFrameIntoClientArea. Negative margins create the "sheet - // of glass" effect, where the client area is rendered as a solid - // surface with no window border. - const MARGINS margins = {-1, -1, -1, -1}; - m_DwmExtendFrameIntoClientArea(data->hWnd, &margins); - } - } - if (m_SetWindowThemeAttribute) { - WTA_OPTIONS options; - options.dwFlags = WTNCA_NODRAWCAPTION | WTNCA_NODRAWICON; - options.dwMask = options.dwFlags; - // This is the official way to hide the window caption text and window - // icon. - m_SetWindowThemeAttribute(data->hWnd, WTA_NONCLIENT, &options, - sizeof(options)); - } - handleBlurForWindow(data); - refreshWindow(data->hWnd); -} - -void WinNativeEventFilter::handleThemeChanged(WINDOW *data) { - data->themeEnabled = m_IsThemeActive ? m_IsThemeActive() : FALSE; -} - -void WinNativeEventFilter::handleBlurForWindow(const WINDOW *data) { - if ((QOperatingSystemVersion::current() < - QOperatingSystemVersion::Windows7) || - !(data->dwmCompositionEnabled && data->windowData.blurEnabled)) { - return; - } - // We prefer using DWM blur on Windows 7 because it has better appearance. - // It's supported on Windows Vista as well actually, but Qt has drop it, so - // we won't do it for Vista. - if (QOperatingSystemVersion::current() < - QOperatingSystemVersion::Windows8) { - if (m_DwmEnableBlurBehindWindow) { - // Windows Aero - DWM_BLURBEHIND dwmbb; - dwmbb.dwFlags = DWM_BB_ENABLE; - dwmbb.fEnable = TRUE; - dwmbb.hRgnBlur = nullptr; - dwmbb.fTransitionOnMaximized = FALSE; - m_DwmEnableBlurBehindWindow(data->hWnd, &dwmbb); - } - } else if (m_SetWindowCompositionAttribute) { - ACCENT_POLICY accentPolicy; - accentPolicy.AccentFlags = 0; - // GradientColor only has effect when using with acrylic, so we can set - // it to zero in most cases. It's an AGBR unsigned int, for example, use - // 0xCC000000 for dark blur behind background. - accentPolicy.GradientColor = 0; - accentPolicy.AnimationId = 0; - WINDOWCOMPOSITIONATTRIBDATA attribData; - attribData.dwAttribute = WCA_ACCENT_POLICY; - attribData.pvAttribute = &accentPolicy; - attribData.cbAttribute = sizeof(accentPolicy); - // Windows 10, version 1709 (10.0.16299) - if (QOperatingSystemVersion::current() >= - QOperatingSystemVersion(QOperatingSystemVersion::Windows, 10, 0, - 16299)) { - // Acrylic (Will also blur but is completely different with - // Windows Aero) - accentPolicy.AccentState = ACCENT_ENABLE_ACRYLICBLURBEHIND; - } else if (QOperatingSystemVersion::current() >= - QOperatingSystemVersion::Windows10) { - // Blur (Something like Windows Aero in Windows 7) - accentPolicy.AccentState = ACCENT_ENABLE_BLURBEHIND; - } else if (QOperatingSystemVersion::current() >= - QOperatingSystemVersion::Windows8) { - // Transparent gradient color - accentPolicy.AccentState = ACCENT_ENABLE_TRANSPARENTGRADIENT; - } - m_SetWindowCompositionAttribute(data->hWnd, &attribData); + // The frame shadow is drawn on the non-client area and thus we have + // to make sure the non-client area rendering is enabled first. + const DWMNCRENDERINGPOLICY ncrp = DWMNCRP_ENABLED; + m_lpDwmSetWindowAttribute(data->hWnd, DWMWA_NCRENDERING_POLICY, &ncrp, + sizeof(ncrp)); + // Negative margins have special meaning to + // DwmExtendFrameIntoClientArea. Negative margins create the "sheet + // of glass" effect, where the client area is rendered as a solid + // surface with no window border. + const MARGINS margins = {-1, -1, -1, -1}; + m_lpDwmExtendFrameIntoClientArea(data->hWnd, &margins); } + redrawWindow(data->hWnd); } UINT WinNativeEventFilter::getDotsPerInchForWindow(HWND handle) { @@ -859,9 +679,9 @@ UINT WinNativeEventFilter::getDotsPerInchForWindow(HWND handle) { #endif // Available since Windows 2000. const HDC hdc = GetDC(nullptr); - if (hdc && m_GetDeviceCaps) { - const int dpiX = m_GetDeviceCaps(hdc, LOGPIXELSX); - const int dpiY = m_GetDeviceCaps(hdc, LOGPIXELSY); + if (hdc) { + const int dpiX = m_lpGetDeviceCaps(hdc, LOGPIXELSX); + const int dpiY = m_lpGetDeviceCaps(hdc, LOGPIXELSY); ReleaseDC(nullptr, hdc); // The values of dpiX and dpiY are identical actually, just to // silence a compiler warning. @@ -870,20 +690,20 @@ UINT WinNativeEventFilter::getDotsPerInchForWindow(HWND handle) { return defaultValue; }; if (!handle) { - if (m_GetSystemDpiForProcess) { - return m_GetSystemDpiForProcess(GetCurrentProcess()); - } else if (m_GetDpiForSystem) { - return m_GetDpiForSystem(); + if (m_lpGetSystemDpiForProcess) { + return m_lpGetSystemDpiForProcess(GetCurrentProcess()); + } else if (m_lpGetDpiForSystem) { + return m_lpGetDpiForSystem(); } else { return getScreenDpi(m_defaultDotsPerInch); } } - if (m_GetDpiForWindow) { - return m_GetDpiForWindow(handle); + if (m_lpGetDpiForWindow) { + return m_lpGetDpiForWindow(handle); } - if (m_GetDpiForMonitor) { + if (m_lpGetDpiForMonitor) { UINT dpiX = m_defaultDotsPerInch, dpiY = m_defaultDotsPerInch; - m_GetDpiForMonitor(MonitorFromWindow(handle, MONITOR_DEFAULTTONEAREST), + m_lpGetDpiForMonitor(MonitorFromWindow(handle, MONITOR_DEFAULTTONEAREST), MDT_EFFECTIVE_DPI, &dpiX, &dpiY); // The values of *dpiX and *dpiY are identical. return dpiX; @@ -899,8 +719,8 @@ qreal WinNativeEventFilter::getDevicePixelRatioForWindow(HWND handle) { } int WinNativeEventFilter::getSystemMetricsForWindow(HWND handle, int index) { - if (m_GetSystemMetricsForDpi) { - return m_GetSystemMetricsForDpi(index, + if (m_lpGetSystemMetricsForDpi) { + return m_lpGetSystemMetricsForDpi(index, static_cast(std::round(getPreferedNumber( getDotsPerInchForWindow(handle))))); } else { @@ -911,7 +731,6 @@ int WinNativeEventFilter::getSystemMetricsForWindow(HWND handle, int index) { void WinNativeEventFilter::setWindowData(HWND window, const WINDOWDATA *data) { if (window && data) { createUserData(window, data); - refreshWindow(window); } } @@ -932,11 +751,6 @@ void WinNativeEventFilter::createUserData(HWND handle, const WINDOWDATA *data) { GetWindowLongPtrW(handle, GWLP_USERDATA)); if (userData) { if (data) { - if (userData->windowData.blurEnabled != data->blurEnabled) { - qDebug().noquote() - << "Due to technical issue, you can only enable or " - "disable blur before the window is shown."; - } userData->windowData = *data; } } else { @@ -947,74 +761,19 @@ void WinNativeEventFilter::createUserData(HWND handle, const WINDOWDATA *data) { } SetWindowLongPtrW(handle, GWLP_USERDATA, reinterpret_cast(_data)); - refreshWindow(handle); } } } -void WinNativeEventFilter::refreshWindow(HWND handle) { - if (handle) { - // SWP_FRAMECHANGED: Applies new frame styles set using the - // SetWindowLong function. Sends a WM_NCCALCSIZE message to the window, - // even if the window's size is not being changed. - // SWP_NOACTIVATE: Does not activate the window. If this flag is not - // set, the window is activated and moved to the top of either the - // topmost or non-topmost group (depending on the setting of the - // hWndInsertAfter parameter). - // SWP_NOSIZE: Retains the current size (ignores the cx and cy - // parameters). - // SWP_NOMOVE: Retains the current position (ignores X and Y - // parameters). - // SWP_NOZORDER: Retains the current Z order (ignores the - // hWndInsertAfter parameter). - // SWP_NOOWNERZORDER: Does not change the owner window's position - // in the Z order. - // We will remove the window frame (including the three system buttons) - // when we are processing the WM_NCCALCSIZE message, so we want to - // trigger this message as early as possible to let it happen. - SetWindowPos(handle, nullptr, 0, 0, 0, 0, - SWP_FRAMECHANGED | SWP_NOACTIVATE | SWP_NOSIZE | - SWP_NOMOVE | SWP_NOZORDER | SWP_NOOWNERZORDER); -#if 0 - // Inform the window to adjust it's size to let it's contents fit the - // adjusted window. - RECT rect = {0, 0, 0, 0}; - GetWindowRect(handle, &rect); - const int width = std::abs(rect.right - rect.left); - const int height = std::abs(rect.bottom - rect.top); - SendMessageW(handle, WM_SIZE, SIZE_RESTORED, MAKELPARAM(width, height)); -#endif - // The InvalidateRect function adds a rectangle to the specified - // window's update region. The update region represents the portion of - // the window's client area that must be redrawn. If lpRect is NULL, the - // entire client area is added to the update region. - InvalidateRect(handle, nullptr, TRUE); - // The UpdateWindow function updates the client area of the specified - // window by sending a WM_PAINT message to the window if the window's - // update region is not empty. The function sends a WM_PAINT message - // directly to the window procedure of the specified window, bypassing - // the application queue. If the update region is empty, no message is - // sent. - UpdateWindow(handle); - } -} - void WinNativeEventFilter::initWin32Api() { // Available since Windows 2000. WNEF_RESOLVE_WINAPI(Gdi32, GetDeviceCaps) // Available since Windows XP. WNEF_RESOLVE_WINAPI(Shell32, SHAppBarMessage) // Available since Windows Vista. - WNEF_RESOLVE_WINAPI(UxTheme, SetWindowThemeAttribute) - WNEF_RESOLVE_WINAPI(UxTheme, IsThemeActive) WNEF_RESOLVE_WINAPI(Dwmapi, DwmIsCompositionEnabled) WNEF_RESOLVE_WINAPI(Dwmapi, DwmExtendFrameIntoClientArea) WNEF_RESOLVE_WINAPI(Dwmapi, DwmSetWindowAttribute) - WNEF_RESOLVE_WINAPI(Dwmapi, DwmEnableBlurBehindWindow) - if (QOperatingSystemVersion::current() >= - QOperatingSystemVersion::Windows7) { - WNEF_RESOLVE_WINAPI(User32, SetWindowCompositionAttribute) - } if (QOperatingSystemVersion::current() >= QOperatingSystemVersion::Windows8_1) { WNEF_RESOLVE_WINAPI(SHCore, GetDpiForMonitor) @@ -1105,3 +864,12 @@ qreal WinNativeEventFilter::getPreferedNumber(qreal num) { #endif return result; } + +void WinNativeEventFilter::redrawWindow(HWND handle) +{ + if (handle) { + RedrawWindow(handle, nullptr, nullptr, + RDW_INVALIDATE | RDW_NOERASE | RDW_NOINTERNALPAINT | + RDW_ERASENOW | RDW_ALLCHILDREN); + } +} diff --git a/winnativeeventfilter.h b/winnativeeventfilter.h index d656203..bdc61ca 100644 --- a/winnativeeventfilter.h +++ b/winnativeeventfilter.h @@ -38,7 +38,7 @@ class WinNativeEventFilter : public QAbstractNativeEventFilter { public: using WINDOWDATA = struct _WINDOWDATA { - BOOL blurEnabled = FALSE; + BOOL fixedSize = FALSE, mouseTransparent = FALSE; int borderWidth = -1, borderHeight = -1, titlebarHeight = -1; QVector ignoreAreas, draggableAreas; QSize maximumSize = {-1, -1}, minimumSize = {-1, -1}; @@ -46,8 +46,7 @@ public: using WINDOW = struct _WINDOW { HWND hWnd = nullptr; - BOOL dwmCompositionEnabled = FALSE, themeEnabled = FALSE, - inited = FALSE; + BOOL dwmCompositionEnabled = FALSE, initialized = FALSE; WINDOWDATA windowData; }; @@ -101,13 +100,10 @@ public: #endif private: - void init(WINDOW *data); void initWin32Api(); + void redrawWindow(HWND handle); static void createUserData(HWND handle, const WINDOWDATA *data = nullptr); void handleDwmCompositionChanged(WINDOW *data); - void handleThemeChanged(WINDOW *data); - void handleBlurForWindow(const WINDOW *data); - static void refreshWindow(HWND handle); static qreal getPreferedNumber(qreal num); static UINT getDotsPerInchForWindow(HWND handle); static qreal getDevicePixelRatioForWindow(HWND handle);