From 3e942c3dc8955be577079fbc028ce216e1c594b2 Mon Sep 17 00:00:00 2001 From: SineStriker <55847490+SineStriker@users.noreply.github.com> Date: 周二, 11 2月 2025 19:07:53 +0800 Subject: [PATCH] Fix numerous bugs (#162) --- examples/shared/widgetframe/windowbar_p.h | 5 examples/qml/main.qml | 12 - src/core/contexts/abstractwindowcontext_p.h | 14 + src/quick/quickwindowagent_win.cpp | 83 ++++++++-- examples/mainwindow/light-style.qss | 20 + examples/shared/resources/shared.qrc | 2 src/core/contexts/win32windowcontext.cpp | 90 ++++++++--- src/core/qwindowkit_windows.h | 6 src/core/qwindowkit_windows.cpp | 8 examples/shared/resources/window-bar/pin-fill.svg | 9 + LICENSE | 2 examples/mainwindow/mainwindow.cpp | 54 ++++-- src/quick/quickwindowagent.cpp | 9 examples/shared/widgetframe/windowbar.cpp | 38 ++++ src/core/contexts/abstractwindowcontext.cpp | 33 ++- examples/mainwindow/CMakeLists.txt | 4 examples/shared/widgetframe/windowbar.h | 4 src/core/shared/qwkwindowsextra_p.h | 1 src/core/shared/windows10borderhandler_p.h | 16 + examples/shared/resources/window-bar/pin.svg | 9 + README.md | 2 examples/qml/CMakeLists.txt | 4 src/core/kernel/sharedeventfilter_p.h | 2 src/widgets/widgetwindowagent.cpp | 13 examples/qml/QWKButton.qml | 2 src/core/kernel/nativeeventfilter_p.h | 2 src/CMakeLists.txt | 4 examples/mainwindow/dark-style.qss | 12 + 28 files changed, 323 insertions(+), 137 deletions(-) diff --git a/LICENSE b/LICENSE index 36395aa..d6629a9 100644 --- a/LICENSE +++ b/LICENSE @@ -186,7 +186,7 @@ same "printed page" as the copyright notice for easier identification within third-party archives. - Copyright (C) 2023-2024 Stdware Collections (https://www.github.com/stdware) + Copyright (C) 2023-2025 Stdware Collections (https://www.github.com/stdware) Copyright (C) 2021-2023 wangwenx190 (Yuhang Zhao) Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/README.md b/README.md index c2e1f9d..6c51124 100644 --- a/README.md +++ b/README.md @@ -12,9 +12,7 @@ You can share your findings, thoughts and ideas on improving / implementing QWindowKit functionalities on more platforms and apps! - Chat with us on [Discord](https://discord.gg/grrM4Tmesy) - - Please inform us if your product uses QWK, we would like to show it on this README! - 涓枃鐢ㄦ埛鍙姞鍏� QQ 缇� 876419693 - - 濡傛灉鎮ㄧ殑浜у搧浣跨敤浜哘WK锛岃鍛婄煡鎴戜滑銆傛垜浠笇鏈涘湪杩欎釜鑷堪鏂囦欢涓婂睍绀哄畠锛� ## Supported Platforms diff --git a/examples/mainwindow/CMakeLists.txt b/examples/mainwindow/CMakeLists.txt index 6196bec..24d2eab 100644 --- a/examples/mainwindow/CMakeLists.txt +++ b/examples/mainwindow/CMakeLists.txt @@ -11,6 +11,4 @@ set_target_properties(${PROJECT_NAME} PROPERTIES CXX_STANDARD 17 CXX_STANDARD_REQUIRED TRUE - WIN32_EXECUTABLE TRUE - MACOSX_BUNDLE TRUE -) +) \ No newline at end of file diff --git a/examples/mainwindow/dark-style.qss b/examples/mainwindow/dark-style.qss index 09f32ab..33ac251 100644 --- a/examples/mainwindow/dark-style.qss +++ b/examples/mainwindow/dark-style.qss @@ -32,9 +32,19 @@ background-color: transparent; } +QWK--WindowBar>QAbstractButton#pin-button { + qproperty-iconNormal: url(":/window-bar/pin.svg"); + qproperty-iconChecked: url(":/window-bar/pin-fill.svg"); + qproperty-iconSize: 15px 15px; +} + +QWK--WindowBar>QAbstractButton#pin-button:hover, +QWK--WindowBar>QAbstractButton#pin-button:pressed { + background-color: rgba(255, 255, 255, 15%); +} + QWK--WindowBar>QAbstractButton#min-button { qproperty-iconNormal: url(":/window-bar/minimize.svg"); - qproperty-iconSize: 12px 12px; } QWK--WindowBar>QAbstractButton#min-button:hover, diff --git a/examples/mainwindow/light-style.qss b/examples/mainwindow/light-style.qss index 9180001..1f7b4c9 100644 --- a/examples/mainwindow/light-style.qss +++ b/examples/mainwindow/light-style.qss @@ -1,13 +1,13 @@ /* Window bar */ QWK--WindowBar[bar-active=true] { - /*background-color: #195ABE;*/ - background-color: transparent; + background-color: #195ABE; + /* background-color: transparent; */ } QWK--WindowBar[bar-active=false] { - /*background-color: #195ABE;*/ - background-color: transparent; + background-color: #195ABE; + /* background-color: transparent; */ } @@ -32,9 +32,19 @@ background-color: transparent; } +QWK--WindowBar>QAbstractButton#pin-button { + qproperty-iconNormal: url(":/window-bar/pin.svg"); + qproperty-iconChecked: url(":/window-bar/pin-fill.svg"); + qproperty-iconSize: 15px 15px; +} + +QWK--WindowBar>QAbstractButton#pin-button:hover, +QWK--WindowBar>QAbstractButton#pin-button:pressed { + background-color: rgba(0, 0, 0, 15%); +} + QWK--WindowBar>QAbstractButton#min-button { qproperty-iconNormal: url(":/window-bar/minimize.svg"); - qproperty-iconSize: 12px 12px; } QWK--WindowBar>QAbstractButton#min-button:hover, diff --git a/examples/mainwindow/mainwindow.cpp b/examples/mainwindow/mainwindow.cpp index 7492b43..a8d2c61 100644 --- a/examples/mainwindow/mainwindow.cpp +++ b/examples/mainwindow/mainwindow.cpp @@ -188,24 +188,26 @@ winStyleGroup->addAction(acrylicAction); winStyleGroup->addAction(micaAction); winStyleGroup->addAction(micaAltAction); - connect(winStyleGroup, &QActionGroup::triggered, this, [this, winStyleGroup](QAction *action) { - // Unset all custom style attributes first, otherwise the style will not display correctly - for (const QAction* _act : winStyleGroup->actions()) { - const QString data = _act->data().toString(); - if (data.isEmpty() || data == QStringLiteral("none")) { - continue; - } - windowAgent->setWindowAttribute(data, false); - } - const QString data = action->data().toString(); - if (data == QStringLiteral("none")) { - setProperty("custom-style", false); - } else if (!data.isEmpty()) { - windowAgent->setWindowAttribute(data, true); - setProperty("custom-style", true); - } - style()->polish(this); - }); + connect(winStyleGroup, &QActionGroup::triggered, this, + [this, winStyleGroup](QAction *action) { + // Unset all custom style attributes first, otherwise the style will not display + // correctly + for (const QAction *_act : winStyleGroup->actions()) { + const QString data = _act->data().toString(); + if (data.isEmpty() || data == QStringLiteral("none")) { + continue; + } + windowAgent->setWindowAttribute(data, false); + } + const QString data = action->data().toString(); + if (data == QStringLiteral("none")) { + setProperty("custom-style", false); + } else if (!data.isEmpty()) { + windowAgent->setWindowAttribute(data, true); + setProperty("custom-style", true); + } + style()->polish(this); + }); #elif defined(Q_OS_MAC) auto darkBlurAction = new QAction(tr("Dark blur"), menuBar); @@ -283,6 +285,12 @@ iconButton->setObjectName(QStringLiteral("icon-button")); iconButton->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Preferred); + auto pinButton = new QWK::WindowButton(); + pinButton->setCheckable(true); + pinButton->setObjectName(QStringLiteral("pin-button")); + pinButton->setProperty("system-button", true); + pinButton->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Preferred); + auto minButton = new QWK::WindowButton(); minButton->setObjectName(QStringLiteral("min-button")); minButton->setProperty("system-button", true); @@ -303,6 +311,7 @@ auto windowBar = new QWK::WindowBar(); #ifndef Q_OS_MAC windowBar->setIconButton(iconButton); + windowBar->setPinButton(pinButton); windowBar->setMinButton(minButton); windowBar->setMaxButton(maxButton); windowBar->setCloseButton(closeButton); @@ -313,6 +322,7 @@ windowAgent->setTitleBar(windowBar); #ifndef Q_OS_MAC + windowAgent->setHitTestVisible(pinButton, true); windowAgent->setSystemButton(QWK::WindowAgentBase::WindowIcon, iconButton); windowAgent->setSystemButton(QWK::WindowAgentBase::Minimize, minButton); windowAgent->setSystemButton(QWK::WindowAgentBase::Maximize, maxButton); @@ -331,6 +341,14 @@ #ifndef Q_OS_MAC + connect(windowBar, &QWK::WindowBar::pinRequested, this, [this, pinButton](bool pin){ + if (isHidden() || isMinimized() || isMaximized() || isFullScreen()) { + return; + } + setWindowFlag(Qt::WindowStaysOnTopHint, pin); + show(); + pinButton->setChecked(pin); + }); connect(windowBar, &QWK::WindowBar::minimizeRequested, this, &QWidget::showMinimized); connect(windowBar, &QWK::WindowBar::maximizeRequested, this, [this, maxButton](bool max) { if (max) { diff --git a/examples/qml/CMakeLists.txt b/examples/qml/CMakeLists.txt index fe1cbbf..1755395 100644 --- a/examples/qml/CMakeLists.txt +++ b/examples/qml/CMakeLists.txt @@ -11,6 +11,4 @@ set_target_properties(${PROJECT_NAME} PROPERTIES CXX_STANDARD 17 CXX_STANDARD_REQUIRED TRUE - WIN32_EXECUTABLE TRUE - MACOSX_BUNDLE TRUE -) +) \ No newline at end of file diff --git a/examples/qml/QWKButton.qml b/examples/qml/QWKButton.qml index 8bf58a2..e781d69 100644 --- a/examples/qml/QWKButton.qml +++ b/examples/qml/QWKButton.qml @@ -1,5 +1,5 @@ import QtQuick 2.15 -import QtQuick.Controls.Basic 2.15 +import QtQuick.Controls 2.15 Button { id: root diff --git a/examples/qml/main.qml b/examples/qml/main.qml index edb563e..dd4f284 100644 --- a/examples/qml/main.qml +++ b/examples/qml/main.qml @@ -1,6 +1,6 @@ import QtQuick 2.15 import QtQuick.Window 2.15 -import QtQuick.Controls.Basic 2.15 +import QtQuick.Controls 2.15 import Qt.labs.platform 1.1 import QWindowKit 1.0 @@ -12,8 +12,8 @@ title: qsTr("Hello, world!") Component.onCompleted: { windowAgent.setup(window) + windowAgent.setWindowAttribute("dark-mode", true) window.visible = true - delayInitTimer.start() } QtObject { @@ -30,14 +30,6 @@ running: true repeat: true onTriggered: timeLabel.text = Qt.formatTime(new Date(), "hh:mm:ss") - } - - Timer { - id: delayInitTimer - interval: 100 - running: false - repeat: false - onTriggered: windowAgent.setWindowAttribute("dark-mode", true) } WindowAgent { diff --git a/examples/shared/resources/shared.qrc b/examples/shared/resources/shared.qrc index 5aec715..46efc75 100644 --- a/examples/shared/resources/shared.qrc +++ b/examples/shared/resources/shared.qrc @@ -6,6 +6,8 @@ <file>window-bar/minimize.svg</file> <file>window-bar/restore.svg</file> <file>window-bar/more-line.svg</file> + <file>window-bar/pin.svg</file> + <file>window-bar/pin-fill.svg</file> <file>app/example.png</file> </qresource> </RCC> diff --git a/examples/shared/resources/window-bar/pin-fill.svg b/examples/shared/resources/window-bar/pin-fill.svg new file mode 100644 index 0000000..6be8c4d --- /dev/null +++ b/examples/shared/resources/window-bar/pin-fill.svg @@ -0,0 +1,9 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"> +<svg t="1735286082742" class="icon" viewBox="0 0 1024 1024" version="1.1" + xmlns="http://www.w3.org/2000/svg" p-id="2501" width="512" height="512" + xmlns:xlink="http://www.w3.org/1999/xlink"> + <path + d="M648.728381 130.779429a73.142857 73.142857 0 0 1 22.674286 15.433142l191.561143 191.756191a73.142857 73.142857 0 0 1-22.137905 118.564571l-67.876572 30.061715-127.341714 127.488-10.093714 140.239238a73.142857 73.142857 0 0 1-124.684191 46.445714l-123.66019-123.782095-210.724572 211.699809-51.833904-51.614476 210.846476-211.821714-127.926857-128.024381a73.142857 73.142857 0 0 1 46.299428-124.635429l144.237715-10.776381 125.074285-125.220571 29.379048-67.779048a73.142857 73.142857 0 0 1 96.207238-38.034285z" + p-id="2502" fill="white"></path> +</svg> \ No newline at end of file diff --git a/examples/shared/resources/window-bar/pin.svg b/examples/shared/resources/window-bar/pin.svg new file mode 100644 index 0000000..3c5d0bf --- /dev/null +++ b/examples/shared/resources/window-bar/pin.svg @@ -0,0 +1,9 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"> +<svg t="1735285955420" class="icon" viewBox="0 0 1024 1024" version="1.1" + xmlns="http://www.w3.org/2000/svg" p-id="2053" width="512" height="512" + xmlns:xlink="http://www.w3.org/1999/xlink"> + <path + d="M648.728381 130.779429a73.142857 73.142857 0 0 1 22.674286 15.433142l191.561143 191.756191a73.142857 73.142857 0 0 1-22.137905 118.564571l-67.876572 30.061715-127.341714 127.488-10.093714 140.239238a73.142857 73.142857 0 0 1-124.684191 46.445714l-123.66019-123.782095-210.724572 211.699809-51.833904-51.614476 210.846476-211.821714-127.926857-128.024381a73.142857 73.142857 0 0 1 46.299428-124.635429l144.237715-10.776381 125.074285-125.220571 29.379048-67.779048a73.142857 73.142857 0 0 1 96.207238-38.034285z m-29.086476 67.120761l-34.913524 80.530286-154.087619 154.331429-171.398095 12.751238 303.323428 303.542857 12.044191-167.399619 156.233143-156.428191 80.384-35.59619-191.585524-191.73181z" + p-id="2054" fill="white"></path> +</svg> \ No newline at end of file diff --git a/examples/shared/widgetframe/windowbar.cpp b/examples/shared/widgetframe/windowbar.cpp index 362383f..38a398e 100644 --- a/examples/shared/widgetframe/windowbar.cpp +++ b/examples/shared/widgetframe/windowbar.cpp @@ -79,14 +79,19 @@ return static_cast<QAbstractButton *>(d->widgetAt(WindowBarPrivate::IconButton)); } + QAbstractButton *WindowBar::pinButton() const { + Q_D(const WindowBar); + return static_cast<QAbstractButton *>(d->widgetAt(WindowBarPrivate::PinButton)); + } + QAbstractButton *WindowBar::minButton() const { Q_D(const WindowBar); - return static_cast<QAbstractButton *>(d->widgetAt(WindowBarPrivate::MinimumButton)); + return static_cast<QAbstractButton *>(d->widgetAt(WindowBarPrivate::MinimizeButton)); } QAbstractButton *WindowBar::maxButton() const { Q_D(const WindowBar); - return static_cast<QAbstractButton *>(d->widgetAt(WindowBarPrivate::MaximumButton)); + return static_cast<QAbstractButton *>(d->widgetAt(WindowBarPrivate::MaximizeButton)); } QAbstractButton *WindowBar::closeButton() const { @@ -131,6 +136,17 @@ btn->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Preferred); } + void WindowBar::setPinButton(QAbstractButton *btn) { + Q_D(WindowBar); + auto org = takePinButton(); + if (org) + org->deleteLater(); + if (!btn) + return; + d->setWidgetAt(WindowBarPrivate::PinButton, btn); + connect(btn, &QAbstractButton::clicked, this, &WindowBar::pinRequested); + } + void WindowBar::setMinButton(QAbstractButton *btn) { Q_D(WindowBar); auto org = takeMinButton(); @@ -138,7 +154,7 @@ org->deleteLater(); if (!btn) return; - d->setWidgetAt(WindowBarPrivate::MinimumButton, btn); + d->setWidgetAt(WindowBarPrivate::MinimizeButton, btn); connect(btn, &QAbstractButton::clicked, this, &WindowBar::minimizeRequested); } @@ -149,7 +165,7 @@ org->deleteLater(); if (!btn) return; - d->setWidgetAt(WindowBarPrivate::MaximumButton, btn); + d->setWidgetAt(WindowBarPrivate::MaximizeButton, btn); connect(btn, &QAbstractButton::clicked, this, &WindowBar::maximizeRequested); } @@ -179,9 +195,19 @@ return static_cast<QAbstractButton *>(d->takeWidgetAt(WindowBarPrivate::IconButton)); } + QAbstractButton *WindowBar::takePinButton() { + Q_D(WindowBar); + auto btn = static_cast<QAbstractButton *>(d->takeWidgetAt(WindowBarPrivate::PinButton)); + if (!btn) { + return nullptr; + } + disconnect(btn, &QAbstractButton::clicked, this, &WindowBar::pinRequested); + return btn; + } + QAbstractButton *WindowBar::takeMinButton() { Q_D(WindowBar); - auto btn = static_cast<QAbstractButton *>(d->takeWidgetAt(WindowBarPrivate::MinimumButton)); + auto btn = static_cast<QAbstractButton *>(d->takeWidgetAt(WindowBarPrivate::MinimizeButton)); if (!btn) { return nullptr; } @@ -191,7 +217,7 @@ QAbstractButton *WindowBar::takeMaxButton() { Q_D(WindowBar); - auto btn = static_cast<QAbstractButton *>(d->takeWidgetAt(WindowBarPrivate::MaximumButton)); + auto btn = static_cast<QAbstractButton *>(d->takeWidgetAt(WindowBarPrivate::MaximizeButton)); if (!btn) { return nullptr; } diff --git a/examples/shared/widgetframe/windowbar.h b/examples/shared/widgetframe/windowbar.h index 2e802f2..e9cf04c 100644 --- a/examples/shared/widgetframe/windowbar.h +++ b/examples/shared/widgetframe/windowbar.h @@ -25,6 +25,7 @@ QMenuBar *menuBar() const; QLabel *titleLabel() const; QAbstractButton *iconButton() const; + QAbstractButton *pinButton() const; QAbstractButton *minButton() const; QAbstractButton *maxButton() const; QAbstractButton *closeButton() const; @@ -32,6 +33,7 @@ void setMenuBar(QMenuBar *menuBar); void setTitleLabel(QLabel *label); void setIconButton(QAbstractButton *btn); + void setPinButton(QAbstractButton *btn); void setMinButton(QAbstractButton *btn); void setMaxButton(QAbstractButton *btn); void setCloseButton(QAbstractButton *btn); @@ -39,6 +41,7 @@ QMenuBar *takeMenuBar(); QLabel *takeTitleLabel(); QAbstractButton *takeIconButton(); + QAbstractButton *takePinButton(); QAbstractButton *takeMinButton(); QAbstractButton *takeMaxButton(); QAbstractButton *takeCloseButton(); @@ -53,6 +56,7 @@ void setIconFollowWindow(bool value); Q_SIGNALS: + void pinRequested(bool pin = false); void minimizeRequested(); void maximizeRequested(bool max = false); void closeRequested(); diff --git a/examples/shared/widgetframe/windowbar_p.h b/examples/shared/widgetframe/windowbar_p.h index 36ceb17..35422ae 100644 --- a/examples/shared/widgetframe/windowbar_p.h +++ b/examples/shared/widgetframe/windowbar_p.h @@ -29,8 +29,9 @@ IconButton, MenuWidget, TitleLabel, - MinimumButton, - MaximumButton, + PinButton, + MinimizeButton, + MaximizeButton, CloseButton, }; diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 6279fb5..510c161 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -1,7 +1,9 @@ qm_import(Preprocess) +string(TIMESTAMP _current_year "%Y") + set(QWINDOWKIT_PROJECT_DESCRIPTION "Cross-platform window customization framework") -set(QWINDOWKIT_PROJECT_COPYRIGHT "Copyright 2023 Stdware Collections") +set(QWINDOWKIT_PROJECT_COPYRIGHT "Copyright 2023-${_current_year} Stdware Collections") set(QWINDOWKIT_GENERATED_INCLUDE_DIR ${CMAKE_CURRENT_BINARY_DIR}/../include) set(QWINDOWKIT_BUILD_INCLUDE_DIR ${CMAKE_CURRENT_BINARY_DIR}/../etc/include) diff --git a/src/core/contexts/abstractwindowcontext.cpp b/src/core/contexts/abstractwindowcontext.cpp index f838064..f3a9c7f 100644 --- a/src/core/contexts/abstractwindowcontext.cpp +++ b/src/core/contexts/abstractwindowcontext.cpp @@ -240,13 +240,13 @@ m_windowHandle->installEventFilter(this); // Refresh window attributes - auto attributes = m_windowAttributes; - m_windowAttributes.clear(); - for (auto it = attributes.begin(); it != attributes.end(); ++it) { - if (!windowAttributeChanged(it.key(), it.value(), {})) { + for (auto it = m_windowAttributesOrder.begin(); it != m_windowAttributesOrder.end();) { + if (!windowAttributeChanged(it->first, it->second, {})) { + m_windowAttributes.remove(it->first); + it = m_windowAttributesOrder.erase(it); continue; } - m_windowAttributes.insert(it.key(), it.value()); + ++it; } } @@ -258,7 +258,11 @@ } QVariant AbstractWindowContext::windowAttribute(const QString &key) const { - return m_windowAttributes.value(key); + auto it = m_windowAttributes.find(key); + if (it == m_windowAttributes.end()) { + return {}; + } + return it.value()->second; } bool AbstractWindowContext::setWindowAttribute(const QString &key, const QVariant &attribute) { @@ -267,22 +271,27 @@ if (!attribute.isValid()) { return true; } - if (!m_windowHandle || !windowAttributeChanged(key, attribute, {})) { + if (m_windowHandle && !windowAttributeChanged(key, attribute, {})) { return false; } - m_windowAttributes.insert(key, attribute); + m_windowAttributes.insert(key, + m_windowAttributesOrder.insert(m_windowAttributesOrder.end(), + std::make_pair(key, attribute))); return true; } - if (it.value() == attribute) - return true; - if (!m_windowHandle || !windowAttributeChanged(key, attribute, it.value())) { + auto &listIter = it.value(); + auto &oldAttr = listIter->second; + if (m_windowHandle && !windowAttributeChanged(key, attribute, oldAttr)) { return false; } if (attribute.isValid()) { - it.value() = attribute; + oldAttr = attribute; + m_windowAttributesOrder.splice(m_windowAttributesOrder.end(), m_windowAttributesOrder, + listIter); } else { + m_windowAttributesOrder.erase(listIter); m_windowAttributes.erase(it); } return true; diff --git a/src/core/contexts/abstractwindowcontext_p.h b/src/core/contexts/abstractwindowcontext_p.h index 062494d..e978cb4 100644 --- a/src/core/contexts/abstractwindowcontext_p.h +++ b/src/core/contexts/abstractwindowcontext_p.h @@ -15,7 +15,9 @@ // #include <array> +#include <list> #include <memory> +#include <utility> #include <QtCore/QSet> #include <QtCore/QPointer> @@ -88,9 +90,9 @@ RaiseWindowHook, ShowSystemMenuHook, DefaultColorsHook, - DrawWindows10BorderHook, // Only works on Windows 10, emulated workaround - DrawWindows10BorderHook2, // Only works on Windows 10, native workaround - SystemButtonAreaChangedHook, // Only works on Mac + DrawWindows10BorderHook_Emulated, // Only works on Windows 10, emulated workaround + DrawWindows10BorderHook_Native, // Only works on Windows 10, native workaround + SystemButtonAreaChangedHook, // Only works on Mac }; virtual void virtual_hook(int id, void *data); @@ -122,9 +124,11 @@ QObject *m_titleBar{}; std::array<QObject *, WindowAgentBase::Close + 1> m_systemButtons{}; - QVariantHash m_windowAttributes; + std::list<std::pair<QString, QVariant>> m_windowAttributesOrder; + QHash<QString, decltype(m_windowAttributesOrder)::iterator> m_windowAttributes; + std::unique_ptr<WinIdChangeEventFilter> m_winIdChangeEventFilter; - + void removeSystemButtonsAndHitTestItems(); private: diff --git a/src/core/contexts/win32windowcontext.cpp b/src/core/contexts/win32windowcontext.cpp index 775f2ca..3360b9d 100644 --- a/src/core/contexts/win32windowcontext.cpp +++ b/src/core/contexts/win32windowcontext.cpp @@ -86,7 +86,10 @@ static void setInternalWindowFrameMargins(QWindow *window, const QMargins &margins) { const QVariant marginsVar = QVariant::fromValue(margins); - // TODO: Add comments + // We need to tell Qt we have set a custom margin, because we are hiding + // the title bar by pretending the whole window is filled by client area, + // this however confuses Qt's internal logic. We need to do the following + // hack to let Qt consider the extra margin when changing window geometry. window->setProperty("_q_windowsCustomMargins", marginsVar); #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) if (QPlatformWindow *platformWindow = window->handle()) { @@ -197,8 +200,8 @@ [[maybe_unused]] const auto &cleaner = qScopeGuard([windowThreadProcessId, currentThreadId]() { - ::AttachThreadInput(windowThreadProcessId, currentThreadId, FALSE); // - }); // TODO: Remove it + ::AttachThreadInput(windowThreadProcessId, currentThreadId, FALSE); + }); ::BringWindowToTop(hwnd); // Activate the window too. This will force us to the virtual desktop this @@ -219,12 +222,20 @@ return true; } + const auto windowStyles = ::GetWindowLongPtrW(hWnd, GWL_STYLE); + const bool allowMaximize = windowStyles & WS_MAXIMIZEBOX; + const bool allowMinimize = windowStyles & WS_MINIMIZEBOX; + const bool maxOrFull = isMaximized(hWnd) || isFullScreen(hWnd); ::EnableMenuItem(hMenu, SC_CLOSE, (MF_BYCOMMAND | MFS_ENABLED)); - ::EnableMenuItem(hMenu, SC_MAXIMIZE, - (MF_BYCOMMAND | ((maxOrFull || fixedSize) ? MFS_DISABLED : MFS_ENABLED))); - ::EnableMenuItem(hMenu, SC_RESTORE, - (MF_BYCOMMAND | ((maxOrFull && !fixedSize) ? MFS_ENABLED : MFS_DISABLED))); + ::EnableMenuItem( + hMenu, SC_MAXIMIZE, + (MF_BYCOMMAND | + ((maxOrFull || fixedSize || !allowMaximize) ? MFS_DISABLED : MFS_ENABLED))); + ::EnableMenuItem( + hMenu, SC_RESTORE, + (MF_BYCOMMAND | + ((maxOrFull && !fixedSize && allowMaximize) ? MFS_ENABLED : MFS_DISABLED))); // The first menu item should be selected by default if the menu is brought // up by keyboard. I don't know how to pre-select a menu item but it seems // highlight can do the job. However, there's an annoying issue if we do @@ -236,7 +247,8 @@ // the menu look kind of weird. Currently I don't know how to fix this issue. ::HiliteMenuItem(hWnd, hMenu, SC_RESTORE, (MF_BYCOMMAND | (selectFirstEntry ? MFS_HILITE : MFS_UNHILITE))); - ::EnableMenuItem(hMenu, SC_MINIMIZE, (MF_BYCOMMAND | MFS_ENABLED)); + ::EnableMenuItem(hMenu, SC_MINIMIZE, + (MF_BYCOMMAND | (allowMinimize ? MFS_ENABLED : MFS_DISABLED))); ::EnableMenuItem(hMenu, SC_SIZE, (MF_BYCOMMAND | ((maxOrFull || fixedSize) ? MFS_DISABLED : MFS_ENABLED))); ::EnableMenuItem(hMenu, SC_MOVE, (MF_BYCOMMAND | (maxOrFull ? MFS_DISABLED : MFS_ENABLED))); @@ -355,8 +367,8 @@ static MSG createMessageBlock(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) { MSG msg; - msg.hwnd = hWnd; // re-create MSG structure - msg.message = message; // time and pt fields ignored + msg.hwnd = hWnd; + msg.message = message; msg.wParam = wParam; msg.lParam = lParam; @@ -366,6 +378,8 @@ if (!isNonClientMessage(message)) { ::ScreenToClient(hWnd, &msg.pt); } + + msg.time = ::GetMessageTime(); return msg; } @@ -400,7 +414,7 @@ } // Send to QAbstractEventDispatcher - bool filterNativeEvent(MSG *msg, LRESULT *result) { + static bool filterNativeEvent(MSG *msg, LRESULT *result) { auto dispatcher = QAbstractEventDispatcher::instance(); QT_NATIVE_EVENT_RESULT_TYPE filterResult = *result; if (dispatcher && dispatcher->filterNativeEvent(nativeEventType(), msg, &filterResult)) { @@ -411,7 +425,7 @@ } // Send to QWindowSystemInterface - bool filterNativeEvent(QWindow *window, MSG *msg, LRESULT *result) { + static bool filterNativeEvent(QWindow *window, MSG *msg, LRESULT *result) { QT_NATIVE_EVENT_RESULT_TYPE filterResult = *result; if (QWindowSystemInterface::handleNativeEvent(window, nativeEventType(), msg, &filterResult)) { @@ -624,6 +638,11 @@ // Save window handle mapping g_wndProcHash->insert(hWnd, ctx); + + // Force a WM_NCCALCSIZE message manually to avoid the title bar become visible + // while Qt is re-creating the window (such as setWindowFlag(s) calls). It has + // been observed by our users. + triggerFrameChange(hWnd); } static inline void removeManagedWindow(HWND hWnd) { @@ -688,7 +707,7 @@ } #if QWINDOWKIT_CONFIG(ENABLE_WINDOWS_SYSTEM_BORDERS) - case DrawWindows10BorderHook: { + case DrawWindows10BorderHook_Emulated: { if (!m_windowId) return; @@ -731,7 +750,7 @@ return; } - case DrawWindows10BorderHook2: { + case DrawWindows10BorderHook_Native: { if (!m_windowId) return; @@ -897,7 +916,6 @@ Q_UNUSED(oldAttribute) const auto hwnd = reinterpret_cast<HWND>(m_windowId); - Q_ASSERT(hwnd); if (!hwnd) { return false; } @@ -933,8 +951,26 @@ }; const auto &restoreMargins = [this, &apis, hwnd]() { auto margins = qmargins2margins( - m_windowAttributes.value(QStringLiteral("extra-margins")).value<QMargins>()); + windowAttribute(QStringLiteral("extra-margins")).value<QMargins>()); apis.pDwmExtendFrameIntoClientArea(hwnd, &margins); + }; + + const auto &effectBugWorkaround = [this, hwnd]() { + // We don't need the following *HACK* for QWidget windows. + if (m_host->isWidgetType()) { + return; + } + static QSet<WId> bugWindowSet{}; + if (bugWindowSet.contains(m_windowId)) { + return; + } + bugWindowSet.insert(m_windowId); + RECT rect{}; + ::GetWindowRect(hwnd, &rect); + ::MoveWindow(hwnd, rect.left, rect.top, 1, 1, FALSE); + ::MoveWindow(hwnd, rect.right - 1, rect.bottom - 1, 1, 1, FALSE); + ::MoveWindow(hwnd, rect.left, rect.top, rect.right - rect.left, rect.bottom - rect.top, + FALSE); }; if (key == QStringLiteral("extra-margins")) { @@ -953,7 +989,8 @@ } else { apis.pAllowDarkModeForApp(enable); } - const auto attr = isWin1020H1OrGreater() ? _DWMWA_USE_IMMERSIVE_DARK_MODE : _DWMWA_USE_IMMERSIVE_DARK_MODE_BEFORE_20H1; + const auto attr = isWin1020H1OrGreater() ? _DWMWA_USE_IMMERSIVE_DARK_MODE + : _DWMWA_USE_IMMERSIVE_DARK_MODE_BEFORE_20H1; apis.pDwmSetWindowAttribute(hwnd, attr, &enable, sizeof(enable)); apis.pFlushMenuThemes(); @@ -990,6 +1027,7 @@ } restoreMargins(); } + effectBugWorkaround(); return true; } @@ -1010,6 +1048,7 @@ sizeof(backdropType)); restoreMargins(); } + effectBugWorkaround(); return true; } @@ -1055,6 +1094,7 @@ restoreMargins(); } + effectBugWorkaround(); return true; } @@ -1094,6 +1134,7 @@ apis.pDwmEnableBlurBehindWindow(hwnd, &bb); } } + effectBugWorkaround(); return true; } return false; @@ -1571,7 +1612,7 @@ bool isInTitleBar = isInTitleBarDraggableArea(qtScenePos); WindowAgentBase::SystemButton sysButtonType = WindowAgentBase::Unknown; bool isInCaptionButtons = isInSystemButtons(qtScenePos, &sysButtonType); - bool dontOverrideCursor = false; // ### TODO + static constexpr bool dontOverrideCursor = false; // ### TODO if (isInCaptionButtons) { // Firstly, we set the hit test result to a default value to be able to detect @@ -2019,20 +2060,21 @@ // implement an elaborate client-area preservation technique, and // simply return 0, which means "preserve the entire old client area // and align it with the upper-left corner of our new client area". + const auto clientRect = wParam ? &(reinterpret_cast<LPNCCALCSIZE_PARAMS>(lParam))->rgrc[0] : reinterpret_cast<LPRECT>(lParam); - [[maybe_unused]] const auto& flickerReducer = qScopeGuard([this]() { + [[maybe_unused]] const auto &flickerReducer = qScopeGuard([this]() { // When we receive this message, it means the window size has changed // already, and it seems this message always come before any client // area size notifications (eg. WM_WINDOWPOSCHANGED and WM_SIZE). Let // D3D/VK paint immediately to let user see the latest result as soon // as possible. - const auto& isTargetSurface = [](const QSurface::SurfaceType st){ - return st != QSurface::RasterSurface && st != QSurface::OpenGLSurface - && st != QSurface::RasterGLSurface && st != QSurface::OpenVGSurface; + const auto &isTargetSurface = [](const QSurface::SurfaceType st) { + return st != QSurface::RasterSurface && st != QSurface::OpenGLSurface && + st != QSurface::RasterGLSurface && st != QSurface::OpenVGSurface; }; - if (m_windowHandle && isTargetSurface(m_windowHandle->surfaceType()) - && isDwmCompositionEnabled() && DynamicApis::instance().pDwmFlush) { + if (m_windowHandle && isTargetSurface(m_windowHandle->surfaceType()) && + isDwmCompositionEnabled() && DynamicApis::instance().pDwmFlush) { DynamicApis::instance().pDwmFlush(); } }); diff --git a/src/core/kernel/nativeeventfilter_p.h b/src/core/kernel/nativeeventfilter_p.h index cef78b7..d7d3e3f 100644 --- a/src/core/kernel/nativeeventfilter_p.h +++ b/src/core/kernel/nativeeventfilter_p.h @@ -34,7 +34,7 @@ void removeNativeEventFilter(NativeEventFilter *filter); protected: - QVector<NativeEventFilter *> m_nativeEventFilters; + QList<NativeEventFilter *> m_nativeEventFilters; friend class NativeEventFilter; diff --git a/src/core/kernel/sharedeventfilter_p.h b/src/core/kernel/sharedeventfilter_p.h index eeaf38f..5c1935b 100644 --- a/src/core/kernel/sharedeventfilter_p.h +++ b/src/core/kernel/sharedeventfilter_p.h @@ -33,7 +33,7 @@ void removeSharedEventFilter(SharedEventFilter *filter); protected: - QVector<SharedEventFilter *> m_sharedEventFilters; + QList<SharedEventFilter *> m_sharedEventFilters; friend class SharedEventFilter; diff --git a/src/core/qwindowkit_windows.cpp b/src/core/qwindowkit_windows.cpp index 58c3d0f..640528c 100644 --- a/src/core/qwindowkit_windows.cpp +++ b/src/core/qwindowkit_windows.cpp @@ -71,21 +71,21 @@ return result; } - QPair<DWORD, bool> WindowsRegistryKey::dwordValue(QStringView subKey) const { + std::pair<DWORD, bool> WindowsRegistryKey::dwordValue(QStringView subKey) const { if (!isValid()) - return qMakePair(0, false); + return std::make_pair(0, false); DWORD type; auto subKeyC = reinterpret_cast<const wchar_t *>(subKey.utf16()); if (::RegQueryValueExW(m_key, subKeyC, nullptr, &type, nullptr, nullptr) != ERROR_SUCCESS || type != REG_DWORD) { - return qMakePair(0, false); + return std::make_pair(0, false); } DWORD value = 0; DWORD size = sizeof(value); const bool ok = ::RegQueryValueExW(m_key, subKeyC, nullptr, nullptr, reinterpret_cast<unsigned char *>(&value), &size) == ERROR_SUCCESS; - return qMakePair(value, ok); + return std::make_pair(value, ok); } #endif } \ No newline at end of file diff --git a/src/core/qwindowkit_windows.h b/src/core/qwindowkit_windows.h index d6c0afe..7e9b8a7 100644 --- a/src/core/qwindowkit_windows.h +++ b/src/core/qwindowkit_windows.h @@ -145,7 +145,7 @@ void close(); QString stringValue(QStringView subKey) const; - QPair<DWORD, bool> dwordValue(QStringView subKey) const; + std::pair<DWORD, bool> dwordValue(QStringView subKey) const; private: HKEY m_key; @@ -166,10 +166,10 @@ : QWinRegistryKey(parentHandle, subKey, permissions, access) { } - inline QPair<DWORD, bool> dwordValue(QStringView subKey) const; + inline std::pair<DWORD, bool> dwordValue(QStringView subKey) const; }; - inline QPair<DWORD, bool> WindowsRegistryKey::dwordValue(QStringView subKey) const { + inline std::pair<DWORD, bool> WindowsRegistryKey::dwordValue(QStringView subKey) const { const auto val = value<DWORD>(subKey); if (!val) { return {0, false}; diff --git a/src/core/shared/qwkwindowsextra_p.h b/src/core/shared/qwkwindowsextra_p.h index 65c1faa..8fdc86e 100644 --- a/src/core/shared/qwkwindowsextra_p.h +++ b/src/core/shared/qwkwindowsextra_p.h @@ -21,7 +21,6 @@ #include <QWKCore/qwindowkit_windows.h> #include <QtCore/QtMath> -#include <QtCore/QPair> #include <QtGui/QGuiApplication> #include <QtGui/QStyleHints> #include <QtGui/QPalette> diff --git a/src/core/shared/windows10borderhandler_p.h b/src/core/shared/windows10borderhandler_p.h index cf530e8..8099fe0 100644 --- a/src/core/shared/windows10borderhandler_p.h +++ b/src/core/shared/windows10borderhandler_p.h @@ -46,7 +46,7 @@ } inline void drawBorder() { - ctx->virtual_hook(AbstractWindowContext::DrawWindows10BorderHook2, nullptr); + ctx->virtual_hook(AbstractWindowContext::DrawWindows10BorderHook_Native, nullptr); } inline int borderThickness() const { @@ -94,6 +94,20 @@ break; } + case WM_THEMECHANGED: + case WM_SYSCOLORCHANGE: + case WM_DWMCOLORIZATIONCOLORCHANGED: { + // If we do not refresh this property, the native border will turn white + // permanently (like the dark mode is turned off) after the user changes + // the accent color in system personalization settings. + // So we need this ugly hack to re-apply dark mode to get rid of this + // strange Windows bug. + if (ctx->windowAttribute(QStringLiteral("dark-mode")).toBool()) { + ctx->setWindowAttribute(QStringLiteral("dark-mode"), true); + } + break; + } + default: break; } diff --git a/src/quick/quickwindowagent.cpp b/src/quick/quickwindowagent.cpp index 9270311..aaa321c 100644 --- a/src/quick/quickwindowagent.cpp +++ b/src/quick/quickwindowagent.cpp @@ -44,17 +44,14 @@ return false; } + // Make sure the native window handle is actually created before we apply + // various hooks. But we don't need the actual window handle so just ignore it. + std::ignore = window->winId(); d->setup(window, new QuickItemDelegate()); d->hostWindow = window; #if defined(Q_OS_WINDOWS) && QWINDOWKIT_CONFIG(ENABLE_WINDOWS_SYSTEM_BORDERS) d->setupWindows10BorderWorkaround(); -#endif - -#ifdef Q_OS_WINDOWS - if (!windowAttribute(QStringLiteral("windows-system-border-enabled")).toBool()) { - window->setFlag(Qt::FramelessWindowHint); - } #endif return true; } diff --git a/src/quick/quickwindowagent_win.cpp b/src/quick/quickwindowagent_win.cpp index a47f4c0..acfdd42 100644 --- a/src/quick/quickwindowagent_win.cpp +++ b/src/quick/quickwindowagent_win.cpp @@ -12,6 +12,13 @@ namespace QWK { + static inline bool isWindows1022H2OrGreater() { + RTL_OSVERSIONINFOW rovi = Private::GetRealOSVersion(); + return (rovi.dwMajorVersion > 10) || + (rovi.dwMajorVersion == 10 && + (rovi.dwMinorVersion > 0 || rovi.dwBuildNumber >= 19045)); + } + #if QWINDOWKIT_CONFIG(ENABLE_WINDOWS_SYSTEM_BORDERS) class BorderItem : public QQuickPaintedItem, public Windows10BorderHandler { @@ -19,6 +26,7 @@ explicit BorderItem(QQuickItem *parent, AbstractWindowContext *context); ~BorderItem() override; + bool shouldEnableEmulatedPainter() const; void updateGeometry() override; public: @@ -27,14 +35,34 @@ protected: bool sharedEventFilter(QObject *obj, QEvent *event) override; - -# if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) - volatile bool needPaint = false; + bool nativeEventFilter(const QByteArray &eventType, void *message, + QT_NATIVE_EVENT_RESULT_TYPE *result) override; private: + volatile bool needPaint = false; + void _q_afterSynchronizing(); -# endif + void _q_windowActivityChanged(); }; + + bool BorderItem::shouldEnableEmulatedPainter() const { +# if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) + auto api = window()->rendererInterface()->graphicsApi(); + switch (api) { + case QSGRendererInterface::OpenGL: + // FIXME: experimental, try to find the exact fixed version. + return !isWindows1022H2OrGreater(); + case QSGRendererInterface::Direct3D11: +# if QT_VERSION >= QT_VERSION_CHECK(6, 6, 0) + case QSGRendererInterface::Direct3D12: +# endif + return false; + default: + break; + } +# endif + return true; + } BorderItem::BorderItem(QQuickItem *parent, AbstractWindowContext *context) : QQuickPaintedItem(parent), Windows10BorderHandler(context) { @@ -56,6 +84,8 @@ connect(window(), &QQuickWindow::afterSynchronizing, this, &BorderItem::_q_afterSynchronizing, Qt::DirectConnection); # endif + connect(window(), &QQuickWindow::activeChanged, this, + &BorderItem::_q_windowActivityChanged); // First update if (context->windowId()) { @@ -73,16 +103,7 @@ void BorderItem::paint(QPainter *painter) { Q_UNUSED(painter) - -# if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) - if (auto api = window()->rendererInterface()->graphicsApi(); - !(api == QSGRendererInterface::Direct3D11 - -# if QT_VERSION >= QT_VERSION_CHECK(6, 6, 0) - || api == QSGRendererInterface::Direct3D12 -# endif - )) { -# endif + if (shouldEnableEmulatedPainter()) { QRect rect(QPoint(0, 0), size().toSize()); QRegion region(rect); void *args[] = { @@ -90,12 +111,10 @@ &rect, ®ion, }; - ctx->virtual_hook(AbstractWindowContext::DrawWindows10BorderHook, args); -# if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) + ctx->virtual_hook(AbstractWindowContext::DrawWindows10BorderHook_Emulated, args); } else { needPaint = true; } -# endif } void BorderItem::itemChange(ItemChange change, const ItemChangeData &data) { @@ -124,14 +143,40 @@ return Windows10BorderHandler::sharedEventFilter(obj, event); } -# if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) + bool BorderItem::nativeEventFilter(const QByteArray &eventType, void *message, + QT_NATIVE_EVENT_RESULT_TYPE *result) { + const auto msg = static_cast<const MSG *>(message); + switch (msg->message) { + case WM_THEMECHANGED: + case WM_SYSCOLORCHANGE: + case WM_DWMCOLORIZATIONCOLORCHANGED: { + update(); + break; + } + + case WM_SETTINGCHANGE: { + if (isImmersiveColorSetChange(msg->wParam, msg->lParam)) { + update(); + } + break; + } + + default: + break; + } + return Windows10BorderHandler::nativeEventFilter(eventType, message, result); + } + void BorderItem::_q_afterSynchronizing() { if (needPaint) { needPaint = false; drawBorder(); } } -# endif + + void BorderItem::_q_windowActivityChanged() { + update(); + } void QuickWindowAgentPrivate::setupWindows10BorderWorkaround() { // Install painting hook diff --git a/src/widgets/widgetwindowagent.cpp b/src/widgets/widgetwindowagent.cpp index ea6b22c..e2a36d1 100644 --- a/src/widgets/widgetwindowagent.cpp +++ b/src/widgets/widgetwindowagent.cpp @@ -55,20 +55,19 @@ return false; } + // Qt will create invisible native window container for native QWidget + // without this attribute, and this behavior will break QWK functionality. + // So far enabling this attribute is a must for QWK users. w->setAttribute(Qt::WA_DontCreateNativeAncestors); - w->setAttribute(Qt::WA_NativeWindow); // Create new window id + // Make sure the native window handle is actually created before we apply + // various hooks. + w->setAttribute(Qt::WA_NativeWindow); d->setup(w, new WidgetItemDelegate()); d->hostWidget = w; #if defined(Q_OS_WINDOWS) && QWINDOWKIT_CONFIG(ENABLE_WINDOWS_SYSTEM_BORDERS) d->setupWindows10BorderWorkaround(); -#endif - -#ifdef Q_OS_WINDOWS - if (!windowAttribute(QStringLiteral("windows-system-border-enabled")).toBool()) { - w->setWindowFlag(Qt::FramelessWindowHint); - } #endif return true; } -- Gitblit v1.9.1