Feb 26, 2010

libnotify, notification-daemon shortcomings and my solution

Everyone who uses OSS desktop these days probably seen libnotify magic in action - small popup windows that appear at some corner of the screen, announcing events from other apps.

libnotify itself, however, is just a convenience lib for dispatching these notifications over dbus, so the latter can pass it app listening on this interface or even start it beforehand.
Standard app for rendering such messages is notification-daemon, which is developed alongside with libnotify, but there are drop-in replacements like xfce4-notifyd or e17 notification module. In dbus rpc mechanism call signatures are clearly defined and visible, so it's pretty easy to implement replacement for aforementioned daemons, plus vanilla notification-daemon has introspection calls and dbus itself can be easily monitored (via dbus-monitor utility) which make it's implementation even more transparent.

Now, polling every window for updates manually is quite inefficient - new mail, xmpp messages, IRC chat lines, system events etc sometimes arrive every few seconds, and going over all the windows (and by that I mean workspaces where they're stacked) just to check them is a huge waste of time, especially when some (or even most, in case of IRC) of these are not really important.
Either response time or focus (and, in extreme case, sanity) has to be sacrificed in such approach. Luckily, there's another way to monitor this stuff - small pop-up notifications allow to see what's happening right away, w/o much attention-switching or work required from an end-user.

But that's the theory.
In practice, I've found that enabling notifications in IRC or jabber is pretty much pointless, since you'll be swarmed by these as soon as any real activity starts there. And w/o them it's a stupid wasteful poll practice, mentioned above.

Notification-daemon has no tricks to remedy the situation, but since the whole thing is so abstract and transparent I've had no problem making my own fix.
Notification digest

Solution I came up with is to batch the notification messages into a digests as soon as there are too many of them, displaying such digest pop-ups with some time interval, so I can keep a grip on what's going on just by glancing at these as they arrive, switching my activities if something there is important enough.

Having played with schedulers and network shaping/policing before, not much imagination was required to devise a way to control the message flow rate.
I chose token-bucket algorithm at first, but since prolonged flood of I-don't-care-about activity have gradually decreasing value, I didn't want to receive digests of it every N seconds, so I batched it with a gradual digest interval increase and leaky-bucket mechanism, so digests won't get too fat over these intervals.
Well, the result exceeded my expectations, and now I can use libnotify freely even to indicate that some rsync just finished in a terminal on another workspace. Wonder why such stuff isn't built into existing notification daemons...
Then, there was another, even more annoying issue: notifications during fullscreen apps! WTF!?
Wonder if everyone got used to this ugly flickering in fullscreen mplayer, huge lags in GL games like SpringRTS or I'm just re-inventing the wheel here, since it's done in gnome or kde (knotify, huh?), but since I'm not gonna use either one I just added fullscreen-app check before notification output, queueing them to digest if that is the case.
Ok, a few words about implementation.
Token bucket itself is based on activestate recipe with some heavy improvements to adjust flow on constant under/over-flow, plus with a bit more pythonic style and features, take a look here. Leaky bucket implemented by this class.
Aside from that it's just dbus magic and a quite extensive CLI interface to control the filters.
Main dbus magic, however, lies outside the script, since dbus calls cannot be intercepted and the scheduler can't get'em with notification-daemon already listening on this interface.
Solution is easy, of course - scheduler can replace the real daemon and proxy mangled calls to it as necessary. It takes this sed line for notification-daemon as well, since interface is hard-coded there.
Needs fgc module, but it's just a hundred lines on meaningful code.

One more step to making linux desktop more comfortable. Oh, joy ;)