https://bugs.kde.org/show_bug.cgi?id=515889

Leart Turely <[email protected]> changed:

           What    |Removed                     |Added
----------------------------------------------------------------------------
                 CC|                            |[email protected]

--- Comment #21 from Leart Turely <[email protected]> ---
`AT_SPI_BUS_ADDRESS=1 krita` can show a working global menu.

Root cause found. Analysis and the minimal reproducer by DeepSeek V4.1 Flash,
with the reporter.

Full analysis: https://github.com/worksoup/appmenu_test/blob/main/BUGREPORT.md
Reproducer (Qt 5 + Qt 6, CMake, windowed):
https://github.com/worksoup/appmenu_test

What happens
------------
Before Krita's real application object exists, KisOpenGLModeProber creates a
throw-away QGuiApplication
(plus a QWindow and a QOpenGLContext) to probe the OpenGL renderers, and
destroys them again. On Qt 6,
destroying that probe QWindow calls QAccessible::isActive() - Qt 5.15's QWindow
destructor has no such
call - and that makes the xcb/wayland QPA plugin instantiate its accessibility
implementation (the
at-spi bridge), which creates the process-wide session-bus connection while the
throw-away application
is still the current qApp.

Why that breaks the real application
------------------------------------
Qt's D-Bus connection is a process-level resource. When it is created on the
main thread while a qApp
already exists, delivery starts suspended (dispatchEnabled == false) and a
QMetaObject::invokeMethod call
is queued on that qApp to set it to true once its event loop runs. The
throw-away application is destroyed
before any event loop runs, so that queued call disappears with it; the cached
connection (global
QDBusConnectionManager, defaultBuses[]) is handed to the real application
without ever being re-armed.
Every incoming D-Bus method call is therefore queued and never answered, and
the Plasma applet can only
time out.

Note that the code that builds the throw-away application already excludes most
D-Bus producers
(QT_NO_XDG_DESKTOP_PORTAL=1, setDesktopSettingsAware(false)); the Qt 6
destructor-time accessibility query
is the one that no existing guard covers. The menu is exported correctly
(/MenuBar/N is registered and
Plasma writes _KDE_NET_WM_APPMENU_*), and only the connection-level Peer.Ping
still answers - which makes
the failure easy to misdiagnose.

Measurements (Qt 6.11.2; Introspect/AboutToShow from another process; 124 =
timeout)
-----------------------------------------------------------------------------------
  - throw-away QGuiApplication + QWindow: 124 / 124 (bug reproduced)
  - same, but the probe application does not create a QWindow: 0 / 0
  - same + AT_SPI_BUS_ADDRESS set while the probe is alive: 0 / 0 (fixed)
  - same + blocking DBUS_SESSION_BUS_ADDRESS while the probe is alive: no
usable session bus at all
  - Qt 5.15.19 with the same code: 0 / 0

Workaround
----------
Set AT_SPI_BUS_ADDRESS to any non-empty value while the throw-away probe
application is alive (Krita's
EnvironmentSetter RAII fits), and optionally call
QDBusConnection::disconnectFromBus("a11y") before the
real application is created. The at-spi bridge then takes its early-return path
and never calls
QDBusConnection::sessionBus(), so the process' first connection is created by
the real application and
delivery resumes normally. Measured: AT_SPI_BUS_ADDRESS=1 krita shows a working
global menu. Keep it
scoped to the probe - set process-wide it also hides the symptom but silently
disables accessibility for
the whole process.

Why this is reported to KDE rather than to Qt
---------------------------------------------
The code that constructs the throw-away application already excludes most
D-Bus-producing functionality,
so Krita appears to have taken Qt's D-Bus connection-manager behaviour into
account; hence this report
goes to KDE first (the Krita-side mitigation above is enough to remove the
symptom). Mechanically, however,
this looks like a Qt defect: the resume step is bound to a qApp that is later
destroyed
(enableDispatchDelayed(qApp)), and the cached connection (defaultBuses[]) is
handed out without being
re-armed - and QAccessible::isActive(), which is meant to be a cheap guard,
ends up instantiating the
at-spi bridge and creating a D-Bus connection.

I have not checked whether Qt already has a report for this. If someone
familiar with Qt can confirm that
this is a Qt bug, help forwarding it to Qt (bugreports.qt.io, project QTBUG,
component DBus) would be
appreciated.

-- 
You are receiving this mail because:
You are watching all bug changes.

Reply via email to