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

Gnome环境下点击通知后如何正确显示窗口?

解决Gnome环境下点击通知后通知中心遮挡窗口的问题

我用PyQt结合DBus实现点击通知唤起窗口的功能,核心代码如下:

def callback(*_):
    mainWindow.show()
    mainWindow.raise_() 
    mainWindow.activateWindow()

notification.addAction('default', '', callback) # 通过dbus发送通知
notification.show() 

但在Gnome环境下出现异常:点击通知后,窗口虽能正常唤起,但通知中心会保持在窗口上方,不会自动收起。完整可复现代码如下:

import sys
from PyQt6.QtWidgets import QApplication, QWidget, QPushButton
from PyQt6.QtCore import pyqtSlot
import dbus
from collections import OrderedDict

DBusGMainLoop = None
try:
    from dbus.mainloop.glib import DBusGMainLoop
except:
    print("Could not import DBusGMainLoop, is package 'python-dbus.mainloop.glib' installed?")

APP_NAME = 'test'
DBUS_IFACE = None
NOTIFICATIONS = {}

class Urgency:
    """freedesktop.org notification urgency levels"""
    LOW, NORMAL, CRITICAL = range(3)

class UninitializedError(RuntimeError):
    """Error raised if you try to show an error before initializing"""
    pass

def init():
    """Initializes the DBus connection"""
    global DBUS_IFACE

    name = "org.freedesktop.Notifications"
    path = "/org/freedesktop/Notifications"
    interface = "org.freedesktop.Notifications"

    mainloop = None
    if DBusGMainLoop is not None:
        mainloop = DBusGMainLoop(set_as_default=True)

    bus = dbus.SessionBus(mainloop)
    proxy = bus.get_object(name, path)
    DBUS_IFACE = dbus.Interface(proxy, interface)

    if mainloop is not None:
        DBUS_IFACE.connect_to_signal('ActionInvoked', _onActionInvoked)
        DBUS_IFACE.connect_to_signal('NotificationClosed', _onNotificationClosed)

def _onActionInvoked(nid, action):
    """Called when a notification action is clicked"""
    nid, action = int(nid), str(action)
    try:
        notification = NOTIFICATIONS[nid]
    except KeyError:
        return
    notification._onActionInvoked(action)

def _onNotificationClosed(nid, reason):
    """Called when the notification is closed"""
    nid, reason = int(nid), int(reason)
    try:
        notification = NOTIFICATIONS[nid]
    except KeyError:
        return
    notification._onNotificationClosed(notification)
    del NOTIFICATIONS[nid]

class Notification(object):
    """Notification object"""

    id = 0
    timeout = -1
    _onNotificationClosed = lambda *args: None

    def __init__(self, title, body='', icon='', timeout=-1):
        self.title = title
        self.body = body
        self.icon = icon
        self.timeout = timeout
        self.hints = {}
        self.actions = OrderedDict()
        self.data = {}

    def show(self):
        if DBUS_IFACE is None:
            raise UninitializedError("You must call 'notify.init()' before 'notify.show()'")

        nid = DBUS_IFACE.Notify(APP_NAME,
                           self.id,
                           self.icon,
                           self.title,
                           self.body,
                           self._makeActionsList(),
                           self.hints,
                           self.timeout,
                        )

        self.id = int(nid)
        NOTIFICATIONS[self.id] = self
        return True

    def close(self):
        """Ask the notification server to close the notification"""
        if self.id != 0:
            DBUS_IFACE.CloseNotification(self.id)

    def onClosed(self, callback):
        """Set the callback called when the notification is closed"""
        self._onNotificationClosed = callback

    def addAction(self, action, label, callback, user_data=None):
        """Add an action to the notification."""
        self.actions[action] = (label, callback, user_data)

    def _makeActionsList(self):
        """Make the actions array to send over DBus"""
        arr = []
        for action, (label, callback, user_data) in self.actions.items():
            arr.append(action)
            arr.append(label)
        return arr

    def _onActionInvoked(self, action):
        """Called when the user activates a notification action"""
        try:
            label, callback, user_data = self.actions[action]
        except KeyError:
            return

        if user_data is None:
            callback(self, action)
        else:
            callback(self, action, user_data)
            
def window():
   app = QApplication(sys.argv)
   widget = QWidget()

   button = QPushButton("Notify", widget)
   button.move(110,85)
   def button_callback():
    notification = Notification("Test")
    def notification_callback(*_):
        widget.show()
        widget.raise_() 
        widget.activateWindow()
        notification.close()
    notification.addAction("default", "", notification_callback)
    notification.show()
   button.clicked.connect(button_callback)

   widget.setGeometry(50,50,320,200)
   widget.setWindowTitle("My Window")
   widget.show()
   init()
   sys.exit(app.exec())

if __name__ == '__main__':
   window()

解决方案

问题根源是Gnome通知中心会抢占焦点,仅靠Qt的窗口激活方法无法触发系统自动收起通知中心。需要通过DBus调用Gnome Shell的接口手动关闭通知中心,同时优化窗口激活逻辑:

  1. 添加关闭Gnome通知中心的方法:
def close_gnome_notification_center():
    try:
        bus = dbus.SessionBus()
        shell_proxy = bus.get_object('org.gnome.Shell', '/org/gnome/Shell')
        shell_interface = dbus.Interface(shell_proxy, 'org.gnome.Shell')
        # 调用Gnome Shell内部方法切换通知中心状态(打开则关闭)
        shell_interface.Eval('Main.panel.statusArea.dateMenu._toggle()')
    except dbus.DBusException:
        # 非Gnome环境忽略此异常
        pass
  1. 修改通知回调函数,调整执行顺序:
def notification_callback(*_):
    # 先关闭通知中心
    close_gnome_notification_center()
    # 确保窗口从最小化状态恢复
    widget.showNormal()
    # 提升窗口层级并激活
    widget.raise_()
    widget.activateWindow()
    notification.close()

说明

  • close_gnome_notification_center通过DBus调用Gnome Shell的内部API,切换通知中心的显示状态(如果当前是打开状态就会关闭)。
  • 使用showNormal()替代show(),确保窗口在最小化时能正常恢复显示。
  • 先关闭通知中心再激活窗口,保证窗口能获得最高显示层级,避免被遮挡。

内容的提问来源于stack exchange,提问作者Edson Passos

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.19 18:35:10