Fix numerous bugs (#162)
* Fix win10 top border issue in opengl qml
* Fix #163
---------
Co-authored-by: Yuhang Zhao <zhaoyuhang@rankyee.com>
Co-authored-by: Zhao Yuhang <2546789017@qq.com>
| | |
| | | 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"); |
| | |
| | | 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 |
| | | - 如果您的产品使用了QWK,请告知我们。我们希望在这个自述文件上展示它! |
| | | |
| | | ## Supported Platforms |
| | | |
| | |
| | | set_target_properties(${PROJECT_NAME} PROPERTIES |
| | | CXX_STANDARD 17 |
| | | CXX_STANDARD_REQUIRED TRUE |
| | | WIN32_EXECUTABLE TRUE |
| | | MACOSX_BUNDLE TRUE |
| | | ) |
| | | ) |
| | |
| | | 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, |
| | |
| | | /* 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; */ |
| | | } |
| | | |
| | | |
| | |
| | | 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, |
| | |
| | | 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); |
| | |
| | | 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); |
| | |
| | | auto windowBar = new QWK::WindowBar(); |
| | | #ifndef Q_OS_MAC |
| | | windowBar->setIconButton(iconButton); |
| | | windowBar->setPinButton(pinButton); |
| | | windowBar->setMinButton(minButton); |
| | | windowBar->setMaxButton(maxButton); |
| | | windowBar->setCloseButton(closeButton); |
| | |
| | | |
| | | 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); |
| | |
| | | |
| | | |
| | | #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) { |
| | |
| | | set_target_properties(${PROJECT_NAME} PROPERTIES |
| | | CXX_STANDARD 17 |
| | | CXX_STANDARD_REQUIRED TRUE |
| | | WIN32_EXECUTABLE TRUE |
| | | MACOSX_BUNDLE TRUE |
| | | ) |
| | | ) |
| | |
| | | import QtQuick 2.15 |
| | | import QtQuick.Controls.Basic 2.15 |
| | | import QtQuick.Controls 2.15 |
| | | |
| | | Button { |
| | | id: root |
| | |
| | | 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 |
| | | |
| | |
| | | title: qsTr("Hello, world!") |
| | | Component.onCompleted: { |
| | | windowAgent.setup(window) |
| | | windowAgent.setWindowAttribute("dark-mode", true) |
| | | window.visible = true |
| | | delayInitTimer.start() |
| | | } |
| | | |
| | | QtObject { |
| | |
| | | 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 { |
| | |
| | | <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> |
New file |
| | |
| | | <?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> |
New file |
| | |
| | | <?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> |
| | |
| | | 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 { |
| | |
| | | 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(); |
| | |
| | | org->deleteLater(); |
| | | if (!btn) |
| | | return; |
| | | d->setWidgetAt(WindowBarPrivate::MinimumButton, btn); |
| | | d->setWidgetAt(WindowBarPrivate::MinimizeButton, btn); |
| | | connect(btn, &QAbstractButton::clicked, this, &WindowBar::minimizeRequested); |
| | | } |
| | | |
| | |
| | | org->deleteLater(); |
| | | if (!btn) |
| | | return; |
| | | d->setWidgetAt(WindowBarPrivate::MaximumButton, btn); |
| | | d->setWidgetAt(WindowBarPrivate::MaximizeButton, btn); |
| | | connect(btn, &QAbstractButton::clicked, this, &WindowBar::maximizeRequested); |
| | | } |
| | | |
| | |
| | | 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; |
| | | } |
| | |
| | | |
| | | 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; |
| | | } |
| | |
| | | QMenuBar *menuBar() const; |
| | | QLabel *titleLabel() const; |
| | | QAbstractButton *iconButton() const; |
| | | QAbstractButton *pinButton() const; |
| | | QAbstractButton *minButton() const; |
| | | QAbstractButton *maxButton() const; |
| | | QAbstractButton *closeButton() const; |
| | |
| | | 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); |
| | |
| | | QMenuBar *takeMenuBar(); |
| | | QLabel *takeTitleLabel(); |
| | | QAbstractButton *takeIconButton(); |
| | | QAbstractButton *takePinButton(); |
| | | QAbstractButton *takeMinButton(); |
| | | QAbstractButton *takeMaxButton(); |
| | | QAbstractButton *takeCloseButton(); |
| | |
| | | void setIconFollowWindow(bool value); |
| | | |
| | | Q_SIGNALS: |
| | | void pinRequested(bool pin = false); |
| | | void minimizeRequested(); |
| | | void maximizeRequested(bool max = false); |
| | | void closeRequested(); |
| | |
| | | IconButton, |
| | | MenuWidget, |
| | | TitleLabel, |
| | | MinimumButton, |
| | | MaximumButton, |
| | | PinButton, |
| | | MinimizeButton, |
| | | MaximizeButton, |
| | | CloseButton, |
| | | }; |
| | | |
| | |
| | | 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) |
| | | |
| | |
| | | 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; |
| | | } |
| | | } |
| | | |
| | |
| | | } |
| | | |
| | | 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) { |
| | |
| | | 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; |
| | |
| | | // |
| | | |
| | | #include <array> |
| | | #include <list> |
| | | #include <memory> |
| | | #include <utility> |
| | | |
| | | #include <QtCore/QSet> |
| | | #include <QtCore/QPointer> |
| | |
| | | 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); |
| | | |
| | |
| | | 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: |
| | |
| | | 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()) { |
| | |
| | | |
| | | [[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 |
| | |
| | | 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 |
| | |
| | | // 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))); |
| | |
| | | |
| | | 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; |
| | | |
| | |
| | | if (!isNonClientMessage(message)) { |
| | | ::ScreenToClient(hWnd, &msg.pt); |
| | | } |
| | | |
| | | msg.time = ::GetMessageTime(); |
| | | return msg; |
| | | } |
| | | |
| | |
| | | } |
| | | |
| | | // 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)) { |
| | |
| | | } |
| | | |
| | | // 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)) { |
| | |
| | | |
| | | // 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) { |
| | |
| | | } |
| | | |
| | | #if QWINDOWKIT_CONFIG(ENABLE_WINDOWS_SYSTEM_BORDERS) |
| | | case DrawWindows10BorderHook: { |
| | | case DrawWindows10BorderHook_Emulated: { |
| | | if (!m_windowId) |
| | | return; |
| | | |
| | |
| | | return; |
| | | } |
| | | |
| | | case DrawWindows10BorderHook2: { |
| | | case DrawWindows10BorderHook_Native: { |
| | | if (!m_windowId) |
| | | return; |
| | | |
| | |
| | | Q_UNUSED(oldAttribute) |
| | | |
| | | const auto hwnd = reinterpret_cast<HWND>(m_windowId); |
| | | Q_ASSERT(hwnd); |
| | | if (!hwnd) { |
| | | return false; |
| | | } |
| | |
| | | }; |
| | | 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")) { |
| | |
| | | } 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(); |
| | |
| | | } |
| | | restoreMargins(); |
| | | } |
| | | effectBugWorkaround(); |
| | | return true; |
| | | } |
| | | |
| | |
| | | sizeof(backdropType)); |
| | | restoreMargins(); |
| | | } |
| | | effectBugWorkaround(); |
| | | return true; |
| | | } |
| | | |
| | |
| | | |
| | | restoreMargins(); |
| | | } |
| | | effectBugWorkaround(); |
| | | return true; |
| | | } |
| | | |
| | |
| | | apis.pDwmEnableBlurBehindWindow(hwnd, &bb); |
| | | } |
| | | } |
| | | effectBugWorkaround(); |
| | | return true; |
| | | } |
| | | return false; |
| | |
| | | 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 |
| | |
| | | // 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(); |
| | | } |
| | | }); |
| | |
| | | void removeNativeEventFilter(NativeEventFilter *filter); |
| | | |
| | | protected: |
| | | QVector<NativeEventFilter *> m_nativeEventFilters; |
| | | QList<NativeEventFilter *> m_nativeEventFilters; |
| | | |
| | | friend class NativeEventFilter; |
| | | |
| | |
| | | void removeSharedEventFilter(SharedEventFilter *filter); |
| | | |
| | | protected: |
| | | QVector<SharedEventFilter *> m_sharedEventFilters; |
| | | QList<SharedEventFilter *> m_sharedEventFilters; |
| | | |
| | | friend class SharedEventFilter; |
| | | |
| | |
| | | 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 |
| | | } |
| | |
| | | |
| | | 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; |
| | |
| | | : 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}; |
| | |
| | | #include <QWKCore/qwindowkit_windows.h> |
| | | |
| | | #include <QtCore/QtMath> |
| | | #include <QtCore/QPair> |
| | | #include <QtGui/QGuiApplication> |
| | | #include <QtGui/QStyleHints> |
| | | #include <QtGui/QPalette> |
| | |
| | | } |
| | | |
| | | inline void drawBorder() { |
| | | ctx->virtual_hook(AbstractWindowContext::DrawWindows10BorderHook2, nullptr); |
| | | ctx->virtual_hook(AbstractWindowContext::DrawWindows10BorderHook_Native, nullptr); |
| | | } |
| | | |
| | | inline int borderThickness() const { |
| | |
| | | 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; |
| | | } |
| | |
| | | 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; |
| | | } |
| | |
| | | |
| | | 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 { |
| | |
| | | explicit BorderItem(QQuickItem *parent, AbstractWindowContext *context); |
| | | ~BorderItem() override; |
| | | |
| | | bool shouldEnableEmulatedPainter() const; |
| | | void updateGeometry() override; |
| | | |
| | | public: |
| | |
| | | |
| | | 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) { |
| | |
| | | connect(window(), &QQuickWindow::afterSynchronizing, this, |
| | | &BorderItem::_q_afterSynchronizing, Qt::DirectConnection); |
| | | # endif |
| | | connect(window(), &QQuickWindow::activeChanged, this, |
| | | &BorderItem::_q_windowActivityChanged); |
| | | |
| | | // First update |
| | | if (context->windowId()) { |
| | |
| | | |
| | | 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[] = { |
| | |
| | | &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) { |
| | |
| | | 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 |
| | |
| | | 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; |
| | | } |