Bindings/python/Growl.py
author Bjoern Haeuser
Sat Apr 04 17:40:32 2009 +0200 (2009-04-04)
changeset 4183 2e72fc363066
parent 4182 69b5347d8262
permissions -rw-r--r--
Fixed the deprecation warning about using md5 module,
switched to the suggested hashlib module
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
Bjoern@4183
    17
import hashlib
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):
Bjoern@4183
   132
		checksum = hashlib.md5()
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)