Package: release.debian.org Severity: normal X-Debbugs-Cc: kf6-kcmut...@packages.debian.org, Debian Qt/KDE Maintainers <debian-qt-kde@lists.debian.org> Control: affects -1 + src:kf6-kcmutils User: release.debian....@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 <couc...@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....@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 +