Second checkin, bugfixes and such.
[rox-postal.git] / postal.py
blob047c159f237e7ed43d4426c91e57974dbe6c5224
1 """
2 postal.py - An imap folder checker panel applet for ROX
4 Copyright 2005-2006 Kenneth Hayber <ken@hayber.us>,
5 All rights reserved.
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
19 """
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
26 # globals
27 APP_NAME = 'Postal'
28 APP_DIR = rox.app_dir
29 APP_SIZE = [28, 28]
31 # Options.xml processing
32 from rox import Menu
33 rox.setup_app_options(APP_NAME, site='hayber.us')
34 Menu.set_save_name(APP_NAME, site='hayber.us')
36 #Options go here
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()
49 def which(filename):
50 """Return the full path of an executable if found on the path"""
51 if (filename == None) or (filename == ''):
52 return None
54 env_path = os.getenv('PATH').split(':')
55 for p in env_path:
56 if os.access(p+'/'+filename, os.X_OK):
57 return p+'/'+filename
58 return None
61 import imaplib
63 class IMAPCheck(applet.Applet):
64 """An Applet (no, really)"""
66 total_unseen = 0
67 prev_total = 0
68 size = 0
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
81 self.resize_image(8)
82 self.add(self.image)
84 self.vertical = self.get_panel_orientation() in ('Right', 'Left')
85 if self.vertical:
86 self.set_size_request(8, -1)
87 else:
88 self.set_size_request(-1, 8)
90 # set the tooltip
91 self.tooltips = gtk.Tooltips()
93 # menus
94 self.build_appmenu()
96 # event handling
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)
103 self.checkit()
105 def checkit(self):
106 tasks.Task(self.check_mail())
108 def check_mail(self):
109 """ """
110 try:
111 im = imaplib.IMAP4(SERVER.value)
112 im.login(USERNAME.value, PASSWORD.value)
113 except:
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(',')
121 results = ""
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] == '':
129 count = 0
130 else:
131 count = int(result[1][0])
132 else:
133 count = -1
134 if count == -1:
135 yield None
137 result = im.search(None, "UNSEEN")
138 if result[0] == 'OK':
139 if result[1][0] == '':
140 unseen = 0
141 else:
142 unseen = len(result[1][0].split())
143 self.total_unseen += unseen
144 else:
145 unseen = -1
146 if count > 0:
147 results += "%s (%d/%d)\n" % (mailbox, unseen, count)
148 yield None
150 self.tooltips.set_tip(self, str(results[:-1]), tip_private=None)
151 if self.total_unseen:
152 self.pixbuf = self.ismail
153 else:
154 self.pixbuf = self.nomail
155 self.resize_image(self.size)
157 try: im.close()
158 except: pass
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
166 def run_it(self):
167 """Open the given file with ROX."""
168 try:
169 rox.filer.spawn_rox((which(MAILER.value),))
170 except:
171 rox.report_exception()
173 def resize(self, widget, rectangle):
174 """Called when the panel sends a size."""
176 if self.vertical:
177 size = rectangle[2]
178 else:
179 size = rectangle[3]
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)
187 self.size = size
189 def play_sound(self):
190 """Play a sound"""
191 process = popen2.Popen3(SOUND.value)
192 yield tasks.InputBlocker(process.fromchild)
193 process.wait()
196 #draw the total new mail count on top of the icon
197 # if self.window:
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:
208 self.run_it()
209 elif event.button == 2:
210 self.checkit()
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)
219 if pos: pos = pos[2]
220 if pos:
221 side, margin = pos.split(',')
222 margin = int(margin)
223 else:
224 side, margin = None, 2
225 return side
227 def get_options(self, widget=None, rebuild=False, response=False):
228 """Used as the notify callback when options change."""
229 pass
231 def show_options(self, button=None):
232 """Open the options edit dialog."""
233 rox.edit_options()
235 def get_info(self):
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."""
242 items = []
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."""
255 self.destroy()