diff --git a/.gitignore b/.gitignore index 8020eb2..3d9474b 100644 --- a/.gitignore +++ b/.gitignore @@ -23,3 +23,4 @@ /qtwayland-everywhere-src-6.5.3.tar.xz /qtwayland-everywhere-src-6.6.0.tar.xz /qtwayland-everywhere-src-6.6.1.tar.xz +/qtwayland-everywhere-src-6.7.0.tar.xz diff --git a/qt6-qtwayland.spec b/qt6-qtwayland.spec index c395bd2..a82e14d 100644 --- a/qt6-qtwayland.spec +++ b/qt6-qtwayland.spec @@ -11,8 +11,8 @@ Summary: Qt6 - Wayland platform support and QtCompositor module Name: qt6-%{qt_module} -Version: 6.6.1 -Release: 6%{?dist} +Version: 6.7.0 +Release: 1%{?dist} License: LGPL-3.0-only OR GPL-3.0-only WITH Qt-GPL-exception-1.0 Url: http://www.qt.io @@ -26,11 +26,11 @@ Source0: https://download.qt.io/official_releases/qt/%{majmin}/%{version}/submod %endif # Upstream patches -Patch0: qtwayland-client-disable-threaded-gl-on-desktop-nvidia.patch -Patch1: qtwayland-client-fix-qt-keypadmodifier-for-key-events.patch +# Backport Qt Adwaita decorations from upstream to qt6-qtwayland package +# https://issues.redhat.com/browse/DESKTOP-883 +Patch0: qtwayland-add-gnome-like-csd-plugin.patch # Upstreamable patches -Patch10: qtwayland-use-adwaita-decorations-by-default.patch # filter qml provides %global __provides_exclude_from ^%{_qt6_archdatadir}/qml/.*\\.so$ @@ -43,6 +43,7 @@ BuildRequires: qt6-qtbase-static BuildRequires: qt6-qtbase-private-devel %{?_qt6:Requires: %{_qt6}%{?_isa} = %{_qt6_version}} BuildRequires: qt6-qtdeclarative-devel +BuildRequires: qt6-qtsvg-devel BuildRequires: pkgconfig(xkbcommon) BuildRequires: pkgconfig(wayland-scanner) @@ -94,7 +95,8 @@ Requires: %{name}%{?_isa} = %{version}-%{release} %build %cmake_qt6 \ -DQT_BUILD_EXAMPLES:BOOL=%{?examples:ON}%{!?examples:OFF} \ - -DQT_BUILD_TESTS=%{?build_tests:ON}%{!?build_tests:OFF} + -DQT_BUILD_TESTS=%{?build_tests:ON}%{!?build_tests:OFF} \ + -DQT_INSTALL_EXAMPLES_SOURCES=%{?examples:ON}%{!?examples:OFF} %cmake_build @@ -199,6 +201,12 @@ popd %endif %changelog +* Fri Apr 19 2024 Jan Grulich - 6.7.0-1 +- 6.7.0 + Resolves: RHEL-27845 + Resolves: RHEL-31172 + Resolves: RHEL-33724 + * Tue Apr 02 2024 Jan Grulich - 6.6.1-6 - Add -tests subpackage with unit tests that can run in CI Resolves: RHEL-28239 diff --git a/qtwayland-add-gnome-like-csd-plugin.patch b/qtwayland-add-gnome-like-csd-plugin.patch new file mode 100644 index 0000000..3e47e8c --- /dev/null +++ b/qtwayland-add-gnome-like-csd-plugin.patch @@ -0,0 +1,1116 @@ +From 40116ae353fd5f40d386c5398911d1aee483bb13 Mon Sep 17 00:00:00 2001 +From: Jan Grulich +Date: Tue, 12 Dec 2023 10:08:17 +0100 +Subject: [PATCH] Add GNOME-like client-side decoration plugin + +Adds a client-side decoration plugin implementing GNOME's Adwaita style. +This is trying to follow GTK4 Adwaita style, using xdg-desktop-portal to +get user's configuration in order to get whether a light or dark colors +should be used and to get the titlebar button layout. This plugin is now +used on GNOME by default, while defaulting to the original behavior for +non-GNOME DEs. It depends on QtSvg used to draw titlebar buttons so in +case QtSvg is not found, this plugin will not be build. + +[ChangeLog][QtWaylandClient][Added GNOME-like client-side decoration +plugin] + +Fixes: QTBUG-120070 +Change-Id: I0f1777c4e0aa3467dafbbae8004b594cc82f9aa0 +Reviewed-by: David Edmundson +--- + CMakeLists.txt | 2 + + dependencies.yaml | 3 + + src/client/qwaylandwindow.cpp | 19 + + src/configure.cmake | 8 + + src/plugins/decorations/CMakeLists.txt | 3 + + .../decorations/adwaita/CMakeLists.txt | 25 + + src/plugins/decorations/adwaita/adwaita.json | 3 + + src/plugins/decorations/adwaita/main.cpp | 36 + + .../adwaita/qwaylandadwaitadecoration.cpp | 731 ++++++++++++++++++ + .../adwaita/qwaylandadwaitadecoration_p.h | 155 ++++ + 10 files changed, 985 insertions(+) + create mode 100644 src/plugins/decorations/adwaita/CMakeLists.txt + create mode 100644 src/plugins/decorations/adwaita/adwaita.json + create mode 100644 src/plugins/decorations/adwaita/main.cpp + create mode 100644 src/plugins/decorations/adwaita/qwaylandadwaitadecoration.cpp + create mode 100644 src/plugins/decorations/adwaita/qwaylandadwaitadecoration_p.h + +diff --git a/CMakeLists.txt b/CMakeLists.txt +index 6649dfccb..c498e15b3 100644 +--- a/CMakeLists.txt ++++ b/CMakeLists.txt +@@ -28,9 +28,11 @@ find_package(Qt6 ${PROJECT_VERSION} CONFIG REQUIRED COMPONENTS + ) + + find_package(Qt6 ${PROJECT_VERSION} QUIET CONFIG OPTIONAL_COMPONENTS ++ DBus + Gui + OpenGL + Quick ++ Svg + ) + + # special case begin +diff --git a/src/client/qwaylandwindow.cpp b/src/client/qwaylandwindow.cpp +index 215193a7b..c0a415725 100644 +--- a/src/client/qwaylandwindow.cpp ++++ b/src/client/qwaylandwindow.cpp +@@ -26,6 +26,7 @@ + + #include + #include ++#include + #include + + #include +@@ -36,6 +37,8 @@ + + QT_BEGIN_NAMESPACE + ++using namespace Qt::StringLiterals; ++ + namespace QtWaylandClient { + + Q_LOGGING_CATEGORY(lcWaylandBackingstore, "qt.qpa.wayland.backingstore") +@@ -1092,6 +1095,22 @@ bool QWaylandWindow::createDecoration() + } + } + ++ if (targetKey.isEmpty()) { ++ auto unixServices = dynamic_cast( ++ QGuiApplicationPrivate::platformIntegration()->services()); ++ const QByteArray currentDesktop = unixServices->desktopEnvironment(); ++ if (currentDesktop == "GNOME") { ++ if (decorations.contains("adwaita"_L1)) ++ targetKey = "adwaita"_L1; ++ else if (decorations.contains("gnome"_L1)) ++ targetKey = "gnome"_L1; ++ } else { ++ // Do not use Adwaita/GNOME decorations on other DEs ++ decorations.removeAll("adwaita"_L1); ++ decorations.removeAll("gnome"_L1); ++ } ++ } ++ + if (targetKey.isEmpty()) + targetKey = decorations.first(); // first come, first served. + +diff --git a/src/configure.cmake b/src/configure.cmake +index eda1f0850..45f945333 100644 +--- a/src/configure.cmake ++++ b/src/configure.cmake +@@ -245,6 +245,11 @@ qt_feature("wayland-vulkan-server-buffer" PRIVATE + qt_feature("wayland-datadevice" PRIVATE + CONDITION QT_FEATURE_draganddrop OR QT_FEATURE_clipboard + ) ++qt_feature("wayland-decoration-adwaita" PRIVATE ++ LABEL "GNOME-like client-side decorations" ++ CONDITION NOT WIN32 AND QT_FEATURE_wayland_client AND TARGET Qt::DBus AND TARGET Qt::Svg ++) ++ + + qt_configure_add_summary_entry(ARGS "wayland-client") + qt_configure_add_summary_entry(ARGS "wayland-server") +@@ -257,6 +262,9 @@ qt_configure_add_summary_entry(ARGS "wayland-dmabuf-server-buffer") + qt_configure_add_summary_entry(ARGS "wayland-shm-emulation-server-buffer") + qt_configure_add_summary_entry(ARGS "wayland-vulkan-server-buffer") + qt_configure_end_summary_section() # end of "Qt Wayland Drivers" section ++qt_configure_add_summary_section(NAME "Qt Wayland Decoration Plugins") ++qt_configure_add_summary_entry(ARGS "wayland-decoration-adwaita") ++qt_configure_end_summary_section() # end of "Qt Wayland Decoration Plugins" section + + qt_configure_add_report_entry( + TYPE ERROR +diff --git a/src/plugins/decorations/CMakeLists.txt b/src/plugins/decorations/CMakeLists.txt +index 73c59e4a5..abe3c375b 100644 +--- a/src/plugins/decorations/CMakeLists.txt ++++ b/src/plugins/decorations/CMakeLists.txt +@@ -2,5 +2,8 @@ + # SPDX-License-Identifier: BSD-3-Clause + + # Generated from decorations.pro. ++if (QT_FEATURE_wayland_decoration_adwaita) ++ add_subdirectory(adwaita) ++endif() + + add_subdirectory(bradient) +diff --git a/src/plugins/decorations/adwaita/CMakeLists.txt b/src/plugins/decorations/adwaita/CMakeLists.txt +new file mode 100644 +index 000000000..b318c2b8b +--- /dev/null ++++ b/src/plugins/decorations/adwaita/CMakeLists.txt +@@ -0,0 +1,25 @@ ++# Copyright (C) 2023 The Qt Company Ltd. ++# SPDX-License-Identifier: BSD-3-Clause ++ ++##################################################################### ++## QWaylandAdwaitaDecorationPlugin Plugin: ++##################################################################### ++ ++qt_internal_add_plugin(QWaylandAdwaitaDecorationPlugin ++ OUTPUT_NAME adwaita ++ PLUGIN_TYPE wayland-decoration-client ++ SOURCES ++ main.cpp ++ qwaylandadwaitadecoration.cpp ++ LIBRARIES ++ Qt::Core ++ Qt::DBus ++ Qt::Gui ++ Qt::Svg ++ Qt::WaylandClientPrivate ++ Wayland::Client ++) ++ ++#### Keys ignored in scope 1:.:.:bradient.pro:: ++# OTHER_FILES = "bradient.json" ++ +diff --git a/src/plugins/decorations/adwaita/adwaita.json b/src/plugins/decorations/adwaita/adwaita.json +new file mode 100644 +index 000000000..69ec79e9b +--- /dev/null ++++ b/src/plugins/decorations/adwaita/adwaita.json +@@ -0,0 +1,3 @@ ++{ ++ "Keys": [ "adwaita", "gnome" ] ++} +diff --git a/src/plugins/decorations/adwaita/main.cpp b/src/plugins/decorations/adwaita/main.cpp +new file mode 100644 +index 000000000..e5b1be830 +--- /dev/null ++++ b/src/plugins/decorations/adwaita/main.cpp +@@ -0,0 +1,36 @@ ++// Copyright (C) 2023 Jan Grulich ++// Copyright (C) 2023 The Qt Company Ltd. ++// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only ++ ++#include ++ ++#include "qwaylandadwaitadecoration_p.h" ++ ++QT_BEGIN_NAMESPACE ++ ++using namespace Qt::StringLiterals; ++ ++namespace QtWaylandClient { ++ ++class QWaylandAdwaitaDecorationPlugin : public QWaylandDecorationPlugin ++{ ++ Q_OBJECT ++ Q_PLUGIN_METADATA(IID QWaylandDecorationFactoryInterface_iid FILE "adwaita.json") ++public: ++ QWaylandAbstractDecoration *create(const QString &key, const QStringList ¶ms) override; ++}; ++ ++QWaylandAbstractDecoration *QWaylandAdwaitaDecorationPlugin::create(const QString &key, const QStringList ¶ms) ++{ ++ Q_UNUSED(params); ++ if (!key.compare("adwaita"_L1, Qt::CaseInsensitive) || ++ !key.compare("gnome"_L1, Qt::CaseInsensitive)) ++ return new QWaylandAdwaitaDecoration(); ++ return nullptr; ++} ++ ++} ++ ++QT_END_NAMESPACE ++ ++#include "main.moc" +diff --git a/src/plugins/decorations/adwaita/qwaylandadwaitadecoration.cpp b/src/plugins/decorations/adwaita/qwaylandadwaitadecoration.cpp +new file mode 100644 +index 000000000..2d3575bce +--- /dev/null ++++ b/src/plugins/decorations/adwaita/qwaylandadwaitadecoration.cpp +@@ -0,0 +1,731 @@ ++// Copyright (C) 2023 Jan Grulich ++// Copyright (C) 2023 The Qt Company Ltd. ++// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only ++ ++#include "qwaylandadwaitadecoration_p.h" ++ ++// QtCore ++#include ++#include ++ ++// QtDBus ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++// QtGui ++#include ++#include ++#include ++ ++#include ++#include ++ ++// QtSvg ++#include ++ ++// QtWayland ++#include ++#include ++ ++ ++QT_BEGIN_NAMESPACE ++ ++using namespace Qt::StringLiterals; ++ ++namespace QtWaylandClient { ++ ++static constexpr int ceButtonSpacing = 12; ++static constexpr int ceButtonWidth = 24; ++static constexpr int ceCornerRadius = 12; ++static constexpr int ceShadowsWidth = 10; ++static constexpr int ceTitlebarHeight = 38; ++static constexpr int ceWindowBorderWidth = 1; ++static constexpr qreal ceTitlebarSeperatorWidth = 0.5; ++ ++static QMap buttonMap = { ++ { QWaylandAdwaitaDecoration::CloseIcon, "window-close-symbolic"_L1 }, ++ { QWaylandAdwaitaDecoration::MinimizeIcon, "window-minimize-symbolic"_L1 }, ++ { QWaylandAdwaitaDecoration::MaximizeIcon, "window-maximize-symbolic"_L1 }, ++ { QWaylandAdwaitaDecoration::RestoreIcon, "window-restore-symbolic"_L1 } ++}; ++ ++const QDBusArgument &operator>>(const QDBusArgument &argument, QMap &map) ++{ ++ argument.beginMap(); ++ map.clear(); ++ ++ while (!argument.atEnd()) { ++ QString key; ++ QVariantMap value; ++ argument.beginMapEntry(); ++ argument >> key >> value; ++ argument.endMapEntry(); ++ map.insert(key, value); ++ } ++ ++ argument.endMap(); ++ return argument; ++} ++ ++Q_LOGGING_CATEGORY(lcQWaylandAdwaitaDecorationLog, "qt.qpa.qwaylandadwaitadecoration", QtWarningMsg) ++ ++QWaylandAdwaitaDecoration::QWaylandAdwaitaDecoration() ++ : QWaylandAbstractDecoration() ++{ ++ m_lastButtonClick = QDateTime::currentDateTime(); ++ ++ QTextOption option(Qt::AlignHCenter | Qt::AlignVCenter); ++ option.setWrapMode(QTextOption::NoWrap); ++ m_windowTitle.setTextOption(option); ++ m_windowTitle.setTextFormat(Qt::PlainText); ++ ++ const QPlatformTheme *theme = QGuiApplicationPrivate::platformTheme(); ++ if (const QFont *font = theme->font(QPlatformTheme::TitleBarFont)) ++ m_font = std::make_unique(*font); ++ if (!m_font) // Fallback to GNOME's default font ++ m_font = std::make_unique("Cantarell"_L1, 10); ++ ++ QTimer::singleShot(0, this, &QWaylandAdwaitaDecoration::loadConfiguration); ++} ++ ++QMargins QWaylandAdwaitaDecoration::margins(QWaylandAbstractDecoration::MarginsType marginsType) const ++{ ++ const bool onlyShadows = marginsType == QWaylandAbstractDecoration::ShadowsOnly; ++ const bool shadowsExcluded = marginsType == ShadowsExcluded; ++ ++ if (waylandWindow()->windowStates() & Qt::WindowMaximized) { ++ // Maximized windows don't have anything around, no shadows, border, ++ // etc. Only report titlebar height in case we are not asking for shadow ++ // margins. ++ return QMargins(0, onlyShadows ? 0 : ceTitlebarHeight, 0, 0); ++ } ++ ++ const QWaylandWindow::ToplevelWindowTilingStates tilingStates = waylandWindow()->toplevelWindowTilingStates(); ++ ++ // Since all sides (left, right, bottom) are going to be same ++ const int marginsBase = shadowsExcluded ? ceWindowBorderWidth : ceShadowsWidth + ceWindowBorderWidth; ++ const int sideMargins = onlyShadows ? ceShadowsWidth : marginsBase; ++ const int topMargins = onlyShadows ? ceShadowsWidth : ceTitlebarHeight + marginsBase; ++ ++ return QMargins(tilingStates & QWaylandWindow::WindowTiledLeft ? 0 : sideMargins, ++ tilingStates & QWaylandWindow::WindowTiledTop ? onlyShadows ? 0 : ceTitlebarHeight : topMargins, ++ tilingStates & QWaylandWindow::WindowTiledRight ? 0 : sideMargins, ++ tilingStates & QWaylandWindow::WindowTiledBottom ? 0 : sideMargins); ++} ++ ++void QWaylandAdwaitaDecoration::paint(QPaintDevice *device) ++{ ++ const QRect surfaceRect = waylandWindow()->windowContentGeometry() + margins(ShadowsOnly); ++ ++ QPainter p(device); ++ p.setRenderHint(QPainter::Antialiasing); ++ ++ /* ++ * Titlebar and window border ++ */ ++ const int titleBarWidth = surfaceRect.width() - margins().left() - margins().right(); ++ QPainterPath path; ++ ++ // Maximized or tiled won't have rounded corners ++ if (waylandWindow()->windowStates() & Qt::WindowMaximized ++ || waylandWindow()->toplevelWindowTilingStates() != QWaylandWindow::WindowNoState) ++ path.addRect(margins().left(), margins().bottom(), titleBarWidth, margins().top()); ++ else ++ path.addRoundedRect(margins().left(), margins().bottom(), titleBarWidth, ++ margins().top() + ceCornerRadius, ceCornerRadius, ceCornerRadius); ++ ++ p.save(); ++ p.setPen(color(Border)); ++ p.fillPath(path.simplified(), color(Background)); ++ p.drawPath(path); ++ p.drawRect(margins().left(), margins().top(), titleBarWidth, surfaceRect.height() - margins().top() - margins().bottom()); ++ p.restore(); ++ ++ ++ /* ++ * Titlebar separator ++ */ ++ p.save(); ++ p.setPen(color(Border)); ++ p.drawLine(QLineF(margins().left(), margins().top() - ceTitlebarSeperatorWidth, ++ surfaceRect.width() - margins().right(), ++ margins().top() - ceTitlebarSeperatorWidth)); ++ p.restore(); ++ ++ ++ /* ++ * Window title ++ */ ++ const QRect top = QRect(margins().left(), margins().bottom(), surfaceRect.width(), ++ margins().top() - margins().bottom()); ++ const QString windowTitleText = waylandWindow()->windowTitle(); ++ if (!windowTitleText.isEmpty()) { ++ if (m_windowTitle.text() != windowTitleText) { ++ m_windowTitle.setText(windowTitleText); ++ m_windowTitle.prepare(); ++ } ++ ++ QRect titleBar = top; ++ if (m_placement == Right) { ++ titleBar.setLeft(margins().left()); ++ titleBar.setRight(static_cast(buttonRect(Minimize).left()) - 8); ++ } else { ++ titleBar.setLeft(static_cast(buttonRect(Minimize).right()) + 8); ++ titleBar.setRight(surfaceRect.width() - margins().right()); ++ } ++ ++ p.save(); ++ p.setClipRect(titleBar); ++ p.setPen(color(Foreground)); ++ QSize size = m_windowTitle.size().toSize(); ++ int dx = (top.width() - size.width()) / 2; ++ int dy = (top.height() - size.height()) / 2; ++ p.setFont(*m_font); ++ QPoint windowTitlePoint(top.topLeft().x() + dx, top.topLeft().y() + dy); ++ p.drawStaticText(windowTitlePoint, m_windowTitle); ++ p.restore(); ++ } ++ ++ ++ /* ++ * Buttons ++ */ ++ if (m_buttons.contains(Close)) ++ drawButton(Close, &p); ++ ++ if (m_buttons.contains(Maximize)) ++ drawButton(Maximize, &p); ++ ++ if (m_buttons.contains(Minimize)) ++ drawButton(Minimize, &p); ++} ++ ++bool QWaylandAdwaitaDecoration::handleMouse(QWaylandInputDevice *inputDevice, const QPointF &local, ++ const QPointF &global, Qt::MouseButtons b, ++ Qt::KeyboardModifiers mods) ++{ ++ Q_UNUSED(global) ++ ++ if (local.y() > margins().top()) ++ updateButtonHoverState(Button::None); ++ ++ // Figure out what area mouse is in ++ QRect surfaceRect = waylandWindow()->windowContentGeometry() + margins(ShadowsOnly); ++ if (local.y() <= surfaceRect.top() + margins().top()) ++ processMouseTop(inputDevice, local, b, mods); ++ else if (local.y() > surfaceRect.bottom() - margins().bottom()) ++ processMouseBottom(inputDevice, local, b, mods); ++ else if (local.x() <= surfaceRect.left() + margins().left()) ++ processMouseLeft(inputDevice, local, b, mods); ++ else if (local.x() > surfaceRect.right() - margins().right()) ++ processMouseRight(inputDevice, local, b, mods); ++ else { ++#if QT_CONFIG(cursor) ++ waylandWindow()->restoreMouseCursor(inputDevice); ++#endif ++ } ++ ++ // Reset clicking state in case a button press is released outside ++ // the button area ++ if (isLeftReleased(b)) { ++ m_clicking = None; ++ requestRepaint(); ++ } ++ ++ setMouseButtons(b); ++ return false; ++} ++ ++bool QWaylandAdwaitaDecoration::handleTouch(QWaylandInputDevice *inputDevice, const QPointF &local, ++ const QPointF &global, QEventPoint::State state, ++ Qt::KeyboardModifiers mods) ++{ ++ Q_UNUSED(inputDevice) ++ Q_UNUSED(global) ++ Q_UNUSED(mods) ++ ++ bool handled = state == QEventPoint::Pressed; ++ ++ if (handled) { ++ if (buttonRect(Close).contains(local)) ++ QWindowSystemInterface::handleCloseEvent(window()); ++ else if (m_buttons.contains(Maximize) && buttonRect(Maximize).contains(local)) ++ window()->setWindowStates(window()->windowStates() ^ Qt::WindowMaximized); ++ else if (m_buttons.contains(Minimize) && buttonRect(Minimize).contains(local)) ++ window()->setWindowState(Qt::WindowMinimized); ++ else if (local.y() <= margins().top()) ++ waylandWindow()->shellSurface()->move(inputDevice); ++ else ++ handled = false; ++ } ++ ++ return handled; ++} ++ ++QString getIconSvg(const QString &iconName) ++{ ++ const QStringList themeNames = { QIcon::themeName(), QIcon::fallbackThemeName(), "Adwaita"_L1 }; ++ ++ qCDebug(lcQWaylandAdwaitaDecorationLog) << "Searched icon themes: " << themeNames; ++ ++ for (const QString &themeName : themeNames) { ++ if (themeName.isEmpty()) ++ continue; ++ ++ for (const QString &path : QIcon::themeSearchPaths()) { ++ if (path.startsWith(QLatin1Char(':'))) ++ continue; ++ ++ const QString fullPath = QString("%1/%2").arg(path).arg(themeName); ++ QDirIterator dirIt(fullPath, {"*.svg"}, QDir::Files, QDirIterator::Subdirectories); ++ while (dirIt.hasNext()) { ++ const QString fileName = dirIt.next(); ++ const QFileInfo fileInfo(fileName); ++ ++ if (fileInfo.fileName() == iconName) { ++ qCDebug(lcQWaylandAdwaitaDecorationLog) << "Using " << iconName << " from " << themeName << " theme"; ++ QFile readFile(fileInfo.filePath()); ++ readFile.open(QFile::ReadOnly); ++ return readFile.readAll(); ++ } ++ } ++ } ++ } ++ ++ qCWarning(lcQWaylandAdwaitaDecorationLog) << "Failed to find an svg icon for " << iconName; ++ ++ return QString(); ++} ++ ++void QWaylandAdwaitaDecoration::loadConfiguration() ++{ ++ qRegisterMetaType(); ++ qDBusRegisterMetaType>(); ++ ++ QDBusConnection connection = QDBusConnection::sessionBus(); ++ ++ QDBusMessage message = QDBusMessage::createMethodCall("org.freedesktop.portal.Desktop"_L1, ++ "/org/freedesktop/portal/desktop"_L1, ++ "org.freedesktop.portal.Settings"_L1, ++ "ReadAll"_L1); ++ message << QStringList{ { "org.gnome.desktop.wm.preferences"_L1 }, ++ { "org.freedesktop.appearance"_L1 } }; ++ ++ QDBusPendingCall pendingCall = connection.asyncCall(message); ++ QDBusPendingCallWatcher *watcher = new QDBusPendingCallWatcher(pendingCall, this); ++ QObject::connect(watcher, &QDBusPendingCallWatcher::finished, this, [this](QDBusPendingCallWatcher *watcher) { ++ QDBusPendingReply> reply = *watcher; ++ if (reply.isValid()) { ++ QMap settings = reply.value(); ++ if (!settings.isEmpty()) { ++ const uint colorScheme = settings.value("org.freedesktop.appearance"_L1).value("color-scheme"_L1).toUInt(); ++ updateColors(colorScheme == 1); // 1 == Prefer Dark ++ const QString buttonLayout = settings.value("org.gnome.desktop.wm.preferences"_L1).value("button-layout"_L1).toString(); ++ if (!buttonLayout.isEmpty()) ++ updateTitlebarLayout(buttonLayout); ++ // Workaround for QGtkStyle not having correct titlebar font ++ const QString titlebarFont = ++ settings.value("org.gnome.desktop.wm.preferences"_L1).value("titlebar-font"_L1).toString(); ++ if (titlebarFont.contains("bold"_L1, Qt::CaseInsensitive)) { ++ m_font->setBold(true); ++ } ++ } ++ } ++ watcher->deleteLater(); ++ }); ++ ++ QDBusConnection::sessionBus().connect(QString(), "/org/freedesktop/portal/desktop"_L1, ++ "org.freedesktop.portal.Settings"_L1, "SettingChanged"_L1, this, ++ SLOT(settingChanged(QString, QString, QDBusVariant))); ++ ++ // Load SVG icons ++ for (auto mapIt = buttonMap.constBegin(); mapIt != buttonMap.constEnd(); mapIt++) { ++ const QString fullName = mapIt.value() + QStringLiteral(".svg"); ++ m_icons[mapIt.key()] = getIconSvg(fullName); ++ } ++ ++ updateColors(false); ++} ++ ++void QWaylandAdwaitaDecoration::updateColors(bool isDark) ++{ ++ qCDebug(lcQWaylandAdwaitaDecorationLog) << "Color scheme changed to: " << (isDark ? "dark" : "light"); ++ ++ m_colors = { { Background, isDark ? QColor(0x303030) : QColor(0xffffff) }, ++ { BackgroundInactive, isDark ? QColor(0x242424) : QColor(0xfafafa) }, ++ { Foreground, isDark ? QColor(0xffffff) : QColor(0x2e2e2e) }, ++ { ForegroundInactive, isDark ? QColor(0x919191) : QColor(0x949494) }, ++ { Border, isDark ? QColor(0x3b3b3b) : QColor(0xdbdbdb) }, ++ { BorderInactive, isDark ? QColor(0x303030) : QColor(0xdbdbdb) }, ++ { ButtonBackground, isDark ? QColor(0x444444) : QColor(0xebebeb) }, ++ { ButtonBackgroundInactive, isDark ? QColor(0x2e2e2e) : QColor(0xf0f0f0) }, ++ { HoveredButtonBackground, isDark ? QColor(0x4f4f4f) : QColor(0xe0e0e0) }, ++ { PressedButtonBackground, isDark ? QColor(0x6e6e6e) : QColor(0xc2c2c2) } }; ++ requestRepaint(); ++} ++ ++void QWaylandAdwaitaDecoration::updateTitlebarLayout(const QString &layout) ++{ ++ const QStringList layouts = layout.split(QLatin1Char(':')); ++ if (layouts.count() != 2) ++ return; ++ ++ // Remove previous configuration ++ m_buttons.clear(); ++ ++ const QString &leftLayout = layouts.at(0); ++ const QString &rightLayout = layouts.at(1); ++ m_placement = leftLayout.contains("close"_L1) ? Left : Right; ++ ++ int pos = 1; ++ const QString &buttonLayout = m_placement == Right ? rightLayout : leftLayout; ++ ++ QStringList buttonList = buttonLayout.split(QLatin1Char(',')); ++ if (m_placement == Right) ++ std::reverse(buttonList.begin(), buttonList.end()); ++ ++ for (const QString &button : buttonList) { ++ if (button == "close"_L1) ++ m_buttons.insert(Close, pos); ++ else if (button == "maximize"_L1) ++ m_buttons.insert(Maximize, pos); ++ else if (button == "minimize"_L1) ++ m_buttons.insert(Minimize, pos); ++ ++ pos++; ++ } ++ ++ qCDebug(lcQWaylandAdwaitaDecorationLog) << "Button layout changed to: " << layout; ++ ++ requestRepaint(); ++} ++ ++void QWaylandAdwaitaDecoration::settingChanged(const QString &group, const QString &key, ++ const QDBusVariant &value) ++{ ++ if (group == "org.gnome.desktop.wm.preferences"_L1 && key == "button-layout"_L1) { ++ const QString layout = value.variant().toString(); ++ updateTitlebarLayout(layout); ++ } else if (group == "org.freedesktop.appearance"_L1 && key == "color-scheme"_L1) { ++ const uint colorScheme = value.variant().toUInt(); ++ updateColors(colorScheme == 1); // 1 == Prefer Dark ++ } ++} ++ ++QRectF QWaylandAdwaitaDecoration::buttonRect(Button button) const ++{ ++ int xPos; ++ int yPos; ++ const int btnPos = m_buttons.value(button); ++ ++ const QRect surfaceRect = waylandWindow()->windowContentGeometry() + margins(QWaylandAbstractDecoration::ShadowsOnly); ++ if (m_placement == Right) { ++ xPos = surfaceRect.width(); ++ xPos -= ceButtonWidth * btnPos; ++ xPos -= ceButtonSpacing * btnPos; ++ xPos -= margins(ShadowsOnly).right(); ++ } else { ++ xPos = 0; ++ xPos += ceButtonWidth * btnPos; ++ xPos += ceButtonSpacing * btnPos; ++ xPos += margins(ShadowsOnly).left(); ++ // We are painting from the left to the right so the real ++ // position doesn't need to by moved by the size of the button. ++ xPos -= ceButtonWidth; ++ } ++ ++ yPos = margins().top(); ++ yPos += margins().bottom(); ++ yPos -= ceButtonWidth; ++ yPos /= 2; ++ ++ return QRectF(xPos, yPos, ceButtonWidth, ceButtonWidth); ++} ++ ++static void renderFlatRoundedButtonFrame(QPainter *painter, const QRect &rect, const QColor &color) ++{ ++ painter->save(); ++ painter->setRenderHint(QPainter::Antialiasing, true); ++ painter->setPen(Qt::NoPen); ++ painter->setBrush(color); ++ painter->drawEllipse(rect); ++ painter->restore(); ++} ++ ++static void renderButtonIcon(const QString &svgIcon, QPainter *painter, const QRect &rect, const QColor &color) ++{ ++ painter->save(); ++ painter->setRenderHints(QPainter::Antialiasing, true); ++ ++ QString icon = svgIcon; ++ QRegularExpression regexp("fill=[\"']#[0-9A-F]{6}[\"']", QRegularExpression::CaseInsensitiveOption); ++ QRegularExpression regexpAlt("fill:#[0-9A-F]{6}", QRegularExpression::CaseInsensitiveOption); ++ QRegularExpression regexpCurrentColor("fill=[\"']currentColor[\"']"); ++ icon.replace(regexp, QString("fill=\"%1\"").arg(color.name())); ++ icon.replace(regexpAlt, QString("fill:%1").arg(color.name())); ++ icon.replace(regexpCurrentColor, QString("fill=\"%1\"").arg(color.name())); ++ QSvgRenderer svgRenderer(icon.toLocal8Bit()); ++ svgRenderer.render(painter, rect); ++ ++ painter->restore(); ++} ++ ++static void renderButtonIcon(QWaylandAdwaitaDecoration::ButtonIcon buttonIcon, QPainter *painter, const QRect &rect) ++{ ++ QString iconName = buttonMap[buttonIcon]; ++ ++ painter->save(); ++ painter->setRenderHints(QPainter::Antialiasing, true); ++ painter->drawPixmap(rect, QIcon::fromTheme(iconName).pixmap(ceButtonWidth, ceButtonWidth)); ++ painter->restore(); ++} ++ ++static QWaylandAdwaitaDecoration::ButtonIcon iconFromButtonAndState(QWaylandAdwaitaDecoration::Button button, bool maximized) ++{ ++ if (button == QWaylandAdwaitaDecoration::Close) ++ return QWaylandAdwaitaDecoration::CloseIcon; ++ else if (button == QWaylandAdwaitaDecoration::Minimize) ++ return QWaylandAdwaitaDecoration::MinimizeIcon; ++ else if (button == QWaylandAdwaitaDecoration::Maximize && maximized) ++ return QWaylandAdwaitaDecoration::RestoreIcon; ++ else ++ return QWaylandAdwaitaDecoration::MaximizeIcon; ++} ++ ++void QWaylandAdwaitaDecoration::drawButton(Button button, QPainter *painter) ++{ ++ const Qt::WindowStates windowStates = waylandWindow()->windowStates(); ++ const bool maximized = windowStates & Qt::WindowMaximized; ++ ++ const QRect btnRect = buttonRect(button).toRect(); ++ renderFlatRoundedButtonFrame(painter, btnRect, color(ButtonBackground, button)); ++ ++ QRect adjustedBtnRect = btnRect; ++ adjustedBtnRect.setSize(QSize(16, 16)); ++ adjustedBtnRect.translate(4, 4); ++ const QString svgIcon = m_icons[iconFromButtonAndState(button, maximized)]; ++ if (!svgIcon.isEmpty()) ++ renderButtonIcon(svgIcon, painter, adjustedBtnRect, color(Foreground)); ++ else // Fallback to use QIcon ++ renderButtonIcon(iconFromButtonAndState(button, maximized), painter, adjustedBtnRect); ++} ++ ++QColor QWaylandAdwaitaDecoration::color(ColorType type, Button button) ++{ ++ const bool active = waylandWindow()->windowStates() & Qt::WindowActive; ++ ++ switch (type) { ++ case Background: ++ case BackgroundInactive: ++ return active ? m_colors[Background] : m_colors[BackgroundInactive]; ++ case Foreground: ++ case ForegroundInactive: ++ return active ? m_colors[Foreground] : m_colors[ForegroundInactive]; ++ case Border: ++ case BorderInactive: ++ return active ? m_colors[Border] : m_colors[BorderInactive]; ++ case ButtonBackground: ++ case ButtonBackgroundInactive: ++ case HoveredButtonBackground: { ++ if (m_clicking == button) { ++ return m_colors[PressedButtonBackground]; ++ } else if (m_hoveredButtons.testFlag(button)) { ++ return m_colors[HoveredButtonBackground]; ++ } ++ return active ? m_colors[ButtonBackground] : m_colors[ButtonBackgroundInactive]; ++ } ++ default: ++ return m_colors[Background]; ++ } ++} ++ ++bool QWaylandAdwaitaDecoration::clickButton(Qt::MouseButtons b, Button btn) ++{ ++ auto repaint = qScopeGuard([this] { requestRepaint(); }); ++ ++ if (isLeftClicked(b)) { ++ m_clicking = btn; ++ return false; ++ } else if (isLeftReleased(b)) { ++ if (m_clicking == btn) { ++ m_clicking = None; ++ return true; ++ } else { ++ m_clicking = None; ++ } ++ } ++ return false; ++} ++ ++bool QWaylandAdwaitaDecoration::doubleClickButton(Qt::MouseButtons b, const QPointF &local, ++ const QDateTime ¤tTime) ++{ ++ if (isLeftClicked(b)) { ++ const qint64 clickInterval = m_lastButtonClick.msecsTo(currentTime); ++ m_lastButtonClick = currentTime; ++ const int doubleClickDistance = 5; ++ const QPointF posDiff = m_lastButtonClickPosition - local; ++ if ((clickInterval <= 500) ++ && ((posDiff.x() <= doubleClickDistance && posDiff.x() >= -doubleClickDistance) ++ && ((posDiff.y() <= doubleClickDistance && posDiff.y() >= -doubleClickDistance)))) { ++ return true; ++ } ++ ++ m_lastButtonClickPosition = local; ++ } ++ ++ return false; ++} ++ ++void QWaylandAdwaitaDecoration::updateButtonHoverState(Button hoveredButton) ++{ ++ bool currentCloseButtonState = m_hoveredButtons.testFlag(Close); ++ bool currentMaximizeButtonState = m_hoveredButtons.testFlag(Maximize); ++ bool currentMinimizeButtonState = m_hoveredButtons.testFlag(Minimize); ++ ++ m_hoveredButtons.setFlag(Close, hoveredButton == Button::Close); ++ m_hoveredButtons.setFlag(Maximize, hoveredButton == Button::Maximize); ++ m_hoveredButtons.setFlag(Minimize, hoveredButton == Button::Minimize); ++ ++ if (m_hoveredButtons.testFlag(Close) != currentCloseButtonState ++ || m_hoveredButtons.testFlag(Maximize) != currentMaximizeButtonState ++ || m_hoveredButtons.testFlag(Minimize) != currentMinimizeButtonState) { ++ requestRepaint(); ++ } ++} ++ ++void QWaylandAdwaitaDecoration::processMouseTop(QWaylandInputDevice *inputDevice, const QPointF &local, ++ Qt::MouseButtons b, Qt::KeyboardModifiers mods) ++{ ++ Q_UNUSED(mods) ++ ++ QDateTime currentDateTime = QDateTime::currentDateTime(); ++ QRect surfaceRect = waylandWindow()->windowContentGeometry() + margins(ShadowsOnly); ++ ++ if (!buttonRect(Close).contains(local) && !buttonRect(Maximize).contains(local) ++ && !buttonRect(Minimize).contains(local)) ++ updateButtonHoverState(Button::None); ++ ++ if (local.y() <= surfaceRect.top() + margins().bottom()) { ++ if (local.x() <= margins().left()) { ++ // top left bit ++#if QT_CONFIG(cursor) ++ waylandWindow()->setMouseCursor(inputDevice, Qt::SizeFDiagCursor); ++#endif ++ startResize(inputDevice, Qt::TopEdge | Qt::LeftEdge, b); ++ } else if (local.x() > surfaceRect.right() - margins().left()) { ++ // top right bit ++#if QT_CONFIG(cursor) ++ waylandWindow()->setMouseCursor(inputDevice, Qt::SizeBDiagCursor); ++#endif ++ startResize(inputDevice, Qt::TopEdge | Qt::RightEdge, b); ++ } else { ++ // top resize bit ++#if QT_CONFIG(cursor) ++ waylandWindow()->setMouseCursor(inputDevice, Qt::SizeVerCursor); ++#endif ++ startResize(inputDevice, Qt::TopEdge, b); ++ } ++ } else if (local.x() <= surfaceRect.left() + margins().left()) { ++ processMouseLeft(inputDevice, local, b, mods); ++ } else if (local.x() > surfaceRect.right() - margins().right()) { ++ processMouseRight(inputDevice, local, b, mods); ++ } else if (buttonRect(Close).contains(local)) { ++ if (clickButton(b, Close)) { ++ QWindowSystemInterface::handleCloseEvent(window()); ++ m_hoveredButtons.setFlag(Close, false); ++ } ++ updateButtonHoverState(Close); ++ } else if (m_buttons.contains(Maximize) && buttonRect(Maximize).contains(local)) { ++ updateButtonHoverState(Maximize); ++ if (clickButton(b, Maximize)) { ++ window()->setWindowStates(window()->windowStates() ^ Qt::WindowMaximized); ++ m_hoveredButtons.setFlag(Maximize, false); ++ } ++ } else if (m_buttons.contains(Minimize) && buttonRect(Minimize).contains(local)) { ++ updateButtonHoverState(Minimize); ++ if (clickButton(b, Minimize)) { ++ window()->setWindowState(Qt::WindowMinimized); ++ m_hoveredButtons.setFlag(Minimize, false); ++ } ++ } else if (doubleClickButton(b, local, currentDateTime)) { ++ window()->setWindowStates(window()->windowStates() ^ Qt::WindowMaximized); ++ } else { ++ // Show window menu ++ if (b == Qt::MouseButton::RightButton) ++ waylandWindow()->shellSurface()->showWindowMenu(inputDevice); ++#if QT_CONFIG(cursor) ++ waylandWindow()->restoreMouseCursor(inputDevice); ++#endif ++ startMove(inputDevice, b); ++ } ++} ++ ++void QWaylandAdwaitaDecoration::processMouseBottom(QWaylandInputDevice *inputDevice, const QPointF &local, ++ Qt::MouseButtons b, Qt::KeyboardModifiers mods) ++{ ++ Q_UNUSED(mods) ++ if (local.x() <= margins().left()) { ++ // bottom left bit ++#if QT_CONFIG(cursor) ++ waylandWindow()->setMouseCursor(inputDevice, Qt::SizeBDiagCursor); ++#endif ++ startResize(inputDevice, Qt::BottomEdge | Qt::LeftEdge, b); ++ } else if (local.x() > window()->width() + margins().right()) { ++ // bottom right bit ++#if QT_CONFIG(cursor) ++ waylandWindow()->setMouseCursor(inputDevice, Qt::SizeFDiagCursor); ++#endif ++ startResize(inputDevice, Qt::BottomEdge | Qt::RightEdge, b); ++ } else { ++ // bottom bit ++#if QT_CONFIG(cursor) ++ waylandWindow()->setMouseCursor(inputDevice, Qt::SizeVerCursor); ++#endif ++ startResize(inputDevice, Qt::BottomEdge, b); ++ } ++} ++ ++void QWaylandAdwaitaDecoration::processMouseLeft(QWaylandInputDevice *inputDevice, const QPointF &local, ++ Qt::MouseButtons b, Qt::KeyboardModifiers mods) ++{ ++ Q_UNUSED(local) ++ Q_UNUSED(mods) ++#if QT_CONFIG(cursor) ++ waylandWindow()->setMouseCursor(inputDevice, Qt::SizeHorCursor); ++#endif ++ startResize(inputDevice, Qt::LeftEdge, b); ++} ++ ++void QWaylandAdwaitaDecoration::processMouseRight(QWaylandInputDevice *inputDevice, const QPointF &local, ++ Qt::MouseButtons b, Qt::KeyboardModifiers mods) ++{ ++ Q_UNUSED(local) ++ Q_UNUSED(mods) ++#if QT_CONFIG(cursor) ++ waylandWindow()->setMouseCursor(inputDevice, Qt::SizeHorCursor); ++#endif ++ startResize(inputDevice, Qt::RightEdge, b); ++} ++ ++void QWaylandAdwaitaDecoration::requestRepaint() const ++{ ++ // Set dirty flag ++ if (waylandWindow()->decoration()) ++ waylandWindow()->decoration()->update(); ++ ++ // Request re-paint ++ waylandWindow()->window()->requestUpdate(); ++} ++ ++} // namespace QtWaylandClient ++ ++QT_END_NAMESPACE ++ ++#include "moc_qwaylandadwaitadecoration_p.cpp" +diff --git a/src/plugins/decorations/adwaita/qwaylandadwaitadecoration_p.h b/src/plugins/decorations/adwaita/qwaylandadwaitadecoration_p.h +new file mode 100644 +index 000000000..34874e088 +--- /dev/null ++++ b/src/plugins/decorations/adwaita/qwaylandadwaitadecoration_p.h +@@ -0,0 +1,155 @@ ++// Copyright (C) 2023 Jan Grulich ++// Copyright (C) 2023 The Qt Company Ltd. ++// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only ++ ++#ifndef QWAYLANDADWAITADECORATION_P_H ++#define QWAYLANDADWAITADECORATION_P_H ++ ++#include ++ ++#include ++ ++QT_BEGIN_NAMESPACE ++ ++class QDBusVariant; ++class QPainter; ++ ++namespace QtWaylandClient { ++ ++// ++// INFO ++// ------------- ++// ++// This is a Qt decoration plugin implementing Adwaita-like (GNOME) client-side ++// window decorations. It uses xdg-desktop-portal to get the user configuration. ++// This plugin was originally part of QGnomePlatform and later made a separate ++// project named QAdwaitaDecorations. ++// ++// INFO: How SVG icons are used here? ++// We try to find an SVG icon for a particular button from the current icon theme. ++// This icon is then opened as a file, it's content saved and later loaded to be ++// painted with QSvgRenderer, but before it's painted, we try to find following ++// patterns: ++// 1) fill=[\"']#[0-9A-F]{6}[\"'] ++// 2) fill:#[0-9A-F]{6} ++// 3) fill=[\"']currentColor[\"'] ++// The color in this case doesn't match the theme and is replaced by Foreground color. ++// ++// FIXME/TODO: ++// This plugin currently have all the colors for the decorations hardcoded. ++// There might be a way to get these from GTK/libadwaita (not sure), but problem is ++// we want Gtk4 version and using Gtk4 together with QGtk3Theme from QtBase that links ++// to Gtk3 will not work out. Possibly in future we can make a QGtk4Theme providing us ++// what we need to paint the decorations without having to deal with the colors ourself. ++// ++// TODO: Implement shadows ++ ++ ++class QWaylandAdwaitaDecoration : public QWaylandAbstractDecoration ++{ ++ Q_OBJECT ++public: ++ enum ColorType { ++ Background, ++ BackgroundInactive, ++ Foreground, ++ ForegroundInactive, ++ Border, ++ BorderInactive, ++ ButtonBackground, ++ ButtonBackgroundInactive, ++ HoveredButtonBackground, ++ PressedButtonBackground ++ }; ++ ++ enum Placement { ++ Left = 0, ++ Right = 1 ++ }; ++ ++ enum Button { ++ None = 0x0, ++ Close = 0x1, ++ Minimize = 0x02, ++ Maximize = 0x04 ++ }; ++ Q_DECLARE_FLAGS(Buttons, Button); ++ ++ enum ButtonIcon { ++ CloseIcon, ++ MinimizeIcon, ++ MaximizeIcon, ++ RestoreIcon ++ }; ++ ++ QWaylandAdwaitaDecoration(); ++ virtual ~QWaylandAdwaitaDecoration() = default; ++ ++protected: ++ QMargins margins(MarginsType marginsType = Full) const override; ++ void paint(QPaintDevice *device) override; ++ bool handleMouse(QWaylandInputDevice *inputDevice, const QPointF &local, const QPointF &global, ++ Qt::MouseButtons b, Qt::KeyboardModifiers mods) override; ++ bool handleTouch(QWaylandInputDevice *inputDevice, const QPointF &local, const QPointF &global, ++ QEventPoint::State state, Qt::KeyboardModifiers mods) override; ++ ++private Q_SLOTS: ++ void settingChanged(const QString &group, const QString &key, const QDBusVariant &value); ++ ++private: ++ // Makes a call to xdg-desktop-portal (Settings) to load initial configuration ++ void loadConfiguration(); ++ // Updates color scheme from light to dark and vice-versa ++ void updateColors(bool isDark); ++ // Updates titlebar layout with position and button order ++ void updateTitlebarLayout(const QString &layout); ++ ++ // Returns a bounding rect for a given button type ++ QRectF buttonRect(Button button) const; ++ // Draw given button type using SVG icon (when found) or fallback to QPixmap icon ++ void drawButton(Button button, QPainter *painter); ++ ++ // Returns color for given type and button ++ QColor color(ColorType type, Button button = None); ++ ++ // Returns whether the left button was clicked i.e. pressed and released ++ bool clickButton(Qt::MouseButtons b, Button btn); ++ // Returns whether the left button was double-clicked ++ bool doubleClickButton(Qt::MouseButtons b, const QPointF &local, const QDateTime ¤tTime); ++ // Updates button hover state ++ void updateButtonHoverState(Button hoveredButton); ++ ++ void processMouseTop(QWaylandInputDevice *inputDevice, const QPointF &local, Qt::MouseButtons b, ++ Qt::KeyboardModifiers mods); ++ void processMouseBottom(QWaylandInputDevice *inputDevice, const QPointF &local, ++ Qt::MouseButtons b, Qt::KeyboardModifiers mods); ++ void processMouseLeft(QWaylandInputDevice *inputDevice, const QPointF &local, ++ Qt::MouseButtons b, Qt::KeyboardModifiers mods); ++ void processMouseRight(QWaylandInputDevice *inputDevice, const QPointF &local, ++ Qt::MouseButtons b, Qt::KeyboardModifiers mods); ++ // Request to repaint the decorations. This will be invoked when button hover changes or ++ // when there is a setting change (e.g. layout change). ++ void requestRepaint() const; ++ ++ // Button states ++ Button m_clicking = None; ++ Buttons m_hoveredButtons = None; ++ QDateTime m_lastButtonClick; ++ QPointF m_lastButtonClickPosition; ++ ++ // Configuration ++ QMap m_buttons; ++ QMap m_colors; ++ QMap m_icons; ++ std::unique_ptr m_font; ++ Placement m_placement = Right; ++ ++ QStaticText m_windowTitle; ++}; ++Q_DECLARE_OPERATORS_FOR_FLAGS(QWaylandAdwaitaDecoration::Buttons) ++ ++} // namespace QtWaylandClient ++ ++QT_END_NAMESPACE ++ ++#endif // QWAYLANDADWAITADECORATION_P_H diff --git a/qtwayland-client-disable-threaded-gl-on-desktop-nvidia.patch b/qtwayland-client-disable-threaded-gl-on-desktop-nvidia.patch deleted file mode 100644 index e990de2..0000000 --- a/qtwayland-client-disable-threaded-gl-on-desktop-nvidia.patch +++ /dev/null @@ -1,40 +0,0 @@ -From e4156bad6398dcbe8740041148d95ee9ed437d8b Mon Sep 17 00:00:00 2001 -From: David Redondo -Date: Wed, 31 Jan 2024 09:01:48 +0100 -Subject: [PATCH] client: Disable threaded GL on desktop NVIDIA - -Otherwise QtQuick windows freeze when resized. -In order to still use threaded rendering on -embedded platforms where resizing is not required -we check if XDG_CURRENT_DESKTOP which should be -set by desktop environments. - -Task-number: QTBUG-95817 -Pick-to: 6.6.2 -Change-Id: Ic2348c3169e0ade8c5463e3d7c7a1c45037a89a7 -Reviewed-by: Eskil Abrahamsen Blomfeldt -Reviewed-by: David Edmundson -(cherry picked from commit 6d83cf94b568fa9e591761a182cf84e3959fbf32) -Reviewed-by: Qt Cherry-pick Bot -(cherry picked from commit 38348ce5b06624cb6e36f814ebdfbc3ec61f1691) -Reviewed-by: David Redondo ---- - -diff --git a/src/hardwareintegration/client/wayland-egl/qwaylandeglclientbufferintegration.cpp b/src/hardwareintegration/client/wayland-egl/qwaylandeglclientbufferintegration.cpp -index cc7ee90..3b97aef 100644 ---- a/src/hardwareintegration/client/wayland-egl/qwaylandeglclientbufferintegration.cpp -+++ b/src/hardwareintegration/client/wayland-egl/qwaylandeglclientbufferintegration.cpp -@@ -92,6 +92,13 @@ - break; - } - } -+ -+ // On desktop NVIDIA resizing QtQuick freezes them when using threaded rendering QTBUG-95817 -+ // In order to support threaded rendering on embedded platforms where resizing is not needed -+ // we check if XDG_CURRENT_DESKTOP is set which desktop environments should set -+ if (qstrcmp(vendor, "NVIDIA") == 0 && qEnvironmentVariableIsSet("XDG_CURRENT_DESKTOP")) { -+ m_supportsThreading = false; -+ } - } - - bool QWaylandEglClientBufferIntegration::isValid() const diff --git a/qtwayland-client-fix-qt-keypadmodifier-for-key-events.patch b/qtwayland-client-fix-qt-keypadmodifier-for-key-events.patch deleted file mode 100644 index 677d5f6..0000000 --- a/qtwayland-client-fix-qt-keypadmodifier-for-key-events.patch +++ /dev/null @@ -1,35 +0,0 @@ -From e0796865151b06dddc5c5665f9ca8bdc8021fcd8 Mon Sep 17 00:00:00 2001 -From: Nicolas Fella -Date: Wed, 24 Jan 2024 01:39:24 +0100 -Subject: [PATCH] Client: Fix Qt::KeypadModifier for key events - -Use the right QXkbCommon::modifiers overload that can resolve the -modifier. - -f614fdfa5dc522f805c7c061535df6a0dc7409b9 did this for wayland-server, -do the same for the client side - -Pick-to: 6.5 -Change-Id: Iff0c105cb31201241d4972a7772cf997cede3fc3 -Reviewed-by: Liang Qi -(cherry picked from commit 17e46725c5971a3067d5abd5506fde6c03d0935f) -Reviewed-by: Qt Cherry-pick Bot -(cherry picked from commit bec69733f67e65de456e63db600f4e871ae2b53b) ---- - src/client/qwaylandinputdevice.cpp | 3 +-- - 1 file changed, 1 insertion(+), 2 deletions(-) - -diff --git a/src/client/qwaylandinputdevice.cpp b/src/client/qwaylandinputdevice.cpp -index a4f8757e3..8d6ebba08 100644 ---- a/src/client/qwaylandinputdevice.cpp -+++ b/src/client/qwaylandinputdevice.cpp -@@ -1335,8 +1335,7 @@ void QWaylandInputDevice::Keyboard::keyboard_key(uint32_t serial, uint32_t time, - auto code = key + 8; // map to wl_keyboard::keymap_format::keymap_format_xkb_v1 - - xkb_keysym_t sym = xkb_state_key_get_one_sym(mXkbState.get(), code); -- -- Qt::KeyboardModifiers modifiers = mParent->modifiers(); -+ Qt::KeyboardModifiers modifiers = QXkbCommon::modifiers(mXkbState.get(), sym); - - int qtkey = keysymToQtKey(sym, modifiers, mXkbState.get(), code); - QString text = QXkbCommon::lookupString(mXkbState.get(), code); diff --git a/qtwayland-use-adwaita-decorations-by-default.patch b/qtwayland-use-adwaita-decorations-by-default.patch deleted file mode 100644 index 3fab6f2..0000000 --- a/qtwayland-use-adwaita-decorations-by-default.patch +++ /dev/null @@ -1,14 +0,0 @@ -diff --git a/src/client/qwaylandintegration.cpp b/src/client/qwaylandintegration.cpp -index 06a1aec..bb387f1 100644 ---- a/src/client/qwaylandintegration.cpp -+++ b/src/client/qwaylandintegration.cpp -@@ -87,6 +87,9 @@ QWaylandIntegration::QWaylandIntegration() - QWaylandWindow::fixedToplevelPositions = - !qEnvironmentVariableIsSet("QT_WAYLAND_DISABLE_FIXED_POSITIONS"); - -+ if (!qEnvironmentVariableIsSet("QT_WAYLAND_DECORATION")) -+ qputenv("QT_WAYLAND_DECORATION", "adwaita"); -+ - sInstance = this; - } - diff --git a/sources b/sources index a75c4f7..3f98f53 100644 --- a/sources +++ b/sources @@ -1 +1 @@ -SHA512 (qtwayland-everywhere-src-6.6.1.tar.xz) = 7f6533754daad7a2804ddddcd5139608c2b8f1ef92ae8a238c1ed4fc41c8a3ee532da0b2e57266d07d4d39d1ec6c83eca487c73788a108af30035b0dae262c76 +SHA512 (qtwayland-everywhere-src-6.7.0.tar.xz) = cda0e0736f85656d05b2399970413ffc5082af4256c8b3087c3f1d06cad5ef5ad7cb8838513723569193df02cd3c3df3d5478d99464606c62c42629ef75c225f