From f7968b0aac982facc44d9d26b97709f154187139 Mon Sep 17 00:00:00 2001 From: Yuhang Zhao <2546789017@qq.com> Date: Mon, 30 Mar 2020 20:35:33 +0800 Subject: [PATCH] Update. Signed-off-by: Yuhang Zhao <2546789017@qq.com> --- README.md | 5 +- framelesswidget.pro | 2 +- winnativeeventfilter.cpp | 483 ++++++++++++++++++--------------------- winnativeeventfilter.h | 33 ++- 4 files changed, 239 insertions(+), 284 deletions(-) diff --git a/README.md b/README.md index 47c2283..3651bff 100644 --- a/README.md +++ b/README.md @@ -10,13 +10,12 @@ Copy *winnativeeventfilter.h* and *winnativeeventfilter.cpp* to your project and - Don't change the window flags (for example, enable the Qt::FramelessWindowHint flag) because it will break the functionality of this code. I'll get rid of the window frame (including the titlebar of course) in Win32 native events. - All traditional Win32 APIs are replaced by their DPI-aware ones, if there is one. - Start from Windows 10, normal windows usually have a one pixel width border line, I don't add it because not everyone like it. You can draw one manually if you really need it. -- The frame shadow will get lost if the window is full transparent. It can't be solved unless you draw the frame shadow manually. +- The frame shadow will get lost if the window is totally transparent. It can't be solved unless you draw the frame shadow manually. - There are some known issues when you draw something manually on widgets using Direct3D or some other graphic frameworks. Don't know why currently. But things work fine if you let Qt do all the job, including painting. - On Windows 7, if you disabled the Windows Aero, the frame shadow will be disabled as well because it's DWM's resposibility to draw the frame shadow. - Only top level windows can be frameless. Applying this code to child windows or widgets will result in unexpected behavior. -- The border width (8 if not scaled), border height (8 if not scaled) and titlebar height (38 if not scaled) are acquired by Win32 APIs and are the same with other standard windows, and thus you should not modify them. +- The border width (8 if not scaled), border height (8 if not scaled) and titlebar height (30/38 if not scaled) are acquired by Win32 APIs and are the same with other standard windows, and thus you should not modify them. - You can also copy all the code to `[virtual protected] bool QWidget::nativeEvent(const QByteArray &eventType, void *message, long *result)` or `[virtual protected] bool QWindow::nativeEvent(const QByteArray &eventType, void *message, long *result)`, it's the same with install a native event filter to the application. -- If you find the three system buttons (minimize, maximize and close) show up when resizing the window rapidly, copy all the code to the `nativeEvent` function of your widget or window, and they will not appear anymore. Don't know why currently. - For UNIX platforms, things are much easier. Just use the `startSystemMove` and `startSystemResize` APIs introduced in Qt 5.15. ## References diff --git a/framelesswidget.pro b/framelesswidget.pro index a6be555..a440a4c 100644 --- a/framelesswidget.pro +++ b/framelesswidget.pro @@ -5,7 +5,7 @@ CONFIG += c++17 strict_c++ utf8_source warn_on win32 { CONFIG -= embed_manifest_exe RC_FILE = resources.rc - LIBS += -luser32 -lshell32 -lgdi32 -ldwmapi + LIBS += -luser32 -lshell32 -lgdi32 -ldwmapi -luxtheme -ld2d1 } DEFINES += QT_NO_CAST_FROM_ASCII QT_NO_CAST_TO_ASCII HEADERS += winnativeeventfilter.h diff --git a/winnativeeventfilter.cpp b/winnativeeventfilter.cpp index a71fd10..b10dc6b 100644 --- a/winnativeeventfilter.cpp +++ b/winnativeeventfilter.cpp @@ -4,19 +4,51 @@ #include #include #include +#include +#include #include -#include +#include #include +#ifndef SM_CXPADDEDBORDER +// Only available since Windows Vista +#define SM_CXPADDEDBORDER 92 +#endif + #ifndef WM_NCUAHDRAWCAPTION +// Not documented, only available since Windows Vista #define WM_NCUAHDRAWCAPTION 0x00AE #endif #ifndef WM_NCUAHDRAWFRAME +// Not documented, only available since Windows Vista #define WM_NCUAHDRAWFRAME 0x00AF #endif -static QScopedPointer instance; +#ifndef WM_DWMCOMPOSITIONCHANGED +// Only available since Windows 7 +#define WM_DWMCOMPOSITIONCHANGED 0x031E +#endif + +namespace { + +QScopedPointer instance; + +bool isWindowTopLevel(HWND handle) { + if (!handle) { + return false; + } + const auto wid = reinterpret_cast(handle); + const auto topLevelWindows = QGuiApplication::topLevelWindows(); + for (auto &&window : qAsConst(topLevelWindows)) { + if (window->handle() && (window->winId() == wid)) { + return true; + } + } + return false; +} + +} // namespace WinNativeEventFilter::WinNativeEventFilter() { QLibrary shcoreLib(QString::fromUtf8("SHCore")); @@ -37,16 +69,17 @@ WinNativeEventFilter::WinNativeEventFilter() { m_GetSystemMetricsForDpi = reinterpret_cast( user32Lib.resolve("GetSystemMetricsForDpi")); } -} - -WinNativeEventFilter::~WinNativeEventFilter() { - if (!m_data.isEmpty()) { - for (auto data : m_data) { - delete data; - } + // Windows 10, version 1803 (10.0.17134) + if (QOperatingSystemVersion::current() >= + QOperatingSystemVersion(QOperatingSystemVersion::Windows, 10, 0, + 17134)) { + m_GetSystemDpiForProcess = reinterpret_cast( + user32Lib.resolve("GetSystemDpiForProcess")); } } +WinNativeEventFilter::~WinNativeEventFilter() = default; + void WinNativeEventFilter::setup() { if (instance.isNull()) { instance.reset(new WinNativeEventFilter); @@ -92,29 +125,38 @@ bool WinNativeEventFilter::nativeEventFilter(const QByteArray &eventType, // Anyway, we should skip it in this case. return false; } - if (GetParent(msg->hwnd)) { - // GetParent only returns NULL if the window is a top level window. - // Only top level windows can be frameless, we should never apply our - // code to child windows or widgets. + if (!isWindowTopLevel(msg->hwnd)) { + // Only top level windows can be frameless. return false; } -#if 0 - if (!IsWindowVisible(msg->hwnd)) { - // Skip dummy windows created internally by Qt. - return false; + if (!m_windowData.contains(msg->hwnd)) { + m_windowData.insert(msg->hwnd, qMakePair(FALSE, FALSE)); + init(msg->hwnd); } -#endif - if (!m_data.contains(msg->hwnd)) { - LPWINDOW _data = new WINDOW; - _data->hwnd = msg->hwnd; - m_data.insert(msg->hwnd, _data); - init(_data); - } - const auto data = m_data.value(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)); + break; + } case WM_NCCALCSIZE: { - handleNcCalcSize(data, msg->wParam, msg->lParam); 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; + } + } // This line removes the window frame (including the titlebar). // But the frame shadow is lost at the same time. We'll bring it // back later. @@ -127,7 +169,7 @@ bool WinNativeEventFilter::nativeEventFilter(const QByteArray &eventType, case WM_DWMCOMPOSITIONCHANGED: { // Bring the frame shadow back through DWM. // Don't paint the shadow manually using QPainter or QGraphicsEffect. - handleDwmCompositionChanged(data); + handleDwmCompositionChanged(msg->hwnd); *result = 0; return true; } @@ -140,7 +182,7 @@ bool WinNativeEventFilter::nativeEventFilter(const QByteArray &eventType, return true; } case WM_NCPAINT: { - if (data->compositionEnabled) { + if (m_windowData.value(msg->hwnd).first) { break; } else { // Only block WM_NCPAINT when composition is disabled. If it's @@ -152,273 +194,202 @@ bool WinNativeEventFilter::nativeEventFilter(const QByteArray &eventType, } case WM_NCACTIVATE: { // DefWindowProc won't repaint the window border if lParam (normally - // a HRGN) is -1. This is recommended in: - // https://blogs.msdn.microsoft.com/wpfsdk/2008/09/08/custom-window-chrome-in-wpf/ - *result = DefWindowProcW(data->hwnd, msg->message, msg->wParam, -1); + // a HRGN) is -1. + *result = DefWindowProcW(msg->hwnd, msg->message, msg->wParam, -1); return true; } - case WM_WINDOWPOSCHANGED: { - handleWindowPosChanged(data, msg->lParam); - *result = 0; - // Don't return true here because return true means we'll take over - // the whole event from Qt and thus it will block Qt's paint events and - // the window will not update itself anymore in the end. - break; - } case WM_NCHITTEST: { - *result = handleNcHitTest(data, msg->lParam); + const auto getHTResult = [this, msg]() -> LRESULT { + RECT winRect; + GetWindowRect(msg->hwnd, &winRect); + const LONG ww = qAbs(winRect.right - winRect.left); + const LONG wh = qAbs(winRect.bottom - winRect.top); + POINT mouse = {LONG(GET_X_LPARAM(msg->lParam)), + LONG(GET_Y_LPARAM(msg->lParam))}; + ScreenToClient(msg->hwnd, &mouse); + // These values are DPI-aware. + const LONG bw = borderWidth(msg->hwnd); + const LONG bh = borderHeight(msg->hwnd); + const LONG tbh = titlebarHeight(msg->hwnd); + if (IsMaximized(msg->hwnd)) { + if (mouse.y < tbh) { + return HTCAPTION; + } + return HTCLIENT; + } + if (mouse.y < bh) { + if (mouse.x < bw) { + return HTTOPLEFT; + } + if (mouse.x > (ww - bw)) { + return HTTOPRIGHT; + } + return HTTOP; + } + if (mouse.y > (wh - bh)) { + if (mouse.x < bw) { + return HTBOTTOMLEFT; + } + if (mouse.x > (ww - bw)) { + return HTBOTTOMRIGHT; + } + return HTBOTTOM; + } + if (mouse.x < bw) { + return HTLEFT; + } + if (mouse.x > (ww - bw)) { + return HTRIGHT; + } + if (mouse.y < tbh) { + return HTCAPTION; + } + return HTCLIENT; + }; + *result = getHTResult(); return true; } + case WM_GETMINMAXINFO: { + // 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); + mmi.ptMaxPosition.x = qAbs(rcWorkArea.left - rcMonitorArea.left); + mmi.ptMaxPosition.y = qAbs(rcWorkArea.top - rcMonitorArea.top); + mmi.ptMaxSize.x = qAbs(rcWorkArea.right - rcWorkArea.left); + mmi.ptMaxSize.y = qAbs(rcWorkArea.bottom - rcWorkArea.top); + mmi.ptMaxTrackSize.x = mmi.ptMaxSize.x; + mmi.ptMaxTrackSize.y = mmi.ptMaxSize.y; + *result = 0; + return true; + } + break; + } + 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 + // when composition is enabled and blocking WM_NCUAHDRAWCAPTION should + // be enough to prevent painting when theming is enabled. + if (!m_windowData.value(msg->hwnd).first && + !m_windowData.value(msg->hwnd).second) { + 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); + const LRESULT ret = DefWindowProcW(msg->hwnd, msg->message, + msg->wParam, msg->lParam); + SetWindowLongPtrW(msg->hwnd, GWL_STYLE, oldStyle); + *result = ret; + return true; + } + break; + } + case WM_THEMECHANGED: { + const BOOL dwm = m_windowData.value(msg->hwnd).first; + m_windowData[msg->hwnd] = qMakePair(dwm, IsThemeActive()); + break; + } default: break; } return false; } -void WinNativeEventFilter::init(LPWINDOW data) { +void WinNativeEventFilter::init(HWND handle) { // 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); - SetWindowLongPtrW(data->hwnd, GWL_EXSTYLE, WS_EX_APPWINDOW | WS_EX_LAYERED); + SetWindowLongPtrW(handle, GWL_STYLE, WS_OVERLAPPEDWINDOW); + SetWindowLongPtrW(handle, GWL_EXSTYLE, WS_EX_APPWINDOW | WS_EX_LAYERED); // Make the window a layered window so the legacy GDI API can be used to // draw to it without messing up the area on top of the DWM frame. Note: // This is not necessary if other drawing APIs are used, eg. GDI+, OpenGL, // Direct2D, Direct3D, DirectComposition, etc. - SetLayeredWindowAttributes(data->hwnd, RGB(255, 0, 255), 0, LWA_COLORKEY); + SetLayeredWindowAttributes(handle, RGB(255, 0, 255), 0, LWA_COLORKEY); // Make sure our window has the frame shadow. - handleDwmCompositionChanged(data); + handleDwmCompositionChanged(handle); + // Tell the window to redraw itself. + SetWindowPos(handle, nullptr, 0, 0, 0, 0, + SWP_FRAMECHANGED | SWP_NOACTIVATE | SWP_NOMOVE | + SWP_NOOWNERZORDER | SWP_NOSIZE | SWP_NOZORDER); // For debug purposes. - qDebug().noquote() << "Window handle:" << data->hwnd; - qDebug().noquote() << "Window DPI:" << windowDpi(data->hwnd) - << "Window DPR:" << windowDpr(data->hwnd); - qDebug().noquote() << "Window border width:" << borderWidth(data->hwnd) - << "Window border height:" << borderHeight(data->hwnd) - << "Window titlebar height:" - << titlebarHeight(data->hwnd); + qDebug().noquote() << "Window handle:" << handle; + qDebug().noquote() << "Window DPI:" << windowDpi(handle) + << "Window DPR:" << windowDpr(handle); + qDebug().noquote() << "Window border width:" << borderWidth(handle) + << "Window border height:" << borderHeight(handle) + << "Window titlebar height:" << titlebarHeight(handle); } -void WinNativeEventFilter::handleNcCalcSize(LPWINDOW data, WPARAM wParam, - LPARAM lParam) { - union { - LPARAM lParam; - RECT *rect; - } params; - params.lParam = lParam; - // DefWindowProc must be called in both the maximized and non-maximized - // cases, otherwise tile/cascade windows won't work. - const RECT nonclient = *params.rect; - DefWindowProcW(data->hwnd, WM_NCCALCSIZE, wParam, params.lParam); - const RECT client = *params.rect; - if (IsMaximized(data->hwnd)) { - WINDOWINFO wi; - SecureZeroMemory(&wi, sizeof(wi)); - wi.cbSize = sizeof(wi); - GetWindowInfo(data->hwnd, &wi); - // Maximized windows always have a non-client border that hangs over - // the edge of the screen, so the size proposed by WM_NCCALCSIZE is - // fine. Just adjust the top border to remove the window title. - RECT rect; - rect.left = client.left; - rect.top = nonclient.top + wi.cyWindowBorders; - rect.right = client.right; - rect.bottom = client.bottom; - *params.rect = rect; - const HMONITOR mon = - MonitorFromWindow(data->hwnd, MONITOR_DEFAULTTOPRIMARY); - MONITORINFO mi; - SecureZeroMemory(&mi, sizeof(mi)); - mi.cbSize = sizeof(mi); - GetMonitorInfoW(mon, &mi); - // 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(params.rect, &mi.rcMonitor)) { - APPBARDATA abd; - SecureZeroMemory(&abd, sizeof(abd)); - abd.cbSize = sizeof(abd); - const UINT taskbarState = SHAppBarMessage(ABM_GETSTATE, &abd); - if (taskbarState & ABS_AUTOHIDE) { - UINT edge = -1; - abd.hWnd = FindWindowW(L"Shell_TrayWnd", nullptr); - if (abd.hWnd) { - const HMONITOR taskbarMonitor = - MonitorFromWindow(abd.hWnd, MONITOR_DEFAULTTOPRIMARY); - if (taskbarMonitor == mon) { - SHAppBarMessage(ABM_GETTASKBARPOS, &abd); - edge = abd.uEdge; - } - } - if (edge == ABE_BOTTOM) { - params.rect->bottom--; - } else if (edge == ABE_LEFT) { - params.rect->left++; - } else if (edge == ABE_TOP) { - params.rect->top++; - } else if (edge == ABE_RIGHT) { - params.rect->right--; - } - } - } - } else { - // For the non-maximized case, set the output RECT to what it was - // before WM_NCCALCSIZE modified it. This will make the client size - // the same as the non-client size. - *params.rect = nonclient; - } -} - -void WinNativeEventFilter::updateRegion(LPWINDOW data) { - const RECT old_rgn = data->region; - const RECT null_rgn = {0, 0, 0, 0}; - if (IsMaximized(data->hwnd)) { - WINDOWINFO wi; - SecureZeroMemory(&wi, sizeof(wi)); - wi.cbSize = sizeof(wi); - GetWindowInfo(data->hwnd, &wi); - // For maximized windows, a region is needed to cut off the - // non-client borders that hang over the edge of the screen. - data->region.left = wi.rcClient.left - wi.rcWindow.left; - data->region.top = wi.rcClient.top - wi.rcWindow.top; - data->region.right = wi.rcClient.right - wi.rcWindow.left; - data->region.bottom = wi.rcClient.bottom - wi.rcWindow.top; - } else if (!data->compositionEnabled) { - // For ordinary themed windows when composition is disabled, a - // region is needed to remove the rounded top corners. Make it as - // large as possible to avoid having to change it when the window is - // resized. - data->region.left = 0; - data->region.top = 0; - data->region.right = 32767; - data->region.bottom = 32767; - } else { - // Don't mess with the region when composition is enabled and the - // window is not maximized, otherwise it will lose its shadow. - data->region = null_rgn; - } - // Avoid unnecessarily updating the region to avoid unnecessary redraws. - if (EqualRect(&data->region, &old_rgn)) { - return; - } - // Treat empty regions as NULL regions. - if (EqualRect(&data->region, &null_rgn)) { - SetWindowRgn(data->hwnd, nullptr, TRUE); - } else { - SetWindowRgn(data->hwnd, CreateRectRgnIndirect(&data->region), TRUE); - } -} - -void WinNativeEventFilter::handleDwmCompositionChanged(LPWINDOW data) { +void WinNativeEventFilter::handleDwmCompositionChanged(HWND handle) { BOOL enabled = FALSE; DwmIsCompositionEnabled(&enabled); - data->compositionEnabled = enabled; + const BOOL theme = m_windowData.value(handle).second; + m_windowData[handle] = qMakePair(enabled, theme); + WTA_OPTIONS options; + options.dwMask = WTNCA_NODRAWCAPTION | WTNCA_NODRAWICON; // 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) { + options.dwFlags = WTNCA_NODRAWCAPTION | WTNCA_NODRAWICON; // 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; - DwmSetWindowAttribute(data->hwnd, DWMWA_NCRENDERING_POLICY, &ncrp, + DwmSetWindowAttribute(handle, 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}; - DwmExtendFrameIntoClientArea(data->hwnd, &margins); + DwmExtendFrameIntoClientArea(handle, &margins); } - updateRegion(data); -} - -void WinNativeEventFilter::handleWindowPosChanged(LPWINDOW data, - LPARAM lParam) { - RECT client; - GetClientRect(data->hwnd, &client); - const UINT old_width = data->width; - const UINT old_height = data->height; - data->width = client.right; - data->height = client.bottom; - const bool client_changed = - (data->width != old_width) || (data->height != old_height); - if (client_changed || - (reinterpret_cast(lParam)->flags & - SWP_FRAMECHANGED)) { - updateRegion(data); - } - if (client_changed) { - // Invalidate the changed parts of the rectangle drawn in WM_PAINT. - RECT rect; - if (data->width > old_width) { - rect = {LONG(old_width - 1), 0, LONG(old_width), LONG(old_height)}; - } else { - rect = {LONG(data->width - 1), 0, LONG(data->width), - LONG(data->height)}; - } - if (data->height > old_height) { - rect = {0, LONG(old_height - 1), LONG(old_width), LONG(old_height)}; - } else { - rect = {0, LONG(data->height - 1), LONG(data->width), - LONG(data->height)}; - } - InvalidateRect(data->hwnd, &rect, TRUE); - } -} - -LRESULT WinNativeEventFilter::handleNcHitTest(LPWINDOW data, LPARAM lParam) { - POINT mouse = {GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam)}; - ScreenToClient(data->hwnd, &mouse); - // These values are DPI-aware. - const UINT border_width = borderWidth(data->hwnd); - const UINT border_height = borderHeight(data->hwnd); - const UINT titlebar_height = titlebarHeight(data->hwnd); - if (IsMaximized(data->hwnd)) { - if (mouse.y < LONG(titlebar_height)) { - return HTCAPTION; - } - return HTCLIENT; - } - if (mouse.y < LONG(border_height)) { - if (mouse.x < LONG(border_width)) { - return HTTOPLEFT; - } - if (mouse.x > LONG(data->width - border_width)) { - return HTTOPRIGHT; - } - return HTTOP; - } - if (mouse.y > LONG(data->height - border_height)) { - if (mouse.x < LONG(border_width)) { - return HTBOTTOMLEFT; - } - if (mouse.x > LONG(data->width - border_width)) { - return HTBOTTOMRIGHT; - } - return HTBOTTOM; - } - if (mouse.x < LONG(border_width)) { - return HTLEFT; - } - if (mouse.x > LONG(data->width - border_width)) { - return HTRIGHT; - } - if (mouse.y < LONG(titlebar_height)) { - return HTCAPTION; - } - return HTCLIENT; + SetWindowThemeAttribute(handle, WTA_NONCLIENT, &options, sizeof(options)); } UINT WinNativeEventFilter::getDpiForWindow(HWND handle) const { + const auto getScreenDpi = [this]() -> UINT { + // Available since Windows 7. + ID2D1Factory *m_pDirect2dFactory = nullptr; + if (SUCCEEDED(D2D1CreateFactory(D2D1_FACTORY_TYPE_SINGLE_THREADED, + &m_pDirect2dFactory)) && + m_pDirect2dFactory) { + m_pDirect2dFactory->ReloadSystemMetrics(); + FLOAT dpiX = m_defaultDPI, dpiY = m_defaultDPI; + m_pDirect2dFactory->GetDesktopDpi(&dpiX, &dpiY); + // The values of *dpiX and *dpiY are identical. + return dpiX; + } + // Available since Windows 2000. + const HDC hdc = GetDC(nullptr); + if (hdc) { + const int dpiX = GetDeviceCaps(hdc, LOGPIXELSX); + const int dpiY = GetDeviceCaps(hdc, LOGPIXELSY); + ReleaseDC(nullptr, hdc); + // The values of dpiX and dpiY are identical actually, just to + // silence a compiler warning. + return dpiX == dpiY ? dpiX : dpiY; + } + return m_defaultDPI; + }; if (!handle) { - if (m_GetDpiForSystem) { + if (m_GetSystemDpiForProcess) { + return m_GetSystemDpiForProcess(GetCurrentProcess()); + } else if (m_GetDpiForSystem) { return m_GetDpiForSystem(); } else { - return m_defaultDPI; + return getScreenDpi(); } } if (m_GetDpiForWindow) { @@ -431,17 +402,7 @@ UINT WinNativeEventFilter::getDpiForWindow(HWND handle) const { // The values of *dpiX and *dpiY are identical. return dpiX; } - // TODO: Is there an elegant way to acquire the system DPI in - // Win7/8/10(before 1607)? - const HDC hdc = GetDC(nullptr); - if (hdc) { - const int dpiX = GetDeviceCaps(hdc, LOGPIXELSX); - const int dpiY = GetDeviceCaps(hdc, LOGPIXELSY); - ReleaseDC(nullptr, hdc); - // Not necessary, just silence a compiler warning. - return dpiX == dpiY ? dpiX : dpiY; - } - return m_defaultDPI; + return getScreenDpi(); } qreal WinNativeEventFilter::getDprForWindow(HWND handle) const { diff --git a/winnativeeventfilter.h b/winnativeeventfilter.h index 47184ce..8bdc490 100644 --- a/winnativeeventfilter.h +++ b/winnativeeventfilter.h @@ -2,19 +2,13 @@ #include #include +#include #include class WinNativeEventFilter : public QAbstractNativeEventFilter { Q_DISABLE_COPY_MOVE(WinNativeEventFilter) public: - typedef struct tagWINDOW { - HWND hwnd = nullptr; - UINT width = 0, height = 0; - RECT region = {0, 0, 0, 0}; - BOOL compositionEnabled = FALSE; - } WINDOW, *LPWINDOW; - explicit WinNativeEventFilter(); ~WinNativeEventFilter() override; @@ -35,33 +29,34 @@ public: #endif private: - void init(LPWINDOW data); - void handleNcCalcSize(LPWINDOW data, WPARAM wParam, LPARAM lParam); - void updateRegion(LPWINDOW data); - void handleDwmCompositionChanged(LPWINDOW data); - void handleWindowPosChanged(LPWINDOW data, LPARAM lParam); - LRESULT handleNcHitTest(LPWINDOW data, LPARAM lParam); + void init(HWND handle); + void handleDwmCompositionChanged(HWND handle); UINT getDpiForWindow(HWND handle) const; qreal getDprForWindow(HWND handle) const; int getSystemMetricsForWindow(HWND handle, int index) const; private: + // GetDpiForMonitor is only available from Windows 8.1 so we will load it at + // run-time instead of linking to it directly. using MONITOR_DPI_TYPE = enum MONITOR_DPI_TYPE { MDT_EFFECTIVE_DPI = 0, MDT_ANGULAR_DPI = 1, MDT_RAW_DPI = 2, MDT_DEFAULT = MDT_EFFECTIVE_DPI }; - QHash m_data; + // Window handle, DwmComposition, Theme + QHash> m_windowData; const UINT m_defaultDPI = 96; const qreal m_defaultDPR = 1.0; - using lpGetDpiForWindow = UINT (*)(HWND); + using lpGetSystemDpiForProcess = UINT(WINAPI *)(HANDLE); + lpGetSystemDpiForProcess m_GetSystemDpiForProcess = nullptr; + using lpGetDpiForWindow = UINT(WINAPI *)(HWND); lpGetDpiForWindow m_GetDpiForWindow = nullptr; - using lpGetDpiForSystem = UINT (*)(); + using lpGetDpiForSystem = UINT(WINAPI *)(); lpGetDpiForSystem m_GetDpiForSystem = nullptr; - using lpGetSystemMetricsForDpi = int (*)(int, UINT); + using lpGetSystemMetricsForDpi = int(WINAPI *)(int, UINT); lpGetSystemMetricsForDpi m_GetSystemMetricsForDpi = nullptr; - using lpGetDpiForMonitor = HRESULT (*)(HMONITOR, MONITOR_DPI_TYPE, UINT *, - UINT *); + using lpGetDpiForMonitor = HRESULT(WINAPI *)(HMONITOR, MONITOR_DPI_TYPE, + UINT *, UINT *); lpGetDpiForMonitor m_GetDpiForMonitor = nullptr; };