I recently caught a glimpse of how Gmail Notifier works on a friend’s Mac. It looked pretty cool. Unfortunately for me, though, there’s no reasonable facsimile in Linux. Sure, there are a couple options, but they aren’t available in Gentoo’s package management system. Given my recent experience dealing with E-mail from Perl, I figured it would be just as easy to write my own E-mail notifier as it would be to manually install these programs (along with their dependencies). I was right. I just spent the last ~20 minutes (while idling through a meeting) writing such an app. The code follows below. Its only dependency is XOSD.
Disclaimer: I blatantly cribbed some of my code from Flavio Poletti (for the MTA stuff) and Bill Luebkert (for the password input).
Future work: right now the code simply polls the mail server once every three minutes. In the future I’ll post an update that uses IMAP Idle to reduce bandwidth.
#!/usr/bin/perl -w use Term::ReadKey; END { ReadMode ('restore'); } # just in case use Mail::IMAPClient; use IO::Socket::SSL; use File::HomeDir; my $username = '[email protected]'; my $sleeptime = 180; # Time between checks, in seconds. my $conffile = File::HomeDir->my_home . "/.checkmail"; ###################################################### $canceled = 0; $inwhile = 0; sub get_passwd { # legal clear passwd chrs (26+26+10+24=86): "a-zA-Z0-9!#$%&()*+,-./:;<=> ?@[\]^"; my @legal_clear = ('a'..'z', 'A'..'Z', '0'..'9', split //, '!#$%&()*+,-./:;<=> ?@[\]^'); my %legal_clear; foreach (@legal_clear) { $legal_clear{$_} = 1; } $| = 1; # unbuffer stdout to force unterminated line out ReadMode ('cbreak'); my $ch = ''; while (defined ($ch = ReadKey ())) { last if $ch eq "\x0D" or $ch eq "\x0A"; if ($ch eq "\x08") { # backspace print "\b \b" if $passwd; # back up 1 chop $passwd; next; } if ($ch eq "\x15") { # ^U print "\b \b" x length $passwd; # back 1 for each char $passwd = ''; next; } if (not exists $legal_clear{$ch}) { print "\n'$ch' not a legal password character\n"; print 'Password: '; next; } $passwd .= $ch; } print "\n"; ReadMode ('restore'); return $passwd; } $SIG{'INT'} = 'INT_handler'; sub INT_handler { exit(0) if(!$inwhile); $canceled = 1; print "\nCaught Signal; exiting gracefully!\n"; } print "Password: "; my $password = &get_passwd(); while(!$canceled) { $inwhile = 1; my $socket = IO::Socket::SSL->new( PeerAddr => 'imap.gmail.com', PeerPort => 993, ) or (print STDERR "Warning: lost internet connection!\n" && next); # Perhaps we lost the internet connection? my $greeting = <$socket>; my ($id, $answer) = split /\s+/, $greeting; die "problems logging in: $greeting" if $answer ne 'OK'; my $client = Mail::IMAPClient->new( Socket => $socket, User => $username, Password => $password, Uid => 1, ) or die "new(): $@"; $client->State(Mail::IMAPClient::Connected()); $client->login() or die 'login(): ' . $client->LastError(); die("Failed authentication!\n") unless $client->IsAuthenticated(); $client->examine('INBOX') or die "Could not examine: $@\n"; my @msgs = $client->unseen or die "Could not search the inbox! $@\n"; my $last_max = -2; if(-e $conffile) { # Load the old largest open(CONFFILE, "<" . $conffile) or die("Error opening " . $conffile . "\n"); while() { my $line = $_; $last_max = $1 if($line =~ /^\s*last_max_uid\s*=\s*(\d+)\s*$/i); } close(CONFFILE); } my $max = -1; my @over; for my $msg (@msgs) { $max = $msg if $msg > $max; push(@over, $msg) if $msg > $last_max; } if($max >= 0) { open(CONFFILE, ">" . $conffile) or die("Error opening $conffile for writing!\n"); print CONFFILE "last_max_uid = " . $max . "\n"; close(CONFFILE); } if($last_max >= 0) { open(OSDC, "| osd_cat -c green -p middle -A center -s 2 -l 5 -f \"-bitstream-bitstream vera serif-*-*-*-*-17-*-*-*-*-*-*-*\""); for my $m (@over) { my $hashref = $client->parse_headers($m, "From") or die "Could not parse_headers: $@\n"; print OSDC "New mail from " . $hashref->{"From"}->[0] . "!\n"; } close(OSDC); } $client->logout(); sleep $sleeptime; }