Mail Notifier

Gmail Notifications in Linux

Tagged: Software

Screenshot of the notifier notifying.

An example of the notifier, well, notifying.

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 = ‘youremail@domain.com’;
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;
}
← Older Post Blog Archive Newer Post →