如何通过Python/DBus在 KDE Plasma 6(Wayland)中获取活动窗口标题?
我在基于 CachyOS(Arch Linux) 的 KDE Plasma 6(Wayland) 上进行一个语音转文本项目(Python)。我需要获取活动窗口的标题,以便对输入进行上下文感知的处理。
问题:
基于X11的标准解决方案(如 xdotool 或 xprop)在原生Wayland窗口上会失败,并抛出 BadWindow 错误:
XGetWindowProperty\[\_NET_ACTIVE_WINDOW\] failed (code=1)
xdo_get_active_window reported an error
X Error of failed request: BadWindow (invalid Window parameter)
我查阅过的内容:
我查看了StackOverflow上的若干现有解法,但它们要么过时,要么是X11特定:
- 如何在CLI中获取KDE的所有打开窗口列表:建议使用
qdbus和wmctrl。然而,qdbus在我的系统上默认并未安装,且wmctrl不支持原生Wayland窗口。 - 如何在Linux的终端中显示GUI应用程序的窗口标题:主要讨论
xdotool和xtitle,它们都依赖X11,在我的Wayland会话中会失败。 - 在KDE中用Python检测活动窗口:这个已有13年历史的回答建议使用
PyKDE4(已弃用)或qdbus。Plasma 6已将许多接口移动,这些旧方法不再工作。
我的尝试:
因为 qdbus 缺失,我尝试使用 dbus-send:
dbus-send --session --print-reply --dest=org.kde.KWin /KWin org.kde.KWin.activeWindow
结果: Error org.freedesktop.DBus.Error.UnknownMethod: No such method 'activeWindow' in interface 'org.kde.KWin' at object path 'KWin'
环境:
- 操作系统:CachyOS(基于Arch)
- 桌面环境:KDE Plasma 6.3.5
- Python 3.13.3
问题:
在Plasma 6/Wayland下,如何可靠地获取活动窗口标题(例如,使用 dbus-send 或某个Python库)?
解决方案
使用无障碍总线(AT-SPI)来绕过Wayland的安全限制
在Plasma 6(Wayland)上,对KWin的常规D-Bus查询由于增强的安全隔离,常返回空数据或受限数据。获取活动窗口标题最可靠的方法是通过无障碍技术服务提供者接口(AT-SPI)。
1.系统要求与设置
首先,确保无障碍总线处于活动状态。在像CachyOS这样的基于Arch的系统上,安装必要的组件:
sudo pacman -S orca at-spi2-core
(注:安装 orca 是确保所有KDE/Qt无障碍桥接和菜单选项解锁的最简单方法,即使你不使用语音输出。)
2.在 Plasma 6中启用无障碍功能
你必须让系统启动无障碍总线,并让Qt应用导出它们的元数据。
通过终端:
在KDE配置中启用ScreenReader桥
kwriteconfig6 --file kaccessrc --group ScreenReader --key Enabled true
将环境变量添加到 ~/.bashrc或 ~/.config/fish/config.fish中:
export QT_ACCESSIBILITY=1
通过图形界面: 前往 系统设置 > 无障碍 > 屏幕阅读器,勾选 启用屏幕阅读器。
重要: 要使这些更改生效,必须登出并重新登录(或重启)。
3.静音(可选)
如果你不想让系统把一切都读成声音,可以运行Orca设置一次以禁用语音,但保持数据总线活动:
orca -s
前往 “Speech” 选项卡并取消勾选 “Enable speech”
4. Python实现
此脚本使用 GObject Introspection 监听 Atspi 总线。它遍历桌面上的子对象,找到具有 ACTIVE 或 FOCUSED 状态的对象。
依赖项: sudo pacman -S python-gobject
try:
gi.require_version('Atspi', '2.0')
from gi.repository import Atspi
except Exception as e:
print(f"Error: GObject-Introspection not found ({e})")
exit(1)
def get_active_window_title_atspi():
if Atspi.init() != 0:
return "Atspi was not initialisiest"
desktop = Atspi.get_desktop(0)
for i in range(desktop.get_child_count()):
app = desktop.get_child_at_index(i)
if not app:
continue
for j in range(app.get_child_count()):
window = app.get_child_at_index(j)
if not window:
continue
state_set = window.get_state_set()
states = state_set.get_states()
if Atspi.StateType.ACTIVE in states or Atspi.StateType.FOCUSED in states:
return f"App: {app.get_name()} | Titel: {window.get_name()}"
return "no active window found by AT-SPI"
if __name__ == "__main__":
print(get_active_window_title_atspi))
之所以有效:
尽管Wayland将应用彼此隔离,但它必须向无障碍总线广播窗口元数据,以便屏幕阅读器能够工作。作为一个“无障碍客户端”,你的Python脚本即使在KWin的标准D-Bus接口被锁定时,也能“看到”活动窗口的标题。