From 0b2de17b126c9a68dfd160037d9a5e125a939f37 Mon Sep 17 00:00:00 2001 From: Byeonghoon Yoo Date: Tue, 21 Apr 2026 13:56:32 +0900 Subject: [PATCH] fix: use D-Bus auto-activation for AT-SPI bus in isolated sessions MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The hardcoded `/usr/lib/at-spi-bus-launcher` path is only correct on Arch. Upstream at-spi2-core has migrated to `/usr/libexec/`, so on Fedora, Debian sid, Ubuntu 24.04+, and Flatpak runtimes the AT-SPI bus never starts and every AT-SPI tool (accessibility_tree, find_ui_elements, wait_for_element, list_windows, focus_window) fails with "Couldn't connect to accessibility bus". Replace the manual launcher invocation with a D-Bus auto-activation trigger (`gdbus call org.a11y.Bus.GetAddress`). dbus-daemon resolves the service file and execs the launcher at whatever path the current distro's package installed it to, so kwin-mcp no longer encodes any binary path. at-spi2-registryd is auto-activated on first access by the same mechanism, so no manual bootstrap is needed. Also drop AT_SPI_PID from the cleanup trap — auto-activated processes are torn down when dbus-run-session closes the session bus. Co-Authored-By: Claude Opus 4.7 (1M context) --- CHANGELOG.md | 1 + src/kwin_mcp/session.py | 31 ++++++++++++++++++++++--------- 2 files changed, 23 insertions(+), 9 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 66eaada..d395d90 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Fixed - Segfault on Python 3.14 caused by missing `argtypes` on variadic `ei_seat_bind_capabilities` ctypes call +- `accessibility_tree`, `find_ui_elements`, `wait_for_element`, `list_windows`, and `focus_window` now work in isolated virtual sessions on Fedora, Debian, Ubuntu, and any other distro that installs the AT-SPI bus launcher outside `/usr/lib` (e.g. `/usr/libexec`). Instead of executing a hardcoded path, the AT-SPI bus is now brought up via D-Bus auto-activation (`gdbus call org.a11y.Bus.GetAddress`), which defers binary-path resolution to `dbus-daemon` and the distro-provided service file. The registry daemon is auto-activated transparently when apps first touch the a11y bus, so no manual bootstrap is required. Thanks to @davidselassie for reporting the Fedora failure in [#8](https://github.com/isac322/kwin-mcp/pull/8). ## [0.7.0] - 2026-03-29 diff --git a/src/kwin_mcp/session.py b/src/kwin_mcp/session.py index 8d0582b..84b0db3 100644 --- a/src/kwin_mcp/session.py +++ b/src/kwin_mcp/session.py @@ -333,19 +333,32 @@ def _build_wrapper_script(self, config: SessionConfig) -> str: return f"""\ echo "DBUS_SESSION_BUS_ADDRESS=$DBUS_SESSION_BUS_ADDRESS" -# Ensure all child processes are cleaned up on exit +# Ensure all child processes are cleaned up on exit. +# The AT-SPI bus launcher and registryd are started via D-Bus +# auto-activation below; they are terminated automatically when our +# isolated session bus exits (dbus-run-session tears the bus down on +# parent exit), so we only need to track KWin explicitly here. cleanup() {{ - kill $KWIN_PID $AT_SPI_PID 2>/dev/null - wait $KWIN_PID $AT_SPI_PID 2>/dev/null + kill $KWIN_PID 2>/dev/null + wait $KWIN_PID 2>/dev/null }} trap cleanup EXIT TERM INT HUP -# Start the AT-SPI accessibility bus. -# ATSPI_DBUS_IMPLEMENTATION is set in _build_env() to force dbus-daemon -# instead of dbus-broker (which reuses the host's AT-SPI bus). -/usr/lib/at-spi-bus-launcher --launch-immediately & -AT_SPI_PID=$! -sleep 0.2 +# Bring up the AT-SPI accessibility bus via D-Bus auto-activation. +# Calling org.a11y.Bus.GetAddress makes dbus-daemon resolve the +# service file (/usr/share/dbus-1/services/org.a11y.Bus.service) and +# exec the launcher at whatever path the current distro uses — Arch +# /usr/lib, Fedora/Debian/Ubuntu /usr/libexec, Flatpak /app/libexec, +# etc. Hardcoding a path breaks on everything except Arch. +# ATSPI_DBUS_IMPLEMENTATION=dbus-daemon (set in _build_env) prevents +# dbus-broker from sharing the host's a11y bus. +# The registryd comes up on its own when apps first touch the a11y +# bus, so no manual bootstrap is needed here. +gdbus call --session \\ + --dest=org.a11y.Bus \\ + --object-path=/org/a11y/bus \\ + --method=org.a11y.Bus.GetAddress >/dev/null 2>&1 || true +sleep 0.3 # Pre-set D-Bus activation environment BEFORE starting KWin. # When KWin triggers portal auto-activation, portal-kde will get