From ed748d94e8b192b5785f2146c83415e4ec36d5f9 Mon Sep 17 00:00:00 2001 From: Fedora Release Engineering Date: Fri, 25 Jul 2025 11:33:58 +0000 Subject: [PATCH 01/12] Rebuilt for https://fedoraproject.org/wiki/Fedora_43_Mass_Rebuild --- qt6-qtbase.spec | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/qt6-qtbase.spec b/qt6-qtbase.spec index 3998162..0107464 100644 --- a/qt6-qtbase.spec +++ b/qt6-qtbase.spec @@ -46,7 +46,7 @@ BuildRequires: pkgconfig(libsystemd) Name: qt6-qtbase Summary: Qt6 - QtBase components Version: 6.9.1 -Release: 1%{?dist} +Release: 2%{?dist} License: LGPL-3.0-only OR GPL-3.0-only WITH Qt-GPL-exception-1.0 Url: http://qt-project.org/ @@ -928,6 +928,9 @@ make check -k ||: %changelog +* Fri Jul 25 2025 Fedora Release Engineering - 6.9.1-2 +- Rebuilt for https://fedoraproject.org/wiki/Fedora_43_Mass_Rebuild + * Mon Jun 02 2025 Jan Grulich - 6.9.1-1 - 6.9.1 From 0e85496669f757f803f46943e2e487a09eb6419b Mon Sep 17 00:00:00 2001 From: Adam Williamson Date: Mon, 28 Jul 2025 14:25:11 -0700 Subject: [PATCH 02/12] Adjust for https://fedoraproject.org/wiki/Changes/dropingOfCertPemFile removals Backport of https://codereview.qt-project.org/c/qt/qtbase/+/664056 --- ...t-store-locations-for-modern-Red-Hat.patch | 65 +++++++++++++++++++ qt6-qtbase.spec | 9 ++- 2 files changed, 73 insertions(+), 1 deletion(-) create mode 100644 0001-Update-SSL-trust-store-locations-for-modern-Red-Hat.patch diff --git a/0001-Update-SSL-trust-store-locations-for-modern-Red-Hat.patch b/0001-Update-SSL-trust-store-locations-for-modern-Red-Hat.patch new file mode 100644 index 0000000..4ee642c --- /dev/null +++ b/0001-Update-SSL-trust-store-locations-for-modern-Red-Hat.patch @@ -0,0 +1,65 @@ +From 10a6f2b2d27ad2372c314bd1f2b21e225ece069b Mon Sep 17 00:00:00 2001 +From: Adam Williamson +Date: Mon, 28 Jul 2025 11:52:41 -0700 +Subject: [PATCH] Update SSL trust store locations for modern Red Hat + +Red Hat family distros haven't used /usr/share/ssl since 2004: +https://bugzilla.redhat.com/show_bug.cgi?id=143392 + +/etc/ssl/certs has never been a canonical location on Red Hat +family distros, and as of +https://fedoraproject.org/wiki/Changes/dropingOfCertPemFile is +being removed. The same change also removes +/etc/pki/tls/certs/ca-bundle.crt . This updates both magic lists +with the modern canonical locations for Red Hat and derived +distros. The tls-ca-bundle.pem bundle has been around in Fedora +since around 2013: +https://src.fedoraproject.org/rpms/ca-certificates/c/d538ada99cda951da7da3a72dc5eea06f02be212 +The directory-hash dir has been around since around 2021: +https://src.fedoraproject.org/rpms/ca-certificates/c/1c8b67fb5ab3954a308d019461bb30d50412087e + +As I blogged in 2015, it is generally a bad idea to be trying to +second-guess your SSL library about this stuff. OpenSSL has +`SSL_CTX_set_default_verify_paths` etc.; why not use them? At +least in the first instance; you can fall back to magic lists if +that fails. + +https://www.happyassassin.net/posts/2015/01/12/a-note-about-ssltls-trusted-certificate-stores-and-platforms/ + +Signed-off-by: Adam Williamson +--- + src/network/ssl/qsslsocket.cpp | 3 ++- + src/plugins/tls/openssl/qtlsbackend_openssl.cpp | 3 ++- + 2 files changed, 4 insertions(+), 2 deletions(-) + +diff --git a/src/network/ssl/qsslsocket.cpp b/src/network/ssl/qsslsocket.cpp +index 395394d4325..b4d954be5a4 100644 +--- a/src/network/ssl/qsslsocket.cpp ++++ b/src/network/ssl/qsslsocket.cpp +@@ -2962,7 +2962,8 @@ QList QSslSocketPrivate::unixRootCertDirectories() + static const QByteArray dirs[] = { + ba("/etc/ssl/certs/"), // (K)ubuntu, OpenSUSE, Mandriva ... + ba("/usr/lib/ssl/certs/"), // Gentoo, Mandrake +- ba("/usr/share/ssl/"), // Centos, Redhat, SuSE ++ ba("/usr/share/ssl/"), // Red Hat pre-2004, SuSE ++ ba("/etc/pki/ca-trust/extracted/pem/directory-hash/"), // Red Hat 2021+ + ba("/usr/local/ssl/"), // Normal OpenSSL Tarball + ba("/var/ssl/certs/"), // AIX + ba("/usr/local/ssl/certs/"), // Solaris +diff --git a/src/plugins/tls/openssl/qtlsbackend_openssl.cpp b/src/plugins/tls/openssl/qtlsbackend_openssl.cpp +index 85b17c9d491..78e62bd6b21 100644 +--- a/src/plugins/tls/openssl/qtlsbackend_openssl.cpp ++++ b/src/plugins/tls/openssl/qtlsbackend_openssl.cpp +@@ -388,7 +388,8 @@ QList systemCaCertificates() + { + const QList directories = QSslSocketPrivate::unixRootCertDirectories(); + QSet certFiles = { +- QStringLiteral("/etc/pki/tls/certs/ca-bundle.crt"), // Fedora, Mandriva ++ QStringLiteral("/etc/pki/ca-trust/extracted/pem/tls-ca-bundle.pem"), // Red Hat 2013+ ++ QStringLiteral("/etc/pki/tls/certs/ca-bundle.crt"), // Red Hat older, Mandriva + QStringLiteral("/usr/local/share/certs/ca-root-nss.crt") // FreeBSD's ca_root_nss + }; + static const QStringList nameFilters = {u"*.pem"_s, u"*.crt"_s}; +-- +2.49.0 + diff --git a/qt6-qtbase.spec b/qt6-qtbase.spec index 0107464..e56e1df 100644 --- a/qt6-qtbase.spec +++ b/qt6-qtbase.spec @@ -46,7 +46,7 @@ BuildRequires: pkgconfig(libsystemd) Name: qt6-qtbase Summary: Qt6 - QtBase components Version: 6.9.1 -Release: 2%{?dist} +Release: 3%{?dist} License: LGPL-3.0-only OR GPL-3.0-only WITH Qt-GPL-exception-1.0 Url: http://qt-project.org/ @@ -95,6 +95,10 @@ Patch56: qtbase-mysql.patch # fix FTBFS against libglvnd-1.3.4+ Patch58: qtbase-libglvnd.patch +# Adjust to https://fedoraproject.org/wiki/Changes/dropingOfCertPemFile +# https://codereview.qt-project.org/c/qt/qtbase/+/664056 +Patch59: 0001-Update-SSL-trust-store-locations-for-modern-Red-Hat.patch + # upstream patches # Do not check any files in %%{_qt6_plugindir}/platformthemes/ for requires. @@ -928,6 +932,9 @@ make check -k ||: %changelog +* Mon Jul 28 2025 Adam Williamson - 6.9.1-3 +- Adjust for https://fedoraproject.org/wiki/Changes/dropingOfCertPemFile removals + * Fri Jul 25 2025 Fedora Release Engineering - 6.9.1-2 - Rebuilt for https://fedoraproject.org/wiki/Fedora_43_Mass_Rebuild From 362bc3dbd4e53a87f8a8c4bdd72152827c79c992 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Franti=C5=A1ek=20Zatloukal?= Date: Tue, 5 Aug 2025 23:55:00 +0200 Subject: [PATCH 03/12] Rebuilt for icu 77.1 --- qt6-qtbase.spec | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/qt6-qtbase.spec b/qt6-qtbase.spec index e56e1df..b47c52a 100644 --- a/qt6-qtbase.spec +++ b/qt6-qtbase.spec @@ -46,7 +46,7 @@ BuildRequires: pkgconfig(libsystemd) Name: qt6-qtbase Summary: Qt6 - QtBase components Version: 6.9.1 -Release: 3%{?dist} +Release: 4%{?dist} License: LGPL-3.0-only OR GPL-3.0-only WITH Qt-GPL-exception-1.0 Url: http://qt-project.org/ @@ -932,6 +932,9 @@ make check -k ||: %changelog +* Tue Aug 05 2025 František Zatloukal - 6.9.1-4 +- Rebuilt for icu 77.1 + * Mon Jul 28 2025 Adam Williamson - 6.9.1-3 - Adjust for https://fedoraproject.org/wiki/Changes/dropingOfCertPemFile removals From 060d33bcdda4251c4dfef8425574448754770712 Mon Sep 17 00:00:00 2001 From: Jan Grulich Date: Thu, 28 Aug 2025 12:17:19 +0200 Subject: [PATCH 04/12] 6.9.2 --- .gitignore | 1 + ...L-trust-store-locations-for-modern-Red-Hat.patch | 13 +++++-------- qt6-qtbase.spec | 9 ++++++--- sources | 2 +- 4 files changed, 13 insertions(+), 12 deletions(-) diff --git a/.gitignore b/.gitignore index aa1bacc..400b424 100644 --- a/.gitignore +++ b/.gitignore @@ -35,3 +35,4 @@ /qtbase-everywhere-src-6.9.0-rc.tar.xz /qtbase-everywhere-src-6.9.0.tar.xz /qtbase-everywhere-src-6.9.1.tar.xz +/qtbase-everywhere-src-6.9.2.tar.xz diff --git a/0001-Update-SSL-trust-store-locations-for-modern-Red-Hat.patch b/0001-Update-SSL-trust-store-locations-for-modern-Red-Hat.patch index 4ee642c..6812535 100644 --- a/0001-Update-SSL-trust-store-locations-for-modern-Red-Hat.patch +++ b/0001-Update-SSL-trust-store-locations-for-modern-Red-Hat.patch @@ -33,10 +33,10 @@ Signed-off-by: Adam Williamson 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/src/network/ssl/qsslsocket.cpp b/src/network/ssl/qsslsocket.cpp -index 395394d4325..b4d954be5a4 100644 +index 9e7b51d7..b3be3490 100644 --- a/src/network/ssl/qsslsocket.cpp +++ b/src/network/ssl/qsslsocket.cpp -@@ -2962,7 +2962,8 @@ QList QSslSocketPrivate::unixRootCertDirectories() +@@ -2963,7 +2963,8 @@ QList QSslSocketPrivate::unixRootCertDirectories() static const QByteArray dirs[] = { ba("/etc/ssl/certs/"), // (K)ubuntu, OpenSUSE, Mandriva ... ba("/usr/lib/ssl/certs/"), // Gentoo, Mandrake @@ -47,10 +47,10 @@ index 395394d4325..b4d954be5a4 100644 ba("/var/ssl/certs/"), // AIX ba("/usr/local/ssl/certs/"), // Solaris diff --git a/src/plugins/tls/openssl/qtlsbackend_openssl.cpp b/src/plugins/tls/openssl/qtlsbackend_openssl.cpp -index 85b17c9d491..78e62bd6b21 100644 +index c8bda9bc..024b5f55 100644 --- a/src/plugins/tls/openssl/qtlsbackend_openssl.cpp +++ b/src/plugins/tls/openssl/qtlsbackend_openssl.cpp -@@ -388,7 +388,8 @@ QList systemCaCertificates() +@@ -389,7 +389,8 @@ QList systemCaCertificates() { const QList directories = QSslSocketPrivate::unixRootCertDirectories(); QSet certFiles = { @@ -59,7 +59,4 @@ index 85b17c9d491..78e62bd6b21 100644 + QStringLiteral("/etc/pki/tls/certs/ca-bundle.crt"), // Red Hat older, Mandriva QStringLiteral("/usr/local/share/certs/ca-root-nss.crt") // FreeBSD's ca_root_nss }; - static const QStringList nameFilters = {u"*.pem"_s, u"*.crt"_s}; --- -2.49.0 - + diff --git a/qt6-qtbase.spec b/qt6-qtbase.spec index b47c52a..e829610 100644 --- a/qt6-qtbase.spec +++ b/qt6-qtbase.spec @@ -45,8 +45,8 @@ BuildRequires: pkgconfig(libsystemd) Name: qt6-qtbase Summary: Qt6 - QtBase components -Version: 6.9.1 -Release: 4%{?dist} +Version: 6.9.2 +Release: 1%{?dist} License: LGPL-3.0-only OR GPL-3.0-only WITH Qt-GPL-exception-1.0 Url: http://qt-project.org/ @@ -432,7 +432,7 @@ translationdir=%{_qt6_translationdir} Name: Qt6 Description: Qt6 Configuration -Version: 6.9.1 +Version: 6.9.2 EOF # rpm macros @@ -932,6 +932,9 @@ make check -k ||: %changelog +* Thu Aug 28 2025 Jan Grulich - 6.9.2-1 +- 6.9.2 + * Tue Aug 05 2025 František Zatloukal - 6.9.1-4 - Rebuilt for icu 77.1 diff --git a/sources b/sources index 881f3b1..7656a84 100644 --- a/sources +++ b/sources @@ -1 +1 @@ -SHA512 (qtbase-everywhere-src-6.9.1.tar.xz) = f0fdf4e2c10db73d8036e4b1324f04f48eced78feeed450b0ffa1fac5574daa4173996a4d25da761f5eeaa6037eab02418a88a59fd760d96a08d813c9b8136ed +SHA512 (qtbase-everywhere-src-6.9.2.tar.xz) = 3fab05fd17165b5e4501236f736c2b4c8d28929e17ce19de614851ae69adad88d621360c441d1ff2954ab24dee0d461d948cf699bc1c58f4b766ca8379de651e From 1646c99cab985492126b1868e4c1f0d87b1d928c Mon Sep 17 00:00:00 2001 From: Jan Grulich Date: Thu, 25 Sep 2025 12:17:25 +0200 Subject: [PATCH 05/12] 6.10.0 RC --- .gitignore | 1 + ...t-store-locations-for-modern-Red-Hat.patch | 62 ----------- qt6-qtbase.spec | 105 ++++++++++++------ sources | 2 +- 4 files changed, 71 insertions(+), 99 deletions(-) delete mode 100644 0001-Update-SSL-trust-store-locations-for-modern-Red-Hat.patch diff --git a/.gitignore b/.gitignore index 400b424..ca94231 100644 --- a/.gitignore +++ b/.gitignore @@ -36,3 +36,4 @@ /qtbase-everywhere-src-6.9.0.tar.xz /qtbase-everywhere-src-6.9.1.tar.xz /qtbase-everywhere-src-6.9.2.tar.xz +/qtbase-everywhere-src-6.10.0-rc.tar.xz diff --git a/0001-Update-SSL-trust-store-locations-for-modern-Red-Hat.patch b/0001-Update-SSL-trust-store-locations-for-modern-Red-Hat.patch deleted file mode 100644 index 6812535..0000000 --- a/0001-Update-SSL-trust-store-locations-for-modern-Red-Hat.patch +++ /dev/null @@ -1,62 +0,0 @@ -From 10a6f2b2d27ad2372c314bd1f2b21e225ece069b Mon Sep 17 00:00:00 2001 -From: Adam Williamson -Date: Mon, 28 Jul 2025 11:52:41 -0700 -Subject: [PATCH] Update SSL trust store locations for modern Red Hat - -Red Hat family distros haven't used /usr/share/ssl since 2004: -https://bugzilla.redhat.com/show_bug.cgi?id=143392 - -/etc/ssl/certs has never been a canonical location on Red Hat -family distros, and as of -https://fedoraproject.org/wiki/Changes/dropingOfCertPemFile is -being removed. The same change also removes -/etc/pki/tls/certs/ca-bundle.crt . This updates both magic lists -with the modern canonical locations for Red Hat and derived -distros. The tls-ca-bundle.pem bundle has been around in Fedora -since around 2013: -https://src.fedoraproject.org/rpms/ca-certificates/c/d538ada99cda951da7da3a72dc5eea06f02be212 -The directory-hash dir has been around since around 2021: -https://src.fedoraproject.org/rpms/ca-certificates/c/1c8b67fb5ab3954a308d019461bb30d50412087e - -As I blogged in 2015, it is generally a bad idea to be trying to -second-guess your SSL library about this stuff. OpenSSL has -`SSL_CTX_set_default_verify_paths` etc.; why not use them? At -least in the first instance; you can fall back to magic lists if -that fails. - -https://www.happyassassin.net/posts/2015/01/12/a-note-about-ssltls-trusted-certificate-stores-and-platforms/ - -Signed-off-by: Adam Williamson ---- - src/network/ssl/qsslsocket.cpp | 3 ++- - src/plugins/tls/openssl/qtlsbackend_openssl.cpp | 3 ++- - 2 files changed, 4 insertions(+), 2 deletions(-) - -diff --git a/src/network/ssl/qsslsocket.cpp b/src/network/ssl/qsslsocket.cpp -index 9e7b51d7..b3be3490 100644 ---- a/src/network/ssl/qsslsocket.cpp -+++ b/src/network/ssl/qsslsocket.cpp -@@ -2963,7 +2963,8 @@ QList QSslSocketPrivate::unixRootCertDirectories() - static const QByteArray dirs[] = { - ba("/etc/ssl/certs/"), // (K)ubuntu, OpenSUSE, Mandriva ... - ba("/usr/lib/ssl/certs/"), // Gentoo, Mandrake -- ba("/usr/share/ssl/"), // Centos, Redhat, SuSE -+ ba("/usr/share/ssl/"), // Red Hat pre-2004, SuSE -+ ba("/etc/pki/ca-trust/extracted/pem/directory-hash/"), // Red Hat 2021+ - ba("/usr/local/ssl/"), // Normal OpenSSL Tarball - ba("/var/ssl/certs/"), // AIX - ba("/usr/local/ssl/certs/"), // Solaris -diff --git a/src/plugins/tls/openssl/qtlsbackend_openssl.cpp b/src/plugins/tls/openssl/qtlsbackend_openssl.cpp -index c8bda9bc..024b5f55 100644 ---- a/src/plugins/tls/openssl/qtlsbackend_openssl.cpp -+++ b/src/plugins/tls/openssl/qtlsbackend_openssl.cpp -@@ -389,7 +389,8 @@ QList systemCaCertificates() - { - const QList directories = QSslSocketPrivate::unixRootCertDirectories(); - QSet certFiles = { -- QStringLiteral("/etc/pki/tls/certs/ca-bundle.crt"), // Fedora, Mandriva -+ QStringLiteral("/etc/pki/ca-trust/extracted/pem/tls-ca-bundle.pem"), // Red Hat 2013+ -+ QStringLiteral("/etc/pki/tls/certs/ca-bundle.crt"), // Red Hat older, Mandriva - QStringLiteral("/usr/local/share/certs/ca-root-nss.crt") // FreeBSD's ca_root_nss - }; - diff --git a/qt6-qtbase.spec b/qt6-qtbase.spec index e829610..38022a1 100644 --- a/qt6-qtbase.spec +++ b/qt6-qtbase.spec @@ -40,12 +40,14 @@ BuildRequires: pkgconfig(libsystemd) ## skip for now, until we're better at it --rex #global tests 1 -#global unstable 0 +%global unstable 1 +%if 0%{?unstable} %global prerelease rc +%endif Name: qt6-qtbase Summary: Qt6 - QtBase components -Version: 6.9.2 +Version: 6.10.0%{?unstable:~%{prerelease}} Release: 1%{?dist} License: LGPL-3.0-only OR GPL-3.0-only WITH Qt-GPL-exception-1.0 @@ -95,10 +97,6 @@ Patch56: qtbase-mysql.patch # fix FTBFS against libglvnd-1.3.4+ Patch58: qtbase-libglvnd.patch -# Adjust to https://fedoraproject.org/wiki/Changes/dropingOfCertPemFile -# https://codereview.qt-project.org/c/qt/qtbase/+/664056 -Patch59: 0001-Update-SSL-trust-store-locations-for-modern-Red-Hat.patch - # upstream patches # Do not check any files in %%{_qt6_plugindir}/platformthemes/ for requires. @@ -170,7 +168,13 @@ BuildRequires: pkgconfig(vulkan) BuildRequires: pkgconfig(egl) BuildRequires: pkgconfig(gbm) BuildRequires: pkgconfig(libglvnd) +BuildRequires: pkgconfig(xrender) BuildRequires: pkgconfig(x11) +BuildRequires: pkgconfig(wayland-scanner) +BuildRequires: pkgconfig(wayland-server) +BuildRequires: pkgconfig(wayland-client) +BuildRequires: pkgconfig(wayland-cursor) +BuildRequires: pkgconfig(wayland-egl) %global sqlite 1 BuildRequires: pkgconfig(sqlite3) >= 3.7 @@ -310,7 +314,6 @@ Requires: %{name}%{?_isa} = %{version}-%{release} Summary: Qt6 GUI-related libraries Requires: %{name}%{?_isa} = %{version}-%{release} Recommends: mesa-dri-drivers%{?_isa} -Recommends: qt6-qtwayland%{?_isa} # Required for some locales: https://pagure.io/fedora-kde/SIG/issue/311 Recommends: qt6-qttranslations Obsoletes: adwaita-qt6 <= 1.4.2 @@ -432,7 +435,7 @@ translationdir=%{_qt6_translationdir} Name: Qt6 Description: Qt6 Configuration -Version: 6.9.2 +Version: 6.10.0 EOF # rpm macros @@ -503,7 +506,7 @@ rm -r %{buildroot}%{_qt6_libdir}/cmake/Qt6ExamplesAssetDownloaderPrivate rm -r %{buildroot}%{_qt6_headerdir}/QtExamplesAssetDownloader rm %{buildroot}%{_qt6_descriptionsdir}/ExamplesAssetDownloaderPrivate.json rm %{buildroot}%{_qt6_libdir}/libQt6ExamplesAssetDownloader.* -rm %{buildroot}%{_qt6_libdir}/qt6/metatypes/qt6examplesassetdownloaderprivate_*_metatypes.json +rm %{buildroot}%{_qt6_libdir}/qt6/metatypes/qt6examplesassetdownloaderprivate_metatypes.json # These shouldn't be probably installed rm -r %{buildroot}%{_qt6_libdir}/cmake/Qt6/3rdparty/extra-cmake-modules/*.patch @@ -601,6 +604,11 @@ make check -k ||: %dir %{_qt6_libdir}/cmake/Qt6PrintSupport %dir %{_qt6_libdir}/cmake/Qt6Sql %dir %{_qt6_libdir}/cmake/Qt6Test +%dir %{_qt6_libdir}/cmake/Qt6WaylandClient/ +%dir %{_qt6_libdir}/cmake/Qt6WaylandClientPrivate +%dir %{_qt6_libdir}/cmake/Qt6WaylandGlobalPrivate/ +%dir %{_qt6_libdir}/cmake/Qt6WaylandScannerTools/ +%dir %{_qt6_libdir}/cmake/Qt6WlShellIntegrationPrivate/ %dir %{_qt6_libdir}/cmake/Qt6Widgets %dir %{_qt6_libdir}/cmake/Qt6WidgetsTools %dir %{_qt6_libdir}/cmake/Qt6Xml @@ -637,6 +645,7 @@ make check -k ||: %{_qt6_libexecdir}/qvkgen %{_qt6_libexecdir}/rcc %{_qt6_libexecdir}/uic +%{_qt6_libexecdir}/qtwaylandscanner %{_qt6_headerdir}/QtConcurrent/ %{_qt6_headerdir}/QtCore/ %{_qt6_headerdir}/QtDBus/ @@ -647,6 +656,9 @@ make check -k ||: %{_qt6_headerdir}/QtPrintSupport/ %{_qt6_headerdir}/QtSql/ %{_qt6_headerdir}/QtTest/ +%{_qt6_headerdir}/QtWaylandClient/ +%{_qt6_headerdir}/QtWaylandGlobal/ +%{_qt6_headerdir}/QtWlShellIntegration/ %{_qt6_headerdir}/QtWidgets/ %{_qt6_headerdir}/QtXcb/ %{_qt6_headerdir}/QtXml/ @@ -670,6 +682,10 @@ make check -k ||: %{_qt6_libdir}/libQt6Sql.so %{_qt6_libdir}/libQt6Test.prl %{_qt6_libdir}/libQt6Test.so +%{_qt6_libdir}/libQt6WaylandClient.so +%{_qt6_libdir}/libQt6WlShellIntegration.so +%{_qt6_libdir}/libQt6WaylandClient.prl +%{_qt6_libdir}/libQt6WlShellIntegration.prl %{_qt6_libdir}/libQt6Widgets.prl %{_qt6_libdir}/libQt6Widgets.so %{_qt6_libdir}/libQt6XcbQpa.prl @@ -720,6 +736,11 @@ make check -k ||: %{_qt6_libdir}/cmake/Qt6Sql/Qt6Sql*.cmake %{_qt6_libdir}/cmake/Qt6Sql/Qt6QSQLiteDriverPlugin*.cmake %{_qt6_libdir}/cmake/Qt6Test/*.cmake +%{_qt6_libdir}/cmake/Qt6WaylandClient/*.cmake +%{_qt6_libdir}/cmake/Qt6WaylandClientPrivate/*.cmake +%{_qt6_libdir}/cmake/Qt6WaylandGlobalPrivate/*.cmake +%{_qt6_libdir}/cmake/Qt6WaylandScannerTools/*.cmake +%{_qt6_libdir}/cmake/Qt6WlShellIntegrationPrivate/ %{_qt6_libdir}/cmake/Qt6Widgets/*.cmake %{_qt6_libdir}/cmake/Qt6WidgetsTools/*.cmake %{_qt6_libdir}/cmake/Qt6Xml/*.cmake @@ -733,20 +754,23 @@ make check -k ||: %{_qt6_descriptionsdir}/PrintSupport.json %{_qt6_descriptionsdir}/Sql.json %{_qt6_descriptionsdir}/Test.json +%{_qt6_descriptionsdir}/WaylandClient.json +%{_qt6_descriptionsdir}/WaylandGlobalPrivate.json +%{_qt6_descriptionsdir}/WlShellIntegrationPrivate.json %{_qt6_descriptionsdir}/Widgets.json %{_qt6_descriptionsdir}/Xml.json -%{_qt6_metatypesdir}/qt6concurrent_*_metatypes.json -%{_qt6_metatypesdir}/qt6core_*_metatypes.json -%{_qt6_metatypesdir}/qt6dbus_*_metatypes.json -%{_qt6_metatypesdir}/qt6gui_*_metatypes.json -%{_qt6_metatypesdir}/qt6network_*_metatypes.json -%{_qt6_metatypesdir}/qt6opengl_*_metatypes.json -%{_qt6_metatypesdir}/qt6openglwidgets_*_metatypes.json -%{_qt6_metatypesdir}/qt6printsupport_*_metatypes.json -%{_qt6_metatypesdir}/qt6sql_*_metatypes.json -%{_qt6_metatypesdir}/qt6test_*_metatypes.json -%{_qt6_metatypesdir}/qt6widgets_*_metatypes.json -%{_qt6_metatypesdir}/qt6xml_*_metatypes.json +%{_qt6_metatypesdir}/qt6concurrent_metatypes.json +%{_qt6_metatypesdir}/qt6core_metatypes.json +%{_qt6_metatypesdir}/qt6dbus_metatypes.json +%{_qt6_metatypesdir}/qt6gui_metatypes.json +%{_qt6_metatypesdir}/qt6network_metatypes.json +%{_qt6_metatypesdir}/qt6opengl_metatypes.json +%{_qt6_metatypesdir}/qt6openglwidgets_metatypes.json +%{_qt6_metatypesdir}/qt6printsupport_metatypes.json +%{_qt6_metatypesdir}/qt6sql_metatypes.json +%{_qt6_metatypesdir}/qt6test_metatypes.json +%{_qt6_metatypesdir}/qt6widgets_metatypes.json +%{_qt6_metatypesdir}/qt6xml_metatypes.json %{_qt6_libdir}/pkgconfig/*.pc %{_qt6_mkspecsdir}/* ## private-devel globs @@ -756,13 +780,11 @@ make check -k ||: %{_qt6_headerdir}/QtEglFSDeviceIntegration %{_qt6_headerdir}/QtEglFsKmsGbmSupport %{_qt6_headerdir}/QtEglFsKmsSupport -%dir %{_qt6_libdir}/cmake/Qt6ConcurrentPrivate %dir %{_qt6_libdir}/cmake/Qt6CorePrivate %dir %{_qt6_libdir}/cmake/Qt6DBusPrivate %dir %{_qt6_libdir}/cmake/Qt6GuiPrivate %dir %{_qt6_libdir}/cmake/Qt6NetworkPrivate %dir %{_qt6_libdir}/cmake/Qt6OpenGLPrivate -%dir %{_qt6_libdir}/cmake/Qt6OpenGLWidgetsPrivate %dir %{_qt6_libdir}/cmake/Qt6PrintSupportPrivate %dir %{_qt6_libdir}/cmake/Qt6SqlPrivate %dir %{_qt6_libdir}/cmake/Qt6TestInternalsPrivate @@ -774,13 +796,11 @@ make check -k ||: %dir %{_qt6_libdir}/cmake/Qt6EglFsKmsGbmSupportPrivate %dir %{_qt6_libdir}/cmake/Qt6EglFsKmsSupportPrivate %dir %{_qt6_libdir}/cmake/Qt6XcbQpaPrivate -%{_qt6_libdir}/cmake/Qt6ConcurrentPrivate/*.cmake %{_qt6_libdir}/cmake/Qt6CorePrivate/*.cmake %{_qt6_libdir}/cmake/Qt6DBusPrivate/*.cmake %{_qt6_libdir}/cmake/Qt6GuiPrivate/*.cmake %{_qt6_libdir}/cmake/Qt6NetworkPrivate/*.cmake %{_qt6_libdir}/cmake/Qt6OpenGLPrivate/*.cmake -%{_qt6_libdir}/cmake/Qt6OpenGLWidgetsPrivate/*.cmake %{_qt6_libdir}/cmake/Qt6PrintSupportPrivate/*.cmake %{_qt6_libdir}/cmake/Qt6SqlPrivate/*.cmake %{_qt6_libdir}/cmake/Qt6TestInternalsPrivate/*.cmake @@ -804,10 +824,12 @@ make check -k ||: %{_qt6_descriptionsdir}/EglFsKmsGbmSupportPrivate.json %{_qt6_descriptionsdir}/EglFsKmsSupportPrivate.json %{_qt6_descriptionsdir}/XcbQpaPrivate.json -%{_qt6_metatypesdir}/qt6eglfsdeviceintegrationprivate_*_metatypes.json -%{_qt6_metatypesdir}/qt6eglfskmsgbmsupportprivate_*_metatypes.json -%{_qt6_metatypesdir}/qt6eglfskmssupportprivate_*_metatypes.json -%{_qt6_metatypesdir}/qt6xcbqpaprivate_*_metatypes.json +%{_qt6_metatypesdir}/qt6eglfsdeviceintegrationprivate_metatypes.json +%{_qt6_metatypesdir}/qt6eglfskmsgbmsupportprivate_metatypes.json +%{_qt6_metatypesdir}/qt6eglfskmssupportprivate_metatypes.json +%{_qt6_metatypesdir}/qt6xcbqpaprivate_metatypes.json +%{_qt6_metatypesdir}/qt6waylandclient_metatypes.json +%{_qt6_metatypesdir}/qt6wlshellintegrationprivate_metatypes.json %{_qt6_headerdir}/*/%{qt_version}/ %{_qt6_descriptionsdir}/TestInternalsPrivate.json @@ -820,35 +842,35 @@ make check -k ||: %{_qt6_descriptionsdir}/ExampleIconsPrivate.json %dir %{_qt6_archdatadir}/objects-* %{_qt6_archdatadir}/objects-*/ExampleIconsPrivate_resources_1/ -%{_qt6_metatypesdir}/qt6exampleiconsprivate_*_metatypes.json +%{_qt6_metatypesdir}/qt6exampleiconsprivate_metatypes.json %dir %{_qt6_libdir}/cmake/Qt6DeviceDiscoverySupportPrivate %{_qt6_libdir}/cmake/Qt6DeviceDiscoverySupportPrivate/*.cmake %{_qt6_headerdir}/QtDeviceDiscoverySupport %{_qt6_libdir}/libQt6DeviceDiscoverySupport.*a %{_qt6_libdir}/libQt6DeviceDiscoverySupport.prl %{_qt6_descriptionsdir}/DeviceDiscoverySupportPrivate.json -%{_qt6_metatypesdir}/qt6devicediscoverysupportprivate_*_metatypes.json +%{_qt6_metatypesdir}/qt6devicediscoverysupportprivate_metatypes.json %dir %{_qt6_libdir}/cmake/Qt6FbSupportPrivate %{_qt6_libdir}/cmake/Qt6FbSupportPrivate/*.cmake %{_qt6_headerdir}/QtFbSupport %{_qt6_libdir}/libQt6FbSupport.*a %{_qt6_libdir}/libQt6FbSupport.prl %{_qt6_descriptionsdir}/FbSupportPrivate.json -%{_qt6_metatypesdir}/qt6fbsupportprivate_*_metatypes.json +%{_qt6_metatypesdir}/qt6fbsupportprivate_metatypes.json %dir %{_qt6_libdir}/cmake/Qt6InputSupportPrivate %{_qt6_libdir}/cmake/Qt6InputSupportPrivate/*.cmake %{_qt6_headerdir}/QtInputSupport %{_qt6_libdir}/libQt6InputSupport.*a %{_qt6_libdir}/libQt6InputSupport.prl %{_qt6_descriptionsdir}/InputSupportPrivate.json -%{_qt6_metatypesdir}/qt6inputsupportprivate_*_metatypes.json +%{_qt6_metatypesdir}/qt6inputsupportprivate_metatypes.json %dir %{_qt6_libdir}/cmake/Qt6KmsSupportPrivate %{_qt6_libdir}/cmake/Qt6KmsSupportPrivate/*.cmake %{_qt6_headerdir}/QtKmsSupport %{_qt6_libdir}/libQt6KmsSupport.*a %{_qt6_libdir}/libQt6KmsSupport.prl %{_qt6_descriptionsdir}/KmsSupportPrivate.json -%{_qt6_metatypesdir}/qt6kmssupportprivate_*_metatypes.json +%{_qt6_metatypesdir}/qt6kmssupportprivate_metatypes.json %if 0%{?examples} %files examples @@ -883,6 +905,8 @@ make check -k ||: %{_qt6_libdir}/libQt6OpenGL.so.6* %{_qt6_libdir}/libQt6OpenGLWidgets.so.6* %{_qt6_libdir}/libQt6PrintSupport.so.6* +%{_qt6_libdir}/libQt6WaylandClient.so.6* +%{_qt6_libdir}/libQt6WlShellIntegration.so.6* %{_qt6_libdir}/libQt6Widgets.so.6* %{_qt6_libdir}/libQt6XcbQpa.so.6* # Generic @@ -921,17 +945,26 @@ make check -k ||: %{_qt6_plugindir}/platforms/libqlinuxfb.so %{_qt6_plugindir}/platforms/libqminimal.so %{_qt6_plugindir}/platforms/libqoffscreen.so -%{_qt6_plugindir}/platforms/libqxcb.so %{_qt6_plugindir}/platforms/libqvnc.so %{_qt6_plugindir}/platforms/libqvkkhrdisplay.so +%{_qt6_plugindir}/platforms/libqwayland.so +%{_qt6_plugindir}/platforms/libqxcb.so %{_qt6_plugindir}/xcbglintegrations/libqxcb-glx-integration.so %{_qt6_plugindir}/printsupport/libcupsprintersupport.so # Platformthemes %{_qt6_plugindir}/platformthemes/libqxdgdesktopportal.so %{_qt6_plugindir}/platformthemes/libqgtk3.so - +# Wayland plugins and protocols +%{_qt6_plugindir}/wayland-decoration-client/ +%{_qt6_plugindir}/wayland-graphics-integration-client +%{_qt6_plugindir}/wayland-shell-integration +%{_qt6_datadir}/wayland/extensions/ +%{_qt6_datadir}/wayland/protocols/ %changelog +* Thu Sep 25 2025 Jan Grulich - 6.10.0~rc-1 +- 6.10.0 RC + * Thu Aug 28 2025 Jan Grulich - 6.9.2-1 - 6.9.2 diff --git a/sources b/sources index 7656a84..199aa16 100644 --- a/sources +++ b/sources @@ -1 +1 @@ -SHA512 (qtbase-everywhere-src-6.9.2.tar.xz) = 3fab05fd17165b5e4501236f736c2b4c8d28929e17ce19de614851ae69adad88d621360c441d1ff2954ab24dee0d461d948cf699bc1c58f4b766ca8379de651e +SHA512 (qtbase-everywhere-src-6.10.0-rc.tar.xz) = 19b7b8aead1accc92dcee39a1354e800281e740d27527a46d158f92c66554d12a82642c038f6a40cb92e87ebdd65c3f4a6c8a4cee1604f2a5a7aafb37ccd62ca From 07c9cbcba03b0ce874517241974af260b37a6e45 Mon Sep 17 00:00:00 2001 From: Gwyn Ciesla Date: Tue, 30 Sep 2025 11:07:46 -0500 Subject: [PATCH 06/12] Firebird 5 rebuild --- qt6-qtbase.spec | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/qt6-qtbase.spec b/qt6-qtbase.spec index 38022a1..012286f 100644 --- a/qt6-qtbase.spec +++ b/qt6-qtbase.spec @@ -48,7 +48,7 @@ BuildRequires: pkgconfig(libsystemd) Name: qt6-qtbase Summary: Qt6 - QtBase components Version: 6.10.0%{?unstable:~%{prerelease}} -Release: 1%{?dist} +Release: 2%{?dist} License: LGPL-3.0-only OR GPL-3.0-only WITH Qt-GPL-exception-1.0 Url: http://qt-project.org/ @@ -962,6 +962,9 @@ make check -k ||: %{_qt6_datadir}/wayland/protocols/ %changelog +* Tue Sep 30 2025 Gwyn Ciesla - 6.10.0~rc-2 +- Firebird 5 rebuild + * Thu Sep 25 2025 Jan Grulich - 6.10.0~rc-1 - 6.10.0 RC From 05ee0b98b402f6aec5d9f602348186bd080eb50b Mon Sep 17 00:00:00 2001 From: Jan Grulich Date: Tue, 7 Oct 2025 10:33:18 +0200 Subject: [PATCH 07/12] 6.10.0 --- .gitignore | 1 + qt6-qtbase.spec | 13 ++++++++----- sources | 2 +- 3 files changed, 10 insertions(+), 6 deletions(-) diff --git a/.gitignore b/.gitignore index ca94231..05c8f2b 100644 --- a/.gitignore +++ b/.gitignore @@ -37,3 +37,4 @@ /qtbase-everywhere-src-6.9.1.tar.xz /qtbase-everywhere-src-6.9.2.tar.xz /qtbase-everywhere-src-6.10.0-rc.tar.xz +/qtbase-everywhere-src-6.10.0.tar.xz diff --git a/qt6-qtbase.spec b/qt6-qtbase.spec index 012286f..982ee2d 100644 --- a/qt6-qtbase.spec +++ b/qt6-qtbase.spec @@ -40,15 +40,15 @@ BuildRequires: pkgconfig(libsystemd) ## skip for now, until we're better at it --rex #global tests 1 -%global unstable 1 +#global unstable 0 %if 0%{?unstable} %global prerelease rc %endif Name: qt6-qtbase Summary: Qt6 - QtBase components -Version: 6.10.0%{?unstable:~%{prerelease}} -Release: 2%{?dist} +Version: 6.10.0 +Release: 1%{?dist} License: LGPL-3.0-only OR GPL-3.0-only WITH Qt-GPL-exception-1.0 Url: http://qt-project.org/ @@ -689,7 +689,7 @@ make check -k ||: %{_qt6_libdir}/libQt6Widgets.prl %{_qt6_libdir}/libQt6Widgets.so %{_qt6_libdir}/libQt6XcbQpa.prl -%{_qt6_libdir}/libQt6XcbQpa.so +%{_qt6_libdir}/libQt6XcbQpa.so %{_qt6_libdir}/libQt6Xml.prl %{_qt6_libdir}/libQt6Xml.so %{_qt6_libdir}/cmake/Qt6/3rdparty/extra-cmake-modules/REUSE.toml @@ -962,6 +962,9 @@ make check -k ||: %{_qt6_datadir}/wayland/protocols/ %changelog +* Tue Oct 07 2025 Jan Grulich - 6.10.0-1 +- 6.10.0 + * Tue Sep 30 2025 Gwyn Ciesla - 6.10.0~rc-2 - Firebird 5 rebuild @@ -1176,7 +1179,7 @@ make check -k ||: * Fri Apr 7 2023 Marie Loise Nolden - 6.5.0-2 - fix xcb plugin with new dependency xcb-cursor instead of Xcursor introduction with qt 6.5, add firebird sql plugin cleanly, clean up spec file - + * Mon Apr 03 2023 Jan Grulich - 6.5.0-1 - 6.5.0 diff --git a/sources b/sources index 199aa16..fda36a3 100644 --- a/sources +++ b/sources @@ -1 +1 @@ -SHA512 (qtbase-everywhere-src-6.10.0-rc.tar.xz) = 19b7b8aead1accc92dcee39a1354e800281e740d27527a46d158f92c66554d12a82642c038f6a40cb92e87ebdd65c3f4a6c8a4cee1604f2a5a7aafb37ccd62ca +SHA512 (qtbase-everywhere-src-6.10.0.tar.xz) = bc3b98f8b2387ca207f6cd87478bdefa911442fd9981d38ecd8793856439e415ce9182b524dabc6b0a1ba5c27e22badfc7ca67a0a43e00186ee6865886937917 From 045b22d4d63a862adcc09177c47e8f7124e7532f Mon Sep 17 00:00:00 2001 From: Jan Grulich Date: Wed, 29 Oct 2025 14:28:54 +0100 Subject: [PATCH 08/12] Backport: Wayland - convey preference for server side decorations --- qt6-qtbase.spec | 6 +- ...eference-for-server-side-decorations.patch | 55 +++++++++++++++++++ 2 files changed, 60 insertions(+), 1 deletion(-) create mode 100644 qtbase-wayland-convey-preference-for-server-side-decorations.patch diff --git a/qt6-qtbase.spec b/qt6-qtbase.spec index 982ee2d..5a8df07 100644 --- a/qt6-qtbase.spec +++ b/qt6-qtbase.spec @@ -48,7 +48,7 @@ BuildRequires: pkgconfig(libsystemd) Name: qt6-qtbase Summary: Qt6 - QtBase components Version: 6.10.0 -Release: 1%{?dist} +Release: 2%{?dist} License: LGPL-3.0-only OR GPL-3.0-only WITH Qt-GPL-exception-1.0 Url: http://qt-project.org/ @@ -98,6 +98,7 @@ Patch56: qtbase-mysql.patch Patch58: qtbase-libglvnd.patch # upstream patches +Patch100: qtbase-wayland-convey-preference-for-server-side-decorations.patch # Do not check any files in %%{_qt6_plugindir}/platformthemes/ for requires. # Those themes are there for platform integration. If the required libraries are @@ -962,6 +963,9 @@ make check -k ||: %{_qt6_datadir}/wayland/protocols/ %changelog +* Wed Oct 29 2025 Jan Grulich - 6.10.0-2 +- Backport: Wayland - convey preference for server side decorations + * Tue Oct 07 2025 Jan Grulich - 6.10.0-1 - 6.10.0 diff --git a/qtbase-wayland-convey-preference-for-server-side-decorations.patch b/qtbase-wayland-convey-preference-for-server-side-decorations.patch new file mode 100644 index 0000000..0a5b232 --- /dev/null +++ b/qtbase-wayland-convey-preference-for-server-side-decorations.patch @@ -0,0 +1,55 @@ +From 18550cd9ad04913d50abfc88a6048b7cfaf80687 Mon Sep 17 00:00:00 2001 +From: Igor Khanin +Date: Mon, 13 Oct 2025 14:26:39 +0300 +Subject: [PATCH] wayland: Convey preference for server side decorations + +As discussed in the mailing list, Qt applications generally expect top +level windows to be decorated by the windowing system - unless +explicitly requested by the developer by marking a window as frameless. +Furthermore, Qt's client side decorations for Wayland were only really +intended as a fallback. + +However until now the Wayland xdg-shell integration conveyed to the +compositor (if it supports decorations negotiations) that it didn't +care whose responsibility it was to draw window decorations. This did +not matter under KWin, which resolves such cases in favor of SSDs - but +does matter under other SSD-capable compositors like cosmic-comp (which +considers SSDs to be the fallback option). + +This changes the xdg-shell integration code to be explicit about +requesting SSDs. + +[ChangeLog][QtWaylandClient][Important Behavior Changes] The Wayland +XDG shell integration now requests server side decorations with +compositors supporting the zxdg_decoration_manager_v1 protocol. + +Change-Id: Ia025b1f6ff17248a5710f981a0cecd90c47b6cd8 +Reviewed-by: David Edmundson +--- + +diff --git a/src/plugins/platforms/wayland/plugins/shellintegration/xdg-shell/qwaylandxdgshell.cpp b/src/plugins/platforms/wayland/plugins/shellintegration/xdg-shell/qwaylandxdgshell.cpp +index a1a173f..8386b65 100644 +--- a/src/plugins/platforms/wayland/plugins/shellintegration/xdg-shell/qwaylandxdgshell.cpp ++++ b/src/plugins/platforms/wayland/plugins/shellintegration/xdg-shell/qwaylandxdgshell.cpp +@@ -201,7 +201,7 @@ + delete m_decoration; + m_decoration = nullptr; + } else { +- m_decoration->unsetMode(); ++ m_decoration->requestMode(QWaylandXdgToplevelDecorationV1::mode_server_side); + } + } + } +diff --git a/tests/auto/wayland/xdgdecorationv1/tst_xdgdecorationv1.cpp b/tests/auto/wayland/xdgdecorationv1/tst_xdgdecorationv1.cpp +index 5ee8569..65ac08f 100644 +--- a/tests/auto/wayland/xdgdecorationv1/tst_xdgdecorationv1.cpp ++++ b/tests/auto/wayland/xdgdecorationv1/tst_xdgdecorationv1.cpp +@@ -149,7 +149,7 @@ + QVERIFY(window.frameMargins().isNull()); + + QCOMPOSITOR_TRY_VERIFY(xdgToplevel()); +- QCOMPOSITOR_TRY_VERIFY(toplevelDecoration()->m_unsetModeRequested); ++ QCOMPOSITOR_TRY_VERIFY(toplevelDecoration()->m_requestedMode == XdgToplevelDecorationV1::mode_server_side); + QVERIFY(window.frameMargins().isNull()); // We're still waiting for a configure + exec([&] { + toplevelDecoration()->sendConfigure(XdgToplevelDecorationV1::mode_client_side); From 2ff34253dc5789679d4b7889902c51341ad6b374 Mon Sep 17 00:00:00 2001 From: Jan Grulich Date: Mon, 10 Nov 2025 11:20:53 +0100 Subject: [PATCH 09/12] Backport wayland fixes for mouse scrolling --- qt6-qtbase.spec | 9 +- ...compress-high-frequency-mouse-events.patch | 174 +++++++++++++ ...compression-and-fix-scroll-end-event.patch | 235 +++++++++++++++++ ...e-wayland-optimize-scroll-operations.patch | 242 ++++++++++++++++++ 4 files changed, 659 insertions(+), 1 deletion(-) create mode 100644 qtbase-wayland-compress-high-frequency-mouse-events.patch create mode 100644 qtbase-wayland-enable-event-compression-and-fix-scroll-end-event.patch create mode 100644 qtbase-wayland-optimize-scroll-operations.patch diff --git a/qt6-qtbase.spec b/qt6-qtbase.spec index 5a8df07..9af205f 100644 --- a/qt6-qtbase.spec +++ b/qt6-qtbase.spec @@ -48,7 +48,7 @@ BuildRequires: pkgconfig(libsystemd) Name: qt6-qtbase Summary: Qt6 - QtBase components Version: 6.10.0 -Release: 2%{?dist} +Release: 3%{?dist} License: LGPL-3.0-only OR GPL-3.0-only WITH Qt-GPL-exception-1.0 Url: http://qt-project.org/ @@ -99,6 +99,10 @@ Patch58: qtbase-libglvnd.patch # upstream patches Patch100: qtbase-wayland-convey-preference-for-server-side-decorations.patch +Patch101: qtbase-wayland-compress-high-frequency-mouse-events.patch +Patch102: qtbase-wayland-optimize-scroll-operations.patch +Patch103: qtbase-wayland-enable-event-compression-and-fix-scroll-end-event.patch + # Do not check any files in %%{_qt6_plugindir}/platformthemes/ for requires. # Those themes are there for platform integration. If the required libraries are @@ -963,6 +967,9 @@ make check -k ||: %{_qt6_datadir}/wayland/protocols/ %changelog +* Mon Nov 10 2025 Jan Grulich - 6.10.0-3 +- Backport wayland fixes for mouse scrolling + * Wed Oct 29 2025 Jan Grulich - 6.10.0-2 - Backport: Wayland - convey preference for server side decorations diff --git a/qtbase-wayland-compress-high-frequency-mouse-events.patch b/qtbase-wayland-compress-high-frequency-mouse-events.patch new file mode 100644 index 0000000..e33e22a --- /dev/null +++ b/qtbase-wayland-compress-high-frequency-mouse-events.patch @@ -0,0 +1,174 @@ +From 095759818854e5a011aa8f859e566bbc6368ab76 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?B=C5=82a=C5=BCej=20Szczygie=C5=82?= +Date: Sun, 27 Jul 2025 00:50:00 +0200 +Subject: [PATCH] wayland: Compress high frequency mouse events +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit + +Add support for Qt::AA_CompressHighFrequencyEvents on Wayland. + +The highest USB HID polling rate is 8 kHz (125 μs). Most mice use lower +polling rate [125 Hz - 1000 Hz]. Reject all events faster than 100 μs, +because it definitely means the application main thread is freezed by +the long operation and events are delivered one after another from the +queue. Since now we rely on the 0 ms timer to deliver the last pending +event when application main thread is no longer freezed. + +Pick-to: 6.10 +Task-number: QTBUG-138706 +Change-Id: Ie9d539e233c5551b1756d599b65495571e195f9d +Reviewed-by: David Edmundson +--- + src/corelib/global/qnamespace.qdoc | 1 + + .../platforms/wayland/qwaylandinputdevice.cpp | 38 +++++++++++++++++++ + .../platforms/wayland/qwaylandinputdevice_p.h | 12 ++++++ + .../platforms/wayland/qwaylandintegration.cpp | 2 + + tests/auto/wayland/client/tst_client.cpp | 2 + + 5 files changed, 55 insertions(+) + +diff --git a/src/corelib/global/qnamespace.qdoc b/src/corelib/global/qnamespace.qdoc +index c845cfa7..f463d736 100644 +--- a/src/corelib/global/qnamespace.qdoc ++++ b/src/corelib/global/qnamespace.qdoc +@@ -227,6 +227,7 @@ + application later. + On Windows 8 and above the default value is also true, but it only applies + to touch events. Mouse and window events remain unaffected by this flag. ++ On Wayland the default value is also true, but it only applies to mouse events. + On other platforms, the default is false. + (In the future, the compression feature may be implemented across platforms.) + You can test the attribute to see whether compression is enabled. +diff --git a/src/plugins/platforms/wayland/qwaylandinputdevice.cpp b/src/plugins/platforms/wayland/qwaylandinputdevice.cpp +index 170e80f8..45c78765 100644 +--- a/src/plugins/platforms/wayland/qwaylandinputdevice.cpp ++++ b/src/plugins/platforms/wayland/qwaylandinputdevice.cpp +@@ -63,6 +63,34 @@ Q_LOGGING_CATEGORY(lcQpaWaylandInput, "qt.qpa.wayland.input"); + // reasonable number of them. As of 2021 most touchscreen panels support 10 concurrent touchpoints. + static const int MaxTouchPoints = 10; + ++QWaylandEventCompressionPrivate::QWaylandEventCompressionPrivate() ++{ ++ timeElapsed.start(); ++ delayTimer.setSingleShot(true); ++} ++ ++bool QWaylandEventCompressionPrivate::compressEvent() ++{ ++ using namespace std::chrono_literals; ++ ++ if (!QCoreApplication::testAttribute(Qt::AA_CompressHighFrequencyEvents)) ++ return false; ++ ++ const auto elapsed = timeElapsed.durationElapsed(); ++ timeElapsed.start(); ++ if (elapsed < 100us || delayTimer.isActive()) ++ { ++ // The highest USB HID polling rate is 8 kHz (125 μs). Most mice use lowe polling rate [125 Hz - 1000 Hz]. ++ // Reject all events faster than 100 μs, because it definitely means the application main thread is ++ // freezed by long operation and events are delivered one after another from the queue. Since now we rely ++ // on the 0 ms timer to deliver the last pending event when application main thread is no longer freezed. ++ delayTimer.start(0); ++ return true; ++ } ++ ++ return false; ++} ++ + QWaylandInputDevice::Keyboard::Keyboard(QWaylandInputDevice *p) + : mParent(p) + { +@@ -140,6 +168,8 @@ QWaylandInputDevice::Pointer::Pointer(QWaylandInputDevice *seat) + cursorTimerCallback(); + }); + #endif ++ ++ mEventCompression.delayTimer.callOnTimeout(this, &QWaylandInputDevice::Pointer::flushFrameEvent); + } + + QWaylandInputDevice::Pointer::~Pointer() +@@ -922,6 +952,11 @@ void QWaylandInputDevice::Pointer::pointer_axis(uint32_t time, uint32_t axis, in + + void QWaylandInputDevice::Pointer::pointer_frame() + { ++ if (mEventCompression.compressEvent()) { ++ qCDebug(lcQpaWaylandInput) << "compressed pointer_frame event"; ++ return; ++ } ++ + flushFrameEvent(); + } + +@@ -1051,6 +1086,7 @@ void QWaylandInputDevice::Pointer::setFrameEvent(QWaylandPointerEvent *event) + flushFrameEvent(); + } + ++ delete mFrameData.event; + mFrameData.event = event; + + if (version() < WL_POINTER_FRAME_SINCE_VERSION) { +@@ -1170,6 +1206,8 @@ void QWaylandInputDevice::Pointer::flushScrollEvent() + + void QWaylandInputDevice::Pointer::flushFrameEvent() + { ++ mEventCompression.delayTimer.stop(); ++ + if (auto *event = mFrameData.event) { + if (auto window = event->surface) { + window->handleMouse(mParent, *event); +diff --git a/src/plugins/platforms/wayland/qwaylandinputdevice_p.h b/src/plugins/platforms/wayland/qwaylandinputdevice_p.h +index bcaf0258..0b24999e 100644 +--- a/src/plugins/platforms/wayland/qwaylandinputdevice_p.h ++++ b/src/plugins/platforms/wayland/qwaylandinputdevice_p.h +@@ -75,6 +75,16 @@ class CursorSurface; + + Q_DECLARE_LOGGING_CATEGORY(lcQpaWaylandInput); + ++struct QWaylandEventCompressionPrivate ++{ ++ QWaylandEventCompressionPrivate(); ++ ++ bool compressEvent(); ++ ++ QElapsedTimer timeElapsed; ++ QTimer delayTimer; ++}; ++ + class Q_WAYLANDCLIENT_EXPORT QWaylandInputDevice + : public QObject + , public QtWayland::wl_seat +@@ -381,6 +391,8 @@ public: + bool mScrollBeginSent = false; + QPointF mScrollDeltaRemainder; + ++ QWaylandEventCompressionPrivate mEventCompression; ++ + void setFrameEvent(QWaylandPointerEvent *event); + void flushScrollEvent(); + void flushFrameEvent(); +diff --git a/src/plugins/platforms/wayland/qwaylandintegration.cpp b/src/plugins/platforms/wayland/qwaylandintegration.cpp +index 669d47ee..fc869de6 100644 +--- a/src/plugins/platforms/wayland/qwaylandintegration.cpp ++++ b/src/plugins/platforms/wayland/qwaylandintegration.cpp +@@ -87,6 +87,8 @@ QWaylandIntegration::QWaylandIntegration(const QString &platformName) + : mPlatformName(platformName), mFontDb(new QGenericUnixFontDatabase()) + #endif + { ++ QCoreApplication::setAttribute(Qt::AA_CompressHighFrequencyEvents); ++ + mDisplay.reset(new QWaylandDisplay(this)); + mPlatformServices.reset(new QWaylandPlatformServices(mDisplay.data())); + +diff --git a/tests/auto/wayland/client/tst_client.cpp b/tests/auto/wayland/client/tst_client.cpp +index 04400e3f..09ac4f7f 100644 +--- a/tests/auto/wayland/client/tst_client.cpp ++++ b/tests/auto/wayland/client/tst_client.cpp +@@ -276,6 +276,8 @@ void tst_WaylandClient::events() + exec([&] { + pointer()->sendEnter(s, window.frameOffset() + mousePressPos); + pointer()->sendFrame(client()); ++ pointer()->sendMotion(client(), window.frameOffset() + mousePressPos / 2); ++ pointer()->sendFrame(client()); + pointer()->sendMotion(client(), window.frameOffset() + mousePressPos); + pointer()->sendFrame(client()); + pointer()->sendButton(client(), BTN_LEFT, Pointer::button_state_pressed); diff --git a/qtbase-wayland-enable-event-compression-and-fix-scroll-end-event.patch b/qtbase-wayland-enable-event-compression-and-fix-scroll-end-event.patch new file mode 100644 index 0000000..80e0009 --- /dev/null +++ b/qtbase-wayland-enable-event-compression-and-fix-scroll-end-event.patch @@ -0,0 +1,235 @@ +From 9dd0d936d6691904a4bb212dcf48999a5228b84f Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?B=C5=82a=C5=BCej=20Szczygie=C5=82?= +Date: Thu, 7 Aug 2025 15:42:55 +0200 +Subject: [PATCH] wayland: Enable event compression and fix scroll end event + +We have to deliver the scroll end event in order with other scroll +events, so move scroll end event logic into flushScrollEvent(). + +Track the target scroll window on scroll begin and check whether we +have a target window to prevent a crash. + +Also we can't clear the scroll delta when scroll end event arrives, +because the event compression relies on this value. + +Use scoped pointer for frame event to simplify code and make sure it's +deleted in destructor. + +Amends 095759818854e5a011aa8f859e566bbc6368ab76 +Updates 789681872fb62450fc78d1a06472a40d970a8d57 + +Change-Id: Ifb149c8fde1286b60439be0dab16b1df65279ea8 +Reviewed-by: David Edmundson +--- + .../platforms/wayland/qwaylandinputdevice.cpp | 98 ++++++++++--------- + .../platforms/wayland/qwaylandinputdevice_p.h | 6 +- + .../platforms/wayland/qwaylandintegration.cpp | 2 + + 3 files changed, 61 insertions(+), 45 deletions(-) + +diff --git a/src/plugins/platforms/wayland/qwaylandinputdevice.cpp b/src/plugins/platforms/wayland/qwaylandinputdevice.cpp +index 7fcda34c57ca..6ce43714a35f 100644 +--- a/src/plugins/platforms/wayland/qwaylandinputdevice.cpp ++++ b/src/plugins/platforms/wayland/qwaylandinputdevice.cpp +@@ -935,10 +935,7 @@ void QWaylandInputDevice::Pointer::pointer_axis(uint32_t time, uint32_t axis, in + + mParent->mTime = time; + +- if (version() < WL_POINTER_FRAME_SINCE_VERSION) { +- qCDebug(lcQpaWaylandInput) << "Flushing new event; no frame event in this version"; +- flushFrameEvent(); +- } ++ maybePointerFrame(); + } + + void QWaylandInputDevice::Pointer::pointer_frame() +@@ -978,11 +975,9 @@ void QWaylandInputDevice::Pointer::pointer_axis_stop(uint32_t time, uint32_t axi + switch (axis) { + case axis_vertical_scroll: + qCDebug(lcQpaWaylandInput) << "Received vertical wl_pointer.axis_stop"; +- mFrameData.delta.setY(0); //TODO: what's the point of doing this? + break; + case axis_horizontal_scroll: + qCDebug(lcQpaWaylandInput) << "Received horizontal wl_pointer.axis_stop"; +- mFrameData.delta.setX(0); + break; + default: + qCWarning(lcQpaWaylandInput) << "wl_pointer.axis_stop: Unknown axis: " << axis +@@ -990,25 +985,7 @@ void QWaylandInputDevice::Pointer::pointer_axis_stop(uint32_t time, uint32_t axi + return; + } + +- // May receive axis_stop for events we haven't sent a ScrollBegin for because +- // most axis_sources do not mandate an axis_stop event to be sent. +- if (!mScrollBeginSent) { +- // TODO: For now, we just ignore these events, but we could perhaps take this as an +- // indication that this compositor will in fact send axis_stop events for these sources +- // and send a ScrollBegin the next time an axis_source event with this type is encountered. +- return; +- } +- +- QWaylandWindow *target = QWaylandWindow::mouseGrab(); +- if (!target) +- target = focusWindow(); +- Qt::KeyboardModifiers mods = mParent->modifiers(); +- const bool inverted = mFrameData.verticalAxisInverted || mFrameData.horizontalAxisInverted; +- WheelEvent wheelEvent(focusWindow(), Qt::ScrollEnd, mParent->mTime, mSurfacePos, mGlobalPos, +- QPoint(), QPoint(), Qt::MouseEventNotSynthesized, mods, inverted); +- target->handleMouse(mParent, wheelEvent); +- mScrollBeginSent = false; +- mScrollDeltaRemainder = QPointF(); ++ mScrollEnd = true; + } + + void QWaylandInputDevice::Pointer::pointer_axis_discrete(uint32_t axis, int32_t value) +@@ -1069,6 +1046,14 @@ void QWaylandInputDevice::Pointer::pointer_axis_relative_direction(uint32_t axis + } + } + ++inline void QWaylandInputDevice::Pointer::maybePointerFrame() ++{ ++ if (version() < WL_POINTER_FRAME_SINCE_VERSION) { ++ qCDebug(lcQpaWaylandInput) << "Flushing new event; no frame event in this version"; ++ pointer_frame(); ++ } ++} ++ + void QWaylandInputDevice::Pointer::setFrameEvent(QWaylandPointerEvent *event) + { + qCDebug(lcQpaWaylandInput) << "Setting frame event " << event->type; +@@ -1077,13 +1062,9 @@ void QWaylandInputDevice::Pointer::setFrameEvent(QWaylandPointerEvent *event) + flushFrameEvent(); + } + +- delete mFrameData.event; +- mFrameData.event = event; ++ mFrameData.event.reset(event); + +- if (version() < WL_POINTER_FRAME_SINCE_VERSION) { +- qCDebug(lcQpaWaylandInput) << "Flushing new event; no frame event in this version"; +- flushFrameEvent(); +- } ++ maybePointerFrame(); + } + + void QWaylandInputDevice::Pointer::FrameData::resetScrollData() +@@ -1163,11 +1144,24 @@ void QWaylandInputDevice::Pointer::flushScrollEvent() + { + QPoint angleDelta = mFrameData.angleDelta(); + ++ // The wayland protocol has separate horizontal and vertical axes, Qt has just the one inverted flag ++ // Pragmatically it should't come up ++ const bool inverted = mFrameData.verticalAxisInverted || mFrameData.horizontalAxisInverted; ++ + // Angle delta is required for Qt wheel events, so don't try to send events if it's zero + if (!angleDelta.isNull()) { +- QWaylandWindow *target = QWaylandWindow::mouseGrab(); +- if (!target) +- target = focusWindow(); ++ QWaylandWindow *target = mScrollTarget; ++ if (!mScrollBeginSent) { ++ if (!target) ++ target = QWaylandWindow::mouseGrab(); ++ if (!target) ++ target = focusWindow(); ++ } ++ if (!target) { ++ qCDebug(lcQpaWaylandInput) << "Flushing scroll event aborted - no scroll target"; ++ mFrameData.resetScrollData(); ++ return; ++ } + + if (isDefinitelyTerminated(mFrameData.axisSource) && !mScrollBeginSent) { + qCDebug(lcQpaWaylandInput) << "Flushing scroll event sending ScrollBegin"; +@@ -1177,21 +1171,38 @@ void QWaylandInputDevice::Pointer::flushScrollEvent() + mParent->modifiers(), false)); + mScrollBeginSent = true; + mScrollDeltaRemainder = QPointF(); ++ mScrollTarget = target; + } + + Qt::ScrollPhase phase = mScrollBeginSent ? Qt::ScrollUpdate : Qt::NoScrollPhase; + QPoint pixelDelta = mFrameData.pixelDeltaAndError(&mScrollDeltaRemainder); +- Qt::MouseEventSource source = mFrameData.wheelEventSource(); +- +- +- // The wayland protocol has separate horizontal and vertical axes, Qt has just the one inverted flag +- // Pragmatically it should't come up +- const bool inverted = mFrameData.verticalAxisInverted || mFrameData.horizontalAxisInverted; + + qCDebug(lcQpaWaylandInput) << "Flushing scroll event" << phase << pixelDelta << angleDelta; + target->handleMouse(mParent, WheelEvent(focusWindow(), phase, mParent->mTime, mSurfacePos, mGlobalPos, +- pixelDelta, angleDelta, source, mParent->modifiers(), inverted)); ++ pixelDelta, angleDelta, mFrameData.wheelEventSource(), mParent->modifiers(), inverted)); ++ } ++ ++ if (mScrollEnd) { ++ if (mScrollBeginSent) { ++ if (auto target = mScrollTarget.get()) { ++ qCDebug(lcQpaWaylandInput) << "Flushing scroll end event"; ++ target->handleMouse(mParent, WheelEvent(focusWindow(), Qt::ScrollEnd, mParent->mTime, mSurfacePos, mGlobalPos, ++ QPoint(), QPoint(), mFrameData.wheelEventSource(), mParent->modifiers(), inverted)); ++ } ++ mScrollBeginSent = false; ++ mScrollDeltaRemainder = QPointF(); ++ } else { ++ // May receive axis_stop for events we haven't sent a ScrollBegin for because ++ // most axis_sources do not mandate an axis_stop event to be sent. ++ ++ // TODO: For now, we just ignore these events, but we could perhaps take this as an ++ // indication that this compositor will in fact send axis_stop events for these sources ++ // and send a ScrollBegin the next time an axis_source event with this type is encountered. ++ } ++ mScrollEnd = false; ++ mScrollTarget.clear(); + } ++ + mFrameData.resetScrollData(); + } + +@@ -1199,7 +1210,7 @@ void QWaylandInputDevice::Pointer::flushFrameEvent() + { + mEventCompression.delayTimer.stop(); + +- if (auto *event = mFrameData.event) { ++ if (auto *event = mFrameData.event.get()) { + if (auto window = event->surface) { + window->handleMouse(mParent, *event); + } else if (mFrameData.event->type == QEvent::MouseButtonRelease) { +@@ -1212,8 +1223,7 @@ void QWaylandInputDevice::Pointer::flushFrameEvent() + event->modifiers); // , Qt::MouseEventSource source = + // Qt::MouseEventNotSynthesized); + } +- delete mFrameData.event; +- mFrameData.event = nullptr; ++ mFrameData.event.reset(); + } + + //TODO: do modifiers get passed correctly here? +diff --git a/src/plugins/platforms/wayland/qwaylandinputdevice_p.h b/src/plugins/platforms/wayland/qwaylandinputdevice_p.h +index 533a10991e5e..aa2ccddbcdb8 100644 +--- a/src/plugins/platforms/wayland/qwaylandinputdevice_p.h ++++ b/src/plugins/platforms/wayland/qwaylandinputdevice_p.h +@@ -370,7 +370,7 @@ private Q_SLOTS: + Qt::MouseButton mLastButton = Qt::NoButton; + + struct FrameData { +- QWaylandPointerEvent *event = nullptr; ++ QScopedPointer event; + + QPointF delta; + QPoint delta120; +@@ -387,10 +387,14 @@ private Q_SLOTS: + } mFrameData; + + bool mScrollBeginSent = false; ++ bool mScrollEnd = false; + QPointF mScrollDeltaRemainder; ++ QPointer mScrollTarget; + + QWaylandEventCompressionPrivate mEventCompression; + ++ void maybePointerFrame(); ++ + void setFrameEvent(QWaylandPointerEvent *event); + void flushScrollEvent(); + void flushFrameEvent(); diff --git a/qtbase-wayland-optimize-scroll-operations.patch b/qtbase-wayland-optimize-scroll-operations.patch new file mode 100644 index 0000000..9e7b52a --- /dev/null +++ b/qtbase-wayland-optimize-scroll-operations.patch @@ -0,0 +1,242 @@ +From 6f25f703fd37a900c139e14a33a4639502bfeae7 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?B=C5=82a=C5=BCej=20Szczygie=C5=82?= +Date: Wed, 13 Aug 2025 00:48:08 +0200 +Subject: [PATCH] wayland: Optimize scroll operation +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit + +Don't copy the dirty region in "recreateBackBufferIfNeeded()" in +"beginPaint()". Mark this region as non-dirty in the back buffer +and paint. Finalize the back buffer (copy remaining dirty region) +before flushing. This allows to optimize scrolling, we no longer +have to do a redundant dirty area copy. + +This matches the "QCALayerBackingStore" logic. + +Task-number: QTBUG-139231 +Change-Id: I6c19491fa8f093de9c9ce7624d8f9b65b0b722c5 +Reviewed-by: Tor Arne Vestbø +Reviewed-by: David Edmundson +--- + .../wayland/qwaylandshmbackingstore.cpp | 111 ++++++++++++------ + .../wayland/qwaylandshmbackingstore_p.h | 3 +- + 2 files changed, 74 insertions(+), 40 deletions(-) + +diff --git a/src/plugins/platforms/wayland/qwaylandshmbackingstore.cpp b/src/plugins/platforms/wayland/qwaylandshmbackingstore.cpp +index d8fc7b18ca34..591e5064ebd6 100644 +--- a/src/plugins/platforms/wayland/qwaylandshmbackingstore.cpp ++++ b/src/plugins/platforms/wayland/qwaylandshmbackingstore.cpp +@@ -159,6 +159,8 @@ QWaylandShmBackingStore::QWaylandShmBackingStore(QWindow *window, QWaylandDispla + // recreateBackBufferIfNeeded always resets mBackBuffer + if (mRequestedSize.isValid() && waylandWindow()) + recreateBackBufferIfNeeded(); ++ else ++ mBackBuffer = nullptr; + qDeleteAll(copy); + wl_event_queue_destroy(oldEventQueue); + }); +@@ -183,11 +185,15 @@ QPaintDevice *QWaylandShmBackingStore::paintDevice() + + void QWaylandShmBackingStore::updateDirtyStates(const QRegion ®ion) + { +- // Update dirty state of buffers based on what was painted. The back buffer will +- // not be dirty since we already painted on it, while other buffers will become dirty. ++ // Update dirty state of buffers based on what was painted. The back buffer will be ++ // less dirty, since we painted to it, while other buffers will become more dirty. ++ // This allows us to minimize copies between front and back buffers on swap in the ++ // cases where the painted region overlaps with the previous frame (front buffer). + for (QWaylandShmBuffer *b : std::as_const(mBuffers)) { + if (b != mBackBuffer) + b->dirtyRegion() += region; ++ else ++ b->dirtyRegion() -= region; + } + } + +@@ -198,7 +204,7 @@ void QWaylandShmBackingStore::beginPaint(const QRegion ®ion) + + const QMargins margins = windowDecorationMargins(); + const QRegion regionTranslated = region.translated(margins.left(), margins.top()); +- const bool bufferWasRecreated = recreateBackBufferIfNeeded(regionTranslated); ++ const bool bufferWasRecreated = recreateBackBufferIfNeeded(); + updateDirtyStates(regionTranslated); + + // Although undocumented, QBackingStore::beginPaint expects the painted region +@@ -223,7 +229,7 @@ void QWaylandShmBackingStore::endPaint() + // Inspired by QCALayerBackingStore. + bool QWaylandShmBackingStore::scroll(const QRegion ®ion, int dx, int dy) + { +- if (!mBackBuffer) ++ if (Q_UNLIKELY(!mBackBuffer || !mFrontBuffer)) + return false; + + const qreal devicePixelRatio = waylandWindow()->scale(); +@@ -236,19 +242,35 @@ bool QWaylandShmBackingStore::scroll(const QRegion ®ion, int dx, int dy) + + recreateBackBufferIfNeeded(); + +- QImage *backBufferImage = mBackBuffer->image(); +- + const QPoint scrollDelta(dx, dy); + const QMargins margins = windowDecorationMargins(); + const QRegion adjustedRegion = region.translated(margins.left(), margins.top()); + +- const QRect boundingRect = adjustedRegion.boundingRect(); +- const QPoint devicePixelDelta = scrollDelta * devicePixelRatio; ++ const QRegion inPlaceRegion = adjustedRegion - mBackBuffer->dirtyRegion(); ++ const QRegion frontBufferRegion = adjustedRegion - inPlaceRegion; ++ ++ if (!inPlaceRegion.isEmpty()) { ++ const QRect inPlaceBoundingRect = inPlaceRegion.boundingRect(); ++ const QPoint devicePixelDelta = scrollDelta * devicePixelRatio; ++ ++ qt_scrollRectInImage(*mBackBuffer->image(), ++ QRect(inPlaceBoundingRect.topLeft() * devicePixelRatio, ++ inPlaceBoundingRect.size() * devicePixelRatio), ++ devicePixelDelta); ++ } + +- qt_scrollRectInImage(*backBufferImage, +- QRect(boundingRect.topLeft() * devicePixelRatio, +- boundingRect.size() * devicePixelRatio), +- devicePixelDelta); ++ if (!frontBufferRegion.isEmpty()) { ++ QPainter painter(mBackBuffer->image()); ++ painter.setCompositionMode(QPainter::CompositionMode_Source); ++ painter.scale(qreal(1) / devicePixelRatio, qreal(1) / devicePixelRatio); ++ for (const QRect &rect : frontBufferRegion) { ++ QRect sourceRect(rect.topLeft() * devicePixelRatio, ++ rect.size() * devicePixelRatio); ++ QRect destinationRect((rect.topLeft() + scrollDelta) * devicePixelRatio, ++ rect.size() * devicePixelRatio); ++ painter.drawImage(destinationRect, *mFrontBuffer->image(), sourceRect); ++ } ++ } + + // We do not mark the source region as dirty, even though it technically has "moved". + // This matches the behavior of other backingstore implementations using qt_scrollRectInImage. +@@ -289,6 +311,8 @@ void QWaylandShmBackingStore::flush(QWindow *window, const QRegion ®ion, cons + if (windowDecoration() && windowDecoration()->isDirty()) + updateDecorations(); + ++ finalizeBackBuffer(); ++ + mFrontBuffer = mBackBuffer; + + QMargins margins = windowDecorationMargins(); +@@ -313,6 +337,8 @@ QWaylandShmBuffer *QWaylandShmBackingStore::getBuffer(const QSize &size, bool &b + mBuffers.removeAt(i); + if (mBackBuffer == buffer) + mBackBuffer = nullptr; ++ if (mFrontBuffer == buffer) ++ mFrontBuffer = nullptr; + delete buffer; + } + } +@@ -341,7 +367,7 @@ QWaylandShmBuffer *QWaylandShmBackingStore::getBuffer(const QSize &size, bool &b + return nullptr; + } + +-bool QWaylandShmBackingStore::recreateBackBufferIfNeeded(const QRegion &nonDirtyRegion) ++bool QWaylandShmBackingStore::recreateBackBufferIfNeeded() + { + wl_display_dispatch_queue_pending(mDisplay->wl_display(), mEventQueue); + +@@ -375,30 +401,6 @@ bool QWaylandShmBackingStore::recreateBackBufferIfNeeded(const QRegion &nonDirty + qsizetype oldSizeInBytes = mBackBuffer ? mBackBuffer->image()->sizeInBytes() : 0; + qsizetype newSizeInBytes = buffer->image()->sizeInBytes(); + +- // mBackBuffer may have been deleted here but if so it means its size was different so we wouldn't copy it anyway +- if (mBackBuffer != buffer && oldSizeInBytes == newSizeInBytes) { +- const QRegion clipRegion = buffer->dirtyRegion() - nonDirtyRegion; +- const auto clipRects = clipRegion.rects(); +- if (!clipRects.empty()) { +- Q_ASSERT(mBackBuffer); +- const QImage *sourceImage = mBackBuffer->image(); +- QImage *targetImage = buffer->image(); +- +- QPainter painter(targetImage); +- painter.setCompositionMode(QPainter::CompositionMode_Source); +- const qreal targetDevicePixelRatio = painter.device()->devicePixelRatio(); +- for (const QRect &clipRect : clipRects) { // Iterate clip rects, because complicated clip region causes higher CPU usage +- if (clipRects.size() > 1) +- painter.save(); +- painter.setClipRect(clipRect); +- painter.scale(qreal(1) / targetDevicePixelRatio, qreal(1) / targetDevicePixelRatio); +- painter.drawImage(QRectF(QPointF(), targetImage->size()), *sourceImage, sourceImage->rect()); +- if (clipRects.size() > 1) +- painter.restore(); +- } +- } +- } +- + mBackBuffer = buffer; + + for (QWaylandShmBuffer *buffer : std::as_const(mBuffers)) { +@@ -412,11 +414,40 @@ bool QWaylandShmBackingStore::recreateBackBufferIfNeeded(const QRegion &nonDirty + if (windowDecoration() && window()->isVisible() && oldSizeInBytes != newSizeInBytes) + windowDecoration()->update(); + +- buffer->dirtyRegion() = QRegion(); +- + return bufferWasRecreated; + } + ++void QWaylandShmBackingStore::finalizeBackBuffer() ++{ ++ Q_ASSERT(mBackBuffer); ++ ++ const QRegion clipRegion = mBackBuffer->dirtyRegion(); ++ if (clipRegion.isEmpty()) ++ return; ++ ++ if (Q_UNLIKELY(!mFrontBuffer || mFrontBuffer == mBackBuffer)) ++ return; ++ ++ const QImage *sourceImage = mFrontBuffer->image(); ++ QImage *targetImage = mBackBuffer->image(); ++ ++ QPainter painter(targetImage); ++ painter.setCompositionMode(QPainter::CompositionMode_Source); ++ const qreal targetDevicePixelRatio = painter.device()->devicePixelRatio(); ++ const auto clipRects = clipRegion.rects(); ++ for (const QRect &clipRect : clipRects) { // Iterate clip rects, because complicated clip region causes higher CPU usage ++ if (clipRects.size() > 1) ++ painter.save(); ++ painter.setClipRect(clipRect); ++ painter.scale(qreal(1) / targetDevicePixelRatio, qreal(1) / targetDevicePixelRatio); ++ painter.drawImage(QRectF(QPointF(), targetImage->size()), *sourceImage, sourceImage->rect()); ++ if (clipRects.size() > 1) ++ painter.restore(); ++ } ++ ++ mBackBuffer->dirtyRegion() = QRegion(); ++} ++ + QImage *QWaylandShmBackingStore::entireSurface() const + { + return mBackBuffer->image(); +@@ -496,6 +527,8 @@ QImage QWaylandShmBackingStore::toImage() const + // instead of flush() for widgets that have renderToTexture children + // (QOpenGLWidget, QQuickWidget). + ++ const_cast(this)->finalizeBackBuffer(); ++ + return *contentSurface(); + } + #endif // opengl +diff --git a/src/plugins/platforms/wayland/qwaylandshmbackingstore_p.h b/src/plugins/platforms/wayland/qwaylandshmbackingstore_p.h +index efd80159e859..cfcafb283265 100644 +--- a/src/plugins/platforms/wayland/qwaylandshmbackingstore_p.h ++++ b/src/plugins/platforms/wayland/qwaylandshmbackingstore_p.h +@@ -73,7 +73,8 @@ class Q_WAYLANDCLIENT_EXPORT QWaylandShmBackingStore : public QPlatformBackingSt + QMargins windowDecorationMargins() const; + QImage *entireSurface() const; + QImage *contentSurface() const; +- bool recreateBackBufferIfNeeded(const QRegion &nonDirtyRegion = QRegion()); ++ bool recreateBackBufferIfNeeded(); ++ void finalizeBackBuffer(); + + QWaylandWindow *waylandWindow() const; + void iterateBuffer(); From 67c126dc18b30b35f68f46409502fac8ea69533a Mon Sep 17 00:00:00 2001 From: Jan Grulich Date: Thu, 20 Nov 2025 08:59:53 +0100 Subject: [PATCH 10/12] 6.10.1 --- .gitignore | 1 + qt6-qtbase.spec | 13 +- ...compress-high-frequency-mouse-events.patch | 174 ------------- ...eference-for-server-side-decorations.patch | 55 ---- ...compression-and-fix-scroll-end-event.patch | 235 ----------------- ...e-wayland-optimize-scroll-operations.patch | 242 ------------------ sources | 2 +- 7 files changed, 8 insertions(+), 714 deletions(-) delete mode 100644 qtbase-wayland-compress-high-frequency-mouse-events.patch delete mode 100644 qtbase-wayland-convey-preference-for-server-side-decorations.patch delete mode 100644 qtbase-wayland-enable-event-compression-and-fix-scroll-end-event.patch delete mode 100644 qtbase-wayland-optimize-scroll-operations.patch diff --git a/.gitignore b/.gitignore index 05c8f2b..41a8a9f 100644 --- a/.gitignore +++ b/.gitignore @@ -38,3 +38,4 @@ /qtbase-everywhere-src-6.9.2.tar.xz /qtbase-everywhere-src-6.10.0-rc.tar.xz /qtbase-everywhere-src-6.10.0.tar.xz +/qtbase-everywhere-src-6.10.1.tar.xz diff --git a/qt6-qtbase.spec b/qt6-qtbase.spec index 9af205f..7da44dd 100644 --- a/qt6-qtbase.spec +++ b/qt6-qtbase.spec @@ -47,8 +47,8 @@ BuildRequires: pkgconfig(libsystemd) Name: qt6-qtbase Summary: Qt6 - QtBase components -Version: 6.10.0 -Release: 3%{?dist} +Version: 6.10.1 +Release: 1%{?dist} License: LGPL-3.0-only OR GPL-3.0-only WITH Qt-GPL-exception-1.0 Url: http://qt-project.org/ @@ -98,10 +98,6 @@ Patch56: qtbase-mysql.patch Patch58: qtbase-libglvnd.patch # upstream patches -Patch100: qtbase-wayland-convey-preference-for-server-side-decorations.patch -Patch101: qtbase-wayland-compress-high-frequency-mouse-events.patch -Patch102: qtbase-wayland-optimize-scroll-operations.patch -Patch103: qtbase-wayland-enable-event-compression-and-fix-scroll-end-event.patch # Do not check any files in %%{_qt6_plugindir}/platformthemes/ for requires. @@ -440,7 +436,7 @@ translationdir=%{_qt6_translationdir} Name: Qt6 Description: Qt6 Configuration -Version: 6.10.0 +Version: 6.10.1 EOF # rpm macros @@ -967,6 +963,9 @@ make check -k ||: %{_qt6_datadir}/wayland/protocols/ %changelog +* Thu Nov 20 2025 Jan Grulich - 6.10.1-1 +- 6.10.1 + * Mon Nov 10 2025 Jan Grulich - 6.10.0-3 - Backport wayland fixes for mouse scrolling diff --git a/qtbase-wayland-compress-high-frequency-mouse-events.patch b/qtbase-wayland-compress-high-frequency-mouse-events.patch deleted file mode 100644 index e33e22a..0000000 --- a/qtbase-wayland-compress-high-frequency-mouse-events.patch +++ /dev/null @@ -1,174 +0,0 @@ -From 095759818854e5a011aa8f859e566bbc6368ab76 Mon Sep 17 00:00:00 2001 -From: =?UTF-8?q?B=C5=82a=C5=BCej=20Szczygie=C5=82?= -Date: Sun, 27 Jul 2025 00:50:00 +0200 -Subject: [PATCH] wayland: Compress high frequency mouse events -MIME-Version: 1.0 -Content-Type: text/plain; charset=UTF-8 -Content-Transfer-Encoding: 8bit - -Add support for Qt::AA_CompressHighFrequencyEvents on Wayland. - -The highest USB HID polling rate is 8 kHz (125 μs). Most mice use lower -polling rate [125 Hz - 1000 Hz]. Reject all events faster than 100 μs, -because it definitely means the application main thread is freezed by -the long operation and events are delivered one after another from the -queue. Since now we rely on the 0 ms timer to deliver the last pending -event when application main thread is no longer freezed. - -Pick-to: 6.10 -Task-number: QTBUG-138706 -Change-Id: Ie9d539e233c5551b1756d599b65495571e195f9d -Reviewed-by: David Edmundson ---- - src/corelib/global/qnamespace.qdoc | 1 + - .../platforms/wayland/qwaylandinputdevice.cpp | 38 +++++++++++++++++++ - .../platforms/wayland/qwaylandinputdevice_p.h | 12 ++++++ - .../platforms/wayland/qwaylandintegration.cpp | 2 + - tests/auto/wayland/client/tst_client.cpp | 2 + - 5 files changed, 55 insertions(+) - -diff --git a/src/corelib/global/qnamespace.qdoc b/src/corelib/global/qnamespace.qdoc -index c845cfa7..f463d736 100644 ---- a/src/corelib/global/qnamespace.qdoc -+++ b/src/corelib/global/qnamespace.qdoc -@@ -227,6 +227,7 @@ - application later. - On Windows 8 and above the default value is also true, but it only applies - to touch events. Mouse and window events remain unaffected by this flag. -+ On Wayland the default value is also true, but it only applies to mouse events. - On other platforms, the default is false. - (In the future, the compression feature may be implemented across platforms.) - You can test the attribute to see whether compression is enabled. -diff --git a/src/plugins/platforms/wayland/qwaylandinputdevice.cpp b/src/plugins/platforms/wayland/qwaylandinputdevice.cpp -index 170e80f8..45c78765 100644 ---- a/src/plugins/platforms/wayland/qwaylandinputdevice.cpp -+++ b/src/plugins/platforms/wayland/qwaylandinputdevice.cpp -@@ -63,6 +63,34 @@ Q_LOGGING_CATEGORY(lcQpaWaylandInput, "qt.qpa.wayland.input"); - // reasonable number of them. As of 2021 most touchscreen panels support 10 concurrent touchpoints. - static const int MaxTouchPoints = 10; - -+QWaylandEventCompressionPrivate::QWaylandEventCompressionPrivate() -+{ -+ timeElapsed.start(); -+ delayTimer.setSingleShot(true); -+} -+ -+bool QWaylandEventCompressionPrivate::compressEvent() -+{ -+ using namespace std::chrono_literals; -+ -+ if (!QCoreApplication::testAttribute(Qt::AA_CompressHighFrequencyEvents)) -+ return false; -+ -+ const auto elapsed = timeElapsed.durationElapsed(); -+ timeElapsed.start(); -+ if (elapsed < 100us || delayTimer.isActive()) -+ { -+ // The highest USB HID polling rate is 8 kHz (125 μs). Most mice use lowe polling rate [125 Hz - 1000 Hz]. -+ // Reject all events faster than 100 μs, because it definitely means the application main thread is -+ // freezed by long operation and events are delivered one after another from the queue. Since now we rely -+ // on the 0 ms timer to deliver the last pending event when application main thread is no longer freezed. -+ delayTimer.start(0); -+ return true; -+ } -+ -+ return false; -+} -+ - QWaylandInputDevice::Keyboard::Keyboard(QWaylandInputDevice *p) - : mParent(p) - { -@@ -140,6 +168,8 @@ QWaylandInputDevice::Pointer::Pointer(QWaylandInputDevice *seat) - cursorTimerCallback(); - }); - #endif -+ -+ mEventCompression.delayTimer.callOnTimeout(this, &QWaylandInputDevice::Pointer::flushFrameEvent); - } - - QWaylandInputDevice::Pointer::~Pointer() -@@ -922,6 +952,11 @@ void QWaylandInputDevice::Pointer::pointer_axis(uint32_t time, uint32_t axis, in - - void QWaylandInputDevice::Pointer::pointer_frame() - { -+ if (mEventCompression.compressEvent()) { -+ qCDebug(lcQpaWaylandInput) << "compressed pointer_frame event"; -+ return; -+ } -+ - flushFrameEvent(); - } - -@@ -1051,6 +1086,7 @@ void QWaylandInputDevice::Pointer::setFrameEvent(QWaylandPointerEvent *event) - flushFrameEvent(); - } - -+ delete mFrameData.event; - mFrameData.event = event; - - if (version() < WL_POINTER_FRAME_SINCE_VERSION) { -@@ -1170,6 +1206,8 @@ void QWaylandInputDevice::Pointer::flushScrollEvent() - - void QWaylandInputDevice::Pointer::flushFrameEvent() - { -+ mEventCompression.delayTimer.stop(); -+ - if (auto *event = mFrameData.event) { - if (auto window = event->surface) { - window->handleMouse(mParent, *event); -diff --git a/src/plugins/platforms/wayland/qwaylandinputdevice_p.h b/src/plugins/platforms/wayland/qwaylandinputdevice_p.h -index bcaf0258..0b24999e 100644 ---- a/src/plugins/platforms/wayland/qwaylandinputdevice_p.h -+++ b/src/plugins/platforms/wayland/qwaylandinputdevice_p.h -@@ -75,6 +75,16 @@ class CursorSurface; - - Q_DECLARE_LOGGING_CATEGORY(lcQpaWaylandInput); - -+struct QWaylandEventCompressionPrivate -+{ -+ QWaylandEventCompressionPrivate(); -+ -+ bool compressEvent(); -+ -+ QElapsedTimer timeElapsed; -+ QTimer delayTimer; -+}; -+ - class Q_WAYLANDCLIENT_EXPORT QWaylandInputDevice - : public QObject - , public QtWayland::wl_seat -@@ -381,6 +391,8 @@ public: - bool mScrollBeginSent = false; - QPointF mScrollDeltaRemainder; - -+ QWaylandEventCompressionPrivate mEventCompression; -+ - void setFrameEvent(QWaylandPointerEvent *event); - void flushScrollEvent(); - void flushFrameEvent(); -diff --git a/src/plugins/platforms/wayland/qwaylandintegration.cpp b/src/plugins/platforms/wayland/qwaylandintegration.cpp -index 669d47ee..fc869de6 100644 ---- a/src/plugins/platforms/wayland/qwaylandintegration.cpp -+++ b/src/plugins/platforms/wayland/qwaylandintegration.cpp -@@ -87,6 +87,8 @@ QWaylandIntegration::QWaylandIntegration(const QString &platformName) - : mPlatformName(platformName), mFontDb(new QGenericUnixFontDatabase()) - #endif - { -+ QCoreApplication::setAttribute(Qt::AA_CompressHighFrequencyEvents); -+ - mDisplay.reset(new QWaylandDisplay(this)); - mPlatformServices.reset(new QWaylandPlatformServices(mDisplay.data())); - -diff --git a/tests/auto/wayland/client/tst_client.cpp b/tests/auto/wayland/client/tst_client.cpp -index 04400e3f..09ac4f7f 100644 ---- a/tests/auto/wayland/client/tst_client.cpp -+++ b/tests/auto/wayland/client/tst_client.cpp -@@ -276,6 +276,8 @@ void tst_WaylandClient::events() - exec([&] { - pointer()->sendEnter(s, window.frameOffset() + mousePressPos); - pointer()->sendFrame(client()); -+ pointer()->sendMotion(client(), window.frameOffset() + mousePressPos / 2); -+ pointer()->sendFrame(client()); - pointer()->sendMotion(client(), window.frameOffset() + mousePressPos); - pointer()->sendFrame(client()); - pointer()->sendButton(client(), BTN_LEFT, Pointer::button_state_pressed); diff --git a/qtbase-wayland-convey-preference-for-server-side-decorations.patch b/qtbase-wayland-convey-preference-for-server-side-decorations.patch deleted file mode 100644 index 0a5b232..0000000 --- a/qtbase-wayland-convey-preference-for-server-side-decorations.patch +++ /dev/null @@ -1,55 +0,0 @@ -From 18550cd9ad04913d50abfc88a6048b7cfaf80687 Mon Sep 17 00:00:00 2001 -From: Igor Khanin -Date: Mon, 13 Oct 2025 14:26:39 +0300 -Subject: [PATCH] wayland: Convey preference for server side decorations - -As discussed in the mailing list, Qt applications generally expect top -level windows to be decorated by the windowing system - unless -explicitly requested by the developer by marking a window as frameless. -Furthermore, Qt's client side decorations for Wayland were only really -intended as a fallback. - -However until now the Wayland xdg-shell integration conveyed to the -compositor (if it supports decorations negotiations) that it didn't -care whose responsibility it was to draw window decorations. This did -not matter under KWin, which resolves such cases in favor of SSDs - but -does matter under other SSD-capable compositors like cosmic-comp (which -considers SSDs to be the fallback option). - -This changes the xdg-shell integration code to be explicit about -requesting SSDs. - -[ChangeLog][QtWaylandClient][Important Behavior Changes] The Wayland -XDG shell integration now requests server side decorations with -compositors supporting the zxdg_decoration_manager_v1 protocol. - -Change-Id: Ia025b1f6ff17248a5710f981a0cecd90c47b6cd8 -Reviewed-by: David Edmundson ---- - -diff --git a/src/plugins/platforms/wayland/plugins/shellintegration/xdg-shell/qwaylandxdgshell.cpp b/src/plugins/platforms/wayland/plugins/shellintegration/xdg-shell/qwaylandxdgshell.cpp -index a1a173f..8386b65 100644 ---- a/src/plugins/platforms/wayland/plugins/shellintegration/xdg-shell/qwaylandxdgshell.cpp -+++ b/src/plugins/platforms/wayland/plugins/shellintegration/xdg-shell/qwaylandxdgshell.cpp -@@ -201,7 +201,7 @@ - delete m_decoration; - m_decoration = nullptr; - } else { -- m_decoration->unsetMode(); -+ m_decoration->requestMode(QWaylandXdgToplevelDecorationV1::mode_server_side); - } - } - } -diff --git a/tests/auto/wayland/xdgdecorationv1/tst_xdgdecorationv1.cpp b/tests/auto/wayland/xdgdecorationv1/tst_xdgdecorationv1.cpp -index 5ee8569..65ac08f 100644 ---- a/tests/auto/wayland/xdgdecorationv1/tst_xdgdecorationv1.cpp -+++ b/tests/auto/wayland/xdgdecorationv1/tst_xdgdecorationv1.cpp -@@ -149,7 +149,7 @@ - QVERIFY(window.frameMargins().isNull()); - - QCOMPOSITOR_TRY_VERIFY(xdgToplevel()); -- QCOMPOSITOR_TRY_VERIFY(toplevelDecoration()->m_unsetModeRequested); -+ QCOMPOSITOR_TRY_VERIFY(toplevelDecoration()->m_requestedMode == XdgToplevelDecorationV1::mode_server_side); - QVERIFY(window.frameMargins().isNull()); // We're still waiting for a configure - exec([&] { - toplevelDecoration()->sendConfigure(XdgToplevelDecorationV1::mode_client_side); diff --git a/qtbase-wayland-enable-event-compression-and-fix-scroll-end-event.patch b/qtbase-wayland-enable-event-compression-and-fix-scroll-end-event.patch deleted file mode 100644 index 80e0009..0000000 --- a/qtbase-wayland-enable-event-compression-and-fix-scroll-end-event.patch +++ /dev/null @@ -1,235 +0,0 @@ -From 9dd0d936d6691904a4bb212dcf48999a5228b84f Mon Sep 17 00:00:00 2001 -From: =?UTF-8?q?B=C5=82a=C5=BCej=20Szczygie=C5=82?= -Date: Thu, 7 Aug 2025 15:42:55 +0200 -Subject: [PATCH] wayland: Enable event compression and fix scroll end event - -We have to deliver the scroll end event in order with other scroll -events, so move scroll end event logic into flushScrollEvent(). - -Track the target scroll window on scroll begin and check whether we -have a target window to prevent a crash. - -Also we can't clear the scroll delta when scroll end event arrives, -because the event compression relies on this value. - -Use scoped pointer for frame event to simplify code and make sure it's -deleted in destructor. - -Amends 095759818854e5a011aa8f859e566bbc6368ab76 -Updates 789681872fb62450fc78d1a06472a40d970a8d57 - -Change-Id: Ifb149c8fde1286b60439be0dab16b1df65279ea8 -Reviewed-by: David Edmundson ---- - .../platforms/wayland/qwaylandinputdevice.cpp | 98 ++++++++++--------- - .../platforms/wayland/qwaylandinputdevice_p.h | 6 +- - .../platforms/wayland/qwaylandintegration.cpp | 2 + - 3 files changed, 61 insertions(+), 45 deletions(-) - -diff --git a/src/plugins/platforms/wayland/qwaylandinputdevice.cpp b/src/plugins/platforms/wayland/qwaylandinputdevice.cpp -index 7fcda34c57ca..6ce43714a35f 100644 ---- a/src/plugins/platforms/wayland/qwaylandinputdevice.cpp -+++ b/src/plugins/platforms/wayland/qwaylandinputdevice.cpp -@@ -935,10 +935,7 @@ void QWaylandInputDevice::Pointer::pointer_axis(uint32_t time, uint32_t axis, in - - mParent->mTime = time; - -- if (version() < WL_POINTER_FRAME_SINCE_VERSION) { -- qCDebug(lcQpaWaylandInput) << "Flushing new event; no frame event in this version"; -- flushFrameEvent(); -- } -+ maybePointerFrame(); - } - - void QWaylandInputDevice::Pointer::pointer_frame() -@@ -978,11 +975,9 @@ void QWaylandInputDevice::Pointer::pointer_axis_stop(uint32_t time, uint32_t axi - switch (axis) { - case axis_vertical_scroll: - qCDebug(lcQpaWaylandInput) << "Received vertical wl_pointer.axis_stop"; -- mFrameData.delta.setY(0); //TODO: what's the point of doing this? - break; - case axis_horizontal_scroll: - qCDebug(lcQpaWaylandInput) << "Received horizontal wl_pointer.axis_stop"; -- mFrameData.delta.setX(0); - break; - default: - qCWarning(lcQpaWaylandInput) << "wl_pointer.axis_stop: Unknown axis: " << axis -@@ -990,25 +985,7 @@ void QWaylandInputDevice::Pointer::pointer_axis_stop(uint32_t time, uint32_t axi - return; - } - -- // May receive axis_stop for events we haven't sent a ScrollBegin for because -- // most axis_sources do not mandate an axis_stop event to be sent. -- if (!mScrollBeginSent) { -- // TODO: For now, we just ignore these events, but we could perhaps take this as an -- // indication that this compositor will in fact send axis_stop events for these sources -- // and send a ScrollBegin the next time an axis_source event with this type is encountered. -- return; -- } -- -- QWaylandWindow *target = QWaylandWindow::mouseGrab(); -- if (!target) -- target = focusWindow(); -- Qt::KeyboardModifiers mods = mParent->modifiers(); -- const bool inverted = mFrameData.verticalAxisInverted || mFrameData.horizontalAxisInverted; -- WheelEvent wheelEvent(focusWindow(), Qt::ScrollEnd, mParent->mTime, mSurfacePos, mGlobalPos, -- QPoint(), QPoint(), Qt::MouseEventNotSynthesized, mods, inverted); -- target->handleMouse(mParent, wheelEvent); -- mScrollBeginSent = false; -- mScrollDeltaRemainder = QPointF(); -+ mScrollEnd = true; - } - - void QWaylandInputDevice::Pointer::pointer_axis_discrete(uint32_t axis, int32_t value) -@@ -1069,6 +1046,14 @@ void QWaylandInputDevice::Pointer::pointer_axis_relative_direction(uint32_t axis - } - } - -+inline void QWaylandInputDevice::Pointer::maybePointerFrame() -+{ -+ if (version() < WL_POINTER_FRAME_SINCE_VERSION) { -+ qCDebug(lcQpaWaylandInput) << "Flushing new event; no frame event in this version"; -+ pointer_frame(); -+ } -+} -+ - void QWaylandInputDevice::Pointer::setFrameEvent(QWaylandPointerEvent *event) - { - qCDebug(lcQpaWaylandInput) << "Setting frame event " << event->type; -@@ -1077,13 +1062,9 @@ void QWaylandInputDevice::Pointer::setFrameEvent(QWaylandPointerEvent *event) - flushFrameEvent(); - } - -- delete mFrameData.event; -- mFrameData.event = event; -+ mFrameData.event.reset(event); - -- if (version() < WL_POINTER_FRAME_SINCE_VERSION) { -- qCDebug(lcQpaWaylandInput) << "Flushing new event; no frame event in this version"; -- flushFrameEvent(); -- } -+ maybePointerFrame(); - } - - void QWaylandInputDevice::Pointer::FrameData::resetScrollData() -@@ -1163,11 +1144,24 @@ void QWaylandInputDevice::Pointer::flushScrollEvent() - { - QPoint angleDelta = mFrameData.angleDelta(); - -+ // The wayland protocol has separate horizontal and vertical axes, Qt has just the one inverted flag -+ // Pragmatically it should't come up -+ const bool inverted = mFrameData.verticalAxisInverted || mFrameData.horizontalAxisInverted; -+ - // Angle delta is required for Qt wheel events, so don't try to send events if it's zero - if (!angleDelta.isNull()) { -- QWaylandWindow *target = QWaylandWindow::mouseGrab(); -- if (!target) -- target = focusWindow(); -+ QWaylandWindow *target = mScrollTarget; -+ if (!mScrollBeginSent) { -+ if (!target) -+ target = QWaylandWindow::mouseGrab(); -+ if (!target) -+ target = focusWindow(); -+ } -+ if (!target) { -+ qCDebug(lcQpaWaylandInput) << "Flushing scroll event aborted - no scroll target"; -+ mFrameData.resetScrollData(); -+ return; -+ } - - if (isDefinitelyTerminated(mFrameData.axisSource) && !mScrollBeginSent) { - qCDebug(lcQpaWaylandInput) << "Flushing scroll event sending ScrollBegin"; -@@ -1177,21 +1171,38 @@ void QWaylandInputDevice::Pointer::flushScrollEvent() - mParent->modifiers(), false)); - mScrollBeginSent = true; - mScrollDeltaRemainder = QPointF(); -+ mScrollTarget = target; - } - - Qt::ScrollPhase phase = mScrollBeginSent ? Qt::ScrollUpdate : Qt::NoScrollPhase; - QPoint pixelDelta = mFrameData.pixelDeltaAndError(&mScrollDeltaRemainder); -- Qt::MouseEventSource source = mFrameData.wheelEventSource(); -- -- -- // The wayland protocol has separate horizontal and vertical axes, Qt has just the one inverted flag -- // Pragmatically it should't come up -- const bool inverted = mFrameData.verticalAxisInverted || mFrameData.horizontalAxisInverted; - - qCDebug(lcQpaWaylandInput) << "Flushing scroll event" << phase << pixelDelta << angleDelta; - target->handleMouse(mParent, WheelEvent(focusWindow(), phase, mParent->mTime, mSurfacePos, mGlobalPos, -- pixelDelta, angleDelta, source, mParent->modifiers(), inverted)); -+ pixelDelta, angleDelta, mFrameData.wheelEventSource(), mParent->modifiers(), inverted)); -+ } -+ -+ if (mScrollEnd) { -+ if (mScrollBeginSent) { -+ if (auto target = mScrollTarget.get()) { -+ qCDebug(lcQpaWaylandInput) << "Flushing scroll end event"; -+ target->handleMouse(mParent, WheelEvent(focusWindow(), Qt::ScrollEnd, mParent->mTime, mSurfacePos, mGlobalPos, -+ QPoint(), QPoint(), mFrameData.wheelEventSource(), mParent->modifiers(), inverted)); -+ } -+ mScrollBeginSent = false; -+ mScrollDeltaRemainder = QPointF(); -+ } else { -+ // May receive axis_stop for events we haven't sent a ScrollBegin for because -+ // most axis_sources do not mandate an axis_stop event to be sent. -+ -+ // TODO: For now, we just ignore these events, but we could perhaps take this as an -+ // indication that this compositor will in fact send axis_stop events for these sources -+ // and send a ScrollBegin the next time an axis_source event with this type is encountered. -+ } -+ mScrollEnd = false; -+ mScrollTarget.clear(); - } -+ - mFrameData.resetScrollData(); - } - -@@ -1199,7 +1210,7 @@ void QWaylandInputDevice::Pointer::flushFrameEvent() - { - mEventCompression.delayTimer.stop(); - -- if (auto *event = mFrameData.event) { -+ if (auto *event = mFrameData.event.get()) { - if (auto window = event->surface) { - window->handleMouse(mParent, *event); - } else if (mFrameData.event->type == QEvent::MouseButtonRelease) { -@@ -1212,8 +1223,7 @@ void QWaylandInputDevice::Pointer::flushFrameEvent() - event->modifiers); // , Qt::MouseEventSource source = - // Qt::MouseEventNotSynthesized); - } -- delete mFrameData.event; -- mFrameData.event = nullptr; -+ mFrameData.event.reset(); - } - - //TODO: do modifiers get passed correctly here? -diff --git a/src/plugins/platforms/wayland/qwaylandinputdevice_p.h b/src/plugins/platforms/wayland/qwaylandinputdevice_p.h -index 533a10991e5e..aa2ccddbcdb8 100644 ---- a/src/plugins/platforms/wayland/qwaylandinputdevice_p.h -+++ b/src/plugins/platforms/wayland/qwaylandinputdevice_p.h -@@ -370,7 +370,7 @@ private Q_SLOTS: - Qt::MouseButton mLastButton = Qt::NoButton; - - struct FrameData { -- QWaylandPointerEvent *event = nullptr; -+ QScopedPointer event; - - QPointF delta; - QPoint delta120; -@@ -387,10 +387,14 @@ private Q_SLOTS: - } mFrameData; - - bool mScrollBeginSent = false; -+ bool mScrollEnd = false; - QPointF mScrollDeltaRemainder; -+ QPointer mScrollTarget; - - QWaylandEventCompressionPrivate mEventCompression; - -+ void maybePointerFrame(); -+ - void setFrameEvent(QWaylandPointerEvent *event); - void flushScrollEvent(); - void flushFrameEvent(); diff --git a/qtbase-wayland-optimize-scroll-operations.patch b/qtbase-wayland-optimize-scroll-operations.patch deleted file mode 100644 index 9e7b52a..0000000 --- a/qtbase-wayland-optimize-scroll-operations.patch +++ /dev/null @@ -1,242 +0,0 @@ -From 6f25f703fd37a900c139e14a33a4639502bfeae7 Mon Sep 17 00:00:00 2001 -From: =?UTF-8?q?B=C5=82a=C5=BCej=20Szczygie=C5=82?= -Date: Wed, 13 Aug 2025 00:48:08 +0200 -Subject: [PATCH] wayland: Optimize scroll operation -MIME-Version: 1.0 -Content-Type: text/plain; charset=UTF-8 -Content-Transfer-Encoding: 8bit - -Don't copy the dirty region in "recreateBackBufferIfNeeded()" in -"beginPaint()". Mark this region as non-dirty in the back buffer -and paint. Finalize the back buffer (copy remaining dirty region) -before flushing. This allows to optimize scrolling, we no longer -have to do a redundant dirty area copy. - -This matches the "QCALayerBackingStore" logic. - -Task-number: QTBUG-139231 -Change-Id: I6c19491fa8f093de9c9ce7624d8f9b65b0b722c5 -Reviewed-by: Tor Arne Vestbø -Reviewed-by: David Edmundson ---- - .../wayland/qwaylandshmbackingstore.cpp | 111 ++++++++++++------ - .../wayland/qwaylandshmbackingstore_p.h | 3 +- - 2 files changed, 74 insertions(+), 40 deletions(-) - -diff --git a/src/plugins/platforms/wayland/qwaylandshmbackingstore.cpp b/src/plugins/platforms/wayland/qwaylandshmbackingstore.cpp -index d8fc7b18ca34..591e5064ebd6 100644 ---- a/src/plugins/platforms/wayland/qwaylandshmbackingstore.cpp -+++ b/src/plugins/platforms/wayland/qwaylandshmbackingstore.cpp -@@ -159,6 +159,8 @@ QWaylandShmBackingStore::QWaylandShmBackingStore(QWindow *window, QWaylandDispla - // recreateBackBufferIfNeeded always resets mBackBuffer - if (mRequestedSize.isValid() && waylandWindow()) - recreateBackBufferIfNeeded(); -+ else -+ mBackBuffer = nullptr; - qDeleteAll(copy); - wl_event_queue_destroy(oldEventQueue); - }); -@@ -183,11 +185,15 @@ QPaintDevice *QWaylandShmBackingStore::paintDevice() - - void QWaylandShmBackingStore::updateDirtyStates(const QRegion ®ion) - { -- // Update dirty state of buffers based on what was painted. The back buffer will -- // not be dirty since we already painted on it, while other buffers will become dirty. -+ // Update dirty state of buffers based on what was painted. The back buffer will be -+ // less dirty, since we painted to it, while other buffers will become more dirty. -+ // This allows us to minimize copies between front and back buffers on swap in the -+ // cases where the painted region overlaps with the previous frame (front buffer). - for (QWaylandShmBuffer *b : std::as_const(mBuffers)) { - if (b != mBackBuffer) - b->dirtyRegion() += region; -+ else -+ b->dirtyRegion() -= region; - } - } - -@@ -198,7 +204,7 @@ void QWaylandShmBackingStore::beginPaint(const QRegion ®ion) - - const QMargins margins = windowDecorationMargins(); - const QRegion regionTranslated = region.translated(margins.left(), margins.top()); -- const bool bufferWasRecreated = recreateBackBufferIfNeeded(regionTranslated); -+ const bool bufferWasRecreated = recreateBackBufferIfNeeded(); - updateDirtyStates(regionTranslated); - - // Although undocumented, QBackingStore::beginPaint expects the painted region -@@ -223,7 +229,7 @@ void QWaylandShmBackingStore::endPaint() - // Inspired by QCALayerBackingStore. - bool QWaylandShmBackingStore::scroll(const QRegion ®ion, int dx, int dy) - { -- if (!mBackBuffer) -+ if (Q_UNLIKELY(!mBackBuffer || !mFrontBuffer)) - return false; - - const qreal devicePixelRatio = waylandWindow()->scale(); -@@ -236,19 +242,35 @@ bool QWaylandShmBackingStore::scroll(const QRegion ®ion, int dx, int dy) - - recreateBackBufferIfNeeded(); - -- QImage *backBufferImage = mBackBuffer->image(); -- - const QPoint scrollDelta(dx, dy); - const QMargins margins = windowDecorationMargins(); - const QRegion adjustedRegion = region.translated(margins.left(), margins.top()); - -- const QRect boundingRect = adjustedRegion.boundingRect(); -- const QPoint devicePixelDelta = scrollDelta * devicePixelRatio; -+ const QRegion inPlaceRegion = adjustedRegion - mBackBuffer->dirtyRegion(); -+ const QRegion frontBufferRegion = adjustedRegion - inPlaceRegion; -+ -+ if (!inPlaceRegion.isEmpty()) { -+ const QRect inPlaceBoundingRect = inPlaceRegion.boundingRect(); -+ const QPoint devicePixelDelta = scrollDelta * devicePixelRatio; -+ -+ qt_scrollRectInImage(*mBackBuffer->image(), -+ QRect(inPlaceBoundingRect.topLeft() * devicePixelRatio, -+ inPlaceBoundingRect.size() * devicePixelRatio), -+ devicePixelDelta); -+ } - -- qt_scrollRectInImage(*backBufferImage, -- QRect(boundingRect.topLeft() * devicePixelRatio, -- boundingRect.size() * devicePixelRatio), -- devicePixelDelta); -+ if (!frontBufferRegion.isEmpty()) { -+ QPainter painter(mBackBuffer->image()); -+ painter.setCompositionMode(QPainter::CompositionMode_Source); -+ painter.scale(qreal(1) / devicePixelRatio, qreal(1) / devicePixelRatio); -+ for (const QRect &rect : frontBufferRegion) { -+ QRect sourceRect(rect.topLeft() * devicePixelRatio, -+ rect.size() * devicePixelRatio); -+ QRect destinationRect((rect.topLeft() + scrollDelta) * devicePixelRatio, -+ rect.size() * devicePixelRatio); -+ painter.drawImage(destinationRect, *mFrontBuffer->image(), sourceRect); -+ } -+ } - - // We do not mark the source region as dirty, even though it technically has "moved". - // This matches the behavior of other backingstore implementations using qt_scrollRectInImage. -@@ -289,6 +311,8 @@ void QWaylandShmBackingStore::flush(QWindow *window, const QRegion ®ion, cons - if (windowDecoration() && windowDecoration()->isDirty()) - updateDecorations(); - -+ finalizeBackBuffer(); -+ - mFrontBuffer = mBackBuffer; - - QMargins margins = windowDecorationMargins(); -@@ -313,6 +337,8 @@ QWaylandShmBuffer *QWaylandShmBackingStore::getBuffer(const QSize &size, bool &b - mBuffers.removeAt(i); - if (mBackBuffer == buffer) - mBackBuffer = nullptr; -+ if (mFrontBuffer == buffer) -+ mFrontBuffer = nullptr; - delete buffer; - } - } -@@ -341,7 +367,7 @@ QWaylandShmBuffer *QWaylandShmBackingStore::getBuffer(const QSize &size, bool &b - return nullptr; - } - --bool QWaylandShmBackingStore::recreateBackBufferIfNeeded(const QRegion &nonDirtyRegion) -+bool QWaylandShmBackingStore::recreateBackBufferIfNeeded() - { - wl_display_dispatch_queue_pending(mDisplay->wl_display(), mEventQueue); - -@@ -375,30 +401,6 @@ bool QWaylandShmBackingStore::recreateBackBufferIfNeeded(const QRegion &nonDirty - qsizetype oldSizeInBytes = mBackBuffer ? mBackBuffer->image()->sizeInBytes() : 0; - qsizetype newSizeInBytes = buffer->image()->sizeInBytes(); - -- // mBackBuffer may have been deleted here but if so it means its size was different so we wouldn't copy it anyway -- if (mBackBuffer != buffer && oldSizeInBytes == newSizeInBytes) { -- const QRegion clipRegion = buffer->dirtyRegion() - nonDirtyRegion; -- const auto clipRects = clipRegion.rects(); -- if (!clipRects.empty()) { -- Q_ASSERT(mBackBuffer); -- const QImage *sourceImage = mBackBuffer->image(); -- QImage *targetImage = buffer->image(); -- -- QPainter painter(targetImage); -- painter.setCompositionMode(QPainter::CompositionMode_Source); -- const qreal targetDevicePixelRatio = painter.device()->devicePixelRatio(); -- for (const QRect &clipRect : clipRects) { // Iterate clip rects, because complicated clip region causes higher CPU usage -- if (clipRects.size() > 1) -- painter.save(); -- painter.setClipRect(clipRect); -- painter.scale(qreal(1) / targetDevicePixelRatio, qreal(1) / targetDevicePixelRatio); -- painter.drawImage(QRectF(QPointF(), targetImage->size()), *sourceImage, sourceImage->rect()); -- if (clipRects.size() > 1) -- painter.restore(); -- } -- } -- } -- - mBackBuffer = buffer; - - for (QWaylandShmBuffer *buffer : std::as_const(mBuffers)) { -@@ -412,11 +414,40 @@ bool QWaylandShmBackingStore::recreateBackBufferIfNeeded(const QRegion &nonDirty - if (windowDecoration() && window()->isVisible() && oldSizeInBytes != newSizeInBytes) - windowDecoration()->update(); - -- buffer->dirtyRegion() = QRegion(); -- - return bufferWasRecreated; - } - -+void QWaylandShmBackingStore::finalizeBackBuffer() -+{ -+ Q_ASSERT(mBackBuffer); -+ -+ const QRegion clipRegion = mBackBuffer->dirtyRegion(); -+ if (clipRegion.isEmpty()) -+ return; -+ -+ if (Q_UNLIKELY(!mFrontBuffer || mFrontBuffer == mBackBuffer)) -+ return; -+ -+ const QImage *sourceImage = mFrontBuffer->image(); -+ QImage *targetImage = mBackBuffer->image(); -+ -+ QPainter painter(targetImage); -+ painter.setCompositionMode(QPainter::CompositionMode_Source); -+ const qreal targetDevicePixelRatio = painter.device()->devicePixelRatio(); -+ const auto clipRects = clipRegion.rects(); -+ for (const QRect &clipRect : clipRects) { // Iterate clip rects, because complicated clip region causes higher CPU usage -+ if (clipRects.size() > 1) -+ painter.save(); -+ painter.setClipRect(clipRect); -+ painter.scale(qreal(1) / targetDevicePixelRatio, qreal(1) / targetDevicePixelRatio); -+ painter.drawImage(QRectF(QPointF(), targetImage->size()), *sourceImage, sourceImage->rect()); -+ if (clipRects.size() > 1) -+ painter.restore(); -+ } -+ -+ mBackBuffer->dirtyRegion() = QRegion(); -+} -+ - QImage *QWaylandShmBackingStore::entireSurface() const - { - return mBackBuffer->image(); -@@ -496,6 +527,8 @@ QImage QWaylandShmBackingStore::toImage() const - // instead of flush() for widgets that have renderToTexture children - // (QOpenGLWidget, QQuickWidget). - -+ const_cast(this)->finalizeBackBuffer(); -+ - return *contentSurface(); - } - #endif // opengl -diff --git a/src/plugins/platforms/wayland/qwaylandshmbackingstore_p.h b/src/plugins/platforms/wayland/qwaylandshmbackingstore_p.h -index efd80159e859..cfcafb283265 100644 ---- a/src/plugins/platforms/wayland/qwaylandshmbackingstore_p.h -+++ b/src/plugins/platforms/wayland/qwaylandshmbackingstore_p.h -@@ -73,7 +73,8 @@ class Q_WAYLANDCLIENT_EXPORT QWaylandShmBackingStore : public QPlatformBackingSt - QMargins windowDecorationMargins() const; - QImage *entireSurface() const; - QImage *contentSurface() const; -- bool recreateBackBufferIfNeeded(const QRegion &nonDirtyRegion = QRegion()); -+ bool recreateBackBufferIfNeeded(); -+ void finalizeBackBuffer(); - - QWaylandWindow *waylandWindow() const; - void iterateBuffer(); diff --git a/sources b/sources index fda36a3..a554569 100644 --- a/sources +++ b/sources @@ -1 +1 @@ -SHA512 (qtbase-everywhere-src-6.10.0.tar.xz) = bc3b98f8b2387ca207f6cd87478bdefa911442fd9981d38ecd8793856439e415ce9182b524dabc6b0a1ba5c27e22badfc7ca67a0a43e00186ee6865886937917 +SHA512 (qtbase-everywhere-src-6.10.1.tar.xz) = fd5dcdc59ec3b39e48563513ae438eb4540a28e72c46961295de2ccb08609289d477ef7e91aac0b8983f2d5b05b901b4f5be10eaca4ac4c6aa8cd598f37a228e From af0ef1e954a1999263a18d967c6d44df281f871f Mon Sep 17 00:00:00 2001 From: Jan Grulich Date: Mon, 8 Dec 2025 08:17:15 +0100 Subject: [PATCH 11/12] Re-add wayland fixes for mouse scrolling --- qt6-qtbase.spec | 9 +- ...compress-high-frequency-mouse-events.patch | 174 +++++++++++++ ...eference-for-server-side-decorations.patch | 55 ++++ ...compression-and-fix-scroll-end-event.patch | 235 +++++++++++++++++ ...e-wayland-optimize-scroll-operations.patch | 242 ++++++++++++++++++ 5 files changed, 714 insertions(+), 1 deletion(-) create mode 100644 qtbase-wayland-compress-high-frequency-mouse-events.patch create mode 100644 qtbase-wayland-convey-preference-for-server-side-decorations.patch create mode 100644 qtbase-wayland-enable-event-compression-and-fix-scroll-end-event.patch create mode 100644 qtbase-wayland-optimize-scroll-operations.patch diff --git a/qt6-qtbase.spec b/qt6-qtbase.spec index 7da44dd..70926ab 100644 --- a/qt6-qtbase.spec +++ b/qt6-qtbase.spec @@ -48,7 +48,7 @@ BuildRequires: pkgconfig(libsystemd) Name: qt6-qtbase Summary: Qt6 - QtBase components Version: 6.10.1 -Release: 1%{?dist} +Release: 2%{?dist} License: LGPL-3.0-only OR GPL-3.0-only WITH Qt-GPL-exception-1.0 Url: http://qt-project.org/ @@ -98,6 +98,10 @@ Patch56: qtbase-mysql.patch Patch58: qtbase-libglvnd.patch # upstream patches +Patch100: qtbase-wayland-convey-preference-for-server-side-decorations.patch +Patch101: qtbase-wayland-compress-high-frequency-mouse-events.patch +Patch102: qtbase-wayland-optimize-scroll-operations.patch +Patch103: qtbase-wayland-enable-event-compression-and-fix-scroll-end-event.patch # Do not check any files in %%{_qt6_plugindir}/platformthemes/ for requires. @@ -963,6 +967,9 @@ make check -k ||: %{_qt6_datadir}/wayland/protocols/ %changelog +* Mon Dec 08 2025 Jan Grulich - 6.10.1-2 +- Re-add wayland fixes for mouse scrolling + * Thu Nov 20 2025 Jan Grulich - 6.10.1-1 - 6.10.1 diff --git a/qtbase-wayland-compress-high-frequency-mouse-events.patch b/qtbase-wayland-compress-high-frequency-mouse-events.patch new file mode 100644 index 0000000..e33e22a --- /dev/null +++ b/qtbase-wayland-compress-high-frequency-mouse-events.patch @@ -0,0 +1,174 @@ +From 095759818854e5a011aa8f859e566bbc6368ab76 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?B=C5=82a=C5=BCej=20Szczygie=C5=82?= +Date: Sun, 27 Jul 2025 00:50:00 +0200 +Subject: [PATCH] wayland: Compress high frequency mouse events +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit + +Add support for Qt::AA_CompressHighFrequencyEvents on Wayland. + +The highest USB HID polling rate is 8 kHz (125 μs). Most mice use lower +polling rate [125 Hz - 1000 Hz]. Reject all events faster than 100 μs, +because it definitely means the application main thread is freezed by +the long operation and events are delivered one after another from the +queue. Since now we rely on the 0 ms timer to deliver the last pending +event when application main thread is no longer freezed. + +Pick-to: 6.10 +Task-number: QTBUG-138706 +Change-Id: Ie9d539e233c5551b1756d599b65495571e195f9d +Reviewed-by: David Edmundson +--- + src/corelib/global/qnamespace.qdoc | 1 + + .../platforms/wayland/qwaylandinputdevice.cpp | 38 +++++++++++++++++++ + .../platforms/wayland/qwaylandinputdevice_p.h | 12 ++++++ + .../platforms/wayland/qwaylandintegration.cpp | 2 + + tests/auto/wayland/client/tst_client.cpp | 2 + + 5 files changed, 55 insertions(+) + +diff --git a/src/corelib/global/qnamespace.qdoc b/src/corelib/global/qnamespace.qdoc +index c845cfa7..f463d736 100644 +--- a/src/corelib/global/qnamespace.qdoc ++++ b/src/corelib/global/qnamespace.qdoc +@@ -227,6 +227,7 @@ + application later. + On Windows 8 and above the default value is also true, but it only applies + to touch events. Mouse and window events remain unaffected by this flag. ++ On Wayland the default value is also true, but it only applies to mouse events. + On other platforms, the default is false. + (In the future, the compression feature may be implemented across platforms.) + You can test the attribute to see whether compression is enabled. +diff --git a/src/plugins/platforms/wayland/qwaylandinputdevice.cpp b/src/plugins/platforms/wayland/qwaylandinputdevice.cpp +index 170e80f8..45c78765 100644 +--- a/src/plugins/platforms/wayland/qwaylandinputdevice.cpp ++++ b/src/plugins/platforms/wayland/qwaylandinputdevice.cpp +@@ -63,6 +63,34 @@ Q_LOGGING_CATEGORY(lcQpaWaylandInput, "qt.qpa.wayland.input"); + // reasonable number of them. As of 2021 most touchscreen panels support 10 concurrent touchpoints. + static const int MaxTouchPoints = 10; + ++QWaylandEventCompressionPrivate::QWaylandEventCompressionPrivate() ++{ ++ timeElapsed.start(); ++ delayTimer.setSingleShot(true); ++} ++ ++bool QWaylandEventCompressionPrivate::compressEvent() ++{ ++ using namespace std::chrono_literals; ++ ++ if (!QCoreApplication::testAttribute(Qt::AA_CompressHighFrequencyEvents)) ++ return false; ++ ++ const auto elapsed = timeElapsed.durationElapsed(); ++ timeElapsed.start(); ++ if (elapsed < 100us || delayTimer.isActive()) ++ { ++ // The highest USB HID polling rate is 8 kHz (125 μs). Most mice use lowe polling rate [125 Hz - 1000 Hz]. ++ // Reject all events faster than 100 μs, because it definitely means the application main thread is ++ // freezed by long operation and events are delivered one after another from the queue. Since now we rely ++ // on the 0 ms timer to deliver the last pending event when application main thread is no longer freezed. ++ delayTimer.start(0); ++ return true; ++ } ++ ++ return false; ++} ++ + QWaylandInputDevice::Keyboard::Keyboard(QWaylandInputDevice *p) + : mParent(p) + { +@@ -140,6 +168,8 @@ QWaylandInputDevice::Pointer::Pointer(QWaylandInputDevice *seat) + cursorTimerCallback(); + }); + #endif ++ ++ mEventCompression.delayTimer.callOnTimeout(this, &QWaylandInputDevice::Pointer::flushFrameEvent); + } + + QWaylandInputDevice::Pointer::~Pointer() +@@ -922,6 +952,11 @@ void QWaylandInputDevice::Pointer::pointer_axis(uint32_t time, uint32_t axis, in + + void QWaylandInputDevice::Pointer::pointer_frame() + { ++ if (mEventCompression.compressEvent()) { ++ qCDebug(lcQpaWaylandInput) << "compressed pointer_frame event"; ++ return; ++ } ++ + flushFrameEvent(); + } + +@@ -1051,6 +1086,7 @@ void QWaylandInputDevice::Pointer::setFrameEvent(QWaylandPointerEvent *event) + flushFrameEvent(); + } + ++ delete mFrameData.event; + mFrameData.event = event; + + if (version() < WL_POINTER_FRAME_SINCE_VERSION) { +@@ -1170,6 +1206,8 @@ void QWaylandInputDevice::Pointer::flushScrollEvent() + + void QWaylandInputDevice::Pointer::flushFrameEvent() + { ++ mEventCompression.delayTimer.stop(); ++ + if (auto *event = mFrameData.event) { + if (auto window = event->surface) { + window->handleMouse(mParent, *event); +diff --git a/src/plugins/platforms/wayland/qwaylandinputdevice_p.h b/src/plugins/platforms/wayland/qwaylandinputdevice_p.h +index bcaf0258..0b24999e 100644 +--- a/src/plugins/platforms/wayland/qwaylandinputdevice_p.h ++++ b/src/plugins/platforms/wayland/qwaylandinputdevice_p.h +@@ -75,6 +75,16 @@ class CursorSurface; + + Q_DECLARE_LOGGING_CATEGORY(lcQpaWaylandInput); + ++struct QWaylandEventCompressionPrivate ++{ ++ QWaylandEventCompressionPrivate(); ++ ++ bool compressEvent(); ++ ++ QElapsedTimer timeElapsed; ++ QTimer delayTimer; ++}; ++ + class Q_WAYLANDCLIENT_EXPORT QWaylandInputDevice + : public QObject + , public QtWayland::wl_seat +@@ -381,6 +391,8 @@ public: + bool mScrollBeginSent = false; + QPointF mScrollDeltaRemainder; + ++ QWaylandEventCompressionPrivate mEventCompression; ++ + void setFrameEvent(QWaylandPointerEvent *event); + void flushScrollEvent(); + void flushFrameEvent(); +diff --git a/src/plugins/platforms/wayland/qwaylandintegration.cpp b/src/plugins/platforms/wayland/qwaylandintegration.cpp +index 669d47ee..fc869de6 100644 +--- a/src/plugins/platforms/wayland/qwaylandintegration.cpp ++++ b/src/plugins/platforms/wayland/qwaylandintegration.cpp +@@ -87,6 +87,8 @@ QWaylandIntegration::QWaylandIntegration(const QString &platformName) + : mPlatformName(platformName), mFontDb(new QGenericUnixFontDatabase()) + #endif + { ++ QCoreApplication::setAttribute(Qt::AA_CompressHighFrequencyEvents); ++ + mDisplay.reset(new QWaylandDisplay(this)); + mPlatformServices.reset(new QWaylandPlatformServices(mDisplay.data())); + +diff --git a/tests/auto/wayland/client/tst_client.cpp b/tests/auto/wayland/client/tst_client.cpp +index 04400e3f..09ac4f7f 100644 +--- a/tests/auto/wayland/client/tst_client.cpp ++++ b/tests/auto/wayland/client/tst_client.cpp +@@ -276,6 +276,8 @@ void tst_WaylandClient::events() + exec([&] { + pointer()->sendEnter(s, window.frameOffset() + mousePressPos); + pointer()->sendFrame(client()); ++ pointer()->sendMotion(client(), window.frameOffset() + mousePressPos / 2); ++ pointer()->sendFrame(client()); + pointer()->sendMotion(client(), window.frameOffset() + mousePressPos); + pointer()->sendFrame(client()); + pointer()->sendButton(client(), BTN_LEFT, Pointer::button_state_pressed); diff --git a/qtbase-wayland-convey-preference-for-server-side-decorations.patch b/qtbase-wayland-convey-preference-for-server-side-decorations.patch new file mode 100644 index 0000000..0a5b232 --- /dev/null +++ b/qtbase-wayland-convey-preference-for-server-side-decorations.patch @@ -0,0 +1,55 @@ +From 18550cd9ad04913d50abfc88a6048b7cfaf80687 Mon Sep 17 00:00:00 2001 +From: Igor Khanin +Date: Mon, 13 Oct 2025 14:26:39 +0300 +Subject: [PATCH] wayland: Convey preference for server side decorations + +As discussed in the mailing list, Qt applications generally expect top +level windows to be decorated by the windowing system - unless +explicitly requested by the developer by marking a window as frameless. +Furthermore, Qt's client side decorations for Wayland were only really +intended as a fallback. + +However until now the Wayland xdg-shell integration conveyed to the +compositor (if it supports decorations negotiations) that it didn't +care whose responsibility it was to draw window decorations. This did +not matter under KWin, which resolves such cases in favor of SSDs - but +does matter under other SSD-capable compositors like cosmic-comp (which +considers SSDs to be the fallback option). + +This changes the xdg-shell integration code to be explicit about +requesting SSDs. + +[ChangeLog][QtWaylandClient][Important Behavior Changes] The Wayland +XDG shell integration now requests server side decorations with +compositors supporting the zxdg_decoration_manager_v1 protocol. + +Change-Id: Ia025b1f6ff17248a5710f981a0cecd90c47b6cd8 +Reviewed-by: David Edmundson +--- + +diff --git a/src/plugins/platforms/wayland/plugins/shellintegration/xdg-shell/qwaylandxdgshell.cpp b/src/plugins/platforms/wayland/plugins/shellintegration/xdg-shell/qwaylandxdgshell.cpp +index a1a173f..8386b65 100644 +--- a/src/plugins/platforms/wayland/plugins/shellintegration/xdg-shell/qwaylandxdgshell.cpp ++++ b/src/plugins/platforms/wayland/plugins/shellintegration/xdg-shell/qwaylandxdgshell.cpp +@@ -201,7 +201,7 @@ + delete m_decoration; + m_decoration = nullptr; + } else { +- m_decoration->unsetMode(); ++ m_decoration->requestMode(QWaylandXdgToplevelDecorationV1::mode_server_side); + } + } + } +diff --git a/tests/auto/wayland/xdgdecorationv1/tst_xdgdecorationv1.cpp b/tests/auto/wayland/xdgdecorationv1/tst_xdgdecorationv1.cpp +index 5ee8569..65ac08f 100644 +--- a/tests/auto/wayland/xdgdecorationv1/tst_xdgdecorationv1.cpp ++++ b/tests/auto/wayland/xdgdecorationv1/tst_xdgdecorationv1.cpp +@@ -149,7 +149,7 @@ + QVERIFY(window.frameMargins().isNull()); + + QCOMPOSITOR_TRY_VERIFY(xdgToplevel()); +- QCOMPOSITOR_TRY_VERIFY(toplevelDecoration()->m_unsetModeRequested); ++ QCOMPOSITOR_TRY_VERIFY(toplevelDecoration()->m_requestedMode == XdgToplevelDecorationV1::mode_server_side); + QVERIFY(window.frameMargins().isNull()); // We're still waiting for a configure + exec([&] { + toplevelDecoration()->sendConfigure(XdgToplevelDecorationV1::mode_client_side); diff --git a/qtbase-wayland-enable-event-compression-and-fix-scroll-end-event.patch b/qtbase-wayland-enable-event-compression-and-fix-scroll-end-event.patch new file mode 100644 index 0000000..80e0009 --- /dev/null +++ b/qtbase-wayland-enable-event-compression-and-fix-scroll-end-event.patch @@ -0,0 +1,235 @@ +From 9dd0d936d6691904a4bb212dcf48999a5228b84f Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?B=C5=82a=C5=BCej=20Szczygie=C5=82?= +Date: Thu, 7 Aug 2025 15:42:55 +0200 +Subject: [PATCH] wayland: Enable event compression and fix scroll end event + +We have to deliver the scroll end event in order with other scroll +events, so move scroll end event logic into flushScrollEvent(). + +Track the target scroll window on scroll begin and check whether we +have a target window to prevent a crash. + +Also we can't clear the scroll delta when scroll end event arrives, +because the event compression relies on this value. + +Use scoped pointer for frame event to simplify code and make sure it's +deleted in destructor. + +Amends 095759818854e5a011aa8f859e566bbc6368ab76 +Updates 789681872fb62450fc78d1a06472a40d970a8d57 + +Change-Id: Ifb149c8fde1286b60439be0dab16b1df65279ea8 +Reviewed-by: David Edmundson +--- + .../platforms/wayland/qwaylandinputdevice.cpp | 98 ++++++++++--------- + .../platforms/wayland/qwaylandinputdevice_p.h | 6 +- + .../platforms/wayland/qwaylandintegration.cpp | 2 + + 3 files changed, 61 insertions(+), 45 deletions(-) + +diff --git a/src/plugins/platforms/wayland/qwaylandinputdevice.cpp b/src/plugins/platforms/wayland/qwaylandinputdevice.cpp +index 7fcda34c57ca..6ce43714a35f 100644 +--- a/src/plugins/platforms/wayland/qwaylandinputdevice.cpp ++++ b/src/plugins/platforms/wayland/qwaylandinputdevice.cpp +@@ -935,10 +935,7 @@ void QWaylandInputDevice::Pointer::pointer_axis(uint32_t time, uint32_t axis, in + + mParent->mTime = time; + +- if (version() < WL_POINTER_FRAME_SINCE_VERSION) { +- qCDebug(lcQpaWaylandInput) << "Flushing new event; no frame event in this version"; +- flushFrameEvent(); +- } ++ maybePointerFrame(); + } + + void QWaylandInputDevice::Pointer::pointer_frame() +@@ -978,11 +975,9 @@ void QWaylandInputDevice::Pointer::pointer_axis_stop(uint32_t time, uint32_t axi + switch (axis) { + case axis_vertical_scroll: + qCDebug(lcQpaWaylandInput) << "Received vertical wl_pointer.axis_stop"; +- mFrameData.delta.setY(0); //TODO: what's the point of doing this? + break; + case axis_horizontal_scroll: + qCDebug(lcQpaWaylandInput) << "Received horizontal wl_pointer.axis_stop"; +- mFrameData.delta.setX(0); + break; + default: + qCWarning(lcQpaWaylandInput) << "wl_pointer.axis_stop: Unknown axis: " << axis +@@ -990,25 +985,7 @@ void QWaylandInputDevice::Pointer::pointer_axis_stop(uint32_t time, uint32_t axi + return; + } + +- // May receive axis_stop for events we haven't sent a ScrollBegin for because +- // most axis_sources do not mandate an axis_stop event to be sent. +- if (!mScrollBeginSent) { +- // TODO: For now, we just ignore these events, but we could perhaps take this as an +- // indication that this compositor will in fact send axis_stop events for these sources +- // and send a ScrollBegin the next time an axis_source event with this type is encountered. +- return; +- } +- +- QWaylandWindow *target = QWaylandWindow::mouseGrab(); +- if (!target) +- target = focusWindow(); +- Qt::KeyboardModifiers mods = mParent->modifiers(); +- const bool inverted = mFrameData.verticalAxisInverted || mFrameData.horizontalAxisInverted; +- WheelEvent wheelEvent(focusWindow(), Qt::ScrollEnd, mParent->mTime, mSurfacePos, mGlobalPos, +- QPoint(), QPoint(), Qt::MouseEventNotSynthesized, mods, inverted); +- target->handleMouse(mParent, wheelEvent); +- mScrollBeginSent = false; +- mScrollDeltaRemainder = QPointF(); ++ mScrollEnd = true; + } + + void QWaylandInputDevice::Pointer::pointer_axis_discrete(uint32_t axis, int32_t value) +@@ -1069,6 +1046,14 @@ void QWaylandInputDevice::Pointer::pointer_axis_relative_direction(uint32_t axis + } + } + ++inline void QWaylandInputDevice::Pointer::maybePointerFrame() ++{ ++ if (version() < WL_POINTER_FRAME_SINCE_VERSION) { ++ qCDebug(lcQpaWaylandInput) << "Flushing new event; no frame event in this version"; ++ pointer_frame(); ++ } ++} ++ + void QWaylandInputDevice::Pointer::setFrameEvent(QWaylandPointerEvent *event) + { + qCDebug(lcQpaWaylandInput) << "Setting frame event " << event->type; +@@ -1077,13 +1062,9 @@ void QWaylandInputDevice::Pointer::setFrameEvent(QWaylandPointerEvent *event) + flushFrameEvent(); + } + +- delete mFrameData.event; +- mFrameData.event = event; ++ mFrameData.event.reset(event); + +- if (version() < WL_POINTER_FRAME_SINCE_VERSION) { +- qCDebug(lcQpaWaylandInput) << "Flushing new event; no frame event in this version"; +- flushFrameEvent(); +- } ++ maybePointerFrame(); + } + + void QWaylandInputDevice::Pointer::FrameData::resetScrollData() +@@ -1163,11 +1144,24 @@ void QWaylandInputDevice::Pointer::flushScrollEvent() + { + QPoint angleDelta = mFrameData.angleDelta(); + ++ // The wayland protocol has separate horizontal and vertical axes, Qt has just the one inverted flag ++ // Pragmatically it should't come up ++ const bool inverted = mFrameData.verticalAxisInverted || mFrameData.horizontalAxisInverted; ++ + // Angle delta is required for Qt wheel events, so don't try to send events if it's zero + if (!angleDelta.isNull()) { +- QWaylandWindow *target = QWaylandWindow::mouseGrab(); +- if (!target) +- target = focusWindow(); ++ QWaylandWindow *target = mScrollTarget; ++ if (!mScrollBeginSent) { ++ if (!target) ++ target = QWaylandWindow::mouseGrab(); ++ if (!target) ++ target = focusWindow(); ++ } ++ if (!target) { ++ qCDebug(lcQpaWaylandInput) << "Flushing scroll event aborted - no scroll target"; ++ mFrameData.resetScrollData(); ++ return; ++ } + + if (isDefinitelyTerminated(mFrameData.axisSource) && !mScrollBeginSent) { + qCDebug(lcQpaWaylandInput) << "Flushing scroll event sending ScrollBegin"; +@@ -1177,21 +1171,38 @@ void QWaylandInputDevice::Pointer::flushScrollEvent() + mParent->modifiers(), false)); + mScrollBeginSent = true; + mScrollDeltaRemainder = QPointF(); ++ mScrollTarget = target; + } + + Qt::ScrollPhase phase = mScrollBeginSent ? Qt::ScrollUpdate : Qt::NoScrollPhase; + QPoint pixelDelta = mFrameData.pixelDeltaAndError(&mScrollDeltaRemainder); +- Qt::MouseEventSource source = mFrameData.wheelEventSource(); +- +- +- // The wayland protocol has separate horizontal and vertical axes, Qt has just the one inverted flag +- // Pragmatically it should't come up +- const bool inverted = mFrameData.verticalAxisInverted || mFrameData.horizontalAxisInverted; + + qCDebug(lcQpaWaylandInput) << "Flushing scroll event" << phase << pixelDelta << angleDelta; + target->handleMouse(mParent, WheelEvent(focusWindow(), phase, mParent->mTime, mSurfacePos, mGlobalPos, +- pixelDelta, angleDelta, source, mParent->modifiers(), inverted)); ++ pixelDelta, angleDelta, mFrameData.wheelEventSource(), mParent->modifiers(), inverted)); ++ } ++ ++ if (mScrollEnd) { ++ if (mScrollBeginSent) { ++ if (auto target = mScrollTarget.get()) { ++ qCDebug(lcQpaWaylandInput) << "Flushing scroll end event"; ++ target->handleMouse(mParent, WheelEvent(focusWindow(), Qt::ScrollEnd, mParent->mTime, mSurfacePos, mGlobalPos, ++ QPoint(), QPoint(), mFrameData.wheelEventSource(), mParent->modifiers(), inverted)); ++ } ++ mScrollBeginSent = false; ++ mScrollDeltaRemainder = QPointF(); ++ } else { ++ // May receive axis_stop for events we haven't sent a ScrollBegin for because ++ // most axis_sources do not mandate an axis_stop event to be sent. ++ ++ // TODO: For now, we just ignore these events, but we could perhaps take this as an ++ // indication that this compositor will in fact send axis_stop events for these sources ++ // and send a ScrollBegin the next time an axis_source event with this type is encountered. ++ } ++ mScrollEnd = false; ++ mScrollTarget.clear(); + } ++ + mFrameData.resetScrollData(); + } + +@@ -1199,7 +1210,7 @@ void QWaylandInputDevice::Pointer::flushFrameEvent() + { + mEventCompression.delayTimer.stop(); + +- if (auto *event = mFrameData.event) { ++ if (auto *event = mFrameData.event.get()) { + if (auto window = event->surface) { + window->handleMouse(mParent, *event); + } else if (mFrameData.event->type == QEvent::MouseButtonRelease) { +@@ -1212,8 +1223,7 @@ void QWaylandInputDevice::Pointer::flushFrameEvent() + event->modifiers); // , Qt::MouseEventSource source = + // Qt::MouseEventNotSynthesized); + } +- delete mFrameData.event; +- mFrameData.event = nullptr; ++ mFrameData.event.reset(); + } + + //TODO: do modifiers get passed correctly here? +diff --git a/src/plugins/platforms/wayland/qwaylandinputdevice_p.h b/src/plugins/platforms/wayland/qwaylandinputdevice_p.h +index 533a10991e5e..aa2ccddbcdb8 100644 +--- a/src/plugins/platforms/wayland/qwaylandinputdevice_p.h ++++ b/src/plugins/platforms/wayland/qwaylandinputdevice_p.h +@@ -370,7 +370,7 @@ private Q_SLOTS: + Qt::MouseButton mLastButton = Qt::NoButton; + + struct FrameData { +- QWaylandPointerEvent *event = nullptr; ++ QScopedPointer event; + + QPointF delta; + QPoint delta120; +@@ -387,10 +387,14 @@ private Q_SLOTS: + } mFrameData; + + bool mScrollBeginSent = false; ++ bool mScrollEnd = false; + QPointF mScrollDeltaRemainder; ++ QPointer mScrollTarget; + + QWaylandEventCompressionPrivate mEventCompression; + ++ void maybePointerFrame(); ++ + void setFrameEvent(QWaylandPointerEvent *event); + void flushScrollEvent(); + void flushFrameEvent(); diff --git a/qtbase-wayland-optimize-scroll-operations.patch b/qtbase-wayland-optimize-scroll-operations.patch new file mode 100644 index 0000000..9e7b52a --- /dev/null +++ b/qtbase-wayland-optimize-scroll-operations.patch @@ -0,0 +1,242 @@ +From 6f25f703fd37a900c139e14a33a4639502bfeae7 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?B=C5=82a=C5=BCej=20Szczygie=C5=82?= +Date: Wed, 13 Aug 2025 00:48:08 +0200 +Subject: [PATCH] wayland: Optimize scroll operation +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit + +Don't copy the dirty region in "recreateBackBufferIfNeeded()" in +"beginPaint()". Mark this region as non-dirty in the back buffer +and paint. Finalize the back buffer (copy remaining dirty region) +before flushing. This allows to optimize scrolling, we no longer +have to do a redundant dirty area copy. + +This matches the "QCALayerBackingStore" logic. + +Task-number: QTBUG-139231 +Change-Id: I6c19491fa8f093de9c9ce7624d8f9b65b0b722c5 +Reviewed-by: Tor Arne Vestbø +Reviewed-by: David Edmundson +--- + .../wayland/qwaylandshmbackingstore.cpp | 111 ++++++++++++------ + .../wayland/qwaylandshmbackingstore_p.h | 3 +- + 2 files changed, 74 insertions(+), 40 deletions(-) + +diff --git a/src/plugins/platforms/wayland/qwaylandshmbackingstore.cpp b/src/plugins/platforms/wayland/qwaylandshmbackingstore.cpp +index d8fc7b18ca34..591e5064ebd6 100644 +--- a/src/plugins/platforms/wayland/qwaylandshmbackingstore.cpp ++++ b/src/plugins/platforms/wayland/qwaylandshmbackingstore.cpp +@@ -159,6 +159,8 @@ QWaylandShmBackingStore::QWaylandShmBackingStore(QWindow *window, QWaylandDispla + // recreateBackBufferIfNeeded always resets mBackBuffer + if (mRequestedSize.isValid() && waylandWindow()) + recreateBackBufferIfNeeded(); ++ else ++ mBackBuffer = nullptr; + qDeleteAll(copy); + wl_event_queue_destroy(oldEventQueue); + }); +@@ -183,11 +185,15 @@ QPaintDevice *QWaylandShmBackingStore::paintDevice() + + void QWaylandShmBackingStore::updateDirtyStates(const QRegion ®ion) + { +- // Update dirty state of buffers based on what was painted. The back buffer will +- // not be dirty since we already painted on it, while other buffers will become dirty. ++ // Update dirty state of buffers based on what was painted. The back buffer will be ++ // less dirty, since we painted to it, while other buffers will become more dirty. ++ // This allows us to minimize copies between front and back buffers on swap in the ++ // cases where the painted region overlaps with the previous frame (front buffer). + for (QWaylandShmBuffer *b : std::as_const(mBuffers)) { + if (b != mBackBuffer) + b->dirtyRegion() += region; ++ else ++ b->dirtyRegion() -= region; + } + } + +@@ -198,7 +204,7 @@ void QWaylandShmBackingStore::beginPaint(const QRegion ®ion) + + const QMargins margins = windowDecorationMargins(); + const QRegion regionTranslated = region.translated(margins.left(), margins.top()); +- const bool bufferWasRecreated = recreateBackBufferIfNeeded(regionTranslated); ++ const bool bufferWasRecreated = recreateBackBufferIfNeeded(); + updateDirtyStates(regionTranslated); + + // Although undocumented, QBackingStore::beginPaint expects the painted region +@@ -223,7 +229,7 @@ void QWaylandShmBackingStore::endPaint() + // Inspired by QCALayerBackingStore. + bool QWaylandShmBackingStore::scroll(const QRegion ®ion, int dx, int dy) + { +- if (!mBackBuffer) ++ if (Q_UNLIKELY(!mBackBuffer || !mFrontBuffer)) + return false; + + const qreal devicePixelRatio = waylandWindow()->scale(); +@@ -236,19 +242,35 @@ bool QWaylandShmBackingStore::scroll(const QRegion ®ion, int dx, int dy) + + recreateBackBufferIfNeeded(); + +- QImage *backBufferImage = mBackBuffer->image(); +- + const QPoint scrollDelta(dx, dy); + const QMargins margins = windowDecorationMargins(); + const QRegion adjustedRegion = region.translated(margins.left(), margins.top()); + +- const QRect boundingRect = adjustedRegion.boundingRect(); +- const QPoint devicePixelDelta = scrollDelta * devicePixelRatio; ++ const QRegion inPlaceRegion = adjustedRegion - mBackBuffer->dirtyRegion(); ++ const QRegion frontBufferRegion = adjustedRegion - inPlaceRegion; ++ ++ if (!inPlaceRegion.isEmpty()) { ++ const QRect inPlaceBoundingRect = inPlaceRegion.boundingRect(); ++ const QPoint devicePixelDelta = scrollDelta * devicePixelRatio; ++ ++ qt_scrollRectInImage(*mBackBuffer->image(), ++ QRect(inPlaceBoundingRect.topLeft() * devicePixelRatio, ++ inPlaceBoundingRect.size() * devicePixelRatio), ++ devicePixelDelta); ++ } + +- qt_scrollRectInImage(*backBufferImage, +- QRect(boundingRect.topLeft() * devicePixelRatio, +- boundingRect.size() * devicePixelRatio), +- devicePixelDelta); ++ if (!frontBufferRegion.isEmpty()) { ++ QPainter painter(mBackBuffer->image()); ++ painter.setCompositionMode(QPainter::CompositionMode_Source); ++ painter.scale(qreal(1) / devicePixelRatio, qreal(1) / devicePixelRatio); ++ for (const QRect &rect : frontBufferRegion) { ++ QRect sourceRect(rect.topLeft() * devicePixelRatio, ++ rect.size() * devicePixelRatio); ++ QRect destinationRect((rect.topLeft() + scrollDelta) * devicePixelRatio, ++ rect.size() * devicePixelRatio); ++ painter.drawImage(destinationRect, *mFrontBuffer->image(), sourceRect); ++ } ++ } + + // We do not mark the source region as dirty, even though it technically has "moved". + // This matches the behavior of other backingstore implementations using qt_scrollRectInImage. +@@ -289,6 +311,8 @@ void QWaylandShmBackingStore::flush(QWindow *window, const QRegion ®ion, cons + if (windowDecoration() && windowDecoration()->isDirty()) + updateDecorations(); + ++ finalizeBackBuffer(); ++ + mFrontBuffer = mBackBuffer; + + QMargins margins = windowDecorationMargins(); +@@ -313,6 +337,8 @@ QWaylandShmBuffer *QWaylandShmBackingStore::getBuffer(const QSize &size, bool &b + mBuffers.removeAt(i); + if (mBackBuffer == buffer) + mBackBuffer = nullptr; ++ if (mFrontBuffer == buffer) ++ mFrontBuffer = nullptr; + delete buffer; + } + } +@@ -341,7 +367,7 @@ QWaylandShmBuffer *QWaylandShmBackingStore::getBuffer(const QSize &size, bool &b + return nullptr; + } + +-bool QWaylandShmBackingStore::recreateBackBufferIfNeeded(const QRegion &nonDirtyRegion) ++bool QWaylandShmBackingStore::recreateBackBufferIfNeeded() + { + wl_display_dispatch_queue_pending(mDisplay->wl_display(), mEventQueue); + +@@ -375,30 +401,6 @@ bool QWaylandShmBackingStore::recreateBackBufferIfNeeded(const QRegion &nonDirty + qsizetype oldSizeInBytes = mBackBuffer ? mBackBuffer->image()->sizeInBytes() : 0; + qsizetype newSizeInBytes = buffer->image()->sizeInBytes(); + +- // mBackBuffer may have been deleted here but if so it means its size was different so we wouldn't copy it anyway +- if (mBackBuffer != buffer && oldSizeInBytes == newSizeInBytes) { +- const QRegion clipRegion = buffer->dirtyRegion() - nonDirtyRegion; +- const auto clipRects = clipRegion.rects(); +- if (!clipRects.empty()) { +- Q_ASSERT(mBackBuffer); +- const QImage *sourceImage = mBackBuffer->image(); +- QImage *targetImage = buffer->image(); +- +- QPainter painter(targetImage); +- painter.setCompositionMode(QPainter::CompositionMode_Source); +- const qreal targetDevicePixelRatio = painter.device()->devicePixelRatio(); +- for (const QRect &clipRect : clipRects) { // Iterate clip rects, because complicated clip region causes higher CPU usage +- if (clipRects.size() > 1) +- painter.save(); +- painter.setClipRect(clipRect); +- painter.scale(qreal(1) / targetDevicePixelRatio, qreal(1) / targetDevicePixelRatio); +- painter.drawImage(QRectF(QPointF(), targetImage->size()), *sourceImage, sourceImage->rect()); +- if (clipRects.size() > 1) +- painter.restore(); +- } +- } +- } +- + mBackBuffer = buffer; + + for (QWaylandShmBuffer *buffer : std::as_const(mBuffers)) { +@@ -412,11 +414,40 @@ bool QWaylandShmBackingStore::recreateBackBufferIfNeeded(const QRegion &nonDirty + if (windowDecoration() && window()->isVisible() && oldSizeInBytes != newSizeInBytes) + windowDecoration()->update(); + +- buffer->dirtyRegion() = QRegion(); +- + return bufferWasRecreated; + } + ++void QWaylandShmBackingStore::finalizeBackBuffer() ++{ ++ Q_ASSERT(mBackBuffer); ++ ++ const QRegion clipRegion = mBackBuffer->dirtyRegion(); ++ if (clipRegion.isEmpty()) ++ return; ++ ++ if (Q_UNLIKELY(!mFrontBuffer || mFrontBuffer == mBackBuffer)) ++ return; ++ ++ const QImage *sourceImage = mFrontBuffer->image(); ++ QImage *targetImage = mBackBuffer->image(); ++ ++ QPainter painter(targetImage); ++ painter.setCompositionMode(QPainter::CompositionMode_Source); ++ const qreal targetDevicePixelRatio = painter.device()->devicePixelRatio(); ++ const auto clipRects = clipRegion.rects(); ++ for (const QRect &clipRect : clipRects) { // Iterate clip rects, because complicated clip region causes higher CPU usage ++ if (clipRects.size() > 1) ++ painter.save(); ++ painter.setClipRect(clipRect); ++ painter.scale(qreal(1) / targetDevicePixelRatio, qreal(1) / targetDevicePixelRatio); ++ painter.drawImage(QRectF(QPointF(), targetImage->size()), *sourceImage, sourceImage->rect()); ++ if (clipRects.size() > 1) ++ painter.restore(); ++ } ++ ++ mBackBuffer->dirtyRegion() = QRegion(); ++} ++ + QImage *QWaylandShmBackingStore::entireSurface() const + { + return mBackBuffer->image(); +@@ -496,6 +527,8 @@ QImage QWaylandShmBackingStore::toImage() const + // instead of flush() for widgets that have renderToTexture children + // (QOpenGLWidget, QQuickWidget). + ++ const_cast(this)->finalizeBackBuffer(); ++ + return *contentSurface(); + } + #endif // opengl +diff --git a/src/plugins/platforms/wayland/qwaylandshmbackingstore_p.h b/src/plugins/platforms/wayland/qwaylandshmbackingstore_p.h +index efd80159e859..cfcafb283265 100644 +--- a/src/plugins/platforms/wayland/qwaylandshmbackingstore_p.h ++++ b/src/plugins/platforms/wayland/qwaylandshmbackingstore_p.h +@@ -73,7 +73,8 @@ class Q_WAYLANDCLIENT_EXPORT QWaylandShmBackingStore : public QPlatformBackingSt + QMargins windowDecorationMargins() const; + QImage *entireSurface() const; + QImage *contentSurface() const; +- bool recreateBackBufferIfNeeded(const QRegion &nonDirtyRegion = QRegion()); ++ bool recreateBackBufferIfNeeded(); ++ void finalizeBackBuffer(); + + QWaylandWindow *waylandWindow() const; + void iterateBuffer(); From 082bad2484993f53851b22458853fb7d6aff554e Mon Sep 17 00:00:00 2001 From: Jan Grulich Date: Mon, 22 Dec 2025 16:15:39 +0100 Subject: [PATCH 12/12] Fix crash in QWaylandShmBackingStore::scroll() --- qt6-qtbase.spec | 7 +++- ...sh-in-qwaylandshmbackingstore-scroll.patch | 39 +++++++++++++++++++ 2 files changed, 44 insertions(+), 2 deletions(-) create mode 100644 qtbase-wayland-fix-crash-in-qwaylandshmbackingstore-scroll.patch diff --git a/qt6-qtbase.spec b/qt6-qtbase.spec index 70926ab..382a945 100644 --- a/qt6-qtbase.spec +++ b/qt6-qtbase.spec @@ -48,7 +48,7 @@ BuildRequires: pkgconfig(libsystemd) Name: qt6-qtbase Summary: Qt6 - QtBase components Version: 6.10.1 -Release: 2%{?dist} +Release: 3%{?dist} License: LGPL-3.0-only OR GPL-3.0-only WITH Qt-GPL-exception-1.0 Url: http://qt-project.org/ @@ -102,7 +102,7 @@ Patch100: qtbase-wayland-convey-preference-for-server-side-decorations.patch Patch101: qtbase-wayland-compress-high-frequency-mouse-events.patch Patch102: qtbase-wayland-optimize-scroll-operations.patch Patch103: qtbase-wayland-enable-event-compression-and-fix-scroll-end-event.patch - +Patch104: qtbase-wayland-fix-crash-in-qwaylandshmbackingstore-scroll.patch # Do not check any files in %%{_qt6_plugindir}/platformthemes/ for requires. # Those themes are there for platform integration. If the required libraries are @@ -967,6 +967,9 @@ make check -k ||: %{_qt6_datadir}/wayland/protocols/ %changelog +* Mon Dec 22 2025 Jan Grulich - 6.10.1-3 +- Fix crash in QWaylandShmBackingStore::scroll() + * Mon Dec 08 2025 Jan Grulich - 6.10.1-2 - Re-add wayland fixes for mouse scrolling diff --git a/qtbase-wayland-fix-crash-in-qwaylandshmbackingstore-scroll.patch b/qtbase-wayland-fix-crash-in-qwaylandshmbackingstore-scroll.patch new file mode 100644 index 0000000..7881aba --- /dev/null +++ b/qtbase-wayland-fix-crash-in-qwaylandshmbackingstore-scroll.patch @@ -0,0 +1,39 @@ +From c93008e4d06abb0072e0e5e57d84a4ae182ecfc0 Mon Sep 17 00:00:00 2001 +From: Błażej Szczygieł +Date: Tue, 21 Oct 2025 19:49:17 +0200 +Subject: [PATCH] wayland: Fix crash in QWaylandShmBackingStore::scroll() + +Fixes a crash when monitor is unplugged while scrolling. + +recreateBackBufferIfNeeded() calls getBuffer() which may set +mFrontBuffer to nullptr. + +Amends: 6f25f703fd37a900c139e14a33a4639502bfeae7 + +Task-number: QTBUG-139231 +Change-Id: Ia5bedce2a3f6580c722f73446de81a26d40ea2f4 +Reviewed-by: David Edmundson +--- + +diff --git a/src/plugins/platforms/wayland/qwaylandshmbackingstore.cpp b/src/plugins/platforms/wayland/qwaylandshmbackingstore.cpp +index b853db2..fa70b53 100644 +--- a/src/plugins/platforms/wayland/qwaylandshmbackingstore.cpp ++++ b/src/plugins/platforms/wayland/qwaylandshmbackingstore.cpp +@@ -229,7 +229,7 @@ + // Inspired by QCALayerBackingStore. + bool QWaylandShmBackingStore::scroll(const QRegion ®ion, int dx, int dy) + { +- if (Q_UNLIKELY(!mBackBuffer || !mFrontBuffer)) ++ if (Q_UNLIKELY(!mBackBuffer)) + return false; + + const qreal devicePixelRatio = waylandWindow()->scale(); +@@ -241,6 +241,8 @@ + return false; + + recreateBackBufferIfNeeded(); ++ if (!mFrontBuffer) ++ return false; + + const QPoint scrollDelta(dx, dy); + const QMargins margins = windowDecorationMargins();