tremby
tremby

Reputation: 10089

pynotify: "closed" signal callback never fired

I'm trying to write a program with pynotify, the Python bindings for libnotify. I want to bring up a critical notification at some point and have it updated every few seconds as information changes, until the user clicks it away. This all works except handling what happens when the user dismisses it.

In order to update a notification I need to call Notification.show after Notification.update. That's fine, but it means that I need to keep track of whether the user has dismissed the notification, otherwise it'll just keep popping up again.

That ought to be possible by two methods I can think of:

  1. Detecting whether the notification is visible or not. I haven't found any way of finding that out.
  2. Storing some variable when the notification is closed, then checking it before updating and calling Notification.show again.

This second method should be possible. Example code I found (there doesn't seem to be any proper documentation for pynotify) led me to call Notification.connect to connect a "closed" signal to a callback. I tried to do that but the callback was never fired.

I Googled and debugged for a long time but couldn't make any progress. Eventually I found some example scripts which come with pynotify. One of them attaches a handler to the "closed" signal: test-xy-stress.py

Its contents are as follows:

#!/usr/bin/env python

import pygtk
pygtk.require('2.0')
import gobject
import gtk
import gtk.gdk
import pynotify
import sys
import random

exposed_signal_id = 0
count = 0

def handle_closed(n):
    print "Closing."

def emit_notification(x, y):
    n = pynotify.Notification("X, Y Test",
        "This notification should point to %d, %d." % (x, y))
    n.set_hint("x", x)
    n.set_hint("y", y)
    n.connect('closed', handle_closed)
    n.show()

def popup_random_bubble():
    display = gtk.gdk.display_get_default()
    screen = display.get_default_screen()
    screen_x2 = screen.get_width() - 1
    screen_y2 = screen.get_height() - 1

    x = random.randint(0, screen_x2)
    y = random.randint(0, screen_y2)
    emit_notification(x, y)
    return True


if __name__ == '__main__':
    if not pynotify.init("XY Stress"):
        sys.exit(1)

    gobject.timeout_add(1000, popup_random_bubble)

    gtk.main()

I ran this and found that the callbacks here never fire either.

Could this be just my system, or is there a bug in pynotify or libnotify somewhere? If this is something beyond help right now, what about option 1 above -- is there any way to do that?

I seem to have libnotify 0.4.5 and pynotify 0.1.1.

Upvotes: 1

Views: 2054

Answers (2)

Jeffeb3
Jeffeb3

Reputation: 111

I've been looking for the same thing. I found someone using gobject.MainLoop instead of gtk.main to help out: linuxquestions.org.

I found this worked for me:

#!/usr/bin/python

import pynotify
import gobject

def OnClicked(notification, signal_text):
    print '1: ' + str(notification)
    print '2: ' + str(signal_text)
    notification.close()
    global loop
    loop.quit()

def OnClosed(notification):
    print 'Ignoring fire'
    notification.close()
    global loop
    loop.quit()

def Main():
    pynotify.init('ProgramName')

    global loop
    loop = gobject.MainLoop()

    notify = pynotify.Notification('Fire!', 'I\'m just kidding...')
    # optionalm, just changes notification color
    notify.set_urgency(pynotify.URGENCY_CRITICAL)
    # optional, it will expire eventually
    notify.set_timeout(pynotify.EXPIRES_NEVER)

    notify.add_action('You Clicked The Button', 'Remove Fire', OnClicked)
    notify.connect("closed",OnClosed)

    notify.show()

    loop.run()

if __name__ == '__main__':
    Main()

Upvotes: 2

Don Question
Don Question

Reputation: 11624

try the following:

add:

from time import  sleep

and at the end of your emit_notification:

sleep(2)
n.close()

(Updated after discussion with the OP:) This should fire your callback! This way you can test together with dbus-monitor, if your DBus-Server does what it's supposed to do. (Idea from the OP.)

It's still not that what you are truly seeking, but for the time being explains at least your puzzlement about the unemitted signal.

What you probably should look into, are the action-properties. I found something interesting here. Seems like you can interact with the user directly in your notifications.

Because pynotify is just a wrapper for the DBus communications you might also try a workaround:

from dbus import SessionBus, Interface
from dbus.mainloop.glib import DBusGMainLoop
DBusGMainLoop(set_as_default=True)

notify_busname = 'org.freedesktop.Notifications'
notify_obj_path ='/org/freedesktop/Notifications'
lbus = SessionBus()

notify_server = lbus.get_object(notify_busname, notify_obj_path)

and after the definition of your handler:

notify_server.connect_to_signal(None, handle_closed)

i also changed for testing purposes the signature of your function to fit the DBus signal:

def handle_closed(*arg, **kwargs):

Upvotes: 0

Related Questions