Upload project.
Signed-off-by: Yuhang Zhao <2546789017@qq.com>
This commit is contained in:
parent
5067559a7f
commit
47e9c486dc
|
@ -6,7 +6,6 @@
|
||||||
*.la
|
*.la
|
||||||
*.lai
|
*.lai
|
||||||
*.so
|
*.so
|
||||||
*.so.*
|
|
||||||
*.dll
|
*.dll
|
||||||
*.dylib
|
*.dylib
|
||||||
|
|
||||||
|
@ -29,8 +28,6 @@ ui_*.h
|
||||||
*.jsc
|
*.jsc
|
||||||
Makefile*
|
Makefile*
|
||||||
*build-*
|
*build-*
|
||||||
*.qm
|
|
||||||
*.prl
|
|
||||||
|
|
||||||
# Qt unit tests
|
# Qt unit tests
|
||||||
target_wrapper.*
|
target_wrapper.*
|
||||||
|
@ -45,8 +42,30 @@ target_wrapper.*
|
||||||
# QtCreator CMake
|
# QtCreator CMake
|
||||||
CMakeLists.txt.user*
|
CMakeLists.txt.user*
|
||||||
|
|
||||||
# QtCreator 4.8< compilation database
|
# My
|
||||||
compile_commands.json
|
[Bb]in/
|
||||||
|
[Bb]in64/
|
||||||
# QtCreator local machine specific files for imported projects
|
[Bb]uild*/
|
||||||
*creator.user*
|
*.7z
|
||||||
|
*.zip
|
||||||
|
*.rar
|
||||||
|
*.tar
|
||||||
|
*.gz
|
||||||
|
*.xz
|
||||||
|
*.exe
|
||||||
|
*.lib
|
||||||
|
*.pdb
|
||||||
|
*.ilk
|
||||||
|
*.exp
|
||||||
|
*.obj
|
||||||
|
build.user.bat
|
||||||
|
build.user.sh
|
||||||
|
user.conf
|
||||||
|
[Dd]oc/
|
||||||
|
[Dd]ocs/
|
||||||
|
Thumbs.db
|
||||||
|
*.rc
|
||||||
|
*.qm
|
||||||
|
*.bin
|
||||||
|
*.run
|
||||||
|
.qmake.conf
|
||||||
|
|
25
README.md
25
README.md
|
@ -1 +1,24 @@
|
||||||
# framelesshelper
|
# FramelessHelper
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
Copy *winnativeeventfilter.h* and *winnativeeventfilter.cpp* to your project and use `WinNativeEventFilter::setup();` before any windows (widgets and Qt Quick windows) show up, just like what the example project does.
|
||||||
|
|
||||||
|
## Notice
|
||||||
|
|
||||||
|
- Any widgets (or Qt Quick elements) in the titlebar area will not be resposible because the mouse events are intercepted. You should change the code to make them be resposible.
|
||||||
|
- 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.
|
||||||
|
- There are some known issues when you draw something manually on widgets using D3D. 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.
|
||||||
|
- 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.
|
||||||
|
- For UNIX platforms, things are much easier. Just use the `startSystemMove` and `startSystemResize` APIs introduced in Qt 5.15.
|
||||||
|
|
||||||
|
## References
|
||||||
|
|
||||||
|
- <https://github.com/rossy/borderless-window>
|
||||||
|
- <https://github.com/Bringer-of-Light/Qt-Nice-Frameless-Window>
|
||||||
|
- <https://github.com/dfct/TrueFramelessWindow>
|
||||||
|
- <https://github.com/qtdevs/FramelessHelper>
|
||||||
|
|
|
@ -0,0 +1,12 @@
|
||||||
|
TARGET = framelesswidget
|
||||||
|
TEMPLATE = app
|
||||||
|
QT += widgets
|
||||||
|
CONFIG += c++17 strict_c++ utf8_source warn_on
|
||||||
|
win32 {
|
||||||
|
CONFIG -= embed_manifest_exe
|
||||||
|
RC_FILE = resources.rc
|
||||||
|
LIBS += -luser32 -lshell32 -lgdi32 -ldwmapi
|
||||||
|
}
|
||||||
|
DEFINES += QT_NO_CAST_FROM_ASCII QT_NO_CAST_TO_ASCII
|
||||||
|
HEADERS += winnativeeventfilter.h
|
||||||
|
SOURCES += main.cpp winnativeeventfilter.cpp
|
|
@ -0,0 +1,20 @@
|
||||||
|
#include "winnativeeventfilter.h"
|
||||||
|
|
||||||
|
#include <QApplication>
|
||||||
|
#include <QWidget>
|
||||||
|
|
||||||
|
int main(int argc, char *argv[]) {
|
||||||
|
QGuiApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
|
||||||
|
QGuiApplication::setAttribute(Qt::AA_UseHighDpiPixmaps);
|
||||||
|
QGuiApplication::setHighDpiScaleFactorRoundingPolicy(
|
||||||
|
Qt::HighDpiScaleFactorRoundingPolicy::PassThrough);
|
||||||
|
|
||||||
|
QApplication application(argc, argv);
|
||||||
|
|
||||||
|
WinNativeEventFilter::setup();
|
||||||
|
|
||||||
|
QWidget widget;
|
||||||
|
widget.show();
|
||||||
|
|
||||||
|
return QApplication::exec();
|
||||||
|
}
|
|
@ -0,0 +1,34 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
||||||
|
<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
|
||||||
|
<dependency>
|
||||||
|
<dependentAssembly>
|
||||||
|
<assemblyIdentity type="win32" name="Microsoft.Windows.Common-Controls" version="6.0.0.0" processorArchitecture="*" publicKeyToken="6595b64144ccf1df" language="*"/>
|
||||||
|
</dependentAssembly>
|
||||||
|
</dependency>
|
||||||
|
<trustInfo xmlns="urn:schemas-microsoft-com:asm.v3">
|
||||||
|
<security>
|
||||||
|
<requestedPrivileges>
|
||||||
|
<requestedExecutionLevel level="asInvoker" uiAccess="false"/>
|
||||||
|
</requestedPrivileges>
|
||||||
|
</security>
|
||||||
|
</trustInfo>
|
||||||
|
<compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1">
|
||||||
|
<application>
|
||||||
|
<!-- Windows 7 and Windows Server 2008 R2 -->
|
||||||
|
<supportedOS Id="{35138b9a-5d96-4fbd-8e2d-a2440225f93a}"/>
|
||||||
|
<!-- Windows 8 and Windows Server 2012 -->
|
||||||
|
<supportedOS Id="{4a2f28e3-53b9-4441-ba9c-d69d4a4a6e38}"/>
|
||||||
|
<!-- Windows 8.1 and Windows Server 2012 R2 -->
|
||||||
|
<supportedOS Id="{1f676c76-80e1-4239-95bb-83d0f6d0da78}"/>
|
||||||
|
<!-- Windows 10 -->
|
||||||
|
<supportedOS Id="{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}"/>
|
||||||
|
</application>
|
||||||
|
</compatibility>
|
||||||
|
<application xmlns="urn:schemas-microsoft-com:asm.v3">
|
||||||
|
<windowsSettings>
|
||||||
|
<dpiAware xmlns="http://schemas.microsoft.com/SMI/2005/WindowsSettings">true/pm</dpiAware>
|
||||||
|
<dpiAwareness xmlns="http://schemas.microsoft.com/SMI/2016/WindowsSettings">PerMonitorV2</dpiAwareness>
|
||||||
|
<longPathAware xmlns="http://schemas.microsoft.com/SMI/2016/WindowsSettings">true</longPathAware>
|
||||||
|
</windowsSettings>
|
||||||
|
</application>
|
||||||
|
</assembly>
|
|
@ -0,0 +1,43 @@
|
||||||
|
#include <windows.h>
|
||||||
|
|
||||||
|
IDI_ICON1 ICON "icon.ico"
|
||||||
|
|
||||||
|
CREATEPROCESS_MANIFEST_RESOURCE_ID RT_MANIFEST "manifest.xml"
|
||||||
|
|
||||||
|
VS_VERSION_INFO VERSIONINFO
|
||||||
|
FILEVERSION 1,0,0,0
|
||||||
|
PRODUCTVERSION 1,0,0,0
|
||||||
|
FILEFLAGSMASK 0x3fL
|
||||||
|
#ifdef _DEBUG
|
||||||
|
FILEFLAGS VS_FF_DEBUG
|
||||||
|
#else
|
||||||
|
FILEFLAGS 0x0L
|
||||||
|
#endif
|
||||||
|
FILEOS VOS_NT_WINDOWS32
|
||||||
|
FILETYPE VFT_APP
|
||||||
|
FILESUBTYPE VFT2_UNKNOWN
|
||||||
|
BEGIN
|
||||||
|
BLOCK "StringFileInfo"
|
||||||
|
BEGIN
|
||||||
|
BLOCK "040904b0"
|
||||||
|
BEGIN
|
||||||
|
VALUE "Comments", "Built by Qt Toolkit."
|
||||||
|
VALUE "CompanyName", "wangwenx190"
|
||||||
|
VALUE "FileDescription", "A frameless widget based Qt application."
|
||||||
|
VALUE "FileVersion", "1.0.0.0"
|
||||||
|
VALUE "InternalName", "flw-test-app"
|
||||||
|
VALUE "LegalCopyright", "MIT License"
|
||||||
|
#ifdef _DEBUG
|
||||||
|
VALUE "OriginalFilename", "framelesswidgetd.exe"
|
||||||
|
#else
|
||||||
|
VALUE "OriginalFilename", "framelesswidget.exe"
|
||||||
|
#endif
|
||||||
|
VALUE "ProductName", "Test Application"
|
||||||
|
VALUE "ProductVersion", "1.0.0.0"
|
||||||
|
END
|
||||||
|
END
|
||||||
|
BLOCK "VarFileInfo"
|
||||||
|
BEGIN
|
||||||
|
VALUE "Translation", 0x409, 1200
|
||||||
|
END
|
||||||
|
END
|
|
@ -0,0 +1,427 @@
|
||||||
|
#include "winnativeeventfilter.h"
|
||||||
|
|
||||||
|
#include <QGuiApplication>
|
||||||
|
#include <QLibrary>
|
||||||
|
#include <QOperatingSystemVersion>
|
||||||
|
#include <dwmapi.h>
|
||||||
|
#include <shellapi.h>
|
||||||
|
#include <windowsx.h>
|
||||||
|
|
||||||
|
#ifndef WM_NCUAHDRAWCAPTION
|
||||||
|
#define WM_NCUAHDRAWCAPTION 0x00AE
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifndef WM_NCUAHDRAWFRAME
|
||||||
|
#define WM_NCUAHDRAWFRAME 0x00AF
|
||||||
|
#endif
|
||||||
|
|
||||||
|
WinNativeEventFilter *WinNativeEventFilter::instance = nullptr;
|
||||||
|
|
||||||
|
WinNativeEventFilter::WinNativeEventFilter() {
|
||||||
|
QLibrary shcoreLib(QString::fromUtf8("SHCore"));
|
||||||
|
if (QOperatingSystemVersion::current() >=
|
||||||
|
QOperatingSystemVersion::Windows8_1) {
|
||||||
|
m_GetDpiForMonitor = reinterpret_cast<lpGetDpiForMonitor>(
|
||||||
|
shcoreLib.resolve("GetDpiForMonitor"));
|
||||||
|
}
|
||||||
|
QLibrary user32Lib(QString::fromUtf8("User32"));
|
||||||
|
// Windows 10, version 1607 (10.0.14393)
|
||||||
|
if (QOperatingSystemVersion::current() >=
|
||||||
|
QOperatingSystemVersion(QOperatingSystemVersion::Windows, 10, 0,
|
||||||
|
14393)) {
|
||||||
|
m_GetDpiForWindow = reinterpret_cast<lpGetDpiForWindow>(
|
||||||
|
user32Lib.resolve("GetDpiForWindow"));
|
||||||
|
m_GetDpiForSystem = reinterpret_cast<lpGetDpiForSystem>(
|
||||||
|
user32Lib.resolve("GetDpiForSystem"));
|
||||||
|
m_GetSystemMetricsForDpi = reinterpret_cast<lpGetSystemMetricsForDpi>(
|
||||||
|
user32Lib.resolve("GetSystemMetricsForDpi"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
WinNativeEventFilter::~WinNativeEventFilter() {
|
||||||
|
instance = nullptr;
|
||||||
|
if (!m_data.isEmpty()) {
|
||||||
|
for (auto data : m_data) {
|
||||||
|
delete data;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void WinNativeEventFilter::setup() {
|
||||||
|
if (!instance) {
|
||||||
|
instance = new WinNativeEventFilter;
|
||||||
|
qApp->installNativeEventFilter(instance);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
UINT WinNativeEventFilter::windowDpi(HWND handle) const {
|
||||||
|
return getDpiForWindow(handle);
|
||||||
|
}
|
||||||
|
|
||||||
|
qreal WinNativeEventFilter::windowDpr(HWND handle) const {
|
||||||
|
return getDprForWindow(handle);
|
||||||
|
}
|
||||||
|
|
||||||
|
int WinNativeEventFilter::borderWidth(HWND handle) const {
|
||||||
|
return getSystemMetricsForWindow(handle, SM_CXFRAME) +
|
||||||
|
getSystemMetricsForWindow(handle, SM_CXPADDEDBORDER);
|
||||||
|
}
|
||||||
|
|
||||||
|
int WinNativeEventFilter::borderHeight(HWND handle) const {
|
||||||
|
return getSystemMetricsForWindow(handle, SM_CYFRAME) +
|
||||||
|
getSystemMetricsForWindow(handle, SM_CXPADDEDBORDER);
|
||||||
|
}
|
||||||
|
|
||||||
|
int WinNativeEventFilter::titlebarHeight(HWND handle) const {
|
||||||
|
return borderHeight(handle) +
|
||||||
|
getSystemMetricsForWindow(handle, SM_CYCAPTION);
|
||||||
|
}
|
||||||
|
|
||||||
|
#if (QT_VERSION >= QT_VERSION_CHECK(6, 0, 0))
|
||||||
|
bool WinNativeEventFilter::nativeEventFilter(const QByteArray &eventType,
|
||||||
|
void *message, qintptr *result)
|
||||||
|
#else
|
||||||
|
bool WinNativeEventFilter::nativeEventFilter(const QByteArray &eventType,
|
||||||
|
void *message, long *result)
|
||||||
|
#endif
|
||||||
|
{
|
||||||
|
Q_UNUSED(eventType)
|
||||||
|
const auto msg = static_cast<LPMSG>(message);
|
||||||
|
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_NCCALCSIZE: {
|
||||||
|
handleNcCalcSize(data, msg->wParam, msg->lParam);
|
||||||
|
if (static_cast<BOOL>(msg->wParam)) {
|
||||||
|
// 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.
|
||||||
|
*result = WVR_REDRAW;
|
||||||
|
} else {
|
||||||
|
*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
|
||||||
|
// borders. Block them to prevent drawing borders over the client
|
||||||
|
// area.
|
||||||
|
*result = 0;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
case WM_NCPAINT: {
|
||||||
|
if (data->compositionEnabled) {
|
||||||
|
break;
|
||||||
|
} else {
|
||||||
|
// Only block WM_NCPAINT when composition is disabled. If it's
|
||||||
|
// blocked when composition is enabled, the window shadow won't
|
||||||
|
// be drawn.
|
||||||
|
*result = 0;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
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);
|
||||||
|
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);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void WinNativeEventFilter::init(LPWINDOW data) {
|
||||||
|
// 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);
|
||||||
|
// 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);
|
||||||
|
// Make sure our window has the frame shadow.
|
||||||
|
handleDwmCompositionChanged(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
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) {
|
||||||
|
BOOL enabled = FALSE;
|
||||||
|
DwmIsCompositionEnabled(&enabled);
|
||||||
|
data->compositionEnabled = 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) {
|
||||||
|
// 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,
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
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<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 {
|
||||||
|
if (!handle) {
|
||||||
|
if (m_GetDpiForSystem) {
|
||||||
|
return m_GetDpiForSystem();
|
||||||
|
} else {
|
||||||
|
return m_defaultDPI;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (m_GetDpiForWindow) {
|
||||||
|
return m_GetDpiForWindow(handle);
|
||||||
|
}
|
||||||
|
if (m_GetDpiForMonitor) {
|
||||||
|
UINT dpiX = m_defaultDPI, dpiY = m_defaultDPI;
|
||||||
|
m_GetDpiForMonitor(MonitorFromWindow(handle, MONITOR_DEFAULTTONEAREST),
|
||||||
|
MDT_EFFECTIVE_DPI, &dpiX, &dpiY);
|
||||||
|
// 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)?
|
||||||
|
return m_defaultDPI;
|
||||||
|
}
|
||||||
|
|
||||||
|
qreal WinNativeEventFilter::getDprForWindow(HWND handle) const {
|
||||||
|
return handle ? (qreal(getDpiForWindow(handle)) / qreal(m_defaultDPI))
|
||||||
|
: m_defaultDPR;
|
||||||
|
}
|
||||||
|
|
||||||
|
int WinNativeEventFilter::getSystemMetricsForWindow(HWND handle,
|
||||||
|
int index) const {
|
||||||
|
const UINT dpi = getDpiForWindow(handle);
|
||||||
|
if (m_GetSystemMetricsForDpi) {
|
||||||
|
return m_GetSystemMetricsForDpi(index, dpi);
|
||||||
|
} else {
|
||||||
|
return GetSystemMetrics(index) * getDprForWindow(handle);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,68 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <QAbstractNativeEventFilter>
|
||||||
|
#include <QHash>
|
||||||
|
#include <qt_windows.h>
|
||||||
|
|
||||||
|
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;
|
||||||
|
|
||||||
|
static void setup();
|
||||||
|
|
||||||
|
UINT windowDpi(HWND handle) const;
|
||||||
|
qreal windowDpr(HWND handle) const;
|
||||||
|
int borderWidth(HWND handle) const;
|
||||||
|
int borderHeight(HWND handle) const;
|
||||||
|
int titlebarHeight(HWND handle) const;
|
||||||
|
|
||||||
|
#if (QT_VERSION >= QT_VERSION_CHECK(6, 0, 0))
|
||||||
|
bool nativeEventFilter(const QByteArray &eventType, void *message,
|
||||||
|
qintptr *result) override;
|
||||||
|
#else
|
||||||
|
bool nativeEventFilter(const QByteArray &eventType, void *message,
|
||||||
|
long *result) override;
|
||||||
|
#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);
|
||||||
|
UINT getDpiForWindow(HWND handle) const;
|
||||||
|
qreal getDprForWindow(HWND handle) const;
|
||||||
|
int getSystemMetricsForWindow(HWND handle, int index) const;
|
||||||
|
|
||||||
|
private:
|
||||||
|
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
|
||||||
|
};
|
||||||
|
static WinNativeEventFilter *instance;
|
||||||
|
QHash<HWND, LPWINDOW> m_data;
|
||||||
|
const UINT m_defaultDPI = 96;
|
||||||
|
const qreal m_defaultDPR = 1.0;
|
||||||
|
using lpGetDpiForWindow = UINT (*)(HWND);
|
||||||
|
lpGetDpiForWindow m_GetDpiForWindow = nullptr;
|
||||||
|
using lpGetDpiForSystem = UINT (*)();
|
||||||
|
lpGetDpiForSystem m_GetDpiForSystem = nullptr;
|
||||||
|
using lpGetSystemMetricsForDpi = int (*)(int, UINT);
|
||||||
|
lpGetSystemMetricsForDpi m_GetSystemMetricsForDpi = nullptr;
|
||||||
|
using lpGetDpiForMonitor = HRESULT (*)(HMONITOR, MONITOR_DPI_TYPE, UINT *,
|
||||||
|
UINT *);
|
||||||
|
lpGetDpiForMonitor m_GetDpiForMonitor = nullptr;
|
||||||
|
};
|
Loading…
Reference in New Issue