~jconti/recent-notifications/trunk

« back to all changes in this revision

Viewing changes to recent_notifications/Notification.py

  • Committer: Jason Conti
  • Date: 2010-02-18 20:17:33 UTC
  • Revision ID: jason.conti@gmail.com-20100218201733-i671lnpp0g78nyco
Adding template for empty icon.

Show diffs side-by-side

added added

removed removed

Lines of Context:
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
17
 
from locale import nl_langinfo, T_FMT
18
 
 
19
 
import Timestamp
20
 
 
21
 
from Icon import load_icon, load_icon_from_file
22
 
 
23
 
logger = logging.getLogger("Notification")
24
 
 
25
 
class ImageDataException(Exception):
26
 
  pass
27
 
 
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:
32
 
      raise ImageDataException("Invalid image_data: " + repr(image_data))
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
 
 
53
 
class MessageException(Exception):
54
 
  pass
55
 
 
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
63
 
  X_CANONICAL_PRIVATE_SYNCHRONOUS = "x-canonical-private-synchronous"
64
 
  def __init__(self, dbus_message = None, timestamp = None):
65
 
    gobject.GObject.__init__(self)
66
 
 
67
 
    self.timestamp = timestamp
68
 
 
69
 
    args = dbus_message.get_args_list()
70
 
 
71
 
    if len(args) != 8:
72
 
      raise MessageException("Invalid message args_list: " + repr(args))
73
 
 
74
 
    self.app_name = str(args[0])
75
 
    self.replaces_id = args[1]
76
 
    self.app_icon = str(args[2])
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]
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
 
 
94
 
    if "image_data" in self.hints:
95
 
      self.image_data = ImageData(self.hints["image_data"])
96
 
    else:
97
 
      self.image_data = None
98
 
 
99
 
    if self.image_data == None and "icon_data" in self.hints:
100
 
      self.image_data = ImageData(self.hints["icon_data"])
101
 
 
102
 
    self.log_message()
103
 
 
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."""
107
 
    icon_name = self.app_icon
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
 
 
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
 
 
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 is_volume_notification(self):
137
 
    """Returns true if this is a volume message. The volume notifications
138
 
    in Ubuntu are a special case, and mostly a blank message. Clutters up
139
 
    the display and provides no useful information, so it is better to
140
 
    discard them."""
141
 
    if Message.X_CANONICAL_PRIVATE_SYNCHRONOUS in self.hints:
142
 
      return str(self.hints[Message.X_CANONICAL_PRIVATE_SYNCHRONOUS]) == "volume"
143
 
 
144
 
  def log_message(self):
145
 
    """Write debug info about a message."""
146
 
    result = [
147
 
        "-" * 50,
148
 
        "Message created at: " + Timestamp.locale_datetime(self.timestamp),
149
 
        "app_name: " + repr(self.app_name),
150
 
        "replaces_id: " + repr(self.replaces_id),
151
 
        "app_icon: " + repr(self.app_icon),
152
 
        "summary: " + repr(self.summary),
153
 
        "body: " + repr(self.body),
154
 
        "actions: " + repr(self.actions),
155
 
        "expire_timeout: " + repr(self.expire_timeout),
156
 
        "hints:"
157
 
        ]
158
 
 
159
 
    # Log all the hints except image_data
160
 
    for key in self.hints:
161
 
      if key != "image_data":
162
 
        result.append("  " + str(key) + ": " + repr(self.hints[key]))
163
 
 
164
 
    # Log info about the image_data
165
 
    if self.image_data != None:
166
 
      result.append("image_data:")
167
 
      result.append("  width: " + repr(self.image_data.width))
168
 
      result.append("  height: " + repr(self.image_data.height))
169
 
      result.append("  rowstride: " + repr(self.image_data.rowstride))
170
 
      result.append("  has_alpha: " + repr(self.image_data.has_alpha))
171
 
      result.append("  bits_per_sample: " + repr(self.image_data.bits_per_sample))
172
 
      result.append("  channels: " + repr(self.image_data.channels))
173
 
 
174
 
    result.append("-" * 50)
175
 
 
176
 
    logger.debug("\n" + "\n".join(result))
177
 
 
178
 
class Notification(gobject.GObject):
179
 
  """Monitors DBUS for org.freedesktop.Notifications.Notify messages, parses them,
180
 
  and notifies listeners when they arrive."""
181
 
  __gsignals__ = {
182
 
      "message-received": (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, [Message])
183
 
  }
184
 
  def __init__(self):
185
 
    gobject.GObject.__init__(self)
186
 
 
187
 
    self._blacklist = None
188
 
    self._match_string = "type='method_call',interface='org.freedesktop.Notifications',member='Notify'"
189
 
 
190
 
    DBusGMainLoop(set_as_default=True)
191
 
    self._bus = dbus.SessionBus()
192
 
    self._bus.add_match_string(self._match_string)
193
 
    self._bus.add_message_filter(self._message_filter)
194
 
 
195
 
  def close(self):
196
 
    """Closes the connection to the session bus."""
197
 
    self._bus.close()
198
 
 
199
 
  def set_blacklist(self, blacklist):
200
 
    """Defines the set of app_names of messages to be discarded."""
201
 
    self._blacklist = blacklist
202
 
 
203
 
  def _message_filter(self, connection, dbus_message):
204
 
    """Triggers when messages are received from the session bus."""
205
 
    if dbus_message.get_member() == "Notify" and dbus_message.get_interface() == "org.freedesktop.Notifications":
206
 
      try:
207
 
        message = Message(dbus_message, Timestamp.now())
208
 
      except:
209
 
        logger.exception("Failed to parse dbus message: " + repr(dbus_message.get_args_list()))
210
 
      else:
211
 
        # Discard unwanted messages
212
 
        if message.is_volume_notification():
213
 
          return
214
 
        if self._blacklist and self._blacklist.get_bool(message.app_name, False):
215
 
          return
216
 
        glib.idle_add(self.emit, "message-received", message)
217
 
 
218
 
if gtk.pygtk_version < (2, 8, 0):
219
 
  gobject.type_register(Message)
220
 
  gobject.type_register(Notification)
221
 
 
222
 
if __name__ == '__main__':
223
 
  main()