diff --git a/README.md b/README.md index d0ab8348..4472f663 100644 --- a/README.md +++ b/README.md @@ -16,7 +16,23 @@ ## Screenshots -TODO +### Windows + +![Light](./doc/win_light.png) + +![Dark](./doc/win_dark.png) + +### Linux + +![Light](./doc/linux_light.png) + +![Dark](./doc/linux_dark.png) + +### macOS + +![Light](./doc/mac_light.png) + +![Dark](./doc/mac_dark.png) ## Roadmap @@ -28,9 +44,39 @@ TODO - [ ] Windows: Maximize button docking feature introduced in Windows 11 - [ ] More feature requests are welcome! -## Feedback +## Build + +```bash +git clone https://github.com/wangwenx190/framelesshelper.git +mkdir build +cd build +cmake -DCMAKE_PREFIX_PATH= -DCMAKE_BUILD_TYPE=Release -GNinja ../framelesshelper +cmake --build . --config Release --target all --parallel +``` + +**Note**: On Linux you need to install the GTK3 and X11 development packages first. + +## Use + +For Qt Widgets applications: subclass `FramelessWidget` or `FramelessMainWindow`. + +For Qt Quick applications: use `FramelessWindow` instead of `Window`. + +Please refer to the demo applications to see more detailed usages: [examples](./examples/) + +## Platform Notes + +### Windows + +- If DWM composition is disabled in some very rare cases (only possible on Windows 7), the top-left corner and top-right corner will appear in round shape. + +### Linux + +- FramelessHelper will force your application to use the _XCB_ platform plugin when running on Wayland. + +### macOS -Please write down your feature requests and bug reports in here: . Thanks! +- The three system buttons on the title bar can't be made hidden for Qt Widgets applications, for some unknown reason. ## License diff --git a/doc/linux_dark.png b/doc/linux_dark.png new file mode 100644 index 00000000..9294eddb Binary files /dev/null and b/doc/linux_dark.png differ diff --git a/doc/linux_light.png b/doc/linux_light.png new file mode 100644 index 00000000..3bd11fbc Binary files /dev/null and b/doc/linux_light.png differ diff --git a/doc/mac_dark.png b/doc/mac_dark.png new file mode 100644 index 00000000..8753e231 Binary files /dev/null and b/doc/mac_dark.png differ diff --git a/doc/mac_light.png b/doc/mac_light.png new file mode 100644 index 00000000..01274d56 Binary files /dev/null and b/doc/mac_light.png differ diff --git a/doc/win_dark.png b/doc/win_dark.png new file mode 100644 index 00000000..cc1e8a02 Binary files /dev/null and b/doc/win_dark.png differ diff --git a/doc/win_light.png b/doc/win_light.png new file mode 100644 index 00000000..5b09941b Binary files /dev/null and b/doc/win_light.png differ diff --git a/include/FramelessHelper/Core/utils.h b/include/FramelessHelper/Core/utils.h index f8d48bbc..fa330458 100644 --- a/include/FramelessHelper/Core/utils.h +++ b/include/FramelessHelper/Core/utils.h @@ -61,9 +61,10 @@ FRAMELESSHELPER_CORE_API void moveWindowToDesktopCenter( [[nodiscard]] FRAMELESSHELPER_CORE_API bool isWin8OrGreater(); [[nodiscard]] FRAMELESSHELPER_CORE_API bool isWin8Point1OrGreater(); [[nodiscard]] FRAMELESSHELPER_CORE_API bool isWin10OrGreater(); -[[nodiscard]] FRAMELESSHELPER_CORE_API bool isWin101607OrGreater(); -[[nodiscard]] FRAMELESSHELPER_CORE_API bool isWin101809OrGreater(); -[[nodiscard]] FRAMELESSHELPER_CORE_API bool isWin101903OrGreater(); +[[nodiscard]] FRAMELESSHELPER_CORE_API bool isWin10_1607OrGreater(); +[[nodiscard]] FRAMELESSHELPER_CORE_API bool isWin10_1809OrGreater(); +[[nodiscard]] FRAMELESSHELPER_CORE_API bool isWin10_1903OrGreater(); +[[nodiscard]] FRAMELESSHELPER_CORE_API bool isWin10_2004OrGreater(); [[nodiscard]] FRAMELESSHELPER_CORE_API bool isWin11OrGreater(); [[nodiscard]] FRAMELESSHELPER_CORE_API bool isDwmCompositionEnabled(); FRAMELESSHELPER_CORE_API void triggerFrameChange(const WId windowId); diff --git a/src/core/framelesshelper_win.cpp b/src/core/framelesshelper_win.cpp index 1585b4d0..e0ed7376 100644 --- a/src/core/framelesshelper_win.cpp +++ b/src/core/framelesshelper_win.cpp @@ -245,12 +245,12 @@ void FramelessHelperWin::addWindow(const UserSettings &settings, const SystemPar } Utils::updateInternalWindowFrameMargins(params.getWindowHandle(), true); Utils::updateWindowFrameMargins(windowId, false); - if (Utils::isWin101607OrGreater()) { + if (Utils::isWin10_1607OrGreater()) { const bool dark = Utils::shouldAppsUseDarkMode(); if (!(settings.options & Option::DontTouchWindowFrameBorderColor)) { Utils::updateWindowFrameBorderColor(windowId, dark); } - if (Utils::isWin101809OrGreater()) { + if (Utils::isWin10_1809OrGreater()) { if (settings.options & Option::SyncNativeControlsThemeWithSystem) { Utils::updateGlobalWin32ControlsTheme(windowId, dark); } @@ -821,7 +821,7 @@ bool FramelessHelperWin::nativeEventFilter(const QByteArray &eventType, void *me } bool systemThemeChanged = ((uMsg == WM_THEMECHANGED) || (uMsg == WM_SYSCOLORCHANGE) || (uMsg == WM_DWMCOLORIZATIONCOLORCHANGED)); - if (Utils::isWin101607OrGreater()) { + if (Utils::isWin10_1607OrGreater()) { if (uMsg == WM_SETTINGCHANGE) { if ((wParam == 0) && (QString::fromWCharArray(reinterpret_cast(lParam)) .compare(qThemeSettingChangeEventName, Qt::CaseInsensitive) == 0)) { @@ -830,7 +830,7 @@ bool FramelessHelperWin::nativeEventFilter(const QByteArray &eventType, void *me if (!(data.settings.options & Option::DontTouchWindowFrameBorderColor)) { Utils::updateWindowFrameBorderColor(windowId, dark); } - if (Utils::isWin101809OrGreater()) { + if (Utils::isWin10_1809OrGreater()) { if (data.settings.options & Option::SyncNativeControlsThemeWithSystem) { Utils::updateGlobalWin32ControlsTheme(windowId, dark); } diff --git a/src/core/framelesswindowsmanager.cpp b/src/core/framelesswindowsmanager.cpp index 881a43fd..e6362aac 100644 --- a/src/core/framelesswindowsmanager.cpp +++ b/src/core/framelesswindowsmanager.cpp @@ -204,6 +204,9 @@ void FramelessWindowsManagerPrivate::initialize() m_colorizationArea = Utils::getDwmColorizationArea(); m_accentColor = Utils::getDwmColorizationColor(); #endif +#ifdef Q_OS_LINUX + m_accentColor = Utils::getWmThemeColor(); +#endif #ifdef Q_OS_MACOS m_accentColor = Utils::getControlsAccentColor(); #endif diff --git a/src/core/utils_linux.cpp b/src/core/utils_linux.cpp index 941e7ac6..6e42c088 100644 --- a/src/core/utils_linux.cpp +++ b/src/core/utils_linux.cpp @@ -34,21 +34,21 @@ FRAMELESSHELPER_BEGIN_NAMESPACE using namespace Global; -static constexpr const auto _NET_WM_MOVERESIZE_SIZE_TOPLEFT = 0; -static constexpr const auto _NET_WM_MOVERESIZE_SIZE_TOP = 1; -static constexpr const auto _NET_WM_MOVERESIZE_SIZE_TOPRIGHT = 2; -static constexpr const auto _NET_WM_MOVERESIZE_SIZE_RIGHT = 3; -static constexpr const auto _NET_WM_MOVERESIZE_SIZE_BOTTOMRIGHT = 4; -static constexpr const auto _NET_WM_MOVERESIZE_SIZE_BOTTOM = 5; -static constexpr const auto _NET_WM_MOVERESIZE_SIZE_BOTTOMLEFT = 6; -static constexpr const auto _NET_WM_MOVERESIZE_SIZE_LEFT = 7; -static constexpr const auto _NET_WM_MOVERESIZE_MOVE = 8; - -static constexpr const char WM_MOVERESIZE_OPERATION_NAME[] = "_NET_WM_MOVERESIZE"; - -static constexpr const char GTK_THEME_NAME_ENV_VAR[] = "GTK_THEME"; -static constexpr const char GTK_THEME_NAME_PROP[] = "gtk-theme-name"; -static constexpr const char GTK_THEME_PREFER_DARK_PROP[] = "gtk-application-prefer-dark-theme"; +[[maybe_unused]] static constexpr const auto _NET_WM_MOVERESIZE_SIZE_TOPLEFT = 0; +[[maybe_unused]] static constexpr const auto _NET_WM_MOVERESIZE_SIZE_TOP = 1; +[[maybe_unused]] static constexpr const auto _NET_WM_MOVERESIZE_SIZE_TOPRIGHT = 2; +[[maybe_unused]] static constexpr const auto _NET_WM_MOVERESIZE_SIZE_RIGHT = 3; +[[maybe_unused]] static constexpr const auto _NET_WM_MOVERESIZE_SIZE_BOTTOMRIGHT = 4; +[[maybe_unused]] static constexpr const auto _NET_WM_MOVERESIZE_SIZE_BOTTOM = 5; +[[maybe_unused]] static constexpr const auto _NET_WM_MOVERESIZE_SIZE_BOTTOMLEFT = 6; +[[maybe_unused]] static constexpr const auto _NET_WM_MOVERESIZE_SIZE_LEFT = 7; +[[maybe_unused]] static constexpr const auto _NET_WM_MOVERESIZE_MOVE = 8; + +[[maybe_unused]] static constexpr const char WM_MOVERESIZE_OPERATION_NAME[] = "_NET_WM_MOVERESIZE"; + +[[maybe_unused]] static constexpr const char GTK_THEME_NAME_ENV_VAR[] = "GTK_THEME"; +[[maybe_unused]] static constexpr const char GTK_THEME_NAME_PROP[] = "gtk-theme-name"; +[[maybe_unused]] static constexpr const char GTK_THEME_PREFER_DARK_PROP[] = "gtk-application-prefer-dark-theme"; FRAMELESSHELPER_STRING_CONSTANT2(GTK_THEME_DARK_REGEX, "[:-]dark") template @@ -80,7 +80,8 @@ template return result; } -[[nodiscard]] static inline int qtEdgesToWmMoveOrResizeOperation(const Qt::Edges edges) +[[maybe_unused]] [[nodiscard]] static inline int + qtEdgesToWmMoveOrResizeOperation(const Qt::Edges edges) { if (edges == Qt::Edges{}) { return -1; @@ -112,7 +113,8 @@ template return -1; } -static inline void doStartSystemMoveResize(const WId windowId, const QPoint &globalPos, const int edges) +[[maybe_unused]] static inline void + doStartSystemMoveResize(const WId windowId, const QPoint &globalPos, const int edges) { Q_ASSERT(windowId); Q_ASSERT(edges >= 0); @@ -161,11 +163,16 @@ void Utils::startSystemMove(QWindow *window, const QPoint &globalPos) if (!window) { return; } +#if (QT_VERSION >= QT_VERSION_CHECK(5, 15, 0)) + Q_UNUSED(globalPos); + window->startSystemMove(); +#else // Qt always gives us logical coordinates, however, the native APIs // are expecting device coordinates. const qreal dpr = window->devicePixelRatio(); const QPoint globalPos2 = QPointF(QPointF(globalPos) * dpr).toPoint(); doStartSystemMoveResize(window->winId(), globalPos2, _NET_WM_MOVERESIZE_MOVE); +#endif } void Utils::startSystemResize(QWindow *window, const Qt::Edges edges, const QPoint &globalPos) @@ -177,6 +184,10 @@ void Utils::startSystemResize(QWindow *window, const Qt::Edges edges, const QPoi if (edges == Qt::Edges{}) { return; } +#if (QT_VERSION >= QT_VERSION_CHECK(5, 15, 0)) + Q_UNUSED(globalPos); + window->startSystemResize(edges); +#else const int section = qtEdgesToWmMoveOrResizeOperation(edges); if (section < 0) { return; @@ -186,6 +197,7 @@ void Utils::startSystemResize(QWindow *window, const Qt::Edges edges, const QPoi const qreal dpr = window->devicePixelRatio(); const QPoint globalPos2 = QPointF(QPointF(globalPos) * dpr).toPoint(); doStartSystemMoveResize(window->winId(), globalPos2, section); +#endif } bool Utils::isTitleBarColorized() diff --git a/src/core/utils_mac.mm b/src/core/utils_mac.mm index 20155b9a..eee2d227 100644 --- a/src/core/utils_mac.mm +++ b/src/core/utils_mac.mm @@ -141,29 +141,6 @@ void setSystemTitleBarVisible(const bool visible) return [nsview window]; } -static inline void mac_windowStartNativeDrag(const WId windowId, const QPoint &globalPos) -{ - Q_ASSERT(windowId); - if (!windowId) { - return; - } - const NSWindow * const nswindow = mac_getNSWindow(windowId); - Q_ASSERT(nswindow); - if (!nswindow) { - return; - } - const CGEventRef clickDown = CGEventCreateMouseEvent( - NULL, kCGEventLeftMouseDown, CGPointMake(globalPos.x(), globalPos.y()), kCGMouseButtonLeft); - NSEvent * const nsevent = [NSEvent eventWithCGEvent:clickDown]; - Q_ASSERT(nsevent); - if (!nsevent) { - CFRelease(clickDown); - return; - } - [nswindow performWindowDragWithEvent:nsevent]; - CFRelease(clickDown); -} - SystemTheme Utils::getSystemTheme() { // ### TODO: how to detect high contrast mode on macOS? @@ -199,14 +176,43 @@ static inline void mac_windowStartNativeDrag(const WId windowId, const QPoint &g if (!window) { return; } - mac_windowStartNativeDrag(window->winId(), globalPos); +#if (QT_VERSION >= QT_VERSION_CHECK(5, 15, 0)) + Q_UNUSED(globalPos); + window->startSystemMove(); +#else + const NSWindow * const nswindow = mac_getNSWindow(window->winId()); + Q_ASSERT(nswindow); + if (!nswindow) { + return; + } + const CGEventRef clickDown = CGEventCreateMouseEvent(NULL, kCGEventLeftMouseDown, + CGPointMake(globalPos.x(), globalPos.y()), kCGMouseButtonLeft); + NSEvent * const nsevent = [NSEvent eventWithCGEvent:clickDown]; + Q_ASSERT(nsevent); + if (!nsevent) { + CFRelease(clickDown); + return; + } + [nswindow performWindowDragWithEvent:nsevent]; + CFRelease(clickDown); +#endif } void Utils::startSystemResize(QWindow *window, const Qt::Edges edges, const QPoint &globalPos) { - Q_UNUSED(window); - Q_UNUSED(edges); + Q_ASSERT(window); + if (!window) { + return; + } + if (edges == Qt::Edges{}) { + return; + } +#if (QT_VERSION >= QT_VERSION_CHECK(5, 15, 0)) Q_UNUSED(globalPos); + window->startSystemResize(edges); +#else + Q_UNUSED(globalPos); +#endif } QColor Utils::getControlsAccentColor() diff --git a/src/core/utils_win.cpp b/src/core/utils_win.cpp index d40535b2..fcee7466 100644 --- a/src/core/utils_win.cpp +++ b/src/core/utils_win.cpp @@ -113,9 +113,25 @@ FRAMELESSHELPER_STRING_CONSTANT(SystemParametersInfoW) FRAMELESSHELPER_STRING_CONSTANT2(SetWindowLongPtrW, "SetWindowLongW") #endif FRAMELESSHELPER_STRING_CONSTANT(ReleaseCapture) +FRAMELESSHELPER_STRING_CONSTANT(SetWindowTheme) +FRAMELESSHELPER_STRING_CONSTANT(SetProcessDpiAwarenessContext) +FRAMELESSHELPER_STRING_CONSTANT(SetProcessDpiAwareness) +FRAMELESSHELPER_STRING_CONSTANT(SetProcessDPIAware) +FRAMELESSHELPER_STRING_CONSTANT(GetDpiForMonitor) +FRAMELESSHELPER_STRING_CONSTANT(MonitorFromPoint) +FRAMELESSHELPER_STRING_CONSTANT(D2D1CreateFactory) +FRAMELESSHELPER_STRING_CONSTANT(ReloadSystemMetrics) +FRAMELESSHELPER_STRING_CONSTANT(GetDC) +FRAMELESSHELPER_STRING_CONSTANT(ReleaseDC) +FRAMELESSHELPER_STRING_CONSTANT(GetDeviceCaps) +FRAMELESSHELPER_STRING_CONSTANT(DwmSetWindowAttribute) +FRAMELESSHELPER_STRING_CONSTANT(EnableMenuItem) +FRAMELESSHELPER_STRING_CONSTANT(SetMenuDefaultItem) +FRAMELESSHELPER_STRING_CONSTANT(HiliteMenuItem) +FRAMELESSHELPER_STRING_CONSTANT(TrackPopupMenu) -#if (QT_VERSION < QT_VERSION_CHECK(5, 9, 0)) -[[nodiscard]] static inline bool isWindowsVersionOrGreater(const DWORD dwMajor, const DWORD dwMinor, const DWORD dwBuild) +[[maybe_unused]] [[nodiscard]] static inline bool + isWindowsVersionOrGreater(const DWORD dwMajor, const DWORD dwMinor, const DWORD dwBuild) { OSVERSIONINFOEXW osvi; SecureZeroMemory(&osvi, sizeof(osvi)); @@ -128,9 +144,8 @@ FRAMELESSHELPER_STRING_CONSTANT(ReleaseCapture) VER_SET_CONDITION(dwlConditionMask, VER_MAJORVERSION, op); VER_SET_CONDITION(dwlConditionMask, VER_MINORVERSION, op); VER_SET_CONDITION(dwlConditionMask, VER_BUILDNUMBER, op); - return (VerifyVersionInfoW(&osvi, VER_MAJORVERSION | VER_MINORVERSION | VER_BUILDNUMBER, dwlConditionMask) != FALSE); + return (VerifyVersionInfoW(&osvi, (VER_MAJORVERSION | VER_MINORVERSION | VER_BUILDNUMBER), dwlConditionMask) != FALSE); } -#endif [[nodiscard]] static inline QString __getSystemErrorMessage(const QString &function, const DWORD code) { @@ -186,7 +201,8 @@ FRAMELESSHELPER_STRING_CONSTANT(ReleaseCapture) } } -[[nodiscard]] static inline DWORD qtEdgesToWin32Orientation(const Qt::Edges edges) +[[maybe_unused]] [[nodiscard]] static inline + DWORD qtEdgesToWin32Orientation(const Qt::Edges edges) { if (edges == Qt::Edges{}) { return 0; @@ -563,12 +579,18 @@ void Utils::showSystemMenu(const WId windowId, const QPoint &pos, const QPoint & const QPoint adjustment = (maxOrFull ? QPoint(0, 0) : offset); const int xPos = (pos.x() + adjustment.x()); const int yPos = (pos.y() + adjustment.y()); - const int ret = TrackPopupMenu(hMenu, (TPM_RETURNCMD | (QGuiApplication::isRightToLeft() - ? TPM_RIGHTALIGN : TPM_LEFTALIGN)), xPos, yPos, 0, hWnd, nullptr); - if (ret != 0) { - if (PostMessageW(hWnd, WM_SYSCOMMAND, ret, 0) == FALSE) { - qWarning() << getSystemErrorMessage(kPostMessageW); + const int result = TrackPopupMenu(hMenu, (TPM_RETURNCMD | (QGuiApplication::isRightToLeft() + ? TPM_RIGHTALIGN : TPM_LEFTALIGN)), xPos, yPos, 0, hWnd, nullptr); + // TrackPopupMenu returns 0: the user cancelled the menu, or some error occurred. + if (result == 0) { + const DWORD dwError = GetLastError(); + if (dwError != ERROR_SUCCESS) { + qWarning() << __getSystemErrorMessage(kTrackPopupMenu, dwError); } + return; + } + if (PostMessageW(hWnd, WM_SYSCOMMAND, result, 0) == FALSE) { + qWarning() << getSystemErrorMessage(kPostMessageW); } } @@ -698,7 +720,7 @@ void Utils::syncWmPaintWithDwm() } } -bool Utils::isWin101607OrGreater() +bool Utils::isWin10_1607OrGreater() { #if (QT_VERSION >= QT_VERSION_CHECK(5, 9, 0)) static const bool result = (QOperatingSystemVersion::current() >= QOperatingSystemVersion(QOperatingSystemVersion::Windows, 10, 0, 14393)); @@ -708,7 +730,7 @@ bool Utils::isWin101607OrGreater() return result; } -bool Utils::isWin101809OrGreater() +bool Utils::isWin10_1809OrGreater() { #if (QT_VERSION >= QT_VERSION_CHECK(6, 3, 0)) static const bool result = (QOperatingSystemVersion::current() >= QOperatingSystemVersion::Windows10_1809); @@ -720,7 +742,7 @@ bool Utils::isWin101809OrGreater() return result; } -bool Utils::isWin101903OrGreater() +bool Utils::isWin10_1903OrGreater() { #if (QT_VERSION >= QT_VERSION_CHECK(6, 3, 0)) static const bool result = (QOperatingSystemVersion::current() >= QOperatingSystemVersion::Windows10_1903); @@ -732,6 +754,18 @@ bool Utils::isWin101903OrGreater() return result; } +bool Utils::isWin10_2004OrGreater() +{ +#if (QT_VERSION >= QT_VERSION_CHECK(6, 3, 0)) + static const bool result = (QOperatingSystemVersion::current() >= QOperatingSystemVersion::Windows10_2004); +#elif (QT_VERSION >= QT_VERSION_CHECK(5, 9, 0)) + static const bool result = (QOperatingSystemVersion::current() >= QOperatingSystemVersion(QOperatingSystemVersion::Windows, 10, 0, 19041)); +#else + static const bool result = isWindowsVersionOrGreater(10, 0, 19041); +#endif + return result; +} + bool Utils::isHighContrastModeEnabled() { HIGHCONTRASTW hc; @@ -750,12 +784,17 @@ quint32 Utils::getPrimaryScreenDpi(const bool horizontal) reinterpret_cast( QSystemLibrary::resolve(kshcore, "GetDpiForMonitor")); if (pGetDpiForMonitor) { - const HMONITOR monitor = MonitorFromPoint(POINT{0, 0}, MONITOR_DEFAULTTOPRIMARY); + const HMONITOR monitor = MonitorFromPoint(POINT{50, 50}, MONITOR_DEFAULTTOPRIMARY); if (monitor) { UINT dpiX = 0, dpiY = 0; - if (SUCCEEDED(pGetDpiForMonitor(monitor, MDT_EFFECTIVE_DPI, &dpiX, &dpiY))) { + const HRESULT hr = pGetDpiForMonitor(monitor, MDT_EFFECTIVE_DPI, &dpiX, &dpiY); + if (SUCCEEDED(hr)) { return (horizontal ? dpiX : dpiY); + } else { + qWarning() << __getSystemErrorMessage(kGetDpiForMonitor, hr); } + } else { + qWarning() << getSystemErrorMessage(kMonitorFromPoint); } } static const auto pD2D1CreateFactory = @@ -763,29 +802,42 @@ quint32 Utils::getPrimaryScreenDpi(const bool horizontal) QSystemLibrary::resolve(kd2d1, "D2D1CreateFactory")); if (pD2D1CreateFactory) { CComPtr d2dFactory = nullptr; - if (SUCCEEDED(pD2D1CreateFactory(D2D1_FACTORY_TYPE_SINGLE_THREADED, __uuidof(ID2D1Factory), - nullptr, reinterpret_cast(&d2dFactory)))) { - if (SUCCEEDED(d2dFactory->ReloadSystemMetrics())) { + HRESULT hr = pD2D1CreateFactory(D2D1_FACTORY_TYPE_SINGLE_THREADED, __uuidof(ID2D1Factory), + nullptr, reinterpret_cast(&d2dFactory)); + if (SUCCEEDED(hr)) { + hr = d2dFactory->ReloadSystemMetrics(); + if (SUCCEEDED(hr)) { FLOAT dpiX = 0.0, dpiY = 0.0; QT_WARNING_PUSH QT_WARNING_DISABLE_DEPRECATED d2dFactory->GetDesktopDpi(&dpiX, &dpiY); QT_WARNING_POP return (horizontal ? quint32(qRound(dpiX)) : quint32(qRound(dpiY))); + } else { + qWarning() << __getSystemErrorMessage(kReloadSystemMetrics, hr); } + } else { + qWarning() << __getSystemErrorMessage(kD2D1CreateFactory, hr); } } const HDC hdc = GetDC(nullptr); if (hdc) { + bool valid = false; const int dpiX = GetDeviceCaps(hdc, LOGPIXELSX); const int dpiY = GetDeviceCaps(hdc, LOGPIXELSY); - ReleaseDC(nullptr, hdc); - if (horizontal && (dpiX > 0)) { - return dpiX; + if ((dpiX > 0) && (dpiY > 0)) { + valid = true; + } else { + qWarning() << getSystemErrorMessage(kGetDeviceCaps); + } + if (ReleaseDC(nullptr, hdc) == 0) { + qWarning() << getSystemErrorMessage(kReleaseDC); } - if (!horizontal && (dpiY > 0)) { - return dpiY; + if (valid) { + return (horizontal ? dpiX : dpiY); } + } else { + qWarning() << getSystemErrorMessage(kGetDC); } return USER_DEFAULT_SCREEN_DPI; } @@ -820,9 +872,14 @@ quint32 Utils::getWindowDpi(const WId windowId, const bool horizontal) const HMONITOR monitor = MonitorFromWindow(hwnd, MONITOR_DEFAULTTONEAREST); if (monitor) { UINT dpiX = 0, dpiY = 0; - if (SUCCEEDED(pGetDpiForMonitor(monitor, MDT_EFFECTIVE_DPI, &dpiX, &dpiY))) { + const HRESULT hr = pGetDpiForMonitor(monitor, MDT_EFFECTIVE_DPI, &dpiX, &dpiY); + if (SUCCEEDED(hr)) { return (horizontal ? dpiX : dpiY); + } else { + qWarning() << __getSystemErrorMessage(kGetDpiForMonitor, hr); } + } else { + qWarning() << getSystemErrorMessage(kMonitorFromWindow); } } return getPrimaryScreenDpi(horizontal); @@ -916,7 +973,7 @@ void Utils::updateWindowFrameBorderColor(const WId windowId, const bool dark) return; } // There's no global dark theme before Win10 1607. - if (!isWin101607OrGreater()) { + if (!isWin10_1607OrGreater()) { return; } static const auto pDwmSetWindowAttribute = @@ -926,11 +983,12 @@ void Utils::updateWindowFrameBorderColor(const WId windowId, const bool dark) return; } const auto hwnd = reinterpret_cast(windowId); + const DWORD mode = (isWin10_2004OrGreater() ? _DWMWA_USE_IMMERSIVE_DARK_MODE : _DWMWA_USE_IMMERSIVE_DARK_MODE_BEFORE_20H1); const BOOL value = (dark ? TRUE : FALSE); - // Whether dark window frame is available or not depends on the runtime system version, - // it's totally OK if it's not available, so just ignore the errors. - pDwmSetWindowAttribute(hwnd, _DWMWA_USE_IMMERSIVE_DARK_MODE_BEFORE_20H1, &value, sizeof(value)); - pDwmSetWindowAttribute(hwnd, _DWMWA_USE_IMMERSIVE_DARK_MODE, &value, sizeof(value)); + const HRESULT hr = pDwmSetWindowAttribute(hwnd, mode, &value, sizeof(value)); + if (FAILED(hr)) { + qWarning() << __getSystemErrorMessage(kDwmSetWindowAttribute, hr); + } } void Utils::fixupQtInternals(const WId windowId) @@ -946,7 +1004,12 @@ void Utils::fixupQtInternals(const WId windowId) qWarning() << getSystemErrorMessage(kGetClassLongPtrW); return; } - const DWORD newClassStyle = (oldClassStyle | CS_HREDRAW | CS_VREDRAW); + // CS_HREDRAW/CS_VREDRAW will trigger a repaint event when the window size changes + // horizontally/vertically, which will cause flicker and jitter during window + // resizing, mostly for the applications which do all the painting by themselves. + // So we remove these flags from the window class here, Qt by default won't add them + // but let's make it extra safe. + const DWORD newClassStyle = (oldClassStyle & ~(CS_HREDRAW | CS_VREDRAW)); SetLastError(ERROR_SUCCESS); if (SetClassLongPtrW(hwnd, GCL_STYLE, static_cast(newClassStyle)) == 0) { qWarning() << getSystemErrorMessage(kSetClassLongPtrW); @@ -964,9 +1027,7 @@ void Utils::fixupQtInternals(const WId windowId) // windows, for example, it will affect DWM's default policy. And Qt will also not add // the "WS_OVERLAPPED" flag to the windows in some cases, which also causes some trouble // for us. To avoid some weird bugs, we do the correction here: remove the WS_POPUP flag - // and add the WS_OVERLAPPED flag, unconditionally. If your window really don't need this - // correction, it also means you should not use this framework, because without this - // correction, our core frameless functionality will be broken in some degree. + // and add the WS_OVERLAPPED flag, unconditionally. const DWORD newWindowStyle = ((oldWindowStyle & ~WS_POPUP) | WS_OVERLAPPED); SetLastError(ERROR_SUCCESS); if (SetWindowLongPtrW(hwnd, GWL_STYLE, static_cast(newWindowStyle)) == 0) { @@ -983,6 +1044,9 @@ void Utils::startSystemMove(QWindow *window, const QPoint &globalPos) if (!window) { return; } +#if (QT_VERSION >= QT_VERSION_CHECK(5, 15, 0)) + window->startSystemMove(); +#else if (ReleaseCapture() == FALSE) { qWarning() << getSystemErrorMessage(kReleaseCapture); return; @@ -991,6 +1055,7 @@ void Utils::startSystemMove(QWindow *window, const QPoint &globalPos) if (PostMessageW(hwnd, WM_SYSCOMMAND, 0xF012 /*SC_DRAGMOVE*/, 0) == FALSE) { qWarning() << getSystemErrorMessage(kPostMessageW); } +#endif } void Utils::startSystemResize(QWindow *window, const Qt::Edges edges, const QPoint &globalPos) @@ -1003,6 +1068,9 @@ void Utils::startSystemResize(QWindow *window, const Qt::Edges edges, const QPoi if (edges == Qt::Edges{}) { return; } +#if (QT_VERSION >= QT_VERSION_CHECK(5, 15, 0)) + window->startSystemResize(edges); +#else if (ReleaseCapture() == FALSE) { qWarning() << getSystemErrorMessage(kReleaseCapture); return; @@ -1011,6 +1079,7 @@ void Utils::startSystemResize(QWindow *window, const Qt::Edges edges, const QPoi if (PostMessageW(hwnd, WM_SYSCOMMAND, qtEdgesToWin32Orientation(edges), 0) == FALSE) { qWarning() << getSystemErrorMessage(kPostMessageW); } +#endif } bool Utils::isWindowFrameBorderVisible() @@ -1082,7 +1151,7 @@ void Utils::installSystemMenuHook(const WId windowId, const Options options, con qWarning() << getSystemErrorMessage(kSetWindowLongPtrW); return; } - //triggerFrameChange(windowId); + //triggerFrameChange(windowId); // Crash Win32UtilsHelperData data = {}; data.originalWindowProc = originalWindowProc; data.options = options; @@ -1112,7 +1181,7 @@ void Utils::uninstallSystemMenuHook(const WId windowId) qWarning() << getSystemErrorMessage(kSetWindowLongPtrW); return; } - //triggerFrameChange(windowId); + //triggerFrameChange(windowId); // Crash g_utilsHelper()->data.remove(windowId); } @@ -1138,6 +1207,8 @@ void Utils::tryToBeCompatibleWithQtFramelessWindowHint(const WId windowId, const Qt::WindowFlags newWindowFlags = (enable ? (originalWindowFlags | Qt::FramelessWindowHint) : (originalWindowFlags & ~Qt::FramelessWindowHint)); setWindowFlags(newWindowFlags); + // The trick is to restore the window style. Qt mainly adds the "WS_EX_LAYERED" + // flag to the extended window style. SetLastError(ERROR_SUCCESS); if (SetWindowLongPtrW(hwnd, GWL_STYLE, originalWindowStyle) == 0) { qWarning() << getSystemErrorMessage(kSetWindowLongPtrW); @@ -1159,6 +1230,7 @@ void Utils::setAeroSnappingEnabled(const WId windowId, const bool enable) qWarning() << getSystemErrorMessage(kGetWindowLongPtrW); return; } + // The key is the existence of the "WS_THICKFRAME" flag. const DWORD newWindowStyle = [enable, oldWindowStyle]() -> DWORD { if (enable) { return ((oldWindowStyle & ~WS_POPUP) | WS_THICKFRAME); @@ -1180,21 +1252,36 @@ void Utils::tryToEnableHighestDpiAwarenessLevel() reinterpret_cast( QSystemLibrary::resolve(kuser32, "SetProcessDpiAwarenessContext")); if (pSetProcessDpiAwarenessContext) { - if (pSetProcessDpiAwarenessContext(DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2) != FALSE) { - return; - } - const DWORD dwError = GetLastError(); - // "ERROR_ACCESS_DENIED" means set externally (mostly due to manifest file). - if (dwError == ERROR_ACCESS_DENIED) { + const auto SetProcessDpiAwarenessContext2 = [](const DPI_AWARENESS_CONTEXT context) -> bool { + Q_ASSERT(context); + if (!context) { + return false; + } + if (pSetProcessDpiAwarenessContext(context) != FALSE) { + return true; + } + const DWORD dwError = GetLastError(); + // "ERROR_ACCESS_DENIED" means set externally (mostly due to manifest file). + // Any attempt to change the DPI awareness level through API will always fail, + // so we treat this situation as succeeded. + if (dwError == ERROR_ACCESS_DENIED) { + qDebug() << "FramelessHelper doesn't have access to change this process's DPI awareness level," + " most likely due to it has been setted externally."; + return true; + } + qWarning() << __getSystemErrorMessage(kSetProcessDpiAwarenessContext, dwError); + return false; + }; + if (SetProcessDpiAwarenessContext2(DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2)) { return; } - if (pSetProcessDpiAwarenessContext(DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE) != FALSE) { + if (SetProcessDpiAwarenessContext2(DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE)) { return; } - if (pSetProcessDpiAwarenessContext(DPI_AWARENESS_CONTEXT_SYSTEM_AWARE) != FALSE) { + if (SetProcessDpiAwarenessContext2(DPI_AWARENESS_CONTEXT_SYSTEM_AWARE)) { return; } - if (pSetProcessDpiAwarenessContext(DPI_AWARENESS_CONTEXT_UNAWARE_GDISCALED) != FALSE) { + if (SetProcessDpiAwarenessContext2(DPI_AWARENESS_CONTEXT_UNAWARE_GDISCALED)) { return; } } @@ -1202,24 +1289,35 @@ void Utils::tryToEnableHighestDpiAwarenessLevel() reinterpret_cast( QSystemLibrary::resolve(kshcore, "SetProcessDpiAwareness")); if (pSetProcessDpiAwareness) { - // This enum value is our own extension, so don't check for "E_ACCESSDENIED" - // because it won't appear in anywhere outside of our own code. - if (SUCCEEDED(pSetProcessDpiAwareness(PROCESS_PER_MONITOR_DPI_AWARE_V2))) { - return; - } - const HRESULT hr = pSetProcessDpiAwareness(PROCESS_PER_MONITOR_DPI_AWARE); - if (SUCCEEDED(hr)) { + const auto SetProcessDpiAwareness2 = [](const PROCESS_DPI_AWARENESS pda) -> bool { + const HRESULT hr = pSetProcessDpiAwareness(pda); + if (SUCCEEDED(hr)) { + return true; + } + // "E_ACCESSDENIED" means set externally (mostly due to manifest file). + // Any attempt to change the DPI awareness level through API will always fail, + // so we treat this situation as succeeded. + if (hr == E_ACCESSDENIED) { + qDebug() << "FramelessHelper doesn't have access to change this process's DPI awareness level," + " most likely due to it has been setted externally."; + return true; + } + qWarning() << __getSystemErrorMessage(kSetProcessDpiAwareness, hr); + return false; + }; + if (SetProcessDpiAwareness2(PROCESS_PER_MONITOR_DPI_AWARE_V2)) { return; } - // "E_ACCESSDENIED" means set externally (mostly due to manifest file). - if (hr == E_ACCESSDENIED) { + if (SetProcessDpiAwareness2(PROCESS_PER_MONITOR_DPI_AWARE)) { return; } - if (SUCCEEDED(pSetProcessDpiAwareness(PROCESS_SYSTEM_DPI_AWARE))) { + if (SetProcessDpiAwareness2(PROCESS_SYSTEM_DPI_AWARE)) { return; } } - SetProcessDPIAware(); + if (SetProcessDPIAware() == FALSE) { + qWarning() << getSystemErrorMessage(kSetProcessDPIAware); + } } SystemTheme Utils::getSystemTheme() @@ -1227,7 +1325,7 @@ SystemTheme Utils::getSystemTheme() if (isHighContrastModeEnabled()) { return SystemTheme::HighContrast; } - if (isWin101607OrGreater() && shouldAppsUseDarkMode()) { + if (isWin10_1607OrGreater() && shouldAppsUseDarkMode()) { return SystemTheme::Dark; } return SystemTheme::Light; @@ -1240,7 +1338,7 @@ void Utils::updateGlobalWin32ControlsTheme(const WId windowId, const bool dark) return; } // There's no global dark theme for common Win32 controls before Win10 1809. - if (!isWin101809OrGreater()) { + if (!isWin10_1809OrGreater()) { return; } static const auto pSetWindowTheme = @@ -1250,14 +1348,16 @@ void Utils::updateGlobalWin32ControlsTheme(const WId windowId, const bool dark) return; } const auto hwnd = reinterpret_cast(windowId); - // The result depends on the runtime system version, no need to check. - pSetWindowTheme(hwnd, (dark ? kSystemDarkThemeResourceName : kSystemLightThemeResourceName), nullptr); + const HRESULT hr = pSetWindowTheme(hwnd, (dark ? kSystemDarkThemeResourceName : kSystemLightThemeResourceName), nullptr); + if (FAILED(hr)) { + qWarning() << __getSystemErrorMessage(kSetWindowTheme, hr); + } } bool Utils::shouldAppsUseDarkMode_windows() { // The global dark mode was first introduced in Windows 10 1607. - if (!isWin101607OrGreater()) { + if (!isWin10_1607OrGreater()) { return false; } const auto resultFromRegistry = []() -> bool { @@ -1268,7 +1368,7 @@ bool Utils::shouldAppsUseDarkMode_windows() static const auto pShouldAppsUseDarkMode = reinterpret_cast( QSystemLibrary::resolve(kuxtheme, MAKEINTRESOURCEA(132))); - if (pShouldAppsUseDarkMode && !isWin101903OrGreater()) { + if (pShouldAppsUseDarkMode && !isWin10_1903OrGreater()) { return (pShouldAppsUseDarkMode() != FALSE); } // Starting from Windows 10 1903, "ShouldAppsUseDarkMode()" always return "TRUE"