Beefy Boxes and Bandwidth Generously Provided by pair Networks
Syntactic Confectionery Delight
 
PerlMonks  

Lily: Yet Another New Mail Notification Utility

by itodd (Acolyte)
on Mar 26, 2003 at 05:25 UTC ( #245867=CUFP: print w/replies, xml ) Need Help??

I feel this bit of perl is worthy enough to be posted here. This is my first post so if I commit a faux pas, please don't be too upset.

If any of you have ever used The Bat mail agent on Windows, you'll be familiar with it's way of scrolling new email headers across the screen. Lily copies that idea.

Patches welcomed.

After taking the advice of ybiC, I've implemented Getopt::Long and Pod::UsageMan into lily.pl. I am also posting the source inline:

Updated: 3/29/03:

I rewrote about 40% of the code to be more effecient. All of the known display bugs are gone.

Updated: 4/13/03:

Lily won't die when she can't login to the POP3 server. Now you don't need to babysit the process.

#!/usr/bin/perl # # Lily 1.2.3 by Todd Boland <itodd@itodd.org> # Biff was named after a dog (who barked at the mail man). # My mail notification utility is named after my dog, Lily. use strict; use Tk; # Widget set use Net::POP3; # To connect to POP3 server use Getopt::Long; # To parse the arguments use Pod::Usage; # For the manual # The pop3 password is sent via command line, hide it! $0 = "lily.pl"; # Variables used in runtime my ($main,@frames,@buffer,%showing,$width); my $stamp_pixmap = do { local $/; <DATA> }; # These variables are for buffer rotation my ($offscreen,$lock); # Variable needed to emulate double click my $next; # Variables used for controlling ticker with the mouse my ($click,$position); # Default optional arguments my $opt_bg = "#000000"; my $opt_fg = "#00FF00"; my $opt_interval= 60; my $opt_timeout = 30; # Optional arguments my($opt_usage, $opt_manual, $opt_agent, $opt_bark, $opt_font, $opt_geo +metry); # Required arguments my($arg_username, $arg_password, $arg_server); # GetOptions parses the arguments from ARGV for me (Thanks ybiC@perlmo +nks) :) GetOptions( "usage!" => \$opt_usage, "help!" => \$opt_usage, "manual!" => \$opt_manual, "username=s" => \$arg_username, "password=s" => \$arg_password, "server=s" => \$arg_server, "timeout=i" => \$opt_timeout, "fg=s" => \$opt_fg, "bg=s" => \$opt_bg, "agent=s" => \$opt_agent, "bark=s" => \$opt_bark, "font=s" => \$opt_font, "geometry=s" => \$opt_geometry, "interval=i" => \$opt_interval, "width=i" => \$width, # Maintain backwards compatibility "sound=s" => \$opt_bark, ); # Should we print the manual or usage? pod2usage(-verbose => 1) and exit if(defined $opt_usage); pod2usage(-verbose => 2) and exit if(defined $opt_manual); # Print usage and exit if the required arguments were left out pod2usage(-verbose => 1, -message => "Required arguments missing.") an +d exit if(!$arg_username || !$arg_password || !$arg_server); # Convert from seconds to ms $opt_interval = $opt_interval . ("0" x 3); # We don't like defunct zombies $SIG{"CHLD"} = 'IGNORE'; # Create main window $main = new MainWindow( -title => "Lily", -bg => $opt_bg, -width => 640, -height => 35, ); # Configure the main window $main->overrideredirect(1); # No decor $main->bind("<Visibility>" => sub { $main->raise }); # Always on to +p $main->withdraw; # Initially hide it $main->Pixmap('stamp', data => $stamp_pixmap); # Create pixmap # What happens when button 1 is pressed $main->bind("<ButtonPress-1>" => sub { $position = $main->pointerx - $main->x; $click = 1; }); # What happens when button 1 is released $main->bind("<ButtonRelease-1>" => sub { $click = 0; }); # What happens when the mouse moves inside the window $main->bind("<Motion>" => sub { if($click) { &move_ticker($main->pointerx - $main->x - $position); $position = $main->pointerx - $main->x; } }); # Update the main window $main->update; # Resize if geometry specified $main->geometry($opt_geometry) if($opt_geometry); # Check mail timer $main->repeat($opt_interval, sub { &check_mail; }); # We need an initial call to check the mail upon starting &check_mail; # Move the ticker $main->repeat(20, sub { return if $click or !defined $frames[0]; &move_ticker(-1); }); # Main program loop MainLoop; # Routine to rotate messages in the buffer to the display sub rotate_buffer { my($offscreen,$headers) = @_; my($new_from,$new_subject) = @{$headers}; # Fetch old headers my($stamp,$label) = $frames[$offscreen]->children; my($old_subject,$old_from) = split(/\n/, $label->cget("-text")); # Put new headers into the label $label->configure( -text => "$new_subject\n$new_from" ); [$old_from, $old_subject]; } # Routine to move the entire ticker sub move_ticker { my $movement = shift; # This prevents underrun return if defined $lock; $lock = 1; # To the right? if($movement > 0) { if($frames[0]->x + $movement > $main->width) { # Move the left side $frames[0]->place(-x => $frames[0]->x - ($main->width + $f +rames[0]->width) + $movement); # Ratate if there are messages in the buffer push(@buffer, &rotate_buffer(0, shift @buffer)) if $#buffer >= 0; } else { # Move along $frames[0]->place(-x => $frames[0]->x + $movement); } # To the left? } elsif($movement < 0) { if($frames[0]->x + $frames[0]->width < 0) { # Move to right side $frames[0]->place(-x => $frames[0]->x + $frames[0]->width ++ $main->width + $movement); # Rotate if there are messages in the buffer unshift(@buffer, &rotate_buffer(0, pop @buffer)) if $#buffer >= 0; } else { # Move along $frames[0]->place(-x => $frames[0]->x + $movement); } } # Update the first frame so we can get the new x coords $frames[0]->update; my $new_x = $frames[0]->x + $width; # Every frame should follow 256 pixels apart for(my $i=1; $i<=$#frames; $i++) { # Figure new x for frame $new_x = $new_x - $main->width - $width if($new_x >= $main->width); # Should we rotate the buffer? if($#buffer >= 0) { if($frames[$i]->x > 0 && $new_x < 0 && $movement > 0) { push @buffer, &rotate_buffer($i, shift @buffer); } elsif($frames[$i]->x < 0 && $new_x > 0 && $movement < 0) { unshift @buffer, &rotate_buffer($i, pop @buffer); } } # Place frame, get new x coord and update $frames[$i]->place(-x => $new_x); $new_x += $width; $frames[$i]->update; } # unlock the routine $lock = undef; } # The routine that checks for new mail sub check_mail { my($new_messages, $bark); # Login to POP3 server my $pop = new Net::POP3( $arg_server, timeout => $opt_timeout, ) or do { warn "Invalid POP3 server."; return; }; # Count the number of messages waiting $new_messages = $pop->login($arg_username, $arg_password) or do { warn "POP3 login failed."; return; }; # List messages for(my $i=1; $i<=$new_messages; $i++) { my($from,$subject,$id); # Fetch headers foreach my $header ($pop->top($i)) { foreach(@{$header}) { # Net::POP3 doesnt chomp for us chomp; # Only snag the first 43 chars to keep em short $from = substr($_,0,43) if(/^(From):\s+/i); $subject= substr($_,0,43) if(/^(Subject):\s+/i); $id = $1 if(/^Message-Id:\s*(.*)$/i); } } # Add ... if string is longer than the allowed length $subject = substr($subject,0,40) . "..." if(length($subject) > + 40); $from = substr($from,0,40) . "..." if(length($from) > 40); # Unless the item is already showing unless($showing{$id}) { # Don't show it again! $showing{$id} = 1; # Did it fit? if(&add_frame($from, $subject) == -1) { # No, put it in the buffer unshift @buffer, [$from, $subject]; } # Toggle barking $bark = 1; } } # Close pop connection $pop->quit(); # Bark? if($opt_bark && $bark) { my $pid = fork; if($pid == 0) { # Die after barking exec($opt_bark); exit; } } } # Routine to add message to display sub add_frame { my($from,$subject) = @_; my($x,$stamp,$label); # Bring up main window $main->deiconify; $main->raise; # Where oh where shall we put it? $x = ($#frames >= 0) ? $width * ($#frames + 1) : 0; # Put it in the buffer return -1 if($x >= $main->width + $width); # Create frame push @frames, $main->Frame( -height => $main->height, -bg => $opt_bg, )->place(-x => $x); # Create Stamp $stamp = $frames[$#frames]->Label( -image => 'stamp', -bg => $opt_bg, )->place(-x=>0,-y=>0); # Configure stamp $stamp->bind("<ButtonRelease-1>" => \&release ) if($opt_agent); $stamp->configure( -cursor => "hand2") if ($opt_agent); # Create label $label = $frames[$#frames]->Label( -justify => "left", -text => "$subject\n$from", -fg => $opt_fg, -bg => $opt_bg, -width => 38, -anchor => "w", )->place(-x=>0,-y=>0); # Configure label $label->configure( -font=> $opt_font) if($opt_font); $label->bind("<ButtonRelease-1>" => \&release ) if($opt_agent); $label->configure( -cursor => "hand2") if ($opt_agent); # Re-configure the frame $frames[$#frames]->update; $width = $label->width + $stamp->width if !defined $width; $frames[$#frames]->configure(-width=>$width); $frames[$#frames]->update; # Place label $label->place( -x => $stamp->width, -y => $frames[$#frames]->height / 2 - $label->height / 2, ); # Place stamp $stamp->place( -x => 0, -y => $frames[$#frames]->height / 2 - $stamp->height / 2, ); } # Mouse release routine sub release { # Cancel any moves $click = 0; # Emulate a double click since Double-ButtonPress # doesn't work on moving labels if(!$next) { # Double click rate is 250ms $main->after(250, sub { $next = 0; }); $next = 1; # It's only the first click, return return; } $next = 0; # Destroy the active frames while(my $frame = pop @frames) { my @children = $frame->children; foreach(@children) { $_->destroy(); } $frame->destroy(); } # Return buffer and frames arays to their defaults undef @buffer; undef @frames; # Launch email client? if($opt_agent) { my $pid = fork; if($pid == 0) { # Launch the mail client exec($opt_agent); exit; } $main->withdraw; } } # The pod =head1 NAME lily.pl =head1 SYNOPSIS lily.pl [-option=VALUE ...] B<-username=USERNAME -password=PASSWORD -s +erver=SERVER> =head1 DESCRIPTION Scrolls From: and Subject: headers of all email found on SERVER using +USERNAME and PASSWORD as the login information. =head1 ARGUMENTS =item B<-server=SERVER> Use SERVER as the POP3 server =item B<-username=USERNAME> Use USERNAME for username on SERVER =item B<-password=PASSWORD> Use PASSWORD for password on SERVER =head1 OPTIONS =item -timeout=SECONDS Timeout after SECONDS seconds =item -fg=COLOR Use COLOR for the foreground =item -bg=COLOR Use COLOR for the background =item -agent=COMMAND Execute COMMAND to launch mail agent =item -bark=COMMAND Execute COMMAND on new mail =item -font=FONT Use FONT to display headers =item -width=PIXELS Make each message PIXELS pixels wide =item -geometry=GEOMETRY Use GEOMETRY to map window =item -interval=SECONDS Check mail every SECONDS seconds =item -manual Display the manual =item -usage Show usage screen =head1 EXAMPLE This is my configuration: lily.pl -bark="plaympeg /home/itodd/newmail.mp3" -agent="mozilla -remo +te xfeDoCommand\(openInbox\) || mozilla -mail" -interval=30 -font="-* +-helvetica-medium-r-*-*-10-*-*-*-*-*-*-*" -geometry="1280x30+0-0" -wi +dth=256 B<-username=USERNAME -password=PASSWORD -server=SERVER> =head1 AUTHOR Todd Boland <itodd@itodd.org> http://www.itodd.org =cut # The stamp xpm __DATA__ /* XPM */ static char * stamp_xpm[] = { "16 16 3 1", " c None", "# c #FFFFFF", "% c #0000FF", " ", " ## ## ## ## ", " ############## ", " #%%%%%%%%%%# ", " #%%%%##%%%%# ", " ##%%%####%%%## ", " ##%%######%%## ", " #%%%%##%%%%# ", " #%%%%##%%%%# ", " ##%%%%##%%%%## ", " ##%%%%##%%%%## ", " #%%%%%%%%%%# ", " #%%%%%%%%%%# ", " ############## ", " ## ## ## ## ", " "};

Replies are listed 'Best First'.
Re: Lily: Yet Another New Mail Notification Utility (onsite code, Getopt::Long, Pod::Usage)
by ybiC (Prior) on Mar 26, 2003 at 18:23 UTC
      Hiya brer itodd,
    First off, welcome to the Monastery, and thanks for sharing your perlish efforts.   8^)

    A couple thoughts come to mind, yo.   You'll likely get more+better response by including your Lily code in the post itself rather than offsite-linking to it.   Within <code> tags and after a <readmore> tag, and without syntax highlighting too, as it doesn't play friendly with the different Monastery color themes.   Not a big huge hairy deal, rather a courtesy to all the fine monks.

    If you send a private /msg to one or two of the editors, they'd probably let you update your post thusly.

    Re: the code itself:   I don't pretend to be any kind of expert perler to critique, but one thing comes to mind - "The Dynamic Duo --or-- Holy Getopt::Long, Pod::UsageMan!" might could do some nice for the Usage and Arguments sections near the top.
      cheers,
      ybiC
        striving toward Perl Adept
        (it's pronounced "why-bick")
      I took your advice (thanks a ton, I hadn't yet heard of Getopt::Long). The source has been updated.

Log In?
Username:
Password:

What's my password?
Create A New User
Domain Nodelet?
Node Status?
node history
Node Type: CUFP [id://245867]
Approved by ybiC
help
Chatterbox?
and the web crawler heard nothing...

How do I use this? | Other CB clients
Other Users?
Others surveying the Monastery: (6)
As of 2022-05-26 11:56 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?
    Do you prefer to work remotely?



    Results (93 votes). Check out past polls.

    Notices?