|
aranor@203
|
1 |
"""
|
|
aranor@692
|
2 |
A Python module that enables posting notifications to the Growl daemon.
|
|
tick@1181
|
3 |
See <http://growl.info/> for more information.
|
|
aranor@203
|
4 |
"""
|
|
toby@1004
|
5 |
__version__ = "0.7"
|
|
toby@1004
|
6 |
__author__ = "Mark Rowe <bdash@users.sourceforge.net>"
|
|
toby@1004
|
7 |
__copyright__ = "(C) 2003 Mark Rowe <bdash@users.sourceforge.net>. Released under the BSD license."
|
|
toby@1004
|
8 |
__contributors__ = ["Ingmar J Stein (Growl Team)",
|
|
boredzo@3900
|
9 |
"Rui Carmo (http://the.taoofmac.com)",
|
|
boredzo@3900
|
10 |
"Jeremy Rossi <jeremy@jeremyrossi.com>",
|
|
boredzo@3679
|
11 |
"Peter Hosey <http://boredzo.org/> (Growl Team)",
|
|
boredzo@3900
|
12 |
]
|
|
toby@1004
|
13 |
|
|
boredzo@3679
|
14 |
import _growl
|
|
toby@1004
|
15 |
import types
|
|
toby@1004
|
16 |
import struct
|
|
toby@1004
|
17 |
import md5
|
|
toby@1004
|
18 |
import socket
|
|
toby@1004
|
19 |
|
|
toby@1004
|
20 |
GROWL_UDP_PORT=9887
|
|
toby@1004
|
21 |
GROWL_PROTOCOL_VERSION=1
|
|
toby@1004
|
22 |
GROWL_TYPE_REGISTRATION=0
|
|
toby@1004
|
23 |
GROWL_TYPE_NOTIFICATION=1
|
|
toby@1004
|
24 |
|
|
toby@1004
|
25 |
GROWL_APP_NAME="ApplicationName"
|
|
toby@1004
|
26 |
GROWL_APP_ICON="ApplicationIcon"
|
|
toby@1004
|
27 |
GROWL_NOTIFICATIONS_DEFAULT="DefaultNotifications"
|
|
toby@1004
|
28 |
GROWL_NOTIFICATIONS_ALL="AllNotifications"
|
|
toby@1004
|
29 |
GROWL_NOTIFICATIONS_USER_SET="AllowedUserNotifications"
|
|
toby@1004
|
30 |
|
|
toby@1004
|
31 |
GROWL_NOTIFICATION_NAME="NotificationName"
|
|
toby@1004
|
32 |
GROWL_NOTIFICATION_TITLE="NotificationTitle"
|
|
toby@1004
|
33 |
GROWL_NOTIFICATION_DESCRIPTION="NotificationDescription"
|
|
toby@1004
|
34 |
GROWL_NOTIFICATION_ICON="NotificationIcon"
|
|
toby@1004
|
35 |
GROWL_NOTIFICATION_APP_ICON="NotificationAppIcon"
|
|
toby@1004
|
36 |
GROWL_NOTIFICATION_PRIORITY="NotificationPriority"
|
|
boredzo@3900
|
37 |
|
|
toby@1004
|
38 |
GROWL_NOTIFICATION_STICKY="NotificationSticky"
|
|
toby@1004
|
39 |
|
|
toby@1004
|
40 |
GROWL_APP_REGISTRATION="GrowlApplicationRegistrationNotification"
|
|
toby@1004
|
41 |
GROWL_APP_REGISTRATION_CONF="GrowlApplicationRegistrationConfirmationNotification"
|
|
ingmarstein@2603
|
42 |
GROWL_NOTIFICATION="GrowlNotification"
|
|
toby@1004
|
43 |
GROWL_SHUTDOWN="GrowlShutdown"
|
|
toby@1004
|
44 |
GROWL_PING="Honey, Mind Taking Out The Trash"
|
|
toby@1004
|
45 |
GROWL_PONG="What Do You Want From Me, Woman"
|
|
toby@1004
|
46 |
GROWL_IS_READY="Lend Me Some Sugar; I Am Your Neighbor!"
|
|
toby@1004
|
47 |
|
|
boredzo@3900
|
48 |
|
|
aranor@692
|
49 |
growlPriority = {"Very Low":-2,"Moderate":-1,"Normal":0,"High":1,"Emergency":2}
|
|
aranor@692
|
50 |
|
|
toby@1004
|
51 |
class netgrowl:
|
|
boredzo@3900
|
52 |
"""Builds a Growl Network Registration packet.
|
|
boredzo@3900
|
53 |
Defaults to emulating the command-line growlnotify utility."""
|
|
boredzo@3900
|
54 |
|
|
boredzo@3900
|
55 |
__notAllowed__ = [GROWL_APP_ICON, GROWL_NOTIFICATION_ICON, GROWL_NOTIFICATION_APP_ICON]
|
|
boredzo@3900
|
56 |
|
|
boredzo@3900
|
57 |
def __init__(self, hostname, password ):
|
|
boredzo@3900
|
58 |
self.hostname = hostname
|
|
boredzo@3900
|
59 |
self.password = password
|
|
boredzo@3900
|
60 |
self.socket = socket.socket(socket.AF_INET,socket.SOCK_DGRAM)
|
|
boredzo@3900
|
61 |
|
|
boredzo@3900
|
62 |
def send(self, data):
|
|
boredzo@3900
|
63 |
self.socket.sendto(data, (self.hostname, GROWL_UDP_PORT))
|
|
boredzo@3900
|
64 |
|
|
boredzo@3900
|
65 |
def PostNotification(self, userInfo):
|
|
boredzo@3900
|
66 |
if userInfo.has_key(GROWL_NOTIFICATION_PRIORITY):
|
|
boredzo@3900
|
67 |
priority = userInfo[GROWL_NOTIFICATION_PRIORITY]
|
|
boredzo@3900
|
68 |
else:
|
|
boredzo@3900
|
69 |
priority = 0
|
|
boredzo@3900
|
70 |
if userInfo.has_key(GROWL_NOTIFICATION_STICKY):
|
|
boredzo@3900
|
71 |
sticky = userInfo[GROWL_NOTIFICATION_STICKY]
|
|
boredzo@3900
|
72 |
else:
|
|
rudy@3928
|
73 |
sticky = False
|
|
boredzo@3900
|
74 |
data = self.encodeNotify(userInfo[GROWL_APP_NAME],
|
|
boredzo@3900
|
75 |
userInfo[GROWL_NOTIFICATION_NAME],
|
|
boredzo@3900
|
76 |
userInfo[GROWL_NOTIFICATION_TITLE],
|
|
boredzo@3900
|
77 |
userInfo[GROWL_NOTIFICATION_DESCRIPTION],
|
|
boredzo@3900
|
78 |
priority,
|
|
boredzo@3900
|
79 |
sticky)
|
|
boredzo@3900
|
80 |
return self.send(data)
|
|
boredzo@3900
|
81 |
|
|
boredzo@3900
|
82 |
def PostRegistration(self, userInfo):
|
|
boredzo@3900
|
83 |
data = self.encodeRegistration(userInfo[GROWL_APP_NAME],
|
|
boredzo@3900
|
84 |
userInfo[GROWL_NOTIFICATIONS_ALL],
|
|
boredzo@3900
|
85 |
userInfo[GROWL_NOTIFICATIONS_DEFAULT])
|
|
boredzo@3900
|
86 |
return self.send(data)
|
|
boredzo@3900
|
87 |
|
|
boredzo@3900
|
88 |
def encodeRegistration(self, application, notifications, defaultNotifications):
|
|
boredzo@3900
|
89 |
data = struct.pack("!BBH",
|
|
boredzo@3900
|
90 |
GROWL_PROTOCOL_VERSION,
|
|
boredzo@3900
|
91 |
GROWL_TYPE_REGISTRATION,
|
|
boredzo@3900
|
92 |
len(application) )
|
|
boredzo@3900
|
93 |
data += struct.pack("BB",
|
|
boredzo@3900
|
94 |
len(notifications),
|
|
boredzo@3900
|
95 |
len(defaultNotifications) )
|
|
boredzo@3900
|
96 |
data += application
|
|
boredzo@3900
|
97 |
for i in notifications:
|
|
boredzo@3900
|
98 |
encoded = i.encode("utf-8")
|
|
boredzo@3900
|
99 |
data += struct.pack("!H", len(encoded))
|
|
boredzo@3900
|
100 |
data += encoded
|
|
boredzo@3900
|
101 |
for i in defaultNotifications:
|
|
boredzo@3900
|
102 |
data += struct.pack("B", i)
|
|
boredzo@3900
|
103 |
return self.encodePassword(data)
|
|
boredzo@3900
|
104 |
|
|
boredzo@3900
|
105 |
def encodeNotify(self, application, notification, title, description,
|
|
boredzo@3900
|
106 |
priority = 0, sticky = False):
|
|
boredzo@3900
|
107 |
|
|
boredzo@3900
|
108 |
application = application.encode("utf-8")
|
|
boredzo@3900
|
109 |
notification = notification.encode("utf-8")
|
|
boredzo@3900
|
110 |
title = title.encode("utf-8")
|
|
boredzo@3900
|
111 |
description = description.encode("utf-8")
|
|
boredzo@3900
|
112 |
flags = (priority & 0x07) * 2
|
|
boredzo@3900
|
113 |
if priority < 0:
|
|
boredzo@3900
|
114 |
flags |= 0x08
|
|
boredzo@3900
|
115 |
if sticky:
|
|
boredzo@3900
|
116 |
flags = flags | 0x0001
|
|
boredzo@3900
|
117 |
data = struct.pack("!BBHHHHH",
|
|
boredzo@3900
|
118 |
GROWL_PROTOCOL_VERSION,
|
|
boredzo@3900
|
119 |
GROWL_TYPE_NOTIFICATION,
|
|
boredzo@3900
|
120 |
flags,
|
|
boredzo@3900
|
121 |
len(notification),
|
|
boredzo@3900
|
122 |
len(title),
|
|
boredzo@3900
|
123 |
len(description),
|
|
boredzo@3900
|
124 |
len(application) )
|
|
boredzo@3900
|
125 |
data += notification
|
|
boredzo@3900
|
126 |
data += title
|
|
boredzo@3900
|
127 |
data += description
|
|
boredzo@3900
|
128 |
data += application
|
|
boredzo@3900
|
129 |
return self.encodePassword(data)
|
|
boredzo@3900
|
130 |
|
|
boredzo@3900
|
131 |
def encodePassword(self, data):
|
|
boredzo@3900
|
132 |
checksum = md5.new()
|
|
boredzo@3900
|
133 |
checksum.update(data)
|
|
boredzo@3900
|
134 |
if self.password:
|
|
boredzo@3900
|
135 |
checksum.update(self.password)
|
|
boredzo@3900
|
136 |
data += checksum.digest()
|
|
boredzo@3900
|
137 |
return data
|
|
toby@1004
|
138 |
|
|
aranor@1007
|
139 |
class _ImageHook(type):
|
|
boredzo@3900
|
140 |
def __getattribute__(self, attr):
|
|
boredzo@3900
|
141 |
global Image
|
|
boredzo@3900
|
142 |
if Image is self:
|
|
boredzo@3900
|
143 |
from _growlImage import Image
|
|
boredzo@3900
|
144 |
|
|
boredzo@3900
|
145 |
return getattr(Image, attr)
|
|
aranor@1007
|
146 |
|
|
aranor@1007
|
147 |
class Image(object):
|
|
boredzo@3900
|
148 |
__metaclass__ = _ImageHook
|
|
aranor@1007
|
149 |
|
|
aranor@1007
|
150 |
class _RawImage(object):
|
|
boredzo@3900
|
151 |
def __init__(self, data): self.rawImageData = data
|
|
toby@1004
|
152 |
|
|
aranor@203
|
153 |
class GrowlNotifier(object):
|
|
boredzo@3900
|
154 |
"""
|
|
boredzo@3900
|
155 |
A class that abstracts the process of registering and posting
|
|
boredzo@3900
|
156 |
notifications to the Growl daemon.
|
|
boredzo@3900
|
157 |
|
|
boredzo@3900
|
158 |
You can either pass `applicationName', `notifications',
|
|
boredzo@3900
|
159 |
`defaultNotifications' and `applicationIcon' to the constructor
|
|
boredzo@3900
|
160 |
or you may define them as class-level variables in a sub-class.
|
|
boredzo@3900
|
161 |
|
|
boredzo@3900
|
162 |
`defaultNotifications' is optional, and defaults to the value of
|
|
boredzo@3900
|
163 |
`notifications'. `applicationIcon' is also optional but defaults
|
|
boredzo@3900
|
164 |
to a pointless icon so is better to be specified.
|
|
boredzo@3900
|
165 |
"""
|
|
boredzo@3900
|
166 |
|
|
boredzo@3900
|
167 |
applicationName = 'GrowlNotifier'
|
|
boredzo@3900
|
168 |
notifications = []
|
|
boredzo@3900
|
169 |
defaultNotifications = []
|
|
boredzo@3900
|
170 |
applicationIcon = None
|
|
boredzo@3900
|
171 |
_notifyMethod = _growl
|
|
boredzo@3900
|
172 |
|
|
boredzo@3900
|
173 |
def __init__(self, applicationName=None, notifications=None, defaultNotifications=None, applicationIcon=None, hostname=None, password=None):
|
|
boredzo@3901
|
174 |
if applicationName:
|
|
boredzo@3901
|
175 |
self.applicationName = applicationName
|
|
Peter@4182
|
176 |
assert self.applicationName, 'An application name is required.'
|
|
boredzo@3901
|
177 |
|
|
boredzo@3901
|
178 |
if notifications:
|
|
boredzo@3901
|
179 |
self.notifications = list(notifications)
|
|
Peter@4182
|
180 |
assert self.notifications, 'A sequence of one or more notification names is required.'
|
|
boredzo@3901
|
181 |
|
|
boredzo@3900
|
182 |
if defaultNotifications is not None:
|
|
boredzo@3900
|
183 |
self.defaultNotifications = list(defaultNotifications)
|
|
boredzo@3901
|
184 |
elif not self.defaultNotifications:
|
|
boredzo@3900
|
185 |
self.defaultNotifications = list(self.notifications)
|
|
boredzo@3900
|
186 |
|
|
boredzo@3900
|
187 |
if applicationIcon is not None:
|
|
boredzo@3900
|
188 |
self.applicationIcon = self._checkIcon(applicationIcon)
|
|
boredzo@3901
|
189 |
elif self.applicationIcon is not None:
|
|
boredzo@3901
|
190 |
self.applicationIcon = self._checkIcon(self.applicationIcon)
|
|
boredzo@3900
|
191 |
|
|
boredzo@3900
|
192 |
if hostname is not None and password is not None:
|
|
boredzo@3900
|
193 |
self._notifyMethod = netgrowl(hostname, password)
|
|
boredzo@3900
|
194 |
elif hostname is not None or password is not None:
|
|
boredzo@3900
|
195 |
raise KeyError, "Hostname and Password are both required for a network notification"
|
|
boredzo@3900
|
196 |
|
|
boredzo@3900
|
197 |
def _checkIcon(self, data):
|
|
boredzo@3900
|
198 |
if isinstance(data, str):
|
|
boredzo@3900
|
199 |
return _RawImage(data)
|
|
boredzo@3900
|
200 |
else:
|
|
boredzo@3900
|
201 |
return data
|
|
boredzo@3900
|
202 |
|
|
boredzo@3900
|
203 |
def register(self):
|
|
boredzo@3900
|
204 |
if self.applicationIcon is not None:
|
|
boredzo@3900
|
205 |
self.applicationIcon = self._checkIcon(self.applicationIcon)
|
|
boredzo@3900
|
206 |
|
|
boredzo@3900
|
207 |
regInfo = {GROWL_APP_NAME: self.applicationName,
|
|
boredzo@3900
|
208 |
GROWL_NOTIFICATIONS_ALL: self.notifications,
|
|
boredzo@3900
|
209 |
GROWL_NOTIFICATIONS_DEFAULT: self.defaultNotifications,
|
|
boredzo@3900
|
210 |
GROWL_APP_ICON:self.applicationIcon,
|
|
boredzo@3900
|
211 |
}
|
|
boredzo@3900
|
212 |
self._notifyMethod.PostRegistration(regInfo)
|
|
boredzo@3900
|
213 |
|
|
boredzo@3900
|
214 |
def notify(self, noteType, title, description, icon=None, sticky=False, priority=None):
|
|
boredzo@3900
|
215 |
assert noteType in self.notifications
|
|
boredzo@3900
|
216 |
notifyInfo = {GROWL_NOTIFICATION_NAME: noteType,
|
|
boredzo@3900
|
217 |
GROWL_APP_NAME: self.applicationName,
|
|
boredzo@3900
|
218 |
GROWL_NOTIFICATION_TITLE: title,
|
|
boredzo@3900
|
219 |
GROWL_NOTIFICATION_DESCRIPTION: description,
|
|
boredzo@3900
|
220 |
}
|
|
boredzo@3900
|
221 |
if sticky:
|
|
boredzo@3900
|
222 |
notifyInfo[GROWL_NOTIFICATION_STICKY] = 1
|
|
boredzo@3900
|
223 |
|
|
boredzo@3900
|
224 |
if priority is not None:
|
|
boredzo@3900
|
225 |
notifyInfo[GROWL_NOTIFICATION_PRIORITY] = priority
|
|
boredzo@3900
|
226 |
|
|
boredzo@3900
|
227 |
if icon:
|
|
boredzo@3900
|
228 |
notifyInfo[GROWL_NOTIFICATION_ICON] = self._checkIcon(icon)
|
|
boredzo@3900
|
229 |
|
|
boredzo@3900
|
230 |
self._notifyMethod.PostNotification(notifyInfo)
|