61
by Jason Conti
Renamed Monitor.py to Notification.py |
1 |
"""
|
2 |
Notification.py
|
|
3 |
by Jason Conti
|
|
4 |
February 19, 2010
|
|
5 |
||
6 |
Monitors DBUS for org.freedesktop.Notifications.Notify messages, parses them,
|
|
7 |
and notifies listeners when they arrive.
|
|
8 |
"""
|
|
9 |
||
10 |
import dbus |
|
11 |
import glib |
|
12 |
import gobject |
|
13 |
import gtk |
|
14 |
import logging |
|
15 |
import time |
|
16 |
||
17 |
from dbus.mainloop.glib import DBusGMainLoop |
|
81
by Jason Conti
Adding support for gettext translations and a pointless en_US translation as an example. Using distutils-extra to automatically build the translations and the bonobo server. Replaced the build_servers target with update_prefix. This performs basically the same as build_servers except that it will do the substitution on any .in file, and it just writes the output file without the .in, instead of adding it to the data file install list. |
18 |
from locale import nl_langinfo, T_FMT |
61
by Jason Conti
Renamed Monitor.py to Notification.py |
19 |
|
20 |
from Icon import load_icon, load_icon_from_file |
|
21 |
||
72
by Jason Conti
Improved expection logging, and updated the logging format. Minor version bump for package release. |
22 |
logger = logging.getLogger("Notification") |
23 |
||
24 |
class ImageDataException(Exception): |
|
25 |
pass
|
|
26 |
||
68
by Jason Conti
Adding support for the image_data hint in the Notifications. Removing CellRendererMessage.py and MessageView.py since they are no longer used. |
27 |
class ImageData(object): |
28 |
"""Parses the image_data hint from a DBUS message."""
|
|
29 |
def __init__(self, image_data): |
|
30 |
if len(image_data) < 7: |
|
72
by Jason Conti
Improved expection logging, and updated the logging format. Minor version bump for package release. |
31 |
raise ImageDataException("Invalid image_data: " + repr(image_data)) |
68
by Jason Conti
Adding support for the image_data hint in the Notifications. Removing CellRendererMessage.py and MessageView.py since they are no longer used. |
32 |
|
33 |
self.width = int(image_data[0]) |
|
34 |
self.height = int(image_data[1]) |
|
35 |
self.rowstride = int(image_data[2]) |
|
36 |
self.has_alpha = bool(image_data[3]) |
|
37 |
self.bits_per_sample = int(image_data[4]) |
|
38 |
self.channels = int(image_data[5]) |
|
39 |
self.data = self.dbus_array_to_str(image_data[6]) |
|
40 |
||
41 |
def dbus_array_to_str(self, array): |
|
42 |
return "".join(map(chr, array)) |
|
43 |
||
44 |
def get_pixbuf(self, size): |
|
45 |
"""Creates a pixbuf from the image data and scale it to the appropriate size."""
|
|
46 |
pixbuf = gtk.gdk.pixbuf_new_from_data(self.data, gtk.gdk.COLORSPACE_RGB, |
|
47 |
self.has_alpha, self.bits_per_sample, self.width, self.height, |
|
48 |
self.rowstride) |
|
49 |
||
50 |
return pixbuf.scale_simple(size, size, gtk.gdk.INTERP_BILINEAR) |
|
51 |
||
72
by Jason Conti
Improved expection logging, and updated the logging format. Minor version bump for package release. |
52 |
class MessageException(Exception): |
53 |
pass
|
|
54 |
||
61
by Jason Conti
Renamed Monitor.py to Notification.py |
55 |
class Message(gobject.GObject): |
56 |
"""Parses a DBUS message in the Notify format specified at:
|
|
57 |
http://www.galago-project.org/specs/notification/0.9/index.html"""
|
|
58 |
# Message urgency
|
|
59 |
LOW = 0 |
|
60 |
NORMAL = 1 |
|
61 |
CRITICAL = 2 |
|
62 |
def __init__(self, dbus_message = None, timestamp = None): |
|
63 |
gobject.GObject.__init__(self) |
|
64 |
||
68
by Jason Conti
Adding support for the image_data hint in the Notifications. Removing CellRendererMessage.py and MessageView.py since they are no longer used. |
65 |
self.timestamp = timestamp |
66 |
||
67 |
args = dbus_message.get_args_list() |
|
68 |
||
69 |
if len(args) != 8: |
|
72
by Jason Conti
Improved expection logging, and updated the logging format. Minor version bump for package release. |
70 |
raise MessageException("Invalid message args_list: " + repr(args)) |
68
by Jason Conti
Adding support for the image_data hint in the Notifications. Removing CellRendererMessage.py and MessageView.py since they are no longer used. |
71 |
|
72 |
self.app_name = str(args[0]) |
|
73 |
self.replaces_id = args[1] |
|
72
by Jason Conti
Improved expection logging, and updated the logging format. Minor version bump for package release. |
74 |
self.app_icon = str(args[2]) |
68
by Jason Conti
Adding support for the image_data hint in the Notifications. Removing CellRendererMessage.py and MessageView.py since they are no longer used. |
75 |
self.summary = str(args[3]) |
76 |
self.body = str(args[4]) |
|
77 |
self.actions = args[5] |
|
78 |
self.hints = dict(args[6]) |
|
79 |
self.expire_timeout = args[7] |
|
61
by Jason Conti
Renamed Monitor.py to Notification.py |
80 |
|
81 |
if "urgency" in self.hints: |
|
82 |
urgency = self.hints["urgency"] |
|
83 |
if urgency == 0: |
|
84 |
self.urgency = Message.LOW |
|
85 |
elif urgency == 2: |
|
86 |
self.urgency = Message.CRITICAL |
|
87 |
else: |
|
88 |
self.urgency = Message.NORMAL |
|
89 |
else: |
|
90 |
self.urgency = Message.NORMAL |
|
91 |
||
68
by Jason Conti
Adding support for the image_data hint in the Notifications. Removing CellRendererMessage.py and MessageView.py since they are no longer used. |
92 |
if "image_data" in self.hints: |
93 |
self.image_data = ImageData(self.hints["image_data"]) |
|
94 |
else: |
|
95 |
self.image_data = None |
|
96 |
||
97 |
self.log_message() |
|
61
by Jason Conti
Renamed Monitor.py to Notification.py |
98 |
|
81
by Jason Conti
Adding support for gettext translations and a pointless en_US translation as an example. Using distutils-extra to automatically build the translations and the bonobo server. Replaced the build_servers target with update_prefix. This performs basically the same as build_servers except that it will do the substitution on any .in file, and it just writes the output file without the .in, instead of adding it to the data file install list. |
99 |
def formatted_timestamp(self): |
100 |
"""Returned the timestamp in a different format."""
|
|
101 |
#return time.strftime("%I:%M:%S %p", self.timestamp)
|
|
102 |
return time.strftime(nl_langinfo(T_FMT), self.timestamp) |
|
103 |
||
61
by Jason Conti
Renamed Monitor.py to Notification.py |
104 |
def get_icon(self, size = 48): |
105 |
"""Loads the icon into a pixbuf. Adapted from the load_icon code in
|
|
106 |
bubble.c of notify-osd."""
|
|
72
by Jason Conti
Improved expection logging, and updated the logging format. Minor version bump for package release. |
107 |
icon_name = self.app_icon |
61
by Jason Conti
Renamed Monitor.py to Notification.py |
108 |
|
109 |
# Try to load the pixbuf from a file
|
|
110 |
if icon_name.startswith("file://") or icon_name.startswith("/"): |
|
111 |
icon = load_icon_from_file(icon_name, size) |
|
112 |
if icon != None: |
|
113 |
return icon |
|
114 |
||
115 |
# Try to load the pixbuf from the current icon theme
|
|
116 |
elif icon_name != "": |
|
117 |
icon = load_icon(icon_name, size) |
|
118 |
if icon != None: |
|
119 |
return icon |
|
120 |
||
68
by Jason Conti
Adding support for the image_data hint in the Notifications. Removing CellRendererMessage.py and MessageView.py since they are no longer used. |
121 |
# Try to load the image data from the message
|
122 |
elif self.image_data != None: |
|
123 |
return self.image_data.get_pixbuf(32) |
|
124 |
||
61
by Jason Conti
Renamed Monitor.py to Notification.py |
125 |
return self.get_default_icon(size) |
126 |
||
127 |
def get_default_icon(self, size = 48): |
|
128 |
"""Attempts to load the default message icon, returns None on failure."""
|
|
129 |
if self.urgency == Message.LOW: |
|
130 |
return load_icon("notification-low", size) |
|
131 |
elif self.urgency == Message.CRITICAL: |
|
132 |
return load_icon("notification-critical", size) |
|
133 |
else: |
|
134 |
return load_icon("notification-normal", size) |
|
135 |
||
136 |
def log_message(self): |
|
137 |
"""Write debug info about a message."""
|
|
72
by Jason Conti
Improved expection logging, and updated the logging format. Minor version bump for package release. |
138 |
result = [ |
139 |
"-" * 50, |
|
140 |
"Message created at: " + self.formatted_timestamp(), |
|
141 |
"app_name: " + repr(self.app_name), |
|
142 |
"replaces_id: " + repr(self.replaces_id), |
|
143 |
"app_icon: " + repr(self.app_icon), |
|
144 |
"summary: " + repr(self.summary), |
|
145 |
"body: " + repr(self.body), |
|
146 |
"actions: " + repr(self.actions), |
|
147 |
"hints: " + repr(self.hints), |
|
148 |
"expire_timeout: " + repr(self.expire_timeout), |
|
149 |
"-" * 50 |
|
150 |
]
|
|
151 |
logger.debug("\n" + "\n".join(result)) |
|
61
by Jason Conti
Renamed Monitor.py to Notification.py |
152 |
|
153 |
class Notification(gobject.GObject): |
|
154 |
"""Monitors DBUS for org.freedesktop.Notifications.Notify messages, parses them,
|
|
155 |
and notifies listeners when they arrive."""
|
|
156 |
__gsignals__ = { |
|
157 |
"message-received": (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, [Message]) |
|
158 |
}
|
|
159 |
def __init__(self): |
|
160 |
gobject.GObject.__init__(self) |
|
161 |
||
162 |
self._match_string = "type='method_call',interface='org.freedesktop.Notifications',member='Notify'" |
|
163 |
||
164 |
DBusGMainLoop(set_as_default=True) |
|
165 |
self._bus = dbus.SessionBus() |
|
166 |
self._bus.add_match_string(self._match_string) |
|
167 |
self._bus.add_message_filter(self._message_filter) |
|
168 |
||
169 |
def close(self): |
|
170 |
self._bus.close() |
|
171 |
||
172 |
def _message_filter(self, connection, dbus_message): |
|
173 |
if dbus_message.get_member() == "Notify" and dbus_message.get_interface() == "org.freedesktop.Notifications": |
|
72
by Jason Conti
Improved expection logging, and updated the logging format. Minor version bump for package release. |
174 |
try: |
175 |
message = Message(dbus_message, time.localtime()) |
|
176 |
except: |
|
177 |
logger.exception("Failed to parse dbus message: " + repr(dbus_message.get_args_list())) |
|
178 |
else: |
|
179 |
glib.idle_add(self.emit, "message-received", message) |
|
61
by Jason Conti
Renamed Monitor.py to Notification.py |
180 |
|
181 |
if gtk.pygtk_version < (2, 8, 0): |
|
182 |
gobject.type_register(Message) |
|
183 |
gobject.type_register(Notification) |
|
184 |
||
185 |
if __name__ == '__main__': |
|
186 |
main() |