commit 41fd1077f7479057671e4dc902f5381cf70bbf8f Author: MSVSphere Packaging Team Date: Tue Nov 26 19:07:59 2024 +0300 import qt6-qtwayland-6.7.1-3.el10 diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..a497a00 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +SOURCES/qtwayland-everywhere-src-6.7.1.tar.xz diff --git a/.qt6-qtwayland.metadata b/.qt6-qtwayland.metadata new file mode 100644 index 0000000..c52a57c --- /dev/null +++ b/.qt6-qtwayland.metadata @@ -0,0 +1 @@ +f25df31c2b833c648ec3c6363dddca3d082cb49f SOURCES/qtwayland-everywhere-src-6.7.1.tar.xz diff --git a/SOURCES/qtwayland-add-gnome-like-csd-plugin.patch b/SOURCES/qtwayland-add-gnome-like-csd-plugin.patch new file mode 100644 index 0000000..3e47e8c --- /dev/null +++ b/SOURCES/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/SPECS/qt6-qtwayland.spec b/SPECS/qt6-qtwayland.spec new file mode 100644 index 0000000..78a4b3e --- /dev/null +++ b/SPECS/qt6-qtwayland.spec @@ -0,0 +1,355 @@ + +%global qt_module qtwayland + +#global unstable 1 +%if 0%{?unstable} +%global prerelease rc2 +%endif + +%global examples 1 +%global build_tests 1 + +Summary: Qt6 - Wayland platform support and QtCompositor module +Name: qt6-%{qt_module} +Version: 6.7.1 +Release: 3%{?dist} + +License: LGPL-3.0-only OR GPL-3.0-only WITH Qt-GPL-exception-1.0 +Url: http://www.qt.io +%global majmin %(echo %{version} | cut -d. -f1-2) +%global qt_version %(echo %{version} | cut -d~ -f1) + +%if 0%{?unstable} +Source0: https://download.qt.io/development_releases/qt/%{majmin}/%{qt_version}/submodules/%{qt_module}-everywhere-src-%{qt_version}-%{prerelease}.tar.xz +%else +Source0: https://download.qt.io/official_releases/qt/%{majmin}/%{version}/submodules/%{qt_module}-everywhere-src-%{version}.tar.xz +%endif + +# Upstream patches +# 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 + +# filter qml provides +%global __provides_exclude_from ^%{_qt6_archdatadir}/qml/.*\\.so$ + +BuildRequires: gcc-c++ +BuildRequires: cmake +BuildRequires: ninja-build +BuildRequires: qt6-qtbase-devel >= %{version} +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) +BuildRequires: pkgconfig(wayland-server) +BuildRequires: pkgconfig(wayland-client) +BuildRequires: pkgconfig(wayland-cursor) +BuildRequires: pkgconfig(wayland-egl) +BuildRequires: pkgconfig(egl) +BuildRequires: pkgconfig(gl) +BuildRequires: pkgconfig(xcomposite) +BuildRequires: pkgconfig(xrender) +BuildRequires: pkgconfig(libudev) +BuildRequires: pkgconfig(libinput) + +BuildRequires: libXext-devel + +%description +%{summary}. + +%package devel +Summary: Development files for %{name} +Requires: %{name}%{?_isa} = %{version}-%{release} +Requires: qt6-qtbase-devel%{?_isa} +Requires: qt6-qtdeclarative-devel%{?_isa} +%description devel +%{summary}. + +%if 0%{?examples} +%package examples +Summary: Programming examples for %{name} +Requires: %{name}%{?_isa} = %{version}-%{release} +# BuildRequires: qt6-qtwayland-devel >= %{version} +%description examples +%{summary}. +%endif + +%if 0%{?build_tests} +%package tests +Summary: Unit tests for %{name} +Requires: %{name}%{?_isa} = %{version}-%{release} +%description tests +%{summary}. +%endif + +%prep +%autosetup -n %{qt_module}-everywhere-src-%{qt_version}%{?unstable:-%{prerelease}} -p1 + + +%build +%cmake_qt6 \ + -DQT_BUILD_EXAMPLES:BOOL=%{?examples:ON}%{!?examples:OFF} \ + -DQT_BUILD_TESTS=%{?build_tests:ON}%{!?build_tests:OFF} \ + -DQT_INSTALL_EXAMPLES_SOURCES=%{?examples:ON}%{!?examples:OFF} + +%cmake_build + + +%install +%if 0%{?build_tests} +%qt6_dont_autoinstall_tests +%endif + +%cmake_install + +%if 0%{?build_tests} +%qt6_install_tests +%endif + +## .prl/.la file love +# nuke .prl reference(s) to %%buildroot, excessive (.la-like) libs +pushd %{buildroot}%{_qt6_libdir} +for prl_file in libQt6*.prl ; do + sed -i -e "/^QMAKE_PRL_BUILD_DIR/d" ${prl_file} + if [ -f "$(basename ${prl_file} .prl).so" ]; then + rm -fv "$(basename ${prl_file} .prl).la" + sed -i -e "/^QMAKE_PRL_LIBS/d" ${prl_file} + fi +done +popd + + +%ldconfig_scriptlets + +%files +%doc README +%license LICENSES/* +%{_qt6_libdir}/libQt6WaylandCompositor.so.6* +%{_qt6_libdir}/libQt6WaylandClient.so.6* +%{_qt6_libdir}/libQt6WaylandCompositor.so.6* +%{_qt6_libdir}/libQt6WaylandClient.so.6* +%{_qt6_libdir}/libQt6WaylandEglClientHwIntegration.so.6* +%{_qt6_libdir}/libQt6WaylandEglCompositorHwIntegration.so.6* +%{_qt6_libdir}/libQt6WlShellIntegration.so.6* +%{_qt6_plugindir}/wayland-decoration-client/ +%{_qt6_plugindir}/wayland-graphics-integration-server +%{_qt6_plugindir}/wayland-graphics-integration-client +%{_qt6_plugindir}/wayland-shell-integration +%{_qt6_plugindir}/platforms/libqwayland-egl.so +%{_qt6_plugindir}/platforms/libqwayland-generic.so +#{_qt6_plugindir}/platforms/libqwayland-xcomposite-egl.so +#{_qt6_plugindir}/platforms/libqwayland-xcomposite-glx.so +%{_qt6_qmldir}/QtWayland/ + +%files devel +%{_qt6_libexecdir}/qtwaylandscanner +%{_qt6_headerdir}/QtWaylandCompositor/ +%{_qt6_headerdir}/QtWaylandClient/ +%{_qt6_headerdir}/QtWaylandEglClientHwIntegration/ +%{_qt6_headerdir}/QtWaylandEglCompositorHwIntegration/ +%{_qt6_headerdir}/QtWlShellIntegration/ +%{_qt6_headerdir}/QtWaylandGlobal/ +%{_qt6_libdir}/libQt6WaylandCompositor.so +%{_qt6_libdir}/libQt6WaylandClient.so +%{_qt6_libdir}/libQt6WaylandEglClientHwIntegration.so +%{_qt6_libdir}/libQt6WaylandEglCompositorHwIntegration.so +%{_qt6_libdir}/libQt6WlShellIntegration.so +%{_qt6_libdir}/libQt6WaylandCompositor.prl +%{_qt6_libdir}/libQt6WaylandClient.prl +%{_qt6_libdir}/libQt6WaylandEglClientHwIntegration.prl +%{_qt6_libdir}/libQt6WaylandEglCompositorHwIntegration.prl +%{_qt6_libdir}/libQt6WlShellIntegration.prl +%{_qt6_libdir}/cmake/Qt6WaylandCompositor/Qt6WaylandCompositorConfig*.cmake +%{_qt6_archdatadir}/mkspecs/modules/*.pri +%{_qt6_libdir}/cmake/Qt6/*.cmake +%{_qt6_libdir}/cmake/Qt6BuildInternals/StandaloneTests/QtWaylandTestsConfig.cmake +%{_qt6_libdir}/cmake/Qt6Gui/*.cmake +%{_qt6_libdir}/cmake/Qt6Qml/QmlPlugins/*.cmake +%dir %{_qt6_libdir}/cmake/Qt6WaylandCompositor/ +%{_qt6_libdir}/cmake/Qt6WaylandCompositor/ +%dir %{_qt6_libdir}/cmake/Qt6WaylandClient/ +%{_qt6_libdir}/cmake/Qt6WaylandClient/ +%dir %{_qt6_libdir}/cmake/Qt6WaylandScannerTools/ +%{_qt6_libdir}/cmake/Qt6WaylandScannerTools/ +%dir %{_qt6_libdir}/cmake/Qt6WaylandEglClientHwIntegrationPrivate/ +%{_qt6_libdir}/cmake/Qt6WaylandEglClientHwIntegrationPrivate/ +%dir %{_qt6_libdir}/cmake/Qt6WaylandEglCompositorHwIntegrationPrivate/ +%{_qt6_libdir}/cmake/Qt6WaylandEglCompositorHwIntegrationPrivate/ +%dir %{_qt6_libdir}/cmake/Qt6WlShellIntegrationPrivate/ +%{_qt6_libdir}/cmake/Qt6WlShellIntegrationPrivate/ +%dir %{_qt6_libdir}/cmake/Qt6WaylandGlobalPrivate/ +%{_qt6_libdir}/cmake/Qt6WaylandGlobalPrivate/ +%{_qt6_libdir}/qt6/metatypes/qt6*_metatypes.json +%{_qt6_libdir}/qt6/modules/*.json +%{_qt6_libdir}/pkgconfig/*.pc + + +%if 0%{?examples} +%files examples +%{_qt6_examplesdir}/wayland/ +%endif + +%if 0%{?build_tests} +%files tests +%{_qt6_archdatadir}/tests +%endif + +%changelog +* Tue Nov 26 2024 MSVSphere Packaging Team - 6.7.1-3 +- Rebuilt for MSVSphere 10 + +* Mon Jun 24 2024 Troy Dawson - 6.7.1-3 +- Bump release for June 2024 mass rebuild + +* Tue Jun 04 2024 Jan Grulich - 6.7.1-2 +- Add rpminspect.yaml + Resolves: RHEL-36430 + +* Fri May 31 2024 Jan Grulich - 6.7.1-1 +- 6.7.1 + Resolves: RHEL-36430 + +* 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 + +* Thu Feb 08 2024 Jan Grulich - 6.6.1-5 +- Backport upstream fix: Fix Qt::KeypadModifier for key events + +* Wed Feb 07 2024 Jan Grulich +- Backport upstream fix: disable threaded GL on desktop NVIDIA + +* Fri Jan 26 2024 Fedora Release Engineering - 6.6.1-3 +- Rebuilt for https://fedoraproject.org/wiki/Fedora_40_Mass_Rebuild + +* Mon Jan 22 2024 Fedora Release Engineering - 6.6.1-2 +- Rebuilt for https://fedoraproject.org/wiki/Fedora_40_Mass_Rebuild + +* Mon Nov 27 2023 Jan Grulich - 6.6.1-1 +- 6.6.1 + +* Wed Oct 11 2023 Jan Grulich - 6.6.0-1 +- 6.6.0 + +* Sun Oct 01 2023 Justin Zobel - 6.5.3-1 +- new version + +* Wed Aug 16 2023 Jan Grulich - 6.5.2-3 +- Use QAdwaitaDecorations by default + +* Fri Jul 21 2023 Fedora Release Engineering - 6.5.2-2 +- Rebuilt for https://fedoraproject.org/wiki/Fedora_39_Mass_Rebuild + +* Fri Jul 21 2023 Jan Grulich - 6.5.2-1 +- 6.5.2 + +* Wed Jul 12 2023 Jan Grulich - 6.5.1-3 +- Rebuild for qtbase private API version change + +* Wed Jul 12 2023 Jan Grulich - 6.5.1-2 +- Rebuild for qtbase private API version change + +* Mon May 22 2023 Jan Grulich - 6.5.1-1 +- 6.5.1 + +* Tue Apr 04 2023 Jan Grulich - 6.5.0-1 +- 6.5.0 + +* Thu Mar 23 2023 Jan Grulich - 6.4.3-1 +- 6.4.3 + +* Tue Jan 31 2023 Jan Grulich - 6.4.2-3 +- migrated to SPDX license + +* Fri Jan 20 2023 Fedora Release Engineering - 6.4.2-2 +- Rebuilt for https://fedoraproject.org/wiki/Fedora_38_Mass_Rebuild + +* Mon Jan 16 2023 Jan Grulich - 6.4.2-1 +- 6.4.2 + +* Wed Nov 23 2022 Jan Grulich - 6.4.1-1 +- 6.4.1 + +* Mon Oct 31 2022 Jan Grulich - 6.4.0-1 +- 6.4.0 + +* Fri Jul 29 2022 Jan Grulich - 6.3.1-4 +- Do not take decoration shadows into account when placing popups + +* Tue Jul 26 2022 Jan Grulich - 6.3.1-3 +- Keep toplevel windows in the top left corner of the screen + +* Sat Jul 23 2022 Fedora Release Engineering - 6.3.1-2 +- Rebuilt for https://fedoraproject.org/wiki/Fedora_37_Mass_Rebuild + +* Wed Jul 13 2022 Jan Grulich - 6.3.1-1 +- 6.3.1 + +* Wed May 25 2022 Jan Grulich - 6.3.0-2 +- Enable examples + +* Wed Apr 13 2022 Jan Grulich - 6.3.0-1 +- 6.3.0 + +* Fri Feb 25 2022 Jan Grulich - 6.2.3-2 +- Enable s390x builds + +* Mon Jan 31 2022 Jan Grulich - 6.2.3-1 +- 6.2.3 + +* Fri Jan 21 2022 Fedora Release Engineering - 6.2.2-2 +- Rebuilt for https://fedoraproject.org/wiki/Fedora_36_Mass_Rebuild + +* Tue Dec 14 2021 Jan Grulich - 6.2.2-1 +- 6.2.2 + +* Fri Oct 29 2021 Jan Grulich - 6.2.1-1 +- 6.2.1 + +* Thu Sep 30 2021 Jan Grulich - 6.2.0-1 +- 6.2.0 + +* Mon Sep 27 2021 Jan Grulich - 6.2.0~rc2-1 +- 6.2.0 - rc2 + +* Sat Sep 18 2021 Jan Grulich - 6.2.0~rc-1 +- 6.2.0 - rc + +* Mon Sep 13 2021 Jan Grulich - 6.2.0~beta4-1 +- 6.2.0 - beta4 + +* Thu Aug 12 2021 Jan Grulich - 6.1.2-1 +- 6.1.2 + +* Fri Jul 23 2021 Fedora Release Engineering - 6.1.1-2 +- Rebuilt for https://fedoraproject.org/wiki/Fedora_35_Mass_Rebuild + +* Mon Jun 07 2021 Jan Grulich - 6.1.1-1 +- 6.1.1 + +* Thu May 06 2021 Jan Grulich - 6.1.0-1 +- 6.1.0 + +* Mon Apr 05 2021 Jan Grulich - 6.0.3-1 +- 6.0.3 + +* Thu Feb 04 2021 Jan Grulich - 6.0.1-1 +- 6.0.1 + +* Wed Jan 27 2021 Fedora Release Engineering - 6.0.0-2 +- Rebuilt for https://fedoraproject.org/wiki/Fedora_34_Mass_Rebuild + +* Wed Jan 13 2021 Jan Grulich - 6.0.0 +- 6.0.0