"""
A Python module that enables posting notifications to the Growl daemon.
See for more information.
"""
__version__ = "0.7"
__author__ = "Mark Rowe "
__copyright__ = "(C) 2003 Mark Rowe . Released under the BSD license."
__contributors__ = ["Ingmar J Stein (Growl Team)",
"Rui Carmo (http://the.taoofmac.com)",
"Jeremy Rossi "
]
try:
import _growl
except:
_growl = False
import types
import struct
import md5
import socket
GROWL_UDP_PORT=9887
GROWL_PROTOCOL_VERSION=1
GROWL_TYPE_REGISTRATION=0
GROWL_TYPE_NOTIFICATION=1
GROWL_APP_NAME="ApplicationName"
GROWL_APP_ICON="ApplicationIcon"
GROWL_NOTIFICATIONS_DEFAULT="DefaultNotifications"
GROWL_NOTIFICATIONS_ALL="AllNotifications"
GROWL_NOTIFICATIONS_USER_SET="AllowedUserNotifications"
GROWL_NOTIFICATION_NAME="NotificationName"
GROWL_NOTIFICATION_TITLE="NotificationTitle"
GROWL_NOTIFICATION_DESCRIPTION="NotificationDescription"
GROWL_NOTIFICATION_ICON="NotificationIcon"
GROWL_NOTIFICATION_APP_ICON="NotificationAppIcon"
GROWL_NOTIFICATION_PRIORITY="NotificationPriority"
GROWL_NOTIFICATION_STICKY="NotificationSticky"
GROWL_NOTIFICATION_CLICK_CONTEXT="NotificationClickContext"
GROWL_APP_REGISTRATION="GrowlApplicationRegistrationNotification"
GROWL_APP_REGISTRATION_CONF="GrowlApplicationRegistrationConfirmationNotification"
GROWL_NOTIFICATION="GrowlNotification"
GROWL_SHUTDOWN="GrowlShutdown"
GROWL_PING="Honey, Mind Taking Out The Trash"
GROWL_PONG="What Do You Want From Me, Woman"
GROWL_IS_READY="Lend Me Some Sugar; I Am Your Neighbor!"
GROWL_NOTIFICATION_CLICKED="GrowlClicked!"
GROWL_NOTIFICATION_TIMED_OUT="GrowlTimedOut!"
GROWL_KEY_CLICKED_CONTEXT="ClickedContext"
growlPriority = {"Very Low":-2,"Moderate":-1,"Normal":0,"High":1,"Emergency":2}
class netgrowl:
"""Builds a Growl Network Registration packet.
Defaults to emulating the command-line growlnotify utility."""
__notAllowed__ = [GROWL_APP_ICON, GROWL_NOTIFICATION_ICON, GROWL_NOTIFICATION_APP_ICON]
def __init__(self, hostname, password ):
self.hostname = hostname
self.password = password
self.socket = socket.socket(socket.AF_INET,socket.SOCK_DGRAM)
def send(self, data):
self.socket.sendto(data, (self.hostname, GROWL_UDP_PORT))
def PostNotification(self, userInfo):
if userInfo.has_key(GROWL_NOTIFICATION_PRIORITY):
priority = userInfo[GROWL_NOTIFICATION_PRIORITY]
else:
priority = 0
if userInfo.has_key(GROWL_NOTIFICATION_STICKY):
sticky = userInfo[GROWL_NOTIFICATION_STICKY]
else:
priority = False
data = self.encodeNotify(userInfo[GROWL_APP_NAME],
userInfo[GROWL_NOTIFICATION_NAME],
userInfo[GROWL_NOTIFICATION_TITLE],
userInfo[GROWL_NOTIFICATION_DESCRIPTION],
priority,
sticky)
return self.send(data)
def PostRegistration(self, userInfo):
data = self.encodeRegistration(userInfo[GROWL_APP_NAME],
userInfo[GROWL_NOTIFICATIONS_ALL],
userInfo[GROWL_NOTIFICATIONS_DEFAULT])
return self.send(data)
def encodeRegistration(self, application, notifications, defaultNotifications):
data = struct.pack("!BBH",
GROWL_PROTOCOL_VERSION,
GROWL_TYPE_REGISTRATION,
len(application) )
data += struct.pack("BB",
len(notifications),
len(defaultNotifications) )
data += application
for i in notifications:
encoded = i.encode("utf-8")
data += struct.pack("!H", len(encoded))
data += encoded
for i in defaultNotifications:
data += struct.pack("B", i)
return self.encodePassword(data)
def encodeNotify(self, application, notification, title, description,
priority = 0, sticky = False):
application = application.encode("utf-8")
notification = notification.encode("utf-8")
title = title.encode("utf-8")
description = description.encode("utf-8")
flags = (priority & 0x07) * 2
if priority < 0:
flags |= 0x08
if sticky:
flags = flags | 0x0001
data = struct.pack("!BBHHHHH",
GROWL_PROTOCOL_VERSION,
GROWL_TYPE_NOTIFICATION,
flags,
len(notification),
len(title),
len(description),
len(application) )
data += notification
data += title
data += description
data += application
return self.encodePassword(data)
def encodePassword(self, data):
checksum = md5.new()
checksum.update(data)
if self.password:
checksum.update(self.password)
data += checksum.digest()
return data
class _ImageHook(type):
def __getattribute__(self, attr):
global Image
if Image is self:
from _growlImage import Image
return getattr(Image, attr)
class Image(object):
__metaclass__ = _ImageHook
class _RawImage(object):
def __init__(self, data): self.rawImageData = data
class GrowlNotifier(object):
"""
A class that abstracts the process of registering and posting
notifications to the Growl daemon.
You can either pass `applicationName', `notifications',
`defaultNotifications' and `applicationIcon' to the constructor
or you may define them as class-level variables in a sub-class.
`defaultNotifications' is optional, and defaults to the value of
`notifications'. `applicationIcon' is also optional but defaults
to a pointless icon so is better to be specified.
"""
applicationName = 'GrowlNotifier'
notifications = []
defaultNotifications = []
applicationIcon = None
_notifyMethod = _growl
_notify_cb = None
def __init__(self, applicationName=None, notifications=None, defaultNotifications=None, applicationIcon=None, hostname=None, password=None, notify_cb=None):
assert(applicationName is not None, 'an application name is required')
self.applicationName = applicationName
assert(notifications, 'a sequence of one or more notification names is required')
self.notifications = list(notifications)
if defaultNotifications is not None:
self.defaultNotifications = list(defaultNotifications)
else:
self.defaultNotifications = list(self.notifications)
if applicationIcon is not None:
self.applicationIcon = self._checkIcon(applicationIcon)
if hostname is not None and password is not None:
self._notifyMethod = netgrowl(hostname, password)
elif hostname is not None or password is not None:
raise KeyError, "Hostname and Password are both required for a network notification"
if notify_cb is not None:
self._notify_cb = notify_cb
else:
self._notify_cb = self.notifyCB
if hostname is None and password is None:
self._notifyMethod.Init(applicationName, self._notify_cb)
def _checkIcon(self, data):
if isinstance(data, str):
return _RawImage(data)
else:
return data
def register(self):
if self.applicationIcon is not None:
self.applicationIcon = self._checkIcon(self.applicationIcon)
regInfo = {GROWL_APP_NAME: self.applicationName,
GROWL_NOTIFICATIONS_ALL: self.notifications,
GROWL_NOTIFICATIONS_DEFAULT: self.defaultNotifications,
GROWL_APP_ICON:self.applicationIcon,
}
self._notifyMethod.PostRegistration(regInfo)
def notify(self, noteType, title, description, icon=None, sticky=False, priority=None, context=None):
assert noteType in self.notifications
notifyInfo = {GROWL_NOTIFICATION_NAME: noteType,
GROWL_APP_NAME: self.applicationName,
GROWL_NOTIFICATION_TITLE: title,
GROWL_NOTIFICATION_DESCRIPTION: description,
}
if sticky:
notifyInfo[GROWL_NOTIFICATION_STICKY] = 1
if priority is not None:
notifyInfo[GROWL_NOTIFICATION_PRIORITY] = priority
if icon:
notifyInfo[GROWL_NOTIFICATION_ICON] = self._checkIcon(icon)
if context:
notifyInfo[GROWL_NOTIFICATION_CLICK_CONTEXT] = context
self._notifyMethod.PostNotification(notifyInfo)
def notifyCB(self, userdata):
print "Got notify in pyland", userdata
# vim: se ts=3: