Final code of version 2.0.0!

Some minor issues are known to exist and they'll get fixed before 2.1 is officially released.

Signed-off-by: Yuhang Zhao <2546789017@qq.com>
This commit is contained in:
Yuhang Zhao 2022-04-23 12:19:38 +08:00
parent 8460995d7f
commit a0a9b8d108
13 changed files with 280 additions and 112 deletions

View File

@ -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
Please write down your feature requests and bug reports in here: <https://github.com/wangwenx190/framelesshelper/issues/104>. Thanks!
```bash
git clone https://github.com/wangwenx190/framelesshelper.git
mkdir build
cd build
cmake -DCMAKE_PREFIX_PATH=<YOUR_QT_SDK_DIR_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
- The three system buttons on the title bar can't be made hidden for Qt Widgets applications, for some unknown reason.
## License

BIN
doc/linux_dark.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 303 KiB

BIN
doc/linux_light.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 294 KiB

BIN
doc/mac_dark.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 515 KiB

BIN
doc/mac_light.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 714 KiB

BIN
doc/win_dark.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 352 KiB

BIN
doc/win_light.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 276 KiB

View File

@ -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);

View File

@ -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<LPCWSTR>(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);
}

View File

@ -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

View File

@ -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;
[[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;
static constexpr const char WM_MOVERESIZE_OPERATION_NAME[] = "_NET_WM_MOVERESIZE";
[[maybe_unused]] 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 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<typename T>
@ -80,7 +80,8 @@ template<typename T>
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<typename T>
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()

View File

@ -141,29 +141,6 @@ Q_GLOBAL_STATIC(NSWindowProxyHash, g_nswindowOverrideHash);
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 @@ void Utils::startSystemMove(QWindow *window, const QPoint &globalPos)
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()

View File

@ -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<decltype(&GetDpiForMonitor)>(
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<ID2D1Factory> d2dFactory = nullptr;
if (SUCCEEDED(pD2D1CreateFactory(D2D1_FACTORY_TYPE_SINGLE_THREADED, __uuidof(ID2D1Factory),
nullptr, reinterpret_cast<void **>(&d2dFactory)))) {
if (SUCCEEDED(d2dFactory->ReloadSystemMetrics())) {
HRESULT hr = pD2D1CreateFactory(D2D1_FACTORY_TYPE_SINGLE_THREADED, __uuidof(ID2D1Factory),
nullptr, reinterpret_cast<void **>(&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 (!horizontal && (dpiY > 0)) {
return dpiY;
if (ReleaseDC(nullptr, hdc) == 0) {
qWarning() << getSystemErrorMessage(kReleaseDC);
}
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<HWND>(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<LONG_PTR>(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<LONG_PTR>(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<decltype(&SetProcessDpiAwarenessContext)>(
QSystemLibrary::resolve(kuser32, "SetProcessDpiAwarenessContext"));
if (pSetProcessDpiAwarenessContext) {
if (pSetProcessDpiAwarenessContext(DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2) != FALSE) {
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;
}
const DWORD dwError = GetLastError();
// "ERROR_ACCESS_DENIED" means set externally (mostly due to manifest file).
if (dwError == ERROR_ACCESS_DENIED) {
if (SetProcessDpiAwarenessContext2(DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE)) {
return;
}
if (pSetProcessDpiAwarenessContext(DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE) != FALSE) {
if (SetProcessDpiAwarenessContext2(DPI_AWARENESS_CONTEXT_SYSTEM_AWARE)) {
return;
}
if (pSetProcessDpiAwarenessContext(DPI_AWARENESS_CONTEXT_SYSTEM_AWARE) != FALSE) {
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<decltype(&SetProcessDpiAwareness)>(
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))) {
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;
}
const HRESULT hr = pSetProcessDpiAwareness(PROCESS_PER_MONITOR_DPI_AWARE);
if (SUCCEEDED(hr)) {
if (SetProcessDpiAwareness2(PROCESS_PER_MONITOR_DPI_AWARE)) {
return;
}
// "E_ACCESSDENIED" means set externally (mostly due to manifest file).
if (hr == E_ACCESSDENIED) {
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<HWND>(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<BOOL(WINAPI *)(VOID)>(
QSystemLibrary::resolve(kuxtheme, MAKEINTRESOURCEA(132)));
if (pShouldAppsUseDarkMode && !isWin101903OrGreater()) {
if (pShouldAppsUseDarkMode && !isWin10_1903OrGreater()) {
return (pShouldAppsUseDarkMode() != FALSE);
}
// Starting from Windows 10 1903, "ShouldAppsUseDarkMode()" always return "TRUE"