├── README.md └── notin.py /README.md: -------------------------------------------------------------------------------- 1 | notin' 2 | ====== 3 | 4 | `notin'` is a small notifications D-BUS listener implementing the 5 | org.fredesktop.Notifications [protocol][protocol]. 6 | 7 | `notin'` simply listens for any notifications, and outputs them to `stdout`. 8 | It's meant as a simple companion for an [XMonad][xmonad] or similar tiling 9 | window manager, redirecting its output to dzen, as a small and unobtrusive 10 | notifications bar. 11 | 12 | Testing notin' 13 | ============== 14 | 15 | Just call `python notin.py`, and cause a notification to happen: Flip a song 16 | on Amarok, get a message from Kopete, or call `notify-send foo`, and you'll 17 | see your message appear on notin's output. 18 | 19 | Using notin' 20 | ============ 21 | 22 | Notin's meant to be used with a [dzen][dzen] window: just pipe its output through: 23 | 24 | `python notin.py | dzen2` 25 | 26 | Your notifications will appear on your dzen bar. Note the `-u`: Without it, 27 | Python will buffer `stdout`, and you won't get updates on time! 28 | 29 | [protocol]: http://www.galago-project.org/specs/notification/0.9/x408.html 30 | [xmonad]: http://xmonad.org/ 31 | [dzen]: http://sites.google.com/site/gotmor/dzen 32 | -------------------------------------------------------------------------------- /notin.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | 3 | """notin': A simple org.freedesktop.Notification listener.""" 4 | 5 | import gobject 6 | 7 | import dbus 8 | import dbus.service 9 | import dbus.mainloop.glib 10 | 11 | import re 12 | 13 | import sys 14 | import itertools 15 | 16 | class MessageQueue(object): 17 | def __init__(self): 18 | self.messages = {} 19 | self.queue = [] 20 | self.current_message = None 21 | 22 | # Overkill? 23 | self.keys = itertools.cycle(xrange(1, 2**32)) 24 | 25 | def next_key(self): 26 | while True: 27 | i = self.keys.next() 28 | 29 | if i not in self.messages: 30 | break 31 | 32 | return i 33 | 34 | def enqueue(self, replaces_id, message): 35 | if replaces_id in self.messages: 36 | self.messages[replaces_id] = message 37 | return replaces_id 38 | 39 | key = self.next_key() 40 | self.messages[key] = message 41 | self.queue.append(key) 42 | 43 | return key 44 | 45 | def dequeue(self, message_id): 46 | if message_id in self.queue: 47 | self.queue.remove(message_id) 48 | 49 | if message_id in self.messages: 50 | del self.messages[message_id] 51 | 52 | def expired_message(self, message_id, delta): 53 | if message_id not in self.messages: 54 | return True 55 | 56 | message = self.messages[message_id] 57 | 58 | if message["expire_timeout"] == 0: 59 | return False 60 | 61 | message["expire_timeout"] -= delta 62 | 63 | return message["expire_timeout"] <= 0 64 | 65 | def update(self, delta): 66 | if not (self.current_message or self.queue): 67 | return True 68 | 69 | if self.current_message: 70 | if self.expired_message(self.current_message, delta): 71 | self.dequeue(self.current_message) 72 | self.current_message = None 73 | if not self.queue: 74 | print 75 | sys.stdout.flush() 76 | 77 | if self.queue: 78 | if self.current_message: 79 | self.queue.append(self.current_message) 80 | 81 | self.current_message = self.queue.pop(0) 82 | message = u"[%(app_name)s] %(summary)s: %(body)s" % self.messages[self.current_message] 83 | message = re.sub(r'\n', ' ', message) 84 | print message.encode('utf-8') 85 | sys.stdout.flush() 86 | 87 | 88 | return True 89 | 90 | 91 | class Notin(dbus.service.Object): 92 | def __init__(self, queue, bus, object_path): 93 | dbus.service.Object.__init__(self, bus, object_path) 94 | self.queue = queue 95 | 96 | @dbus.service.method("org.freedesktop.Notifications", 97 | out_signature='as', in_signature='') 98 | def GetCapabilities(self): 99 | return ["body"] 100 | 101 | @dbus.service.method("org.freedesktop.Notifications", 102 | out_signature='u', in_signature='susssasa{sv}u') 103 | def Notify(self, app_name, replaces_id, app_icon, summary, body, actions, 104 | hints, expire_timeout): 105 | 106 | if expire_timeout == -1: 107 | expire_timeout = 5000 108 | 109 | notification = {"app_name": app_name, 110 | "replaces_id": replaces_id, 111 | "app_icon": app_icon, 112 | "summary": summary, 113 | "body": body, 114 | "actions": actions, 115 | "hints": hints, 116 | "expire_timeout": expire_timeout} 117 | 118 | 119 | return self.queue.enqueue(replaces_id, notification) 120 | 121 | @dbus.service.method("org.freedesktop.Notifications", 122 | out_signature='', in_signature='u') 123 | def CloseNotification(self, notification_id): 124 | self.queue.dequeue(notification_id) 125 | self.NotificationClosed(notification_id, 3) 126 | return 127 | 128 | @dbus.service.method("org.freedesktop.Notifications", 129 | out_signature='ssss', in_signature='') 130 | def GetServerInformation(self): 131 | return "notin'", "Tordek", "0.0.1", "1.2" 132 | 133 | @dbus.service.signal("org.freedesktop.Notifications", 134 | signature='uu') 135 | def NotificationClosed(self, id, reason): 136 | return 137 | 138 | def main(): 139 | dbus.mainloop.glib.DBusGMainLoop(set_as_default=True) 140 | 141 | queue = MessageQueue() 142 | system_bus = dbus.SessionBus() 143 | 144 | name = dbus.service.BusName("org.freedesktop.Notifications", 145 | system_bus, replace_existing=True, do_not_queue=True) 146 | obj = Notin(queue, system_bus, '/org/freedesktop/Notifications') 147 | 148 | mainloop = gobject.MainLoop() 149 | 150 | gobject.timeout_add(1000, lambda: queue.update(1000)) 151 | mainloop.run() 152 | 153 | if __name__ == '__main__': 154 | main() 155 | --------------------------------------------------------------------------------