Bug#1107340: unblock: kf6-kcmutils/6.13.0-2
Package: release.debian.org
Severity: normal
X-Debbugs-Cc: kf6-kcmutils@packages.debian.org, Debian Qt/KDE Maintainers <debian-qt-kde@lists.debian.org>
Control: affects -1 + src:kf6-kcmutils
User: release.debian.org@packages.debian.org
Usertags: unblock
Dear Release Team,
please unblock package kf6-kcmutils.
[ Reason ]
It contains the following changes:
* Backport upstream commit:
- Improved accessibility by screen readers and keyboard navigation
throughout System Settings, particularly around the topics of passing
focus between page content and the window’s sidebars and footers.
[ Tests ]
Tested that systemsettings pages display correctly and keyboard
navigation works.
Upstream testsuite passes.
[ Risks ]
Only backport of upstream commits that apply cleanly. Further fixes can
easily be backported or the changes reverted.
[ Checklist ]
[x] all changes are documented in the d/changelog
[x] I reviewed all changes and I approve them
[x] attach debdiff against the package in testing
Thanks!
unblock kf6-kcmutils/6.13.0-2
diff -Nru kf6-kcmutils-6.13.0/debian/changelog kf6-kcmutils-6.13.0/debian/changelog
--- kf6-kcmutils-6.13.0/debian/changelog 2025-04-12 19:37:44.000000000 +0200
+++ kf6-kcmutils-6.13.0/debian/changelog 2025-06-02 23:31:03.000000000 +0200
@@ -1,3 +1,13 @@
+kf6-kcmutils (6.13.0-2) unstable; urgency=medium
+
+ [ Aurélien COUDERC ]
+ * Backport upstream commit:
+ - Improved accessibility by screen readers and keyboard navigation
+ throughout System Settings, particularly around the topics of passing
+ focus between page content and the window’s sidebars and footers.
+
+ -- Aurélien COUDERC <coucouf@debian.org> Mon, 02 Jun 2025 23:31:03 +0200
+
kf6-kcmutils (6.13.0-1) unstable; urgency=medium
[ Patrick Franz ]
diff -Nru kf6-kcmutils-6.13.0/debian/patches/series kf6-kcmutils-6.13.0/debian/patches/series
--- kf6-kcmutils-6.13.0/debian/patches/series 1970-01-01 01:00:00.000000000 +0100
+++ kf6-kcmutils-6.13.0/debian/patches/series 2025-06-02 23:29:50.000000000 +0200
@@ -0,0 +1 @@
+upstream_6dc76c3d_kcmoduleqml-simplify-and-improve-focus-handling.patch
diff -Nru kf6-kcmutils-6.13.0/debian/patches/upstream_6dc76c3d_kcmoduleqml-simplify-and-improve-focus-handling.patch kf6-kcmutils-6.13.0/debian/patches/upstream_6dc76c3d_kcmoduleqml-simplify-and-improve-focus-handling.patch
--- kf6-kcmutils-6.13.0/debian/patches/upstream_6dc76c3d_kcmoduleqml-simplify-and-improve-focus-handling.patch 1970-01-01 01:00:00.000000000 +0100
+++ kf6-kcmutils-6.13.0/debian/patches/upstream_6dc76c3d_kcmoduleqml-simplify-and-improve-focus-handling.patch 2025-06-02 23:29:50.000000000 +0200
@@ -0,0 +1,137 @@
+From 6dc76c3dc14aaaecc926a0af8f092402b23bf22b Mon Sep 17 00:00:00 2001
+From: Christoph Wolk <cwo.kde@posteo.net>
+Date: Sat, 8 Feb 2025 18:21:50 +0100
+Subject: [PATCH] kcmoduleqml: simplify and improve focus handling
+
+kcmoduleqml contains some old tricks to make passing focus between the
+qml and qtwidgets bits, but they are fragile, and break in many cases -
+especially with systemsettings adding another layer of qml. The hacks
+may have been necessary in the past, but things actually work well now,
+so we can essentially just set a focusProxy and be done ... as long as
+we don't care about accessibility. The qml bits receive focus before the
+widgets parts fully hand it over, so the first qml item receiving focus
+will not be announced over screen readers. We do care though, and thus
+some special handling is still needed.
+
+We switch to using focusProxy, and add a bit of surgery to the focus
+transition so that screen readers always stay in the loop about what
+things are focused. We also need a bit of special handling for the
+backtab case, which doesn't quite work automatically with
+activeFocusOnTab set on the qml root.
+---
+ src/kcmoduleqml.cpp | 60 ++++++++++++++++++++++++++++-----------------
+ 1 file changed, 38 insertions(+), 22 deletions(-)
+
+diff --git a/src/kcmoduleqml.cpp b/src/kcmoduleqml.cpp
+index 5666029f..35ac1e54 100644
+--- a/src/kcmoduleqml.cpp
++++ b/src/kcmoduleqml.cpp
+@@ -10,6 +10,7 @@
+ #include <QQuickItem>
+ #include <QQuickWidget>
+ #include <QQuickWindow>
++#include <QTimer>
+ #include <QVBoxLayout>
+
+ #include <KAboutData>
+@@ -20,6 +21,7 @@
+ #include "quick/kquickconfigmodule.h"
+
+ #include <kcmutils_debug.h>
++#include <qquickitem.h>
+
+ class QmlConfigModuleWidget;
+ class KCModuleQmlPrivate
+@@ -64,15 +66,6 @@ public:
+ setFocusPolicy(Qt::StrongFocus);
+ }
+
+- void focusInEvent(QFocusEvent *event) override
+- {
+- if (event->reason() == Qt::TabFocusReason) {
+- m_module->d->rootPlaceHolder->nextItemInFocusChain(true)->forceActiveFocus(Qt::TabFocusReason);
+- } else if (event->reason() == Qt::BacktabFocusReason) {
+- m_module->d->rootPlaceHolder->nextItemInFocusChain(false)->forceActiveFocus(Qt::BacktabFocusReason);
+- }
+- }
+-
+ QSize sizeHint() const override
+ {
+ if (!m_module->d->rootPlaceHolder) {
+@@ -84,21 +77,45 @@ public:
+
+ bool eventFilter(QObject *watched, QEvent *event) override
+ {
+- if (watched == m_module->d->rootPlaceHolder && event->type() == QEvent::FocusIn) {
++ // Everything would work mosty without manual intervention, but as of Qt 6.8
++ // things require special attention so that they work correctly with orca.
++ // The timing between the focusproxied QQuickWidget receiving focus and the
++ // focused qml Item being registered as focused is off and screen readers get
++ // confused. Instead, put initial focus on the root element and switch with a timer
++ // so the qml focuschange happens while the qquickwidget has focus. This
++ // requires activeFocusOnTab on the rootPlaceHolder to work, and that makes other things
++ // a bit messier than they would otherwise need to be.
++ if (event->type() == QEvent::FocusIn && watched == m_module->d->rootPlaceHolder) {
+ auto focusEvent = static_cast<QFocusEvent *>(event);
+ if (focusEvent->reason() == Qt::TabFocusReason) {
+- QWidget *w = m_module->d->quickWidget->nextInFocusChain();
+- while (!w->isEnabled() || !(w->focusPolicy() & Qt::TabFocus)) {
+- w = w->nextInFocusChain();
+- }
+- w->setFocus(Qt::TabFocusReason); // allow tab navigation inside the qquickwidget
++ m_module->d->rootPlaceHolder->forceActiveFocus(Qt::OtherFocusReason);
++ QTimer::singleShot(0, this, [this] {
++ QQuickItem *nextItem = m_module->d->rootPlaceHolder->nextItemInFocusChain(true);
++ if (nextItem) {
++ nextItem->forceActiveFocus(Qt::TabFocusReason);
++ }
++ });
+ return true;
+ } else if (focusEvent->reason() == Qt::BacktabFocusReason) {
+- QWidget *w = m_module->d->quickWidget->previousInFocusChain();
+- while (!w->isEnabled() || !(w->focusPolicy() & Qt::TabFocus)) {
+- w = w->previousInFocusChain();
++ // this can either happen from backtabbing in qml or from backtabbing
++ // from qwidgets past the focusproxy (e.g. from the kcm buttons).
++ if (!m_module->d->rootPlaceHolder->hasActiveFocus()) {
++ // we're in widgets, enter qml from reverse in the focus chain in the same way as above
++ QTimer::singleShot(0, this, [this] {
++ QQuickItem *nextItem = m_module->d->rootPlaceHolder->nextItemInFocusChain(false);
++ if (nextItem) {
++ nextItem->forceActiveFocus(Qt::TabFocusReason);
++ }
++ });
++ return true;
+ }
+- w->setFocus(Qt::BacktabFocusReason);
++ // we're coming from qml, so we focus the widget outside. This also needs singleShot;
++ // if we do it immediately, focus cycles backward along the qml focus chain instead.
++ // Without activeFocusOnTab on the rootPlaceHolder we could just return false and
++ // Qt would handle everything by itself
++ QTimer::singleShot(0, this, [this] {
++ focusNextPrevChild(false);
++ });
+ return true;
+ }
+ }
+@@ -146,15 +163,14 @@ KCModuleQml::KCModuleQml(KQuickConfigModule *configModule, QWidget *parent)
+
+ d->quickWidget = new QQuickWidget(d->configModule->engine().get(), d->widget);
+ d->quickWidget->setResizeMode(QQuickWidget::SizeRootObjectToView);
+- d->quickWidget->setFocusPolicy(Qt::StrongFocus);
+ d->quickWidget->setAttribute(Qt::WA_AlwaysStackOnTop, true);
+ d->quickWidget->setAttribute(Qt::WA_NoMousePropagation, true); // Workaround for QTBUG-109861 to fix drag everywhere
+ d->quickWindow = d->quickWidget->quickWindow();
+ d->quickWindow->setColor(Qt::transparent);
++ d->widget->setFocusProxy(d->quickWidget);
+
+ QQmlComponent *component = new QQmlComponent(d->configModule->engine().get(), this);
+- // this has activeFocusOnTab to notice when the navigation wraps
+- // around, so when we need to go outside and inside
++ // activeFocusOnTab is required to have screen readers not get confused
+ // pushPage/popPage are needed as push of StackView can't be directly invoked from c++
+ // because its parameters are QQmlV4Function which is not public.
+ // The managers of onEnter/ReturnPressed are a workaround of
+--
+GitLab
+
Reply to: