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
+

Reply via email to