PySide6 DBUS屏幕保护信号监听器失效问题求助
问题:无法通过PySide6监听DBus的ScreenSaver ActiveChanged信号
我正尝试监听屏幕保护触发/关闭时发出的org.freedesktop.ScreenSaver接口下/ScreenSaver路径的ActiveChanged(bool)信号,通过dbus-monitor已确认信号正常触发:
$ dbus-monitor "interface='org.freedesktop.ScreenSaver'" ... signal time=1761396762.341364 sender=:1.21 -> destination=(null destination) serial=94408 path=/ScreenSaver; interface=org.freedesktop.ScreenSaver; member=ActiveChanged boolean true ... signal time=1761396765.026613 sender=:1.21 -> destination=(null destination) serial=94464 path=/ScreenSaver; interface=org.freedesktop.ScreenSaver; member=ActiveChanged boolean false
尝试用PySide6/Qt实现信号监听器的代码如下:
from __future__ import annotations from PySide6 import QtDBus, QtCore from PySide6.QtCore import Slot, QObject from typing import Annotated, get_type_hints class DBusEventDispatcher(QtCore.QObject): def __init__(self): super().__init__() sessionBus = QtDBus.QDBusConnection.sessionBus() if not sessionBus.isConnected(): errorMessage = sessionBus.lastError().message() raise Exception("Cannot connect to DBUS: " + errorMessage) success = sessionBus.registerService("my_app") print(success) success = sessionBus.registerObject( '/', 'org.freedesktop.ScreenSaver', self, QtDBus.QDBusConnection.ExportAllSlots ) print(success) #for path in ['org.freedesktop.ScreenSaver', 'org.gnome.ScreenSaver']: # interface = QtDBus.QDBusInterface(path, '/ScreenSaver', '', sessionBus) # if interface != None: # interface.ActiveChanged.connect(self._onActiveChanged) mo = self.metaObject() for m in range(mo.methodOffset(), mo.methodCount()): print(mo.method(m).methodSignature()) self.iface = QtDBus.QDBusInterface( "org.freedesktop.ScreenSaver", # Service "/ScreenSaver", # Path "org.freedesktop.ScreenSaver", # Interface sessionBus ) print(self.iface.isValid()) self.iface.connect( QtCore.SIGNAL("ActiveChanged(bool)"), self.ActiveChanged ) QtCore.QObject.connect( self.iface, QtCore.SIGNAL("ActiveChanged(bool)"), self.ActiveChanged ) # for service in ['org.freedesktop.ScreenSaver']: # 'org.gnome.ScreenSaver' success = sessionBus.connect( 'org.freedesktop.ScreenSaver', # service, '/ScreenSaver', 'org.freedesktop.ScreenSaver', 'ActiveChanged', self, QtCore.SLOT('ActiveChanged(bool)') ) print(success) @QtCore.Slot(bool) def ActiveChanged(self, active: bool): print("ActiveChanged") print(active)
运行脚本后各调用均返回成功,但锁定屏幕时脚本无任何输出:
$ python3 my_app.py True True b'ActiveChanged(bool)' True True
问题
- 脚本可能存在什么问题?
- 是否是系统问题(如systemd规则限制访问)?
- 是否有调试flag可查看Python/DBUS库内部运行情况?
- 其他排查思路?
环境信息
$ python3 --version Python 3.12.3 $ pip3 list Package Version ------------------ ------- cysystemd 2.0.1 pip 24.0 PySide6_Essentials 6.10.0 shiboken6 6.10.0 systemd 0.17.1 systemd-python 235 $ uname -a Linux gerrit-framework 6.14.0-112033-tuxedo #33~24.04.1tux1 SMP PREEMPT_DYNAMIC Tue Sep 30 19:33:36 UTC 2025 x86_64 x86_64 x86_64 GNU/Linux $ cat /etc/issue TUXEDO OS 24.04.3 LTS \l
- KDE-Plasma版本:6.4.5
- KDE-Frameworks版本:6.17.0
- Qt版本:6.8.2
- 图形平台:Wayland
解答
1. 脚本存在的核心问题
- 错误注册服务与对象:你调用
registerService和registerObject将自身注册为org.freedesktop.ScreenSaver服务的实现,这会干扰系统原有服务的信号路由,直接删掉这两行即可。 - 缺少Qt事件循环:原代码没有启动
QCoreApplication的事件循环,DBus信号无法被处理,这是脚本无响应的关键原因。 - 信号连接逻辑错误:指定固定服务名
org.freedesktop.ScreenSaver作为发送者,但实际系统信号的发送者是动态的(如:1.21),应留空监听所有发送者。 - 混用过时信号语法:旧版
QtCore.SIGNAL/SLOT宏容易出现类型匹配问题,建议使用PySide6推荐的新语法。
修复后的代码示例:
from PySide6 import QtDBus, QtCore class DBusEventDispatcher(QtCore.QObject): def __init__(self): super().__init__() self.sessionBus = QtDBus.QDBusConnection.sessionBus() if not self.sessionBus.isConnected(): errorMessage = self.sessionBus.lastError().message() raise Exception(f"无法连接到DBUS: {errorMessage}") # 监听所有发送者的ActiveChanged信号 success = self.sessionBus.connect( "", "/ScreenSaver", "org.freedesktop.ScreenSaver", "ActiveChanged", self, QtCore.SLOT("ActiveChanged(bool)") ) if not success: print(f"信号连接失败: {self.sessionBus.lastError().message()}") @QtCore.Slot(bool) def ActiveChanged(self, active: bool): print("ActiveChanged触发") print(f"屏幕保护状态: {'激活' if active else '关闭'}") if __name__ == "__main__": import sys app = QtCore.QCoreApplication(sys.argv) dispatcher = DBusEventDispatcher() sys.exit(app.exec())
2. 系统层面排查方向
- Wayland下的信号差异:KDE Wayland会话中,屏幕保护信号可能通过
org.kde.screensaver接口发送,而非org.freedesktop.ScreenSaver,可执行dbus-monitor "interface='org.kde.screensaver'"验证。 - DBus权限检查:执行
busctl --user introspect org.freedesktop.ScreenSaver /ScreenSaver org.freedesktop.ScreenSaver,若报错则表示权限受限。 - 会话环境变量:确保脚本运行时继承了用户会话的
DBUS_SESSION_BUS_ADDRESS环境变量,后台服务(如systemd服务)无法访问用户会话DBus。
3. 调试方法
- 启用Qt DBus日志:运行脚本前设置环境变量
export QT_LOGGING_RULES="qt.dbus.*=true",会输出DBus交互的详细日志。 - 精准过滤DBus信号:执行
dbus-monitor --session "type='signal',interface='org.freedesktop.ScreenSaver',member='ActiveChanged'",确认信号的发送者、路径等细节。 - 代码内错误检查:每次DBus操作后调用
sessionBus.lastError().message(),打印具体错误信息。
4. 其他排查思路
- 用pydbus快速验证:使用更简洁的pydbus库排除Qt的问题,示例代码:
from pydbus import SessionBus from gi.repository import GLib bus = SessionBus() def on_active_changed(active): print("ActiveChanged", active) bus.subscribe( interface="org.freedesktop.ScreenSaver", member="ActiveChanged", signal_fired=on_active_changed ) loop = GLib.MainLoop() loop.run() - 检查替代信号:Wayland下KDE可能使用
org.kde.screensaver的Locked信号,或org.freedesktop.login1的SessionLocked/SessionUnlocked信号。 - 确认会话一致性:确保脚本在用户桌面会话中运行,而非通过SSH等无图形环境启动。
内容的提问来源于stack exchange,提问作者Gerrit Addiks
相关产品推荐
相关产品推荐

