2 postal.py - An imap folder checker panel applet for ROX
4 Copyright 2005-2006 Kenneth Hayber <ken@hayber.us>,
7 This program is free software; you can redistribute it and/or modify
8 it under the terms of the GNU General Public License as published by
9 the Free Software Foundation; either version 2 of the License.
11 This program is distributed in the hope that it will be useful
12 but WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 GNU General Public License for more details.
16 You should have received a copy of the GNU General Public License
17 along with this program; if not, write to the Free Software
18 Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
21 # standard library modules
22 import sys
, os
, time
, gtk
, gobject
, rox
, getpass
, pango
, popen2
23 from rox
import applet
, filer
, tasks
24 from rox
.options
import Option
31 # Options.xml processing
33 rox
.setup_app_options(APP_NAME
, site
='hayber.us')
34 Menu
.set_save_name(APP_NAME
, site
='hayber.us')
37 SERVER
= Option('server', 'localhost')
38 PORT
= Option('port', '143')
39 MAILBOXES
= Option('mailboxes', 'Inbox')
40 POLLTIME
= Option('polltime', 10)
41 USERNAME
= Option('username', getpass
.getuser())
42 PASSWORD
= Option('password', '')
43 MAILER
= Option('mailer', 'thunderbird')
44 SOUND
= Option('sound', '')
46 #Enable notification of options changes
47 rox
.app_options
.notify()
50 """Return the full path of an executable if found on the path"""
51 if (filename
== None) or (filename
== ''):
54 env_path
= os
.getenv('PATH').split(':')
56 if os
.access(p
+'/'+filename
, os
.X_OK
):
63 class IMAPCheck(applet
.Applet
):
64 """An Applet (no, really)"""
70 def __init__(self
, id):
71 """Initialize applet."""
73 applet
.Applet
.__init
__(self
, id)
75 # load the applet icon
76 self
.image
= gtk
.Image()
77 self
.nomail
= gtk
.gdk
.pixbuf_new_from_file(os
.path
.join(APP_DIR
, 'images', 'nomail.svg'))
78 self
.errimg
= gtk
.gdk
.pixbuf_new_from_file(os
.path
.join(APP_DIR
, 'images', 'error.svg'))
79 self
.ismail
= gtk
.gdk
.pixbuf_new_from_file(os
.path
.join(APP_DIR
, 'images', 'mail.svg'))
80 self
.pixbuf
= self
.nomail
84 self
.vertical
= self
.get_panel_orientation() in ('Right', 'Left')
86 self
.set_size_request(8, -1)
88 self
.set_size_request(-1, 8)
91 self
.tooltips
= gtk
.Tooltips()
97 self
.add_events(gtk
.gdk
.BUTTON_PRESS_MASK
)
98 self
.connect('button-press-event', self
.button_press
)
99 self
.connect('size-allocate', self
.resize
)
100 self
.connect('delete_event', self
.quit
)
101 rox
.app_options
.add_notify(self
.get_options
)
106 tasks
.Task(self
.check_mail())
108 def check_mail(self
):
111 im
= imaplib
.IMAP4(SERVER
.value
)
112 im
.login(USERNAME
.value
, PASSWORD
.value
)
114 self
.tooltips
.set_tip(self
, _("Error"), tip_private
=None)
115 self
.pixbuf
= self
.errimg
116 self
.resize_image(self
.size
)
117 self
.update
= gobject
.timeout_add(POLLTIME
.int_value
* 60000, self
.checkit
)
118 return #don't care, we'll try again later
120 mailboxes
= MAILBOXES
.value
.split(',')
122 self
.total_unseen
= 0
124 for mailbox
in mailboxes
:
125 mailbox
= mailbox
.strip()
126 result
= im
.select(mailbox
, readonly
=True)
127 if result
[0] == 'OK':
128 if result
[1][0] == '':
131 count
= int(result
[1][0])
137 result
= im
.search(None, "UNSEEN")
138 if result
[0] == 'OK':
139 if result
[1][0] == '':
142 unseen
= len(result
[1][0].split())
143 self
.total_unseen
+= unseen
147 results
+= "%s (%d/%d)\n" % (mailbox
, unseen
, count
)
150 self
.tooltips
.set_tip(self
, str(results
[:-1]), tip_private
=None)
151 if self
.total_unseen
:
152 self
.pixbuf
= self
.ismail
154 self
.pixbuf
= self
.nomail
155 self
.resize_image(self
.size
)
160 self
.update
= gobject
.timeout_add(POLLTIME
.int_value
* 60000, self
.checkit
)
162 if len(SOUND
.value
) and self
.total_unseen
> self
.prev_total
:
163 tasks
.Task(self
.play_sound())
164 self
.prev_total
= self
.total_unseen
167 """Open the given file with ROX."""
169 rox
.filer
.spawn_rox((which(MAILER
.value
),))
171 rox
.report_exception()
173 def resize(self
, widget
, rectangle
):
174 """Called when the panel sends a size."""
180 if size
!= self
.size
:
181 self
.resize_image(size
)
183 def resize_image(self
, size
):
184 """Resize the application image."""
185 scaled_pixbuf
= self
.pixbuf
.scale_simple(size
, size
, gtk
.gdk
.INTERP_BILINEAR
)
186 self
.image
.set_from_pixbuf(scaled_pixbuf
)
189 def play_sound(self
):
191 process
= popen2
.Popen3(SOUND
.value
)
192 yield tasks
.InputBlocker(process
.fromchild
)
196 #draw the total new mail count on top of the icon
198 # gc = self.window.new_gc()
199 # layout = self.create_pango_layout('')
200 # layout.set_markup("<b>%d</b>" % self.total_unseen)
201 # self.window.draw_layout(gc, 3, 3, layout, gtk.gdk.color_parse("black"), None)
202 # self.window.draw_layout(gc, 2, 2, layout, gtk.gdk.color_parse("red"), None)
205 def button_press(self
, window
, event
):
206 """Handle mouse clicks by popping up the matching menu."""
207 if event
.button
== 1:
209 elif event
.button
== 2:
211 elif event
.button
== 3:
212 self
.appmenu
.popup(self
, event
, self
.position_menu
)
214 def get_panel_orientation(self
):
215 """ Return panel orientation and margin for displaying a popup menu.
216 Position in ('Top', 'Bottom', 'Left', 'Right').
218 pos
= self
.socket
.property_get('_ROX_PANEL_MENU_POS', 'STRING', False)
221 side
, margin
= pos
.split(',')
224 side
, margin
= None, 2
227 def get_options(self
, widget
=None, rebuild
=False, response
=False):
228 """Used as the notify callback when options change."""
231 def show_options(self
, button
=None):
232 """Open the options edit dialog."""
236 """Display an InfoWin box."""
237 from rox
import InfoWin
238 InfoWin
.infowin(APP_NAME
)
240 def build_appmenu(self
):
241 """Build the right-click app menu."""
243 items
.append(Menu
.Action(_('Check mail'), 'checkit', '', gtk
.STOCK_REFRESH
))
244 items
.append(Menu
.Action(_('Mail Client'), 'run_it', '', gtk
.STOCK_EXECUTE
))
245 items
.append(Menu
.Separator())
246 items
.append(Menu
.Action(_('Info...'), 'get_info', '', gtk
.STOCK_DIALOG_INFO
))
247 items
.append(Menu
.Action(_('Options...'), 'show_options', '', gtk
.STOCK_PREFERENCES
))
248 items
.append(Menu
.Separator())
249 items
.append(Menu
.Action(_('Close'), 'quit', '', gtk
.STOCK_CLOSE
))
250 self
.appmenu
= Menu
.Menu('other', items
)
251 self
.appmenu
.attach(self
, self
)
253 def quit(self
, *args
):
254 """Quit applet and close everything."""