Nathan McFarland did a Perl port of my PHP netgrowl code that is much more in the Perl spirit that I would ever be bothered to do and was kind enough to allow me to host a copy for easy reference. The original is here, and the latest version is on CPAN under Net-Growl.
# Derived from Rui Carmo's (http://the.taoofmac.com) # netgrowl.php (http://the.taoofmac.com/space/Projects/netgrowl.php) # by Nathan McFarland - (http://nmcfarl.org) # Make sure this is installed as Mac/Growl/Net.pm # in a location list in @INC. # If you do not perl will not be able to find it # To view your @INC run `perl -V` package Mac::Growl::Net; use strict; use IO::Socket; use Exporter; our @ISA = qw(Exporter); our @EXPORT = ('register', 'notify'); use constant GROWL_UDP_PORT=>9887; use constant GROWL_PROTOCOL_VERSION=>1; use constant GROWL_TYPE_REGISTRATION=>0; use constant GROWL_TYPE_NOTIFICATION=>1; sub register{ my %args = @_; my %addr = (PeerAddr => $args{host} || "localhost", PeerPort => Mac::Growl::Net::GROWL_UDP_PORT, Proto => 'udp'); my $s = IO::Socket::INET->new ( %addr ) || die "Could not create socket: $!\n" ; my $p = Mac::Growl::Net::RegistrationPacket->new(%args); $p->addNotification(); print ($s $p->payload()); close($s); } sub notify{ my %args = @_; my %addr = (PeerAddr => $args{host} || "localhost", PeerPort => Mac::Growl::Net::GROWL_UDP_PORT, Proto => 'udp'); my $s = IO::Socket::INET->new ( %addr ) || die "Could not create socket: $!\n" ; my $p = Mac::Growl::Net::NotificationPacket->new( %args); print ($s $p->payload()); close($s); } 1; package Mac::Growl::Net::RegistrationPacket; use Digest::MD5 qw( md5_hex ); use base 'Mac::Growl::Net'; sub new { my $class = shift; my %args = @_; $args{application} ||="growlnotify"; my $self = bless \%args, $class; $self->{notifications} = {}; utf8::encode($self->{application} ); return $self; } sub addNotification{ my ($self, %args)=@_; $args{notification}||="Command-Line Growl Notification"; $args{enabled}="True" if !defined $args{enabled} ; $self->{notifications}->{$args{notification}}=$args{enabled}; } sub payload{ my $self = shift; my ($name, $defaults); my ($name_count, $defaults_count); for (keys %{$self->{notifications}}) { utf8::encode( $_ ); $name .= pack( "n", length($_) ) . $_; $name_count++; if( $self->{notifications}->{$_} ) { $defaults .= pack( "c", $name_count-1 ); $defaults_count++; } } $self->{data} = pack( "c2nc2", $self->GROWL_PROTOCOL_VERSION, $self->GROWL_TYPE_REGISTRATION, length($self->{application}), $name_count, $defaults_count ); $self->{data} .= $self->{application} . $name . $defaults; my $checksum; if( $self->{password} ){ $checksum = pack( "H32", md5_hex( $self->{data} . $self->{password} ) ); }else { $checksum = pack( "H32", md5_hex( $self->{data} )); } $self->{data} .= $checksum; return $self->{data}; } 1; package Mac::Growl::Net::NotificationPacket; use Digest::MD5 qw( md5_hex ); use base 'Mac::Growl::Net'; sub new { my ($class,%args ) = @_; $args{application} ||="growlnotify"; $args{notification} ||="Command-Line Growl Notification"; $args{title} ||="Title"; $args{description} ||="Description"; $args{priority} ||= 0; my $self = bless \%args, $class; utf8::encode($self->{application}); utf8::encode($self->{notification}); utf8::encode($self->{title}); utf8::encode($self->{description}); my $flags = ($args{priority} & 0x07) * 2; if ($args{priority} < 0){ $flags |= 0x08; } if ($args{sticky}){ $flags = $flags | 0x0001; } $self->{data} = pack( "c2n5", $self->GROWL_PROTOCOL_VERSION, $self->GROWL_TYPE_NOTIFICATION, $flags, length($self->{notification}), length($self->{title}), length($self->{description}), length($self->{application}) ); $self->{data} .= $self->{notification}; $self->{data} .= $self->{title}; $self->{data} .= $self->{description}; $self->{data} .= $self->{application}; if ($args{password}) { $self->{checksum} = pack( "H32", md5_hex( $self->{data} . $args{password} ) ); } else { $self->{checksum} = pack( "H32", md5_hex( $self->{data}) ); } $self->{data} .= $self->{checksum}; return $self; } sub payload{ my $self = shift; return $self->{data}; } 1; package main; # only when called directly # perl Mac/Growl/Net.pm if ($0 =~ m{Mac/Growl/Net.pm}) { $0="mac_growl_net_tests"; my %addr = (PeerAddr => "localhost", PeerPort => Mac::Growl::Net::GROWL_UDP_PORT, Proto => 'udp'); my $s = IO::Socket::INET->new ( %addr ) || die "Could not create socket: $!\n" ; my $p = Mac::Growl::Net::RegistrationPacket->new( application=>"Perl Notifier"); $p->addNotification(); print ($s $p->payload()); $p = Mac::Growl::Net::NotificationPacket->new( application=>"Perl Notifier", title=>'warning', description=>'some text', priority=>2, sticky=>'True'); print ($s $p->payload()); close($s); ## or the easy way -- more sockets are created though # when outside the module you can just do # use Mac::Growl::Net; # register(); # notify(); Mac::Growl::Net::register(); Mac::Growl::Net::notify(); } 1;