#!/usr/bin/perl -w ################################################################################# # # spliff v0.3 (11 January 2000) # # Written by Justin R. Miller # # http://openup.com/justin/software/spliff/ # # This software is released as freeware; feel free to modify it # to your liking. I would appreciate it if you would let me know # of any changes or suggestions that you have. # ################################################################################# use Tk; use Tk::widgets qw(ErrorDialog Balloon NoteBook LabFrame FBox); use Mail::IMAPClient; $title = 'spliff v0.3'; $about = "A mail-watching utility\n\nJustin R. Miller\n"; $prefsfile = $ENV{'HOME'} . '/.spliff'; @prefsarray = ('server','username','password','mailbox','interval','size','beep','headingfont','messagefont','x','y','soundplayer','soundfile'); @defaults = ('','','','INBOX','15','5','1','Times 10 bold','Times 10 normal','0','0','',''); &initprefs; $mainwindow = MainWindow->new; $mainwindow->title($title); $mainwindow->geometry('+' . $saved{'x'} . '+' . $saved{'y'}); $mainwindow->resizable(0, 0); $mainwindow->protocol('WM_DELETE_WINDOW', \&savegeometryexit); $mainwindow->bind('', \&update); $mainwindow->bind('', sub { $mainwindow->withdraw() } ); $mainwindow->bind('', \&prefswindow); &dotopframe; &domiddleframe; &dobottomframe; if ($newuser) { $mainwindow->withdraw(); &prefswindow; } else { $mainwindow->after(1000, \&update); $mainwindow->repeat($active{'interval'} * 60000, \&update); } MainLoop; ################################################################################ # # initprefs: # # - read preferences from disk if they exist # # @saved = prefs as they exist in prefs file # @active = prefs as they exist in main window # @prefs = prefs as they exist in prefs window # @defaults = default prefs hardcoded in script # # - set preferences to defaults if they don't exist # - initialize message count at last update to zero # ################################################################################ sub initprefs { if (-e $prefsfile) { # attempt to read saved prefs open(PREFS, "<$prefsfile") || die("Unable to read preferences file $prefsfile"); $readloop = 0; while () { chop($saved{$prefsarray[$readloop]} = $_); $readloop++; } close(PREFS); foreach $pref (@prefsarray) { $prefs{$pref} = $active{$pref} = $saved{$pref}; } } else { # set prefs to default prefs for ($defaultloop = 0; $defaultloop < scalar(@prefsarray); $defaultloop++) { $prefs{$prefsarray[$defaultloop]} = $active{$prefsarray[$defaultloop]} = $saved{$prefsarray[$defaultloop]} = $defaults[$defaultloop]; } $newuser = 1; } $lastcount = 0; } ################################################################################ # # savegeometryexit: # # - remember main window coordinates # - exit program # ################################################################################ sub savegeometryexit { if (!$newuser) { $exitgeometry = $mainwindow->geometry(); $exitgeometry =~ s/\d+x\d+\+//; ($saved{'x'}, $saved{'y'}) = split(/\+/, $exitgeometry); &saveprefs; } exit; } ################################################################################ # # update: # # - connect to server and grab latest message information # - update status message # - check for new mail and perform necessary actions # - update count placeholder # ################################################################################ sub update { # check for necessary variables if (!$active{'server'} || !$active{'username'} || !$active{'password'} || !$active{'mailbox'}) { $status = 'Invalid server configuration'; return; } # attempt to connect to server $imap = Mail::IMAPClient->new(Server => $active{'server'}, User => $active{'username'}, Password => $active{'password'}); if (!$imap) { $status = 'Unable to connect to ' . $active{'server'}; return; } # determine latest message count $count = $imap->message_count($active{'mailbox'}); if (!$count) { $status = 'Unable to read mailbox ' . $active{'mailbox'}; return; } # determine count status message if ($count == 0) { $status = 'There are no messages in ' . $active{'mailbox'}; } else { if ($count <= $active{'size'}) { $shown = $count; } else { $shown = $active{'size'}; } $status = "Most recent $shown of $count messages in " . $active{'mailbox'}; } # read in the message summaries $last = $count - $active{'size'}; for ($loop = $count; $loop > $last; $loop--) { $imap->examine($active{'mailbox'}); $message = $loop - $last; if ($loop > 0) { # reading a valid message $info = $imap->parse_headers($loop, 'From', 'Subject', 'Date'); $from[$message] = $info->{'From'}[0]; $subject[$message] = $info->{'Subject'}[0]; $date[$message] = $info->{'Date'}[0]; } else { # need to fill an empty row $from[$message] = $subject[$message] = $date[$message] = ''; } } # disconnect from server $imap->disconnect; # notify if new mail if ($count > $lastcount) { if ($active{'soundplayer'} && $active{'soundfile'}) { system($active{'soundplayer'} . ' ' . $active{'soundfile'} . '>& /dev/null'); } $mainwindow->bell() if $active{'beep'}; $mainwindow->deiconify(); $mainwindow->raise(); } $lastcount = $count; } ################################################################################ # # prefswindow: # # - set prefs window settings to active settings # - bring up prefs window # ################################################################################ sub prefswindow { if (!$prefswindow) { # create prefs window $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 => 'e')->grid($serverframe->Entry(-textvariable => \$prefs{'server'}), -pady => 5, -sticky => 'nsew'); $serverframe->Label(-text => 'Username:', -justify => 'left', -anchor => 'e')->grid($serverframe->Entry(-textvariable => \$prefs{'username'}), -pady => 5, -sticky => 'nsew'); $serverframe->Label(-text => 'Password:', -justify => 'left', -anchor => 'e')->grid($serverframe->Entry(-textvariable => \$prefs{'password'}, -show => '*'), -pady => 5, -sticky => 'nsew'); $serverframe->Label(-text => 'Mailbox:', -justify => 'left', -anchor => 'e')->grid($serverframe->Entry(-textvariable => \$prefs{'mailbox'}), -pady => 5, -sticky => 'nsew'); $severintervalframe = $serverpage->LabFrame(-label => 'Update interval in minutes', -labelside => 'acrosstop')->pack(-expand => 1, -fill => 'both'); $severintervalframe->Scale(-from => 1, -to => 30, -orient => 'horizontal', -variable => \$prefs{'interval'})->pack(-fill => 'both', -padx => 5, -pady => 2); $appearancepage = $prefsbook->add('appearance', -label => 'Appearance'); $appearanceheadingfontframe = $appearancepage->LabFrame(-label => 'Heading font', -labelside => 'acrosstop')->pack(-expand => 1, -fill => 'both'); $appearanceheadingfontframe->Entry(-textvariable => \$prefs{'headingfont'})->pack(-expand => 1, -fill => 'x', -padx => 5, -pady => 2); $appearancemessagefontframe = $appearancepage->LabFrame(-label => 'Message font', -labelside => 'acrosstop')->pack(-expand => 1, -fill => 'both'); $appearancemessagefontframe->Entry(-textvariable => \$prefs{'messagefont'})->pack(-expand => 1, -fill => 'x', -padx => 5, -pady => 2); $appearancesizeframe = $appearancepage->LabFrame(-label => 'Number of messages to show', -labelside => 'acrosstop')->pack(-expand => 1, -fill => 'both'); $appearancesizeframe->Scale(-from => 1, -to => 10, -orient => 'horizontal', -variable => \$prefs{'size'})->pack(-fill => 'both', -padx => 5, -pady => 2); $soundpage = $prefsbook->add('sound', -label => 'Sound'); $soundplayerframe = $soundpage->LabFrame(-label => 'Sound player', -labelside => 'acrosstop')->pack(-expand => 1, -fill => 'both'); $soundplayerframe->Entry(-textvariable => \$prefs{'soundplayer'})->pack(-side => 'left', -expand => 1, -fill => 'x', -padx => 5, -pady => 2); $soundplayerframe->Button(-text => 'Browse...', -command => [ \&selectfile, 'soundplayer' ] )->pack(-side => 'left', -expand => 1, -fill => 'x', -padx => 5, -pady => 2); $soundfileframe = $soundpage->LabFrame(-label => 'Sound to play on new mail', -labelside => 'acrosstop')->pack(-expand => 1, -fill => 'both'); $soundfileframe->Entry(-textvariable => \$prefs{'soundfile'})->pack(-side => 'left', -expand => 1, -fill => 'x', -padx => 5, -pady => 2); $soundfileframe->Button(-text => 'Browse...', -command => [ \&selectfile, 'soundfile' ] )->pack(-side => 'left', -expand => 1, -fill => 'x', -padx => 5, -pady => 2); $soundbeepframe = $soundpage->LabFrame(-label => 'Beep on new mail', -labelside => 'acrosstop')->pack(-expand => 1, -fill => 'both'); $soundbeepframe->Radiobutton(-text => 'Yes', -variable => \$prefs{'beep'}, -value => 1)->pack(-anchor => 'n', -side => 'left', -expand => '1', -fill => 'both', -padx => 5, -pady => 2); $soundbeepframe->Radiobutton(-text => 'No', -variable => \$prefs{'beep'}, -value => 0)->pack(-anchor => 'n', -side => 'left', -expand => '1', -fill => 'both', -padx => 5, -pady => 2); $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, -underline => 0, -default => 'active')->pack(-side => 'left', -expand => 1, -fill => 'x', -padx => 10, -pady => 5); $prefsbottomframe->Button(-text => 'Apply', -command => \&applyprefs, -underline => 0, -width => 6)->pack(-side => 'left', -expand => 1, -fill => 'x', -padx => 10, -pady => 5); $prefsbottomframe->Button(-text => 'Cancel', -command => \&cancelprefs, -underline => 0, -width => 6)->pack(-side => 'left', -expand => 1, -fill => 'x', -padx => 10, -pady => 5); } else { # raise first tab on notebook if ($prefswindow->state() eq 'withdrawn' && $prefsbook->raised ne 'server') { $prefsbook->raise('server'); } # bring up prefs window $prefswindow->deiconify(); $prefswindow->raise(); } } ################################################################################ # # selectfile: # # - bring up file selector for file named in argument # ################################################################################ sub selectfile { $selectfile = $_[0]; if ($prefs{$selectfile}) { $initialfile = $prefs{$selectfile}; $initialdir = $prefs{$selectfile}; $initialdir =~ s/^(.*\/)\w+\.?\w+$/$1/; } else { $initialfile = ''; $initialdir = $ENV{'HOME'}; } # create the selection dialog $fileselector = $prefswindow->FBox(-initialfile => $initialfile, -initialdir => $initialdir); # grab the return value $prefs{$selectfile} = $fileselector->Show; # go back to old value if user cancelled if (!$prefs{$selectfile}) { $prefs{$selectfile} = $initialfile; } } ################################################################################ # # dotopframe: # # - create main window top frame (column headings) # ################################################################################ sub dotopframe { $topframe = $mainwindow->Frame(-relief => 'raised', -borderwidth => 1)->pack(); $topframe->Label(-text => 'From', -font => $active{'headingfont'}, -width => 15, -padx => 5, -pady => 0, -anchor => 'w')->pack(-side => 'left'); $topframe->Label(-text => 'Subject', -font => $active{'headingfont'}, -width => 25, -padx => 5, -pady => 0, -anchor => 'w')->pack(-side => 'left'); $topframe->Label(-text => 'Date', -font => $active{'headingfont'}, -width => 20, -padx => 5, -pady => 0, -anchor => 'w')->pack(-side => 'left'); } ################################################################################ # # domiddleframe: # # - create main window middle frame (message summaries) # ################################################################################ sub domiddleframe { $middleframe = $mainwindow->Frame()->pack(); for ($loop = $active{'size'}; $loop > 0; $loop--) { $fromcolumn = $middleframe->Label(-textvariable => \$from[$loop], -font => $active{'messagefont'}, -width => 15, -padx => 5, -pady => 0, -anchor => 'w'); $subjectcolumn = $middleframe->Label(-textvariable => \$subject[$loop], -font => $active{'messagefont'}, -width => 25, -padx => 5, -pady => 0, -anchor => 'w'); $datecolumn = $middleframe->Label(-textvariable => \$date[$loop], -font => $active{'messagefont'}, -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); } ################################################################################ # # dobottomframe: # # - create main window bottom frame (status message) # ################################################################################ sub dobottomframe { $bottomframe = $mainwindow->Frame(-relief => 'raised', -borderwidth => 1)->pack(-fill => 'x'); $bottomframe->Label(-textvariable => \$status, -font => $active{'messagefont'}, -pady => 0)->pack(); } ################################################################################ # # okprefs: # # - call apply and save routines # - remove prefs window from view # ################################################################################ sub okprefs { &applyprefs; &saveprefs; $prefswindow->withdraw(); } ################################################################################ # # applyprefs: # # - redraw main window if necessary # - bring main window up and to the front # - apply selected preferences # - call update routine # ################################################################################ sub applyprefs { if ($active{'size'} != $prefs{'size'} || $active{'headingfont'} ne $prefs{'headingfont'} || $active{'messagefont'} ne $prefs{'messagefont'}) { $active{'size'} = $prefs{'size'}; $active{'headingfont'} = $prefs{'headingfont'}; $active{'messagefont'} = $prefs{'messagefont'}; &redraw; } foreach $pref (@prefsarray) { $active{$pref} = $prefs{$pref}; } $mainwindow->deiconify(); $mainwindow->raise(); &update; } ################################################################################ # # cancelprefs: # # - exit if new user # - redraw main window if necessary # - revert to saved preferences # - remove file selector if necessary # - remove prefs window # - call update routine # ################################################################################ sub cancelprefs { if ($newuser) { exit; } if ($active{'size'} != $saved{'size'} || $active{'headingfont'} ne $saved{'headingfont'} || $active{'messagefont'} ne $saved{'messagefont'}) { $active{'size'} = $saved{'size'}; $active{'headingfont'} = $saved{'headingfont'}; $active{'messagefont'} = $saved{'messagefont'}; &redraw; } foreach $pref (@prefsarray) { $active{$pref} = $prefs{$pref} = $saved{$pref}; } $fileselector->destroy() if (Exists($fileselector)); $prefswindow->withdraw(); &update; } ################################################################################ # # saveprefs: # # - modify all preferences on disk if not called by savegeometryexit # - modify only geometry on disk if called by savegeometryexit # - indicate no longer a new user # ################################################################################ sub saveprefs { if (!$exitgeometry) { foreach $pref (@prefsarray) { $saved{$pref} = $active{$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 "$saved{$prefsarray[$saveloop]}\n"; } close(PREFS); $newuser = 0 if ($newuser); } ################################################################################ # # redraw: # # - recreate main window # ################################################################################ sub redraw { $topframe->destroy() if (Exists($topframe)); $middleframe->destroy() if (Exists($middleframe)); $bottomframe->destroy() if (Exists($bottomframe)); &dotopframe; &domiddleframe; &dobottomframe; }