You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

问题

  1. 脚本可能存在什么问题?
  2. 是否是系统问题(如systemd规则限制访问)?
  3. 是否有调试flag可查看Python/DBUS库内部运行情况?
  4. 其他排查思路?

环境信息

$ 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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.12 03:44:50