How to properly show a window after clicking on a notification?
Unsolved
Qt for Python
-
Hello,
I'm wondering what's the proper way to show my window after clicking on a notification?This is what I've tried:
def callback(*_): mainWindow.show() mainWindow.raise_() mainWindow.activateWindow() notification.addAction('default', '', callback) #notification sent using dbus notification.show()
But it has a undesired behaviour:
when clicking on the notification, the notification center (I'm on Gnome) stays open on top of my window. -
Hi and welcome to devnet,
What is your notification object ?
-
@SGaist
Here's a reproducible example:import sys from PyQt6.QtWidgets import QApplication, QWidget, QPushButton from PyQt6.QtGui import QIcon from PyQt6.QtCore import pyqtSlot import dbus from collections import OrderedDict # Copyright (c) 2018 Kurt Jacobson # <kurtcjacobson@gmail.com> # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. 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: # We have a mainloop, so connect callbacks 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: # must have been created by some other program 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: # must have been created by some other program 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): """Initializes a new notification object. Args: title (str): The title of the notification body (str, optional): The body text of the notification icon (str, optional): The icon to display with the notification timeout (TYPE, optional): The time in ms before the notification hides, -1 for default, 0 for never """ self.title = title # title of the notification self.body = body # the body text of the notification self.icon = icon # the path to the icon to use self.timeout = timeout # time in ms before the notification disappears self.hints = {} # dict of various display hints self.actions = OrderedDict() # actions names and their callbacks self.data = {} # arbitrary user data def show(self): if DBUS_IFACE is None: raise UninitializedError("You must call 'notify.init()' before 'notify.show()'") """Asks the notification server to show the notification""" 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. Args: action (str): A sort key identifying the action label (str): The text to display on the action button callback (bound method): The method to call when the action is activated user_data (any, optional): Any user data to be passed to the action callback """ 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()
-
I wonder if the two event loops are not walking on each other's foot.
That said, since you want to use DBus, why not use Qt's QtDBus module to ensure proper integration in your application ?