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