Mac::Growl::Net


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;