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 |
||
16 |
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. |
17 |
from locale import nl_langinfo, T_FMT |
61
by Jason Conti
Renamed Monitor.py to Notification.py |
18 |
|
97
by Jason Conti
Adding the Timestamp module. Using the timestamp module to display timestamps as time ago in words. |
19 |
import Timestamp |
20 |
||
61
by Jason Conti
Renamed Monitor.py to Notification.py |
21 |
from Icon import load_icon, load_icon_from_file |
22 |
||
72
by Jason Conti
Improved expection logging, and updated the logging format. Minor version bump for package release. |
23 |
logger = logging.getLogger("Notification") |
24 |
||
25 |
class ImageDataException(Exception): |
|
26 |
pass
|
|
27 |
||
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. |
28 |
class ImageData(object): |
29 |
"""Parses the image_data hint from a DBUS message."""
|
|
30 |
def __init__(self, image_data): |
|
31 |
if len(image_data) < 7: |
|
72
by Jason Conti
Improved expection logging, and updated the logging format. Minor version bump for package release. |
32 |
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. |
33 |
|
34 |
self.width = int(image_data[0]) |
|
35 |
self.height = int(image_data[1]) |
|
36 |
self.rowstride = int(image_data[2]) |
|
37 |
self.has_alpha = bool(image_data[3]) |
|
38 |
self.bits_per_sample = int(image_data[4]) |
|
39 |
self.channels = int(image_data[5]) |
|
40 |
self.data = self.dbus_array_to_str(image_data[6]) |
|
41 |
||
42 |
def dbus_array_to_str(self, array): |
|
43 |
return "".join(map(chr, array)) |
|
44 |
||
45 |
def get_pixbuf(self, size): |
|
46 |
"""Creates a pixbuf from the image data and scale it to the appropriate size."""
|
|
47 |
pixbuf = gtk.gdk.pixbuf_new_from_data(self.data, gtk.gdk.COLORSPACE_RGB, |
|
48 |
self.has_alpha, self.bits_per_sample, self.width, self.height, |
|
49 |
self.rowstride) |
|
50 |
||
51 |
return pixbuf.scale_simple(size, size, gtk.gdk.INTERP_BILINEAR) |
|
52 |
||
72
by Jason Conti
Improved expection logging, and updated the logging format. Minor version bump for package release. |
53 |
class MessageException(Exception): |
54 |
pass
|
|
55 |
||
61
by Jason Conti
Renamed Monitor.py to Notification.py |
56 |
class Message(gobject.GObject): |
57 |
"""Parses a DBUS message in the Notify format specified at:
|
|
58 |
http://www.galago-project.org/specs/notification/0.9/index.html"""
|
|
59 |
# Message urgency
|
|
60 |
LOW = 0 |
|
61 |
NORMAL = 1 |
|
62 |
CRITICAL = 2 |
|
114
by Jason Conti
Ubuntu has special notifications when the media buttons on a keyboard are used to update the volume. They are mostly blank except for a special x-canonical-private-synchronous hint that contains the string volume. They don't have any useful information, so this commit adds code to detect and discard them. |
63 |
X_CANONICAL_PRIVATE_SYNCHRONOUS = "x-canonical-private-synchronous" |
61
by Jason Conti
Renamed Monitor.py to Notification.py |
64 |
def __init__(self, dbus_message = None, timestamp = None): |
65 |
gobject.GObject.__init__(self) |
|
66 |
||
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. |
67 |
self.timestamp = timestamp |
68 |
||
69 |
args = dbus_message.get_args_list() |
|
70 |
||
71 |
if len(args) != 8: |
|
72
by Jason Conti
Improved expection logging, and updated the logging format. Minor version bump for package release. |
72 |
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. |
73 |
|
74 |
self.app_name = str(args[0]) |
|
75 |
self.replaces_id = args[1] |
|
72
by Jason Conti
Improved expection logging, and updated the logging format. Minor version bump for package release. |
76 |
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. |
77 |
self.summary = str(args[3]) |
78 |
self.body = str(args[4]) |
|
79 |
self.actions = args[5] |
|
80 |
self.hints = dict(args[6]) |
|
81 |
self.expire_timeout = args[7] |
|
61
by Jason Conti
Renamed Monitor.py to Notification.py |
82 |
|
83 |
if "urgency" in self.hints: |
|
84 |
urgency = self.hints["urgency"] |
|
85 |
if urgency == 0: |
|
86 |
self.urgency = Message.LOW |
|
87 |
elif urgency == 2: |
|
88 |
self.urgency = Message.CRITICAL |
|
89 |
else: |
|
90 |
self.urgency = Message.NORMAL |
|
91 |
else: |
|
92 |
self.urgency = Message.NORMAL |
|
93 |
||
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. |
94 |
if "image_data" in self.hints: |
95 |
self.image_data = ImageData(self.hints["image_data"]) |
|
96 |
else: |
|
97 |
self.image_data = None |
|
98 |
||
214
by Jason Conti
* Reorder the icon priority in Notification.get_icon to match the Desktop |
99 |
if "icon_data" in self.hints: |
100 |
self.icon_data = ImageData(self.hints["icon_data"]) |
|
101 |
else: |
|
102 |
self.icon_data = None |
|
103 |
||
104 |
if "image-path" in self.hints: |
|
105 |
self.image_path = self.hints["image-path"] |
|
106 |
else: |
|
107 |
self.image_path = None |
|
123
by Jason Conti
It seems there is another hint in the spec for icon_data, which seems redundant, since it is exactly identical to image_data (which is better documented in the hints section). Adding it because Dropbox notifications use it. |
108 |
|
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. |
109 |
self.log_message() |
61
by Jason Conti
Renamed Monitor.py to Notification.py |
110 |
|
111 |
def get_icon(self, size = 48): |
|
214
by Jason Conti
* Reorder the icon priority in Notification.get_icon to match the Desktop |
112 |
"""Loads the icon into a pixbuf."""
|
217
by Jason Conti
Fix typo in Notification.py. |
113 |
if self.image_path != None: |
214
by Jason Conti
* Reorder the icon priority in Notification.get_icon to match the Desktop |
114 |
icon_name = self.image_path |
115 |
else: |
|
116 |
icon_name = self.app_icon |
|
61
by Jason Conti
Renamed Monitor.py to Notification.py |
117 |
|
214
by Jason Conti
* Reorder the icon priority in Notification.get_icon to match the Desktop |
118 |
# Try to load the image data from the message
|
119 |
if self.image_data != None: |
|
120 |
return self.image_data.get_pixbuf(size) |
|
121 |
||
61
by Jason Conti
Renamed Monitor.py to Notification.py |
122 |
# Try to load the pixbuf from a file
|
214
by Jason Conti
* Reorder the icon priority in Notification.get_icon to match the Desktop |
123 |
elif icon_name.startswith("file://") or icon_name.startswith("/"): |
61
by Jason Conti
Renamed Monitor.py to Notification.py |
124 |
icon = load_icon_from_file(icon_name, size) |
125 |
if icon != None: |
|
126 |
return icon |
|
127 |
||
128 |
# Try to load the pixbuf from the current icon theme
|
|
129 |
elif icon_name != "": |
|
130 |
icon = load_icon(icon_name, size) |
|
131 |
if icon != None: |
|
132 |
return icon |
|
133 |
||
214
by Jason Conti
* Reorder the icon priority in Notification.get_icon to match the Desktop |
134 |
# Try to load the icon data from the message
|
135 |
elif self.icon_data != None: |
|
136 |
return self.icon_data.get_pixbuf(size) |
|
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. |
137 |
|
61
by Jason Conti
Renamed Monitor.py to Notification.py |
138 |
return self.get_default_icon(size) |
139 |
||
140 |
def get_default_icon(self, size = 48): |
|
141 |
"""Attempts to load the default message icon, returns None on failure."""
|
|
142 |
if self.urgency == Message.LOW: |
|
143 |
return load_icon("notification-low", size) |
|
144 |
elif self.urgency == Message.CRITICAL: |
|
145 |
return load_icon("notification-critical", size) |
|
146 |
else: |
|
147 |
return load_icon("notification-normal", size) |
|
148 |
||
114
by Jason Conti
Ubuntu has special notifications when the media buttons on a keyboard are used to update the volume. They are mostly blank except for a special x-canonical-private-synchronous hint that contains the string volume. They don't have any useful information, so this commit adds code to detect and discard them. |
149 |
def is_volume_notification(self): |
150 |
"""Returns true if this is a volume message. The volume notifications
|
|
151 |
in Ubuntu are a special case, and mostly a blank message. Clutters up
|
|
152 |
the display and provides no useful information, so it is better to
|
|
153 |
discard them."""
|
|
154 |
if Message.X_CANONICAL_PRIVATE_SYNCHRONOUS in self.hints: |
|
155 |
return str(self.hints[Message.X_CANONICAL_PRIVATE_SYNCHRONOUS]) == "volume" |
|
156 |
||
61
by Jason Conti
Renamed Monitor.py to Notification.py |
157 |
def log_message(self): |
158 |
"""Write debug info about a message."""
|
|
72
by Jason Conti
Improved expection logging, and updated the logging format. Minor version bump for package release. |
159 |
result = [ |
160 |
"-" * 50, |
|
97
by Jason Conti
Adding the Timestamp module. Using the timestamp module to display timestamps as time ago in words. |
161 |
"Message created at: " + Timestamp.locale_datetime(self.timestamp), |
72
by Jason Conti
Improved expection logging, and updated the logging format. Minor version bump for package release. |
162 |
"app_name: " + repr(self.app_name), |
163 |
"replaces_id: " + repr(self.replaces_id), |
|
164 |
"app_icon: " + repr(self.app_icon), |
|
165 |
"summary: " + repr(self.summary), |
|
166 |
"body: " + repr(self.body), |
|
167 |
"actions: " + repr(self.actions), |
|
168 |
"expire_timeout: " + repr(self.expire_timeout), |
|
85
by Jason Conti
Updated Message.log_message. The image_data hints were cluttering up the log, so now they are listed separately, using only the image metadata, not the actual image data. |
169 |
"hints:"
|
72
by Jason Conti
Improved expection logging, and updated the logging format. Minor version bump for package release. |
170 |
]
|
85
by Jason Conti
Updated Message.log_message. The image_data hints were cluttering up the log, so now they are listed separately, using only the image metadata, not the actual image data. |
171 |
|
172 |
# Log all the hints except image_data
|
|
173 |
for key in self.hints: |
|
174 |
if key != "image_data": |
|
175 |
result.append(" " + str(key) + ": " + repr(self.hints[key])) |
|
176 |
||
177 |
# Log info about the image_data
|
|
178 |
if self.image_data != None: |
|
179 |
result.append("image_data:") |
|
180 |
result.append(" width: " + repr(self.image_data.width)) |
|
181 |
result.append(" height: " + repr(self.image_data.height)) |
|
182 |
result.append(" rowstride: " + repr(self.image_data.rowstride)) |
|
183 |
result.append(" has_alpha: " + repr(self.image_data.has_alpha)) |
|
184 |
result.append(" bits_per_sample: " + repr(self.image_data.bits_per_sample)) |
|
185 |
result.append(" channels: " + repr(self.image_data.channels)) |
|
186 |
||
187 |
result.append("-" * 50) |
|
188 |
||
72
by Jason Conti
Improved expection logging, and updated the logging format. Minor version bump for package release. |
189 |
logger.debug("\n" + "\n".join(result)) |
61
by Jason Conti
Renamed Monitor.py to Notification.py |
190 |
|
191 |
class Notification(gobject.GObject): |
|
192 |
"""Monitors DBUS for org.freedesktop.Notifications.Notify messages, parses them,
|
|
193 |
and notifies listeners when they arrive."""
|
|
194 |
__gsignals__ = { |
|
195 |
"message-received": (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, [Message]) |
|
196 |
}
|
|
197 |
def __init__(self): |
|
198 |
gobject.GObject.__init__(self) |
|
199 |
||
167
by Jason Conti
Fixed a bug in Notification.py if no blacklist is set. |
200 |
self._blacklist = None |
61
by Jason Conti
Renamed Monitor.py to Notification.py |
201 |
self._match_string = "type='method_call',interface='org.freedesktop.Notifications',member='Notify'" |
202 |
||
203 |
DBusGMainLoop(set_as_default=True) |
|
204 |
self._bus = dbus.SessionBus() |
|
205 |
self._bus.add_match_string(self._match_string) |
|
206 |
self._bus.add_message_filter(self._message_filter) |
|
207 |
||
208 |
def close(self): |
|
137
by Jason Conti
Adding an option to blacklist notifications based on app_name. Applications to be blacklisted should be listed in the file ~/.config/recent-notifications/blacklist, one app_name per line. The app_names are case-sensitive and are listed in the notifications as "from app_name" such as "from Gwibber" or "from Pidgin". They can also be found in ~/.cache/recent-notifications.log on the app_name lines. Thanks to Dinero Francis for reporting the issue. |
209 |
"""Closes the connection to the session bus."""
|
61
by Jason Conti
Renamed Monitor.py to Notification.py |
210 |
self._bus.close() |
211 |
||
137
by Jason Conti
Adding an option to blacklist notifications based on app_name. Applications to be blacklisted should be listed in the file ~/.config/recent-notifications/blacklist, one app_name per line. The app_names are case-sensitive and are listed in the notifications as "from app_name" such as "from Gwibber" or "from Pidgin". They can also be found in ~/.cache/recent-notifications.log on the app_name lines. Thanks to Dinero Francis for reporting the issue. |
212 |
def set_blacklist(self, blacklist): |
213 |
"""Defines the set of app_names of messages to be discarded."""
|
|
139
by Jason Conti
Fixed several bugs in Config and it is now used to read the blacklist. |
214 |
self._blacklist = blacklist |
137
by Jason Conti
Adding an option to blacklist notifications based on app_name. Applications to be blacklisted should be listed in the file ~/.config/recent-notifications/blacklist, one app_name per line. The app_names are case-sensitive and are listed in the notifications as "from app_name" such as "from Gwibber" or "from Pidgin". They can also be found in ~/.cache/recent-notifications.log on the app_name lines. Thanks to Dinero Francis for reporting the issue. |
215 |
|
61
by Jason Conti
Renamed Monitor.py to Notification.py |
216 |
def _message_filter(self, connection, dbus_message): |
137
by Jason Conti
Adding an option to blacklist notifications based on app_name. Applications to be blacklisted should be listed in the file ~/.config/recent-notifications/blacklist, one app_name per line. The app_names are case-sensitive and are listed in the notifications as "from app_name" such as "from Gwibber" or "from Pidgin". They can also be found in ~/.cache/recent-notifications.log on the app_name lines. Thanks to Dinero Francis for reporting the issue. |
217 |
"""Triggers when messages are received from the session bus."""
|
61
by Jason Conti
Renamed Monitor.py to Notification.py |
218 |
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. |
219 |
try: |
97
by Jason Conti
Adding the Timestamp module. Using the timestamp module to display timestamps as time ago in words. |
220 |
message = Message(dbus_message, Timestamp.now()) |
72
by Jason Conti
Improved expection logging, and updated the logging format. Minor version bump for package release. |
221 |
except: |
222 |
logger.exception("Failed to parse dbus message: " + repr(dbus_message.get_args_list())) |
|
223 |
else: |
|
137
by Jason Conti
Adding an option to blacklist notifications based on app_name. Applications to be blacklisted should be listed in the file ~/.config/recent-notifications/blacklist, one app_name per line. The app_names are case-sensitive and are listed in the notifications as "from app_name" such as "from Gwibber" or "from Pidgin". They can also be found in ~/.cache/recent-notifications.log on the app_name lines. Thanks to Dinero Francis for reporting the issue. |
224 |
# Discard unwanted messages
|
167
by Jason Conti
Fixed a bug in Notification.py if no blacklist is set. |
225 |
if message.is_volume_notification(): |
226 |
return
|
|
227 |
if self._blacklist and self._blacklist.get_bool(message.app_name, False): |
|
137
by Jason Conti
Adding an option to blacklist notifications based on app_name. Applications to be blacklisted should be listed in the file ~/.config/recent-notifications/blacklist, one app_name per line. The app_names are case-sensitive and are listed in the notifications as "from app_name" such as "from Gwibber" or "from Pidgin". They can also be found in ~/.cache/recent-notifications.log on the app_name lines. Thanks to Dinero Francis for reporting the issue. |
228 |
return
|
72
by Jason Conti
Improved expection logging, and updated the logging format. Minor version bump for package release. |
229 |
glib.idle_add(self.emit, "message-received", message) |
61
by Jason Conti
Renamed Monitor.py to Notification.py |
230 |
|
231 |
if gtk.pygtk_version < (2, 8, 0): |
|
232 |
gobject.type_register(Message) |
|
233 |
gobject.type_register(Notification) |
|
234 |
||
235 |
if __name__ == '__main__': |
|
236 |
main() |