Signed-off-by: Yuhang Zhao <2546789017@qq.com>
This commit is contained in:
Yuhang Zhao 2020-03-30 20:35:33 +08:00
parent daae7d4ac2
commit f7968b0aac
4 changed files with 239 additions and 284 deletions

View File

@ -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. - 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. - 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. - 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. - 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. - 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. - 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. - 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. - For UNIX platforms, things are much easier. Just use the `startSystemMove` and `startSystemResize` APIs introduced in Qt 5.15.
## References ## References

View File

@ -5,7 +5,7 @@ CONFIG += c++17 strict_c++ utf8_source warn_on
win32 { win32 {
CONFIG -= embed_manifest_exe CONFIG -= embed_manifest_exe
RC_FILE = resources.rc 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 DEFINES += QT_NO_CAST_FROM_ASCII QT_NO_CAST_TO_ASCII
HEADERS += winnativeeventfilter.h HEADERS += winnativeeventfilter.h

View File

@ -4,19 +4,51 @@
#include <QGuiApplication> #include <QGuiApplication>
#include <QLibrary> #include <QLibrary>
#include <QOperatingSystemVersion> #include <QOperatingSystemVersion>
#include <QWindow>
#include <d2d1.h>
#include <dwmapi.h> #include <dwmapi.h>
#include <shellapi.h> #include <uxtheme.h>
#include <windowsx.h> #include <windowsx.h>
#ifndef SM_CXPADDEDBORDER
// Only available since Windows Vista
#define SM_CXPADDEDBORDER 92
#endif
#ifndef WM_NCUAHDRAWCAPTION #ifndef WM_NCUAHDRAWCAPTION
// Not documented, only available since Windows Vista
#define WM_NCUAHDRAWCAPTION 0x00AE #define WM_NCUAHDRAWCAPTION 0x00AE
#endif #endif
#ifndef WM_NCUAHDRAWFRAME #ifndef WM_NCUAHDRAWFRAME
// Not documented, only available since Windows Vista
#define WM_NCUAHDRAWFRAME 0x00AF #define WM_NCUAHDRAWFRAME 0x00AF
#endif #endif
static QScopedPointer<WinNativeEventFilter> instance; #ifndef WM_DWMCOMPOSITIONCHANGED
// Only available since Windows 7
#define WM_DWMCOMPOSITIONCHANGED 0x031E
#endif
namespace {
QScopedPointer<WinNativeEventFilter> instance;
bool isWindowTopLevel(HWND handle) {
if (!handle) {
return false;
}
const auto wid = reinterpret_cast<WId>(handle);
const auto topLevelWindows = QGuiApplication::topLevelWindows();
for (auto &&window : qAsConst(topLevelWindows)) {
if (window->handle() && (window->winId() == wid)) {
return true;
}
}
return false;
}
} // namespace
WinNativeEventFilter::WinNativeEventFilter() { WinNativeEventFilter::WinNativeEventFilter() {
QLibrary shcoreLib(QString::fromUtf8("SHCore")); QLibrary shcoreLib(QString::fromUtf8("SHCore"));
@ -37,16 +69,17 @@ WinNativeEventFilter::WinNativeEventFilter() {
m_GetSystemMetricsForDpi = reinterpret_cast<lpGetSystemMetricsForDpi>( m_GetSystemMetricsForDpi = reinterpret_cast<lpGetSystemMetricsForDpi>(
user32Lib.resolve("GetSystemMetricsForDpi")); user32Lib.resolve("GetSystemMetricsForDpi"));
} }
} // Windows 10, version 1803 (10.0.17134)
if (QOperatingSystemVersion::current() >=
WinNativeEventFilter::~WinNativeEventFilter() { QOperatingSystemVersion(QOperatingSystemVersion::Windows, 10, 0,
if (!m_data.isEmpty()) { 17134)) {
for (auto data : m_data) { m_GetSystemDpiForProcess = reinterpret_cast<lpGetSystemDpiForProcess>(
delete data; user32Lib.resolve("GetSystemDpiForProcess"));
}
} }
} }
WinNativeEventFilter::~WinNativeEventFilter() = default;
void WinNativeEventFilter::setup() { void WinNativeEventFilter::setup() {
if (instance.isNull()) { if (instance.isNull()) {
instance.reset(new WinNativeEventFilter); instance.reset(new WinNativeEventFilter);
@ -92,29 +125,38 @@ bool WinNativeEventFilter::nativeEventFilter(const QByteArray &eventType,
// Anyway, we should skip it in this case. // Anyway, we should skip it in this case.
return false; return false;
} }
if (GetParent(msg->hwnd)) { if (!isWindowTopLevel(msg->hwnd)) {
// GetParent only returns NULL if the window is a top level window. // Only top level windows can be frameless.
// Only top level windows can be frameless, we should never apply our
// code to child windows or widgets.
return false; return false;
} }
#if 0 if (!m_windowData.contains(msg->hwnd)) {
if (!IsWindowVisible(msg->hwnd)) { m_windowData.insert(msg->hwnd, qMakePair(FALSE, FALSE));
// Skip dummy windows created internally by Qt. init(msg->hwnd);
return false;
} }
#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) { switch (msg->message) {
case WM_NCCREATE: {
// Work-around a long-existing Windows bug.
const auto userData =
reinterpret_cast<LPCREATESTRUCTW>(msg->lParam)->lpCreateParams;
SetWindowLongPtrW(msg->hwnd, GWLP_USERDATA,
reinterpret_cast<LONG_PTR>(userData));
break;
}
case WM_NCCALCSIZE: { case WM_NCCALCSIZE: {
handleNcCalcSize(data, msg->wParam, msg->lParam);
if (static_cast<BOOL>(msg->wParam)) { if (static_cast<BOOL>(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 &params =
*reinterpret_cast<LPNCCALCSIZE_PARAMS>(msg->lParam);
params.rgrc[0] = monitorInfo.rcWork;
}
}
// This line removes the window frame (including the titlebar). // This line removes the window frame (including the titlebar).
// But the frame shadow is lost at the same time. We'll bring it // But the frame shadow is lost at the same time. We'll bring it
// back later. // back later.
@ -127,7 +169,7 @@ bool WinNativeEventFilter::nativeEventFilter(const QByteArray &eventType,
case WM_DWMCOMPOSITIONCHANGED: { case WM_DWMCOMPOSITIONCHANGED: {
// Bring the frame shadow back through DWM. // Bring the frame shadow back through DWM.
// Don't paint the shadow manually using QPainter or QGraphicsEffect. // Don't paint the shadow manually using QPainter or QGraphicsEffect.
handleDwmCompositionChanged(data); handleDwmCompositionChanged(msg->hwnd);
*result = 0; *result = 0;
return true; return true;
} }
@ -140,7 +182,7 @@ bool WinNativeEventFilter::nativeEventFilter(const QByteArray &eventType,
return true; return true;
} }
case WM_NCPAINT: { case WM_NCPAINT: {
if (data->compositionEnabled) { if (m_windowData.value(msg->hwnd).first) {
break; break;
} else { } else {
// Only block WM_NCPAINT when composition is disabled. If it's // Only block WM_NCPAINT when composition is disabled. If it's
@ -152,273 +194,202 @@ bool WinNativeEventFilter::nativeEventFilter(const QByteArray &eventType,
} }
case WM_NCACTIVATE: { case WM_NCACTIVATE: {
// DefWindowProc won't repaint the window border if lParam (normally // DefWindowProc won't repaint the window border if lParam (normally
// a HRGN) is -1. This is recommended in: // a HRGN) is -1.
// https://blogs.msdn.microsoft.com/wpfsdk/2008/09/08/custom-window-chrome-in-wpf/ *result = DefWindowProcW(msg->hwnd, msg->message, msg->wParam, -1);
*result = DefWindowProcW(data->hwnd, msg->message, msg->wParam, -1);
return true; 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: { 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; 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<LPMINMAXINFO>(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: default:
break; break;
} }
return false; 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 // 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. // window frame later in Win32 events, don't use WS_POPUP to do this.
SetWindowLongPtrW(data->hwnd, GWL_STYLE, WS_OVERLAPPEDWINDOW); SetWindowLongPtrW(handle, GWL_STYLE, WS_OVERLAPPEDWINDOW);
SetWindowLongPtrW(data->hwnd, GWL_EXSTYLE, WS_EX_APPWINDOW | WS_EX_LAYERED); 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 // 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: // 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, // This is not necessary if other drawing APIs are used, eg. GDI+, OpenGL,
// Direct2D, Direct3D, DirectComposition, etc. // 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. // 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. // For debug purposes.
qDebug().noquote() << "Window handle:" << data->hwnd; qDebug().noquote() << "Window handle:" << handle;
qDebug().noquote() << "Window DPI:" << windowDpi(data->hwnd) qDebug().noquote() << "Window DPI:" << windowDpi(handle)
<< "Window DPR:" << windowDpr(data->hwnd); << "Window DPR:" << windowDpr(handle);
qDebug().noquote() << "Window border width:" << borderWidth(data->hwnd) qDebug().noquote() << "Window border width:" << borderWidth(handle)
<< "Window border height:" << borderHeight(data->hwnd) << "Window border height:" << borderHeight(handle)
<< "Window titlebar height:" << "Window titlebar height:" << titlebarHeight(handle);
<< titlebarHeight(data->hwnd);
} }
void WinNativeEventFilter::handleNcCalcSize(LPWINDOW data, WPARAM wParam, void WinNativeEventFilter::handleDwmCompositionChanged(HWND handle) {
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) {
BOOL enabled = FALSE; BOOL enabled = FALSE;
DwmIsCompositionEnabled(&enabled); 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 // 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 // other words, a window should not have frame shadow when Windows Aero is
// not enabled. // not enabled.
// Note that, start from Win8, the DWM composition is always enabled and // Note that, start from Win8, the DWM composition is always enabled and
// can't be disabled. // can't be disabled.
if (enabled) { if (enabled) {
options.dwFlags = WTNCA_NODRAWCAPTION | WTNCA_NODRAWICON;
// The frame shadow is drawn on the non-client area and thus we have to // 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. // make sure the non-client area rendering is enabled first.
const DWMNCRENDERINGPOLICY ncrp = DWMNCRP_ENABLED; const DWMNCRENDERINGPOLICY ncrp = DWMNCRP_ENABLED;
DwmSetWindowAttribute(data->hwnd, DWMWA_NCRENDERING_POLICY, &ncrp, DwmSetWindowAttribute(handle, DWMWA_NCRENDERING_POLICY, &ncrp,
sizeof(ncrp)); sizeof(ncrp));
// Negative margins have special meaning to // Negative margins have special meaning to
// DwmExtendFrameIntoClientArea. Negative margins create the "sheet of // DwmExtendFrameIntoClientArea. Negative margins create the "sheet of
// glass" effect, where the client area is rendered as a solid surface // glass" effect, where the client area is rendered as a solid surface
// with no window border. // with no window border.
const MARGINS margins = {-1, -1, -1, -1}; const MARGINS margins = {-1, -1, -1, -1};
DwmExtendFrameIntoClientArea(data->hwnd, &margins); DwmExtendFrameIntoClientArea(handle, &margins);
} }
updateRegion(data); SetWindowThemeAttribute(handle, WTA_NONCLIENT, &options, sizeof(options));
}
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<const LPWINDOWPOS>(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;
} }
UINT WinNativeEventFilter::getDpiForWindow(HWND handle) const { 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 (!handle) {
if (m_GetDpiForSystem) { if (m_GetSystemDpiForProcess) {
return m_GetSystemDpiForProcess(GetCurrentProcess());
} else if (m_GetDpiForSystem) {
return m_GetDpiForSystem(); return m_GetDpiForSystem();
} else { } else {
return m_defaultDPI; return getScreenDpi();
} }
} }
if (m_GetDpiForWindow) { if (m_GetDpiForWindow) {
@ -431,17 +402,7 @@ UINT WinNativeEventFilter::getDpiForWindow(HWND handle) const {
// The values of *dpiX and *dpiY are identical. // The values of *dpiX and *dpiY are identical.
return dpiX; return dpiX;
} }
// TODO: Is there an elegant way to acquire the system DPI in return getScreenDpi();
// 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;
} }
qreal WinNativeEventFilter::getDprForWindow(HWND handle) const { qreal WinNativeEventFilter::getDprForWindow(HWND handle) const {

View File

@ -2,19 +2,13 @@
#include <QAbstractNativeEventFilter> #include <QAbstractNativeEventFilter>
#include <QHash> #include <QHash>
#include <QPair>
#include <qt_windows.h> #include <qt_windows.h>
class WinNativeEventFilter : public QAbstractNativeEventFilter { class WinNativeEventFilter : public QAbstractNativeEventFilter {
Q_DISABLE_COPY_MOVE(WinNativeEventFilter) Q_DISABLE_COPY_MOVE(WinNativeEventFilter)
public: 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(); explicit WinNativeEventFilter();
~WinNativeEventFilter() override; ~WinNativeEventFilter() override;
@ -35,33 +29,34 @@ public:
#endif #endif
private: private:
void init(LPWINDOW data); void init(HWND handle);
void handleNcCalcSize(LPWINDOW data, WPARAM wParam, LPARAM lParam); void handleDwmCompositionChanged(HWND handle);
void updateRegion(LPWINDOW data);
void handleDwmCompositionChanged(LPWINDOW data);
void handleWindowPosChanged(LPWINDOW data, LPARAM lParam);
LRESULT handleNcHitTest(LPWINDOW data, LPARAM lParam);
UINT getDpiForWindow(HWND handle) const; UINT getDpiForWindow(HWND handle) const;
qreal getDprForWindow(HWND handle) const; qreal getDprForWindow(HWND handle) const;
int getSystemMetricsForWindow(HWND handle, int index) const; int getSystemMetricsForWindow(HWND handle, int index) const;
private: 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 { using MONITOR_DPI_TYPE = enum MONITOR_DPI_TYPE {
MDT_EFFECTIVE_DPI = 0, MDT_EFFECTIVE_DPI = 0,
MDT_ANGULAR_DPI = 1, MDT_ANGULAR_DPI = 1,
MDT_RAW_DPI = 2, MDT_RAW_DPI = 2,
MDT_DEFAULT = MDT_EFFECTIVE_DPI MDT_DEFAULT = MDT_EFFECTIVE_DPI
}; };
QHash<HWND, LPWINDOW> m_data; // Window handle, DwmComposition, Theme
QHash<HWND, QPair<BOOL, BOOL>> m_windowData;
const UINT m_defaultDPI = 96; const UINT m_defaultDPI = 96;
const qreal m_defaultDPR = 1.0; 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; lpGetDpiForWindow m_GetDpiForWindow = nullptr;
using lpGetDpiForSystem = UINT (*)(); using lpGetDpiForSystem = UINT(WINAPI *)();
lpGetDpiForSystem m_GetDpiForSystem = nullptr; lpGetDpiForSystem m_GetDpiForSystem = nullptr;
using lpGetSystemMetricsForDpi = int (*)(int, UINT); using lpGetSystemMetricsForDpi = int(WINAPI *)(int, UINT);
lpGetSystemMetricsForDpi m_GetSystemMetricsForDpi = nullptr; lpGetSystemMetricsForDpi m_GetSystemMetricsForDpi = nullptr;
using lpGetDpiForMonitor = HRESULT (*)(HMONITOR, MONITOR_DPI_TYPE, UINT *, using lpGetDpiForMonitor = HRESULT(WINAPI *)(HMONITOR, MONITOR_DPI_TYPE,
UINT *); UINT *, UINT *);
lpGetDpiForMonitor m_GetDpiForMonitor = nullptr; lpGetDpiForMonitor m_GetDpiForMonitor = nullptr;
}; };