#!/usr/bin/perl -w use Tk; use Tk::widgets qw/Balloon NoteBook LabFrame/; use Mail::IMAPClient; $title = 'spliff v0.2'; $about = "A mail-watching utility\n\nJustin R. Miller\n"; $prefsfile = $ENV{'HOME'} . '/.spliff'; @prefsarray = ('server','username','password','mailbox','interval','limit','beep','boldfont','mediumfont','x','y'); @defaults = ('','','','INBOX','15','5','1','Times 10 bold','Times 10 normal','0','0'); &loadprefs; $mainwindow = MainWindow->new; $mainwindow->title($title); $mainwindow->geometry('+' . $prefs{'x'} . '+' . $prefs{'y'}); $mainwindow->resizable(0, 0); $mainwindow->protocol(WM_DELETE_WINDOW, \&savegeometryexit); $mainwindow->bind("", \&update); $mainwindow->bind("", sub { $mainwindow->withdraw() } ); $mainwindow->bind("", \&editprefs); $size = $prefs{'limit'}; &dotopframe; &domiddleframe; &dobottomframe; $status = 'Connecting...'; $oldcount = 0; if ($newuser) { $mainwindow->withdraw(); &editprefs; } else { $mainwindow->after(1000, \&update); $mainwindow->repeat($prefs{'interval'} * 60000, \&update); } MainLoop; sub loadprefs { if (-e $prefsfile) { open(PREFS, "<$prefsfile") || die("Unable to read preferences file $prefsfile"); $readloop = 0; while () { chop($prefs{$prefsarray[$readloop]} = $_); $readloop++; } close(PREFS); foreach $pref (@prefsarray) { $saved{$pref} = $live{$pref} = $prefs{$pref}; } } else { for ($defaultloop = 0; $defaultloop < scalar(@prefsarray); $defaultloop++) { $prefs{$prefsarray[$defaultloop]} = $live{$prefsarray[$defaultloop]} = $saved{$prefsarray[$defaultloop]} = $defaults[$defaultloop]; } $newuser = 1; } } sub savegeometryexit { if (!$newuser) { $geometry = $mainwindow->geometry(); $geometry =~ s/\d+x\d+\+//; ($prefs{'x'}, $prefs{'y'}) = split(/\+/, $geometry); &saveprefs; } exit; } sub dotopframe { $topframe = $mainwindow->Frame(-relief => 'raised', -borderwidth => 1)->pack(); $topframe->Label(-text => 'From', -font => $prefs{'boldfont'}, -width => 15, -padx => 5, -pady => 0, -anchor => 'w')->pack(-side => 'left'); $topframe->Label(-text => 'Subject', -font => $prefs{'boldfont'}, -width => 25, -padx => 5, -pady => 0, -anchor => 'w')->pack(-side => 'left'); $topframe->Label(-text => 'Date', -font => $prefs{'boldfont'}, -width => 20, -padx => 5, -pady => 0, -anchor => 'w')->pack(-side => 'left'); } sub domiddleframe { $middleframe = $mainwindow->Frame()->pack(); for ($loop = $size; $loop > 0; $loop--) { $fromcolumn = $middleframe->Label(-textvariable => \$from[$loop], -font => $prefs{'mediumfont'}, -width => 15, -padx => 5, -pady => 0, -anchor => 'w'); $subjectcolumn = $middleframe->Label(-textvariable => \$subject[$loop], -font => $prefs{'mediumfont'}, -width => 25, -padx => 5, -pady => 0, -anchor => 'w'); $datecolumn = $middleframe->Label(-textvariable => \$date[$loop], -font => $prefs{'mediumfont'}, -width => 20, -padx => 5, -pady => 0, -anchor => 'w'); $fromcolumn->grid($subjectcolumn, $datecolumn); } $helpballoon = $middleframe->Balloon(-balloonposition => 'mouse'); $helpballoon->attach($middleframe, -msg => " Update\n Hide\n Preferences", -initwait => 1000); } sub dobottomframe { $bottomframe = $mainwindow->Frame(-relief => 'raised', -borderwidth => 1)->pack(-fill => 'x'); $bottomframe->Label(-textvariable => \$status, -font => $prefs{'mediumfont'}, -pady => 0)->pack(); } sub resize { $middleframe->destroy() if (Exists($middleframe)); $bottomframe->destroy() if (Exists($bottomframe)); &domiddleframe; &dobottomframe; $resized = 0; } sub update { if (!$prefs{'server'} || !$prefs{'username'} || !$prefs{'password'} || !$prefs{'mailbox'}) { $status = 'Invalid server configuration'; return; } $imap = Mail::IMAPClient->new(Server => $prefs{'server'}, User => $prefs{'username'}, Password => $prefs{'password'}); if (!$imap) { $status = 'Unable to connect to ' . $prefs{'server'}; return; } $count = $imap->message_count($prefs{'mailbox'}); if ($count < $size) { $size = $count; $shown = $count; &resize; } else { $shown = $prefs{'limit'}; } $status = "Most recent $shown of $count messages in " . $prefs{'mailbox'}; if ($count < 1) { $status = 'There are no messages in ' . $prefs{'mailbox'}; $size = 0; &resize; } $last = $count - $prefs{'limit'}; $last = 0 if ($count < $prefs{'limit'}); for ($loop = $count; $loop > $last; $loop--) { $imap->examine($prefs{'mailbox'}); $info = $imap->parse_headers($loop, 'From', 'Subject', 'Date'); $message = $loop - $last; $from[$message] = $info->{'From'}[0]; $from[$message] =~ s/(.*) <.*/$1/; $from[$message] =~ s/^\"(.*)\"$/$1/; $subject[$message] = $info->{'Subject'}[0]; $date[$message] = $info->{'Date'}[0]; $date[$message] =~ s/\w{3}, (.*\d{4} \d{2}:\d{2}:\d{2}) \-\d{4}/$1/; } $imap->disconnect; if ($count > $oldcount) { $mainwindow->bell() if $prefs{'beep'}; $mainwindow->deiconify(); $mainwindow->raise(); $oldcount = $count; } } sub editprefs { if (!$prefswindow) { $prefswindow = $mainwindow->Toplevel(); $prefswindow->title('spliff preferences'); $prefswindow->resizable(0, 0); $prefswindow->protocol(WM_DELETE_WINDOW, \&cancelprefs); $prefsbook = $prefswindow->NoteBook()->pack(-expand => 1, -fill => 'both', -padx => 5, -pady => 2); $serverpage = $prefsbook->add('server', -label => 'Server'); $serverframe = $serverpage->LabFrame(-label => 'IMAP server info', -labelside => 'acrosstop')->pack(-expand => 1, -fill => 'both'); $serverframe->Label(-text => 'Server', -justify => 'left', -anchor => 'w')->grid($serverframe->Entry(-textvariable => \$live{'server'}), -pady => 3, -sticky => 'nsew'); $serverframe->Label(-text => 'Username', -justify => 'left', -anchor => 'w')->grid($serverframe->Entry(-textvariable => \$live{'username'}), -pady => 3, -sticky => 'nsew'); $serverframe->Label(-text => 'Password', -justify => 'left', -anchor => 'w')->grid($serverframe->Entry(-textvariable => \$live{'password'}, -show => '*'), -pady => 3, -sticky => 'nsew'); $serverframe->Label(-text => 'Mailbox', -justify => 'left', -anchor => 'w')->grid($serverframe->Entry(-textvariable => \$live{'mailbox'}), -pady => 3, -sticky => 'nsew'); $behaviorpage = $prefsbook->add('behavior', -label => 'Behavior'); $behaviorintervalframe = $behaviorpage->LabFrame(-label => 'Update interval in minutes', -labelside => 'acrosstop')->pack(-expand => 1, -fill => 'both'); $behaviorintervalframe->Scale(-from => 1, -to => 30, -orient => 'horizontal', -variable => \$live{'interval'})->pack(-fill => 'both', -padx => 5, -pady => 2); $behaviorlimitframe = $behaviorpage->LabFrame(-label => 'Number of messages to show', -labelside => 'acrosstop')->pack(-expand => 1, -fill => 'x'); $behaviorlimitframe->Scale(-from => 1, -to => 10, -orient => 'horizontal', -variable => \$live{'limit'}, -command => sub { $resized = 1 } )->pack(-fill => 'both', -padx => 5, -pady => 2); $behaviorbeepframe = $behaviorpage->LabFrame(-label => 'Beep on new mail', -labelside => 'acrosstop')->pack(-expand => 1, -fill => 'x'); $behaviorbeepframe->Radiobutton(-text => 'Yes', -variable => \$live{'beep'}, -value => 1)->pack(-anchor => 'n', -side => 'left', -expand => '1', -fill => 'both'); $behaviorbeepframe->Radiobutton(-text => 'No', -variable => \$live{'beep'}, -value => 0)->pack(-anchor => 'n', -side => 'left', -expand => '1', -fill => 'both'); $fontspage = $prefsbook->add('fonts', -label => 'Fonts'); $fontsheadingframe = $fontspage->LabFrame(-label => 'Heading font', -labelside => 'acrosstop')->pack(-expand => 1, -fill => 'both'); $fontsheadingframe->Entry(-textvariable => \$live{'boldfont'})->pack(-expand => 1, -fill => 'x', -padx => 5, -pady => 5); $fontsmessageframe = $fontspage->LabFrame(-label => 'Message font', -labelside => 'acrosstop')->pack(-expand => 1, -fill => 'both'); $fontsmessageframe->Entry(-textvariable => \$live{'mediumfont'})->pack(-expand => 1, -fill => 'x', -padx => 5, -pady => 5); $fontspage->Label(-text => 'Font changes take effect after restart')->pack(-expand => 1, -fill => 'x', -padx => 5); $aboutpage = $prefsbook->add('about', -label => 'About'); $aboutframe = $aboutpage->LabFrame(-label => $title, -labelside => 'acrosstop')->pack(-expand => 1, -fill => 'both'); $aboutframe->Label(-text => $about, -justify => 'left')->pack(-expand => 1, -fill => 'both', padx => 5, -pady => 5); $prefsbottomframe = $prefswindow->Frame()->pack(-expand => 1, -fill => 'x'); $prefsbottomframe->Button(-text => 'Ok', -command => \&okprefs, -width => 6)->pack(-side => 'left', -expand => 1, -fill => 'x', -padx => 10, -pady => 5); $prefsbottomframe->Button(-text => 'Apply', -command => \&applyprefs, -width => 6)->pack(-side => 'left', -expand => 1, -fill => 'x', -padx => 10, -pady => 5); $prefsbottomframe->Button(-text => 'Cancel', -command => \&cancelprefs, -width => 6)->pack(-side => 'left', -expand => 1, -fill => 'x', -padx => 10, -pady => 5); } else { if ($prefswindow->state() eq 'withdrawn') { $prefsbook->raise('server'); } $prefswindow->deiconify(); $prefswindow->raise(); } } sub okprefs { &applyprefs; &saveprefs; $prefswindow->withdraw(); } sub applyprefs { foreach $pref (@prefsarray) { $prefs{$pref} = $live{$pref}; } $mainwindow->deiconify(); $mainwindow->raise(); if ($resized) { $size = $prefs{'limit'}; &resize; } &update; } sub cancelprefs { if ($newuser) { exit; } foreach $pref (@prefsarray) { $prefs{$pref} = $live{$pref} = $saved{$pref}; } if ($size < $prefs{'limit'}) { $size = $prefs{'limit'}; &resize; } else { $resized = 0; } $prefswindow->withdraw(); &update; } sub saveprefs { foreach $pref (@prefs) { $saved{$pref} = $live{$pref} = $prefs{$pref}; } open(PREFS, ">$prefsfile") || die("Unable to write preferences file $prefsfile"); chmod(0600, $prefsfile); for ($saveloop = 0; $saveloop < scalar(@prefsarray); $saveloop++) { print PREFS "$prefs{$prefsarray[$saveloop]}\n"; } close(PREFS); $newuser = 0 if ($newuser); }