~jconti/recent-notifications/trunk

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()