Skip to content


Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
branch: master
Fetching contributors…

Cannot retrieve contributors at this time

executable file 176 lines (153 sloc) 6.531 kb
#!/usr/bin/env python
# usage:
# growlme <command>
# Runs <command> in a subshell, and notifies growl of success or failure
# error codes on completion. The success/failure messages can be customized
# on the command line. The growl remote password can be supplied on the
# command line or provided in a '~/.growlpass' file.
# The first (largest) section of this code is Rui Carmo's
# Copyright 2004 Rui Carmo <>
# Copyright 2010 Greg Brown <>
# Copyright 2010 Robey Pointer <>
# Licensed under the Apache License, Version 2.0 (the "License"); you may not
# use this file except in compliance with the License. You may obtain a copy
# of the License at
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
import optparse
import os
import socket
import struct
import subprocess
import sys
import hashlib
md5_constructor = hashlib.md5
except ImportError:
import md5
md5_constructor =
GROWL_PASSWORD = "password"
class GrowlRegistrationPacket:
Builds a Growl Network Registration packet.
Defaults to emulating the command-line growlnotify utility.
def __init__(self, application="growlnotify", password=None):
self.notifications = []
self.defaults = [] # array of indexes into notifications
self.application = application.encode("utf-8")
self.password = password
def addNotification(self, notification="Command-Line Growl Notification", enabled=True):
"""Adds a notification type and sets whether it is enabled on the GUI"""
if enabled:
self.defaults.append(len(self.notifications) - 1)
def payload(self):
"""Returns the packet payload.""" = struct.pack("!BBH", GROWL_PROTOCOL_VERSION, GROWL_TYPE_REGISTRATION, len(self.application)) += struct.pack("BB", len(self.notifications), len(self.defaults)) += self.application
for notification in self.notifications:
encoded = notification.encode("utf-8") += struct.pack("!H", len(encoded)) += encoded
for default in self.defaults: += struct.pack("B", default)
self.checksum = md5_constructor()
if self.password:
self.checksum.update(self.password) += self.checksum.digest()
class GrowlNotificationPacket:
Builds a Growl Network Notification packet.
Defaults to emulating the command-line growlnotify utility.
def __init__(self, application="growlnotify",
notification="Command-Line Growl Notification", title="Title",
description="Description", priority=0, sticky=False, password=None):
self.application = application.encode("utf-8")
self.notification = notification.encode("utf-8")
self.title = title.encode("utf-8")
self.description = description.encode("utf-8")
flags = (priority & 0x07) * 2
if priority < 0:
flags |= 0x08
if sticky:
flags = flags | 0x0100 = struct.pack("!BBHHHHH", GROWL_PROTOCOL_VERSION, GROWL_TYPE_NOTIFICATION, flags,
len(self.notification), len(self.title), len(self.description),
len(self.application)) += self.notification += self.title += self.description += self.application
self.checksum = md5_constructor()
if password:
self.checksum.update(password) += self.checksum.digest()
def payload(self):
"""Returns the packet payload."""
def send_notification(host, password, title, message):
addr = (host, GROWL_UDP_PORT)
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
p = GrowlRegistrationPacket(application="growlme", password=password)
p.addNotification("Build Notification", enabled=True)
s.sendto(p.payload(), addr)
p = GrowlNotificationPacket(application="growlme", notification="growlme", title=title, description=message, priority=1, password=password)
s.sendto(p.payload(), addr)
if __name__ == '__main__':
growlpass = open(os.path.expanduser("~/.growlpass"), 'r').readline().strip()
growlpass = GROWL_PASSWORD
sshclient = os.environ.get('SSH_CLIENT', 'localhost')
clientip = sshclient.split()[0]
parser = optparse.OptionParser(usage='usage: %prog [options] <command...>')
parser.add_option("-H", "--host", dest='host', default=clientip)
parser.add_option("-P", "--password", dest='password', default=growlpass)
parser.add_option("-m", "--message", dest='success_text', metavar='TEXT', help='message to display on success')
parser.add_option("--fail", dest='fail_text', metavar='TEXT', help='message to display on failure')
parser.add_option("-t", "--title", dest='title', help='growl title')
(opts, args) = parser.parse_args()
if not args:
parser.error("must provide a command to execute")
if opts.title is None:
opts.title = socket.getfqdn() + ": " + " ".join(args)
if opts.success_text is None:
opts.success_text = "Succeeded"
if opts.fail_text is None:
opts.fail_text = "FAILED"
process = subprocess.Popen(args, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, bufsize=-1)
while True:
data =
if data == "":
exit_code = process.wait()
if exit_code == 0:
message = opts.success_text
message = opts.fail_text
send_notification(, opts.password, opts.title, message)
Jump to Line
Something went wrong with that request. Please try again.