Perl programmer for hire: download my resume (PDF).
John Bokma MexIT
freelance Perl programmer

Snarl with Perl on Microsoft Windows

Wednesday, January 17, 2007 | 1 comment

Today, using Google, I found Snarl, a program inspired by Growl (Mac OS X) that allows applications to display notifications on the Windows desktop. Communication with Snarl is done via the window messaging functionality (SendMessage), making it accessible to any programming language. And after installing, for example Win32::GUI, this should include Perl. I had been wondering for some time if such a program was available for Windows, and today I decided to actively search for such a program and I was rewarded.

I studied the C++ example that comes with the Snarl installation and are located default in C:\Program Files\full phat\Snarl\sdk\include\C++. Most of the work of actually sending a message to Snarl was done in the send method (SnarlInterface.cpp):

long SnarlInterface::send(SNARLSTRUCT snarlStruct) {
    HWND hWnd = FindWindow(NULL, "Snarl");
    if (IsWindow(hWnd)) {
        COPYDATASTRUCT cds;
        cds.dwData = 2;
        cds.cbData = sizeof(snarlStruct);
        cds.lpData = &snarlStruct;
        LRESULT lr = SendMessage(hWnd, WM_COPYDATA, 0, (LPARAM)&cds);
        if (lr) {
            return lr;
        }
    }
    return 0;
}

In order to create a similar method in Perl I needed to be able to create data structures binary identical to COPYDATASTRUCT and SNARLSTRUCT in Perl. And Perl has a function perfectly suited to do this: pack. But the template for pack, which defines how the data should be packed into a string, have to be picked with great care. In short, I needed exact information on the data types used.

COPYDATASTRUCT

I was able to find the definition of the COPYDATASTRUCT structure on Microsoft's MSDN site:

typedef struct tagCOPYDATASTRUCT {
    ULONG_PTR dwData;
    DWORD cbData;
    PVOID lpData;
} COPYDATASTRUCT, *PCOPYDATASTRUCT;

Next I used MSDN's search to look up information on each type in the struct and found the following:

So I came up with the template 'L2P' for pack, two times unsigned long (exactly 32 bit each) followed by a pointer to a structure (fixed-length string):

my $copy_data_struct = pack( 'L2P', 2, length( $snarl_struct ), $snarl_struct );

SNARLSTRUCT

The data structure SNARLSTRUCT is defined in SnarlInterface.h as follows:

struct SNARLSTRUCT {
    SNARL_COMMANDS cmd;
    long id;
    long timeout;
    long lngData2;
    char title[SNARL_STRING_LENGTH];
    char text[SNARL_STRING_LENGTH];
    char icon[SNARL_STRING_LENGTH];
};

with SNARL_COMMANDS an enum and SNARL_STRING_LENGTH defined as 1024.

Based on the information given in Data Type Ranges a long is 32 bit and since that page also states that "(in) C++ ... enumeration constants and values of enumerated types are expressed in terms of type int." the value of cmd also requires 32 bit.

So I came up with the template 'l4a1024a1024a1024' for pack, four times a signed long (both an int and a long are 32 bit), followed by three zero padded strings each 1024 bytes long:

my $snarl_struct = pack( 'l4a1024a1024a1024',

        $command,
        $id,
        $timeout,
        $data,
        substr( $title,     0, SNARL_STRING_LENGTH - 1 ),
        substr( $text,      0, SNARL_STRING_LENGTH - 1 ),
        substr( $icon_path, 0, SNARL_STRING_LENGTH - 1 ),
    );

Wrapping it all up: Win32::Snarl

After a simple test program showed that I could Snarl via Perl, I decided to make a simple Perl module called Win32::Snarl. Note that later this month I hope to turn this in a more complete module, including POD, and probably will make the module available via CPAN.

package Win32::Snarl;

use strict;
use warnings;

use Win32::GUI;
use constant WM_COPYDATA => 0x4a;

use constant SNARL_STRING_LENGTH => 1024;

use constant SNARL_SHOW   => 1;
use constant SNARL_UPDATE => 3;


sub new {

    my $class = shift;

    my $self = {};

    bless $self, $class;
    return $self;
}


sub show_message {

    my ( $self, $title, $text, $timeout,
         $icon_path, $window_handle_reply, $reply_message ) = @_;

    return $self->_send(

        SNARL_SHOW, $reply_message, $timeout, $window_handle_reply,
        $title, $text, $icon_path
    );
}


sub update_message {

    my ( $self, $id, $title, $text ) = @_;

    $self->_send( SNARL_UPDATE, $id, 0, 0, $title, $text );
}


sub _send {

    my ( $self, $command, $id, $timeout, $data,
         $title, $text, $icon_path ) = @_;

    my $snarl_window_handle = Win32::GUI::FindWindow( '', 'Snarl' );
    Win32::GUI::IsWindow( $snarl_window_handle ) or return 0;

    $id      ||= 0;
    $timeout ||= 0;
    $data    ||= 0;

    defined $title     or $title = '';
    defined $text      or $text  = '';
    defined $icon_path or $icon_path = '';

    my $snarl_struct = pack( 'l4a1024a1024a1024',

        $command,
        $id,
        $timeout,
        $data,
        substr( $title,     0, SNARL_STRING_LENGTH - 1 ),
        substr( $text,      0, SNARL_STRING_LENGTH - 1 ),
        substr( $icon_path, 0, SNARL_STRING_LENGTH - 1 ),
    );

    my $copy_data_struct = pack( 'L2P', 2, length( $snarl_struct ), $snarl_struct );

    return Win32::GUI::SendMessage(

        $snarl_window_handle,
        WM_COPYDATA,
        0,
        $copy_data_struct
    );
}


1;

Copy the above code into a file called Snarl.pm and save this file into a folder called Win32.

use strict;
use warnings;

use Win32::Snarl;

my $snarl = Win32::Snarl->new;

my $id = $snarl->show_message( 'Snarling with Perl', 'Hello, world!' );

sleep( 10 );

$snarl->update_message( $id, "Snarling with Perl", 'Updated' );

The above Perl code snippet shows how to use the Win32::Snarl module to send notifications to Snarl.

Related

Also today

Please post a comment | read 1 comment by sli | RSS feed