1 from Components.ActionMap import ActionMap
2 from Components.GUIComponent import GUIComponent
3 from Components.HTMLComponent import HTMLComponent
4 from Components.Label import Label
5 from Screens.MessageBox import MessageBox
6 from Components.MenuList import MenuList
7 from Components.MultiContent import MultiContentEntryText, MultiContentTemplateColor
8 from Components.ScrollLabel import ScrollLabel
9 from Components.Button import Button
10 from Components.config import config, ConfigSubsection, ConfigInteger, ConfigText, ConfigEnableDisable
11 from EmailConfig import EmailConfigScreen
12 from Plugins.Plugin import PluginDescriptor
13 from Screens.ChoiceBox import ChoiceBox
14 from Screens.Screen import Screen
15 from enigma import eListboxPythonMultiContent, eListbox, gFont
16 from twisted.mail import imap4
17 from zope.interface import implements
20 from email.header import decode_header
21 from TagStrip import strip_readable
22 from protocol import createFactory
24 config.plugins.emailimap = ConfigSubsection()
25 config.plugins.emailimap.username = ConfigText("user", fixed_size=False)
26 config.plugins.emailimap.password = ConfigText("password", fixed_size=False)
27 config.plugins.emailimap.server = ConfigText("please.config.first", fixed_size=False)
28 config.plugins.emailimap.port = ConfigInteger(143, limits = (1, 65536))
29 config.plugins.emailimap.showDeleted = ConfigEnableDisable(default=False)
31 # 0= fetch all header , 10= fetch only the last 10 headers/messages of a mailbox
32 config.plugins.emailimap.maxheadertoload = ConfigInteger(0, limits = (1, 100))
34 from enigma import getDesktop
35 DESKTOP_WIDTH = getDesktop(0).size().width()
36 DESKTOP_HEIGHT = getDesktop(0).size().height()
39 # It returns the first value, if HD (1280x720),
40 # the second if SD (720x576),
41 # else something scaled accordingly
42 # if one of the parameters is -1, scale proportionally
49 return scale(y2, y1, 1280, 720, DESKTOP_WIDTH)
55 return scale(y2, y1, 720, 576, DESKTOP_HEIGHT)
56 def scale(y2, y1, x2, x1, x):
57 return (y2 - y1) * (x - x1) / (x2 - x1) + y1
59 def decodeHeader(text, default=''):
62 text = text.replace('\r',' ').replace('\n',' ').replace('\t',' ')
63 while text.find(' ') != -1:
64 text = text.replace(' ',' ')
66 for part in decode_header(text):
67 (content, charset) = part
68 # print("decodeHeader content/charset: %s/%s" %(repr(content),charset))
70 textNew += content.decode(charset)
74 return textNew.encode('utf-8')
75 except: # for faulty mail software systems
76 return textNew.decode('iso-8859-1').encode('utf-8')
85 def onConnect(self, proto):
88 class EmailScreen(Screen, EmailHandler):
89 implements(imap4.IMailboxListener)
91 width = scaleH(-1,530)
92 height = scaleV(-1,430)
93 boxlistWidth = scaleH(-1,150)
94 messagelistWidth = width-boxlistWidth
95 infolabelHeight = scaleV(-1,30)
97 <screen position="%d,%d" size="%d,%d" title="Email" >
98 <widget name="boxlist" position="0,0" size="%d,%d" scrollbarMode="showOnDemand" />
99 <widget name="messagelist" position="%d,%d" size="%d,%d" scrollbarMode="showOnDemand" />
100 <widget name="infolabel" position="%d,%d" size="%d,%d" foregroundColor=\"white\" font=\"Regular;%d\" />
102 (DESKTOP_WIDTH-width)/2, (DESKTOP_HEIGHT-height)/2, width, height,
103 boxlistWidth, height-infolabelHeight,
104 boxlistWidth, 0, messagelistWidth, height-infolabelHeight,
105 0, height-infolabelHeight, width, infolabelHeight, scaleV(20,18)
108 currentmailbox = None
111 def __init__(self, session, args = 0):
112 EmailHandler.__init__(self)
113 self.session = session
115 self.skin = EmailScreen.skin
116 Screen.__init__(self, session)
117 createFactory(self, config.plugins.emailimap.username.value, config.plugins.emailimap.server.value, config.plugins.emailimap.port.value)
119 self["actions"] = ActionMap(["InfobarChannelSelection", "WizardActions", "DirectionActions", "MenuActions", "ShortcutActions", "GlobalActions", "HelpActions", "NumberActions", "ChannelSelectBaseActions"],
121 "ok": self.action_ok,
122 "back": self.action_exit,
123 "historyNext": self.selectMessagelist,
124 "historyBack": self.selectBoxlist,
125 "nextBouquet": self.selectMessagelist,
126 "prevBouquet": self.selectBoxlist,
131 "menu": self.action_menu,
133 self["boxlist"] = MenuList([])
134 self["messagelist"] = MailList([])
135 self["infolabel"] = Label("")
136 self.onLayoutFinish.append(self.selectBoxlist)
138 def action_menu(self):
139 self.session.open(EmailConfigScreen).onHide.append(self.onBoxSelected)
141 def selectBoxlist(self):
142 self.currList = "boxlist"
143 self["boxlist"].selectionEnabled(1)
144 self["messagelist"].selectionEnabled(0)
146 def selectMessagelist(self):
147 self.currList = "messagelist"
148 self["boxlist"].selectionEnabled(0)
149 self["messagelist"].selectionEnabled(1)
152 self[self.currList].up()
155 self[self.currList].down()
158 self[self.currList].pageUp()
161 self[self.currList].pageDown()
164 if self.currList == "boxlist":
167 self.onMessageSelected()
169 def onBoxSelected(self):
170 c = self["boxlist"].getCurrent()
172 self.proto.select(UTF7toUTF8(c[1][2]) # select instead of examine to get write access
173 ).addCallback(self.onExamine, c[0] , self.proto
174 ).addErrback(self.onExamineFailed, self.proto
177 def onMessageSelected(self):
178 c = self["messagelist"].getCurrent()
180 self.fetchMessageSize(c[0])
182 def fetchMessageSize(self, message):
183 print "fetchMessageSize",message
184 self.proto.fetchSize(message.uid
185 ).addCallback(self.onMessageSizeLoaded, message, self.proto
186 ).addErrback(self.onMessageLoadFailed, message, self.proto
189 def onMessageSizeLoaded(self, result, message, proto):
190 print "onMessageSizeLoaded", result, message
191 size = int(result[message.uid]['RFC822.SIZE'])
192 self.MAX_MESSAGE_SIZE_TO_OPEN = 4000000
193 if size >= self.MAX_MESSAGE_SIZE_TO_OPEN:
194 #ask here to open message
195 print "message to large to open (size=", size, ")"
197 self.loadMessage(message)
199 # def fetchBodyStructure(self, message):
200 # print "fetchBodyStructure",message
201 # self.proto.fetchBodyStructure(message.uid
202 # ).addCallback(self.onBodystructureLoaded, message, self.proto
203 # ).addErrback(self.onMessageLoadFailed, message, self.proto
206 def loadMessage(self, message):
207 print "loadMessage",message
208 self["infolabel"].setText("loading message")
210 self.proto.fetchMessage(message.uid
211 ).addCallback(self.onMessageLoaded, message, self.proto
212 ).addErrback(self.onMessageLoadFailed, message, self.proto
215 def onMessageLoaded(self, result, message, proto):
216 self["infolabel"].setText("parsing message")
217 print "onMessageLoaded"#,result,message
219 msgstr = result[message.uid]['RFC822']
221 self.loadMessage(message)
223 msg = email.Parser.Parser().parsestr(msgstr)
224 msg.messagebodys = []
227 if msg.is_multipart():
228 for part in msg.walk():
229 if part.get_content_maintype()=="multipart":
231 if part.get_content_maintype() == 'text' and part.get_filename() is None:
232 if part.get_content_subtype() == "html":
233 msg.messagebodys.append(EmailBody(part))
234 elif part.get_content_subtype() == "plain":
235 msg.messagebodys.append(EmailBody(part))
237 print "unkown content type= ", part.get_content_maintype(), "/", part.get_content_subtype()
239 print "found Attachment with ", part.get_content_type(), "and name", part.get_filename()
240 msg.attachments.append(EmailAttachment(part.get_filename(), part.get_content_type(), part.get_payload()))
242 msg.messagebodys.append(EmailBody(msg))
243 self.session.open(ScreenMailView, msg, message.uid, proto, self.flagsList[message.uid]['FLAGS']).onHide.append(self.onBoxSelected)
245 def onMessageLoadFailed(self, failure, message, proto):
246 print "onMessageLoadFailed", failure, message
247 self["infolabel"].setText(failure.getErrorMessage())
249 def action_exit(self):
250 if self.proto is not None:
251 self.proto.logout().addCallback(self.onLogedOut, self.proto).addErrback(self.onLogedOut, self.proto)
255 def onLogedOut(self, result, proto):
256 print "onLogedOut", result
259 def onConnect(self, proto):
260 self["infolabel"].setText("connected")
261 proto.getCapabilities(
262 ).addCallback(self.cbCapabilities, proto
263 ).addErrback(self.ebCapabilities, proto
266 def cbCapabilities(self,reason,proto):
268 print "# If you have problems to log into your imap-server, please send me the output of the following line"
269 print "# cbCapabilities",reason
273 def ebCapabilities(self,reason,proto):
274 print "ebCapabilities",reason
276 def onConnectFailed(self, reason):
277 self["infolabel"].setText(reason.getErrorMessage())
279 def onAuthentication(self, result, proto):
281 self["infolabel"].setText("logged in")
282 # better use LSUB here to get only the subscribed to mailboxes
283 proto.lsub("", "*").addCallback(self.onMailboxList, proto)
285 def doLogin(self, proto):
289 context = proto.context.getContext()
290 d = proto.startTLS(context)
291 d = d.addCallback(proto.authenticate, config.plugins.emailimap.password.value)
293 d = proto.authenticate(config.plugins.emailimap.password.value)
294 d.addCallback(self.onAuthentication, proto)
295 d.addErrback(self.onAuthenticationFailed, proto)
298 def onAuthenticationFailed(self, failure, proto):
299 # If it failed because no SASL mechanisms match
300 print "onAuthenticationFailed", failure, proto
301 self["infolabel"].setText(failure.getErrorMessage())
303 failure.trap(imap4.NoSupportedAuthentication)
304 self.doLoginInsecure(proto)
308 def doLoginInsecure(self, proto):
309 print "login INSECURE"
310 proto.login(config.plugins.emailimap.username.value, config.plugins.emailimap.password.value
311 ).addCallback(self.onAuthentication, proto
312 ).addErrback(self.onInsecureAuthenticationFailed, proto
315 def onInsecureAuthenticationFailed(self, failure, proto):
316 print "onInsecureAuthenticationFailed", failure, proto
317 self["infolabel"].setText(failure.getErrorMessage())
319 def onMailboxList(self, result, proto):
320 print "onMailboxList", result, proto
324 flags, hierarchy_delimiter, name = i #@UnusedVariable
325 list.append((UTF7toUTF8(name).encode('utf-8'), i))
326 if name.lower() == 'inbox':
328 self["boxlist"].setList(list)
329 self["boxlist"].moveToIndex(inboxPos-1)
331 def onExamine(self, result, mboxname, proto):
332 print "onExamine", result, mboxname
333 self.setTitle("Mailbox: "+mboxname)
334 self.currentmailbox = mboxname
335 numMessagesinFolder = int(result['EXISTS'])
336 if numMessagesinFolder <= 0:
337 self["infolabel"].setText("Box '"+mboxname+"' is empty")
338 self["messagelist"].l.setList([])
341 if config.plugins.emailimap.maxheadertoload.value > 0:
342 maxMessagesToFetch = config.plugins.emailimap.maxheadertoload.value
343 startmsg = numMessagesinFolder-maxMessagesToFetch+1
346 rangeToFetch = [startmsg, numMessagesinFolder]
348 rangeToFetch = [1, numMessagesinFolder]
349 self["infolabel"].setText("loading headers "+str(rangeToFetch[0])+"-"+str(rangeToFetch[1])+" of Box '"+mboxname+"'")
352 # proto.fetchEnvelope('%i:%i'%(rangeToFetch[0], rangeToFetch[1]) #'1:*'
353 # ).addCallback(self.onEnvelopeList, proto
356 self.rangeToFetch = rangeToFetch
357 proto.fetchFlags('%i:%i'%(rangeToFetch[0], rangeToFetch[1]) #'1:*'
358 ).addCallback(self.onFlagsList)
360 except imap4.IllegalServerResponse, e:
362 self.selectMessagelist()
364 def onFlagsList(self, result):
365 self.flagsList = result
366 self.proto.fetchHeaders('%i:%i'%(self.rangeToFetch[0], self.rangeToFetch[1]) #'1:*'
367 ).addCallback(self.onHeaderList, self.proto
370 def onExamineFailed(self, failure, proto):
371 print "onExamineFailed", failure, proto
372 self["infolabel"].setText(failure.getErrorMessage())
374 def cbOk(self, result):
375 print("cbOk result: %s" %repr(result))
377 def cbNotOk(self, result):
378 print("cbNotOk result: %s" %(result))
380 def onHeaderList(self, result, proto):
381 print "onHeaderList"#,result,proto
382 self["infolabel"].setText("headers loaded, now parsing ...")
386 # print("onHeaderList :" + repr(self.flagsList[m]['FLAGS']))
387 if '\\Seen' in self.flagsList[m]['FLAGS']:
389 if '\\Deleted' in self.flagsList[m]['FLAGS']:
390 if not config.plugins.emailimap.showDeleted.value:
395 list.append(self.buildMessageListItem(MessageHeader(m, result[m]['RFC822.HEADER']), state))
398 list.append(self.buildMessageListItem(MessageHeader(m, result[m]['RFC822.HEADER'].decode('iso8859-1', 'replace'), state)))
400 # this appear to be errors in the formatting of the mail itself...
401 print "onHeaderList error: %s with: %s" %(e,result[m]['RFC822.HEADER'])
403 self["messagelist"].l.setList(list)
404 self["infolabel"].setText("have "+str(len(list))+" messages ")
406 def buildMessageListItem(self, message, state):
407 if state == IS_UNSEEN:
409 color = 0x00FFFFFF # white
410 elif state == IS_DELETED:
412 color = 0x00FF6666 # redish :)
415 color = 0x00888888 # grey
418 MultiContentEntryText(pos=(5, 0), size=(self.messagelistWidth, scaleV(20,18)+5), font=font, text=message.getSenderString(), color=color, color_sel=color),
419 MultiContentEntryText(pos=(5, scaleV(20,18)+1), size=(self.messagelistWidth, scaleV(20,18)+5), font=font, text=message.get('date', default='kein Datum'), color=color, color_sel=color),
420 MultiContentEntryText(pos=(5, 2*(scaleV(20,18)+1)), size=(self.messagelistWidth, scaleV(20,18)+5), font=font, text=message.getSubject(), color=color, color_sel=color)
423 # IMailboxListener methods
425 def modeChanged(self, writeable):
426 print "modeChanged", writeable
428 def flagsChanged(self, newFlags):
429 print "flagsChanged", newFlags
431 def newMessages(self, exists, recent):
432 print "newMessages", exists, recent
434 class ScreenMailView(Screen):
436 def __init__(self, session, email, uid, proto, flags):
437 self.session = session
439 # print('ScreenMailView ' + repr(email) + ' dir: ' + repr(dir(email)))
440 width = max(4*140,scaleH(-1,550))
441 height = scaleV(-1,476)
442 fontSize = scaleV(24,20)
443 lineHeight = fontSize+5
444 buttonsGap = (width-4*140)/5
446 <screen position="%d,%d" size="%d,%d" title="view Email" >
447 <widget name="from" position="%d,%d" size="%d,%d" font="Regular;%d" />
448 <widget name="date" position="%d,%d" size="%d,%d" font="Regular;%d" />
449 <widget name="subject" position="%d,%d" size="%d,%d" font="Regular;%d" />
450 <eLabel position="%d,%d" size="%d,2" backgroundColor="#aaaaaa" />
451 <widget name="body" position="%d,%d" size="%d,%d" font="Regular;%d" />
452 <ePixmap position="%d,%d" zPosition="4" size="140,40" pixmap="skin_default/buttons/red.png" transparent="1" alphatest="on" />
453 <ePixmap position="%d,%d" zPosition="4" size="140,40" pixmap="skin_default/buttons/green.png" transparent="1" alphatest="on" />
454 <ePixmap position="%d,%d" zPosition="4" size="140,40" pixmap="skin_default/buttons/yellow.png" transparent="1" alphatest="on" />
455 <ePixmap position="%d,%d" zPosition="4" size="140,40" pixmap="skin_default/buttons/blue.png" transparent="1" alphatest="on" />
456 <widget name="buttonred" position="%d,%d" zPosition="5" size="140,40" valign="center" halign="center" font="Regular;%d" transparent="1" foregroundColor="white" shadowColor="black" shadowOffset="-1,-1" />
457 <widget name="buttongreen" position="%d,%d" zPosition="5" size="140,40" valign="center" halign="center" font="Regular;%d" transparent="1" foregroundColor="white" shadowColor="black" shadowOffset="-1,-1" />
458 <widget name="buttonyellow" position="%d,%d" zPosition="5" size="140,40" valign="center" halign="center" font="Regular;%d" transparent="1" foregroundColor="white" shadowColor="black" shadowOffset="-1,-1" />
459 <widget name="buttonblue" position="%d,%d" zPosition="5" size="140,40" valign="center" halign="center" font="Regular;%d" transparent="1" foregroundColor="white" shadowColor="black" shadowOffset="-1,-1" />
461 (DESKTOP_WIDTH-width)/2, (DESKTOP_HEIGHT-height)/2, width, height,
462 0, 0, width, lineHeight, fontSize-1, # from
463 0, lineHeight, width, lineHeight, fontSize-1, # date
464 0, 2*lineHeight, width, lineHeight, fontSize-1, # subject
465 0, 3*lineHeight+1, width, # line
466 0, 3*lineHeight+5, width, height-3*lineHeight-5-5-30-5, fontSize, # body
467 buttonsGap, height-30-5,
468 2*buttonsGap+140, height-30-5,
469 3*buttonsGap+2*140, height-30-5,
470 4*buttonsGap+3*140, height-30-5,
471 buttonsGap, height-30-5, scaleV(18,16),
472 2*buttonsGap+140, height-30-5, scaleV(18,16),
473 3*buttonsGap+2*140, height-30-5, scaleV(18,16),
474 4*buttonsGap+3*140, height-30-5, scaleV(18,16),
476 Screen.__init__(self, session)
477 self["from"] = Label(decodeHeader(_("From") +": %s" %self.email.get('from', 'no-from')))
478 self["date"] = Label(_("Date") +": %s" %self.email.get('date', 'no-date'))
479 self["subject"] = Label(decodeHeader(_("Subject") +": %s" %self.email.get('subject', 'no-subject')))
480 self["body"] = ScrollLabel(_(self.email.messagebodys[0].getData()))
482 self["buttonred"] = Button(_(""))
483 self["buttongreen"] = Button("")
484 self["buttonyellow"] = Button(_("leave unread"))
485 if '\\Deleted' in flags:
486 self["buttonblue"] = Button(_("undelete"))
488 self["buttonblue"] = Button(_("delete"))
489 self["actions"] = ActionMap(["WizardActions", "DirectionActions", "MenuActions", "ShortcutActions"],
492 "up": self["body"].pageUp,
493 "down": self["body"].pageDown,
494 # TODO: perhaps better use left/right for previous/next message
495 "left": self["body"].pageUp,
496 "right": self["body"].pageDown,
497 "red": self.selectBody,
498 "green": self.selectAttachment,
499 "yellow": self.markUnread,
506 proto.fetchFlags(self.uid).addCallback(self.cbOk).addErrback(self.cbNotOk)
507 self.onLayoutFinish.append(self.updateButtons)
509 def cbOk(self, result):
510 print("cbOk result: %s" %repr(result))
512 def cbNotOk(self, result):
513 print("cbNotOk result: %s" %(result))
516 if '\\Deleted' in self.flags:
517 self.session.openWithCallback(self.deleteCB, ChoiceBox, title=_("really undelete Mail?"), list=[(_("yes"), True),(_("no"), False)])
519 self.session.openWithCallback(self.deleteCB, ChoiceBox, title=_("really delete Mail?"), list=[(_("yes"), True),(_("no"), False)])
521 def deleteCB(self, returnValue):
522 if returnValue[1] is True:
523 if '\\Deleted' in self.flags:
524 self.proto.removeFlags(self.uid, ["\\Deleted"]).addCallback(self.cbOk).addErrback(self.cbNotOk)
526 self.proto.addFlags(self.uid, ["\\Deleted"]).addCallback(self.cbOk).addErrback(self.cbNotOk)
527 print("deleteCB: %s" %repr(self.email))
530 def markUnread(self):
531 self.proto.removeFlags(self.uid, ["\\Seen"]).addCallback(self.cbOk).addErrback(self.cbNotOk)
534 def openMessagesHeaders(self):
535 pass #self.session.open(ScreenMailViewHeader,self.profil,self.email)
537 def updateButtons(self):
538 self["buttonred"].setText(_("Bodys"))
539 if len(self.email.attachments):
540 self["buttongreen"].setText(_("Attachments"))
542 self["buttongreen"].setText("")
544 def selectBody(self):
545 if len(self.email.messagebodys):
547 for a in self.email.messagebodys:
548 list.append((a.getContenttype(), a))
549 self.session.openWithCallback(self.selectBodyCB, ChoiceBox, _("select Body"), list)
551 def selectBodyCB(self, choice):
552 if choice is not None:
553 self["body"].setText(choice[1].getData())
555 def selectAttachment(self):
556 if len(self.email.attachments):
558 for a in self.email.attachments:
559 name = a.getFilename()
561 list.append((a.getFilename(), a))
563 list.append((_("no filename"), a))
564 print("selectAttachment : " + repr(list))
565 self.session.openWithCallback(self.selectAttachmentCB, ChoiceBox, _("select Attachment"), list)
567 def selectAttachmentCB(self, choice):
568 if choice is not None:
569 print "Attachment selected", choice[1].getFilename()
570 #showMessageBox(self.session)
572 class MailList(MenuList):
573 def __init__(self, list, enableWrapAround = False):
574 MenuList.__init__(self, list, enableWrapAround, eListboxPythonMultiContent)
575 self.l.setFont(0, gFont("Regular", scaleV(20,18))) # new
576 self.l.setFont(1, gFont("Regular", scaleV(18,16))) # deleted
577 self.l.setFont(2, gFont("Regular", scaleV(18,16))) # seen
579 def postWidgetCreate(self, instance):
580 MenuList.postWidgetCreate(self, instance)
581 instance.setItemHeight(scaleV(70,60))
583 class MessageHeader(object):
584 def __init__(self, uid, message):
585 self.uid = uid #must be int
586 self.message = email.Parser.Parser().parsestr(message)
588 def getSenderString(self):
589 return decodeHeader(self.get("from"), _("no sender"))
591 def getSubject(self):
592 return decodeHeader(self.get("subject"), _("no subject"))
594 def get(self, key, default=None):
595 return self.message.get(key,failobj=default)
598 return "<MessageHeader uid="+str(self.uid)+", subject="+self.get("subject","no-subject")+">"
602 def __init__(self,data):
605 def getEncoding(self):
606 return self.data.get_content_charset()
609 text = self.data.get_payload(decode=True)
610 if self.getEncoding():
612 text = text.decode(self.getEncoding())
613 except UnicodeDecodeError:
615 # print('EmailBody/getData text: ' + text)
616 #=======================================================================
617 # if self.getEncoding():
618 # text = text.decode(self.getEncoding())
619 #=======================================================================
620 if self.getContenttype() == "text/html":
621 print "stripping html"
622 text = strip_readable(text)
623 # print('EmailBody/getData text: ' + text)
626 return text.encode('utf-8')
627 except UnicodeDecodeError:
631 def getContenttype(self):
632 return self.data.get_content_type()
635 class EmailAttachment:
636 def __init__(self, filename, contenttype, data):
637 self.filename = filename
638 self.contenttype = contenttype
641 def save(self,folder):
643 fp = open(folder+"/"+self.getFilename(),"wb")
651 def getFilename(self):
654 def getContenttype(self):
655 return self.contenttype
661 return imap4.decoder(str)[0]
664 return imap4.encoder(str.decode('utf-8'))[0]
666 def main(session, **kwargs):
668 if os.path.isfile('/usr/lib/python2.5/uu.py') is not True:
669 shutil.copy('/usr/lib/enigma2/python/Plugins/Extensions/EmailClient/uu.py', '/usr/lib/python2.5/uu.py')
672 session.openWithCallback(MessageCB, MessageBox, 'In order of missing standart python library files\ni have copied the nessary files now.\nBut you have to restart your Box\n to apply this!', type = MessageBox.TYPE_INFO)
674 session.open(EmailScreen)
676 def MessageCB(*args):
678 session2.open(EmailScreen)
680 def Plugins(path, **kwargs):
684 PluginDescriptor(name="Email Client", description="view Emails via IMAP4",
685 where = PluginDescriptor.WHERE_PLUGINMENU,