def config(self): """Open config dialog to set times and language.""" Config(self) self.time = CONFIG.getint("General", "time") self.timeout = CONFIG.getint("General", "timeout") if self.icon.get_item_label(3) == _("Suspend"): self.check_mails(False)
def ok(event=None): with open(os.path.join(LOCAL_PATH, '.pwd')) as fich: cryptedpwd = fich.read() old = oldpwd.get() pwd = newpwd.get() pwd2 = confpwd.get() if crypt.crypt(old, cryptedpwd) == cryptedpwd: # passwords match if pwd == pwd2: # new passwords match cryptedpwd = crypt.crypt(pwd, crypt.mksalt(crypt.METHOD_SHA512)) with open(os.path.join(LOCAL_PATH, '.pwd'), "w") as fich: fich.write(cryptedpwd) mailboxes = CONFIG.get( "Mailboxes", "active").split(", ") + CONFIG.get( "Mailboxes", "inactive").split(", ") while "" in mailboxes: mailboxes.remove("") for mailbox in mailboxes: server, login, password, folder = decrypt(mailbox, old) if server is not None: encrypt(mailbox, pwd, server, login, password, folder) self.pwd = pwd top.destroy() logging.info('Password changed') return pwd else: showerror(_('Error'), _('Passwords do not match!')) else: showerror(_('Error'), _('Incorrect password!'))
def ok(self): time = float(self.time_entry.get()) * 60000 timeout = float(self.timeout_entry.get()) * 60000 CONFIG.set("General", "time", "%i" % time) CONFIG.set("General", "timeout", "%i" % timeout) CONFIG.set("General", "language", self.lang.get().lower()[:2]) CONFIG.set("General", "font", self.font.get()) CONFIG.set("General", "trayicon", self.gui.get().lower()) save_config() self.destroy()
def __init__(self, master, pwd): """Create the mailbox manager dialog.""" Toplevel.__init__(self, master, class_="CheckMails") self.title(_("Mailbox Manager")) self.minsize(200, 10) self.pwd = pwd self.resizable(False, False) self.protocol("WM_DELETE_WINDOW", self.quit) self.im_add = PhotoImage(master=self, file=ADD) self.im_del = PhotoImage(master=self, file=DEL) self.im_edit = PhotoImage(master=self, file=EDIT) self.mailboxes = {} active = CONFIG.get("Mailboxes", "active").split(", ") inactive = CONFIG.get("Mailboxes", "inactive").split(", ") while "" in active: active.remove("") while "" in inactive: inactive.remove("") active.sort() inactive.sort() self.frame = Frame(self) self.columnconfigure(0, weight=1) self.frame.columnconfigure(1, weight=1) self.frame.grid(row=0, column=0, padx=10, pady=10, sticky="eswn") i = -1 for i, box in enumerate(active): c = Checkbutton(self.frame) c.state(('selected',)) c.grid(row=i, column=0, pady=4, padx=(4, 0)) l = Label(self.frame, text=box) l.grid(row=i, column=1, padx=4, pady=4) b_edit = Button(self.frame, image=self.im_edit, width=1, command=lambda m=box: self.mailbox_info(m)) b_edit.grid(row=i, column=2, padx=4, pady=4) b_del = Button(self.frame, image=self.im_del, width=1, command=lambda m=box: self.del_mailbox(m)) b_del.grid(row=i, column=3, padx=4, pady=4) self.mailboxes[box] = [c, l, b_edit, b_del] for box in inactive: i += 1 c = Checkbutton(self.frame) c.grid(row=i, column=0, pady=4, padx=(4, 0)) l = Label(self.frame, text=box) l.grid(row=i, column=1, padx=4, pady=4) b_edit = Button(self.frame, image=self.im_edit, width=1, command=lambda m=box: self.mailbox_info(m)) b_edit.grid(row=i, column=2, padx=4, pady=4) b_del = Button(self.frame, image=self.im_del, width=1, command=lambda m=box: self.del_mailbox(m)) b_del.grid(row=i, column=3, padx=4, pady=4) self.mailboxes[box] = [c, l, b_edit, b_del] self.b_add = Button(self.frame, image=self.im_add, command=self.mailbox_info, width=1) self.b_add.grid(row=i + 1, column=0, columnspan=4, pady=4, padx=4, sticky='w')
def quit(self): """Save configuration and destroy the dialog.""" active = [] inactive = [] for box, (c, l, b1, b2) in self.mailboxes.items(): if "selected" in c.state(): active.append(box) else: inactive.append(box) CONFIG.set("Mailboxes", "active", ", ".join(active)) CONFIG.set("Mailboxes", "inactive", ", ".join(inactive)) save_config() self.destroy()
def save(self, event=None): self.name = name = self.name_entry.get().strip() if name != self.mailbox: # change name of mailbox os.remove(os.path.join(LOCAL_PATH, self.mailbox)) active = CONFIG.get("Mailboxes", "active").split(", ") inactive = CONFIG.get("Mailboxes", "inactive").split(", ") while "" in active: active.remove("") while "" in inactive: inactive.remove("") if self.mailbox in active: active.remove(self.mailbox) active.append(name) elif self.mailbox in inactive: inactive.remove(self.mailbox) inactive.append(name) CONFIG.set("Mailboxes", "active", ", ".join(active)) CONFIG.set("Mailboxes", "inactive", ", ".join(inactive)) save_config() encrypt(name, self.pwd, self.server_entry.get().strip(), self.login_entry.get().strip(), self.password_entry.get().strip(), self.folder_entry.get().strip()) self.destroy()
def launch_check(self, force_notify=False): """ Check every 20 s if the login to all the mailboxes is done. Once it is the case, launch the unread mail check. """ b = [ self.threads_connect[box].is_alive() for box in self.threads_connect ] if len(b) < len(self.info_conn) or True in b: logging.info("Waiting for connexion ...") try: self.after_cancel(self.check_id) except ValueError: pass self.check_id = self.after(20000, self.launch_check, force_notify) else: logging.info("Launching check") if not self.login_err_queue.empty(): correct = False while not self.login_err_queue.empty(): box = self.login_err_queue.get() action = show_failed_auth_msg(self, box) if action == 'correct': dialog = EditMailbox(self, self.pwd, box) self.wait_window(dialog) self.connect(box) correct = dialog.name or correct else: # remove box from the active mailboxes del (self.boxes[box]) active = CONFIG.get("Mailboxes", "active").split(", ") inactive = CONFIG.get("Mailboxes", "inactive").split(", ") while "" in active: active.remove("") while "" in inactive: inactive.remove("") active.remove(box) inactive.append(box) CONFIG.set("Mailboxes", "active", ", ".join(active)) CONFIG.set("Mailboxes", "inactive", ", ".join(inactive)) if correct: self.after_cancel(self.check_id) self.check_id = self.after(20000, self.launch_check, force_notify) else: self.check_mails(force_notify) else: self.check_mails(force_notify)
def change_icon(self, nbmail): """Display the number of unread mails nbmail in the system tray icon.""" nb = "%i" % nbmail im = Image.open(IMAGE) W, H = im.size draw = ImageDraw.Draw(im) font_path = TTF_FONTS[CONFIG.get("General", "font")] try: font = ImageFont.truetype(font_path, FONTSIZE) w, h = draw.textsize(nb, font=font) draw.text(((W - w) / 2, (H - h) / 2), nb, fill=(255, 0, 0), font=font) except OSError: w, h = draw.textsize(nb) draw.text(((W - w) / 2, (H - h) / 2), nb, fill=(255, 0, 0)) im.save(ICON) self.icon.change_icon(ICON, "checkmails %s" % nb)
def __init__(self, master, notify=False): Toplevel.__init__(self, master, class_="CheckMails") logging.info('Checking for updates') self.title(_("Update")) self.withdraw() self.rowconfigure(0, weight=1) self.columnconfigure(0, weight=1) self.columnconfigure(1, weight=1) self.protocol("WM_DELETE_WINDOW", self.quit) self.notify = notify self.img = PhotoImage(file=IM_QUESTION, master=self) frame = Frame(self) frame.grid(row=0, columnspan=2, sticky="ewsn") Label(frame, image=self.img).pack(side="left", padx=(10, 4), pady=(10, 4)) Label(frame, text=_("A new version of CheckMails is available.\ \nDo you want to download it?"), font="TkDefaultFont 10 bold", wraplength=335).pack(side="left", padx=(4, 10), pady=(10, 4)) self.b1 = Button(self, text=_("Yes"), command=self.download) self.b1.grid(row=1, column=0, padx=10, pady=10, sticky="e") Button(self, text=_("No"), command=self.quit).grid(row=1, column=1, padx=10, pady=10, sticky="w") self.ch = Checkbutton(self, text=_("Check for updates on startup.")) if CONFIG.getboolean("General", "check_update"): self.ch.state(("selected", )) self.ch.grid(row=2, columnspan=2, sticky='w') self.update = None self.thread = Thread(target=self.update_available, daemon=True) self.thread.start() self.after(1000, self.check_update)
def get_info_conn(self): """ Retrieve connection information from the encrypted files and launch checks (if they are noit suspended). """ mailboxes = CONFIG.get("Mailboxes", "active").split(", ") while "" in mailboxes: mailboxes.remove("") self.info_conn = {} if self.pwd is None: if not os.path.exists(os.path.join(LOCAL_PATH, ".pwd")): self.set_password() else: self.ask_password() if self.pwd is not None: for box in mailboxes: server, login, password, folder = decrypt(box, self.pwd) if server is not None: self.info_conn[box] = (server, (login, password), folder) if not self.info_conn: self.notif = _("No active mailbox") run([ "notify-send", "-i", IMAGE2, _("No active mailbox"), _("Use the mailbox manager to configure a mailbox.") ]) elif self.icon.get_item_label(3) == _("Suspend"): self.notif = "" for box in self.info_conn: self.connect(box) try: self.after_cancel(self.check_id) except ValueError: pass self.check_id = self.after(20000, self.launch_check, False)
def reset_password(self): """ Reset the master password and delete all the mailboxes config files since they cannot be decrypted without the password. """ rep = askokcancel( _("Confirmation"), _("The reset of the password will erase all the stored mailbox connection information" ), icon="warning") if rep: mailboxes = CONFIG.get("Mailboxes", "active").split(", ") + CONFIG.get( "Mailboxes", "inactive").split(", ") while "" in mailboxes: mailboxes.remove("") CONFIG.set("Mailboxes", "active", "") CONFIG.set("Mailboxes", "inactive", "") save_config() for mailbox in mailboxes: os.remove(os.path.join(LOCAL_PATH, mailbox)) logging.info('Reset') self.set_password()
def quit(self): CONFIG.set("General", "check_update", str("selected" in self.ch.state())) save_config() self.destroy()
def __init__(self, master): Toplevel.__init__(self, master, class_="CheckMails") self.title(_("Preferences")) style = Style(self) style.map("TCombobox", fieldbackground=[('readonly', 'white')], selectbackground=[('readonly', 'white')], selectforeground=[('readonly', 'black')], foreground=[('readonly', 'black')]) # validation of the entries : only numbers are allowed self._validate_entry_nb = self.register(self.validate_entry_nb) # --- Times Label(self, text=_("Time between two checks")).grid(row=0, column=0, padx=(10, 4), pady=(10, 4), sticky="e") Label(self, justify="right", text=_("Maximum time allowed for login or check\n\ (then the connection is reset)")).grid(row=1, column=0, padx=(10, 4), pady=4, sticky="e") self.time_entry = Entry(self, width=5, justify="center", validate="key", validatecommand=(self._validate_entry_nb, "%P")) self.time_entry.grid(row=0, column=1, padx=(4, 0), pady=(10, 4)) self.time_entry.insert(0, "%g" % (CONFIG.getint("General", "time") / 60000)) self.timeout_entry = Entry(self, width=5, justify="center", validate="key", validatecommand=(self._validate_entry_nb, "%P")) self.timeout_entry.grid(row=1, column=1, padx=(4, 0), pady=4) self.timeout_entry.insert(0, "%g" % (CONFIG.getint("General", "timeout") / 60000)) Label(self, text="min").grid(row=0, column=2, padx=(0, 10), pady=(10, 4)) Label(self, text="min").grid(row=1, column=2, padx=(0, 10), pady=4) frame = Frame(self) frame.grid(row=2, columnspan=3, padx=6, pady=(0, 6)) # --- Language Label(frame, text=_("Language")).grid(row=0, column=0, padx=8, pady=4, sticky="e") lang = {"fr": "Français", "en": "English"} self.lang = StringVar(self, lang[CONFIG.get("General", "language")]) menu_lang = Menu(frame, tearoff=False) Menubutton(frame, menu=menu_lang, width=9, textvariable=self.lang).grid(row=0, column=1, padx=8, pady=4, sticky="w") menu_lang.add_radiobutton(label="English", value="English", variable=self.lang, command=self.translate) menu_lang.add_radiobutton(label="Français", value="Français", variable=self.lang, command=self.translate) # --- gui toolkit Label(frame, text=_("GUI Toolkit for the system tray icon")).grid(row=1, column=0, padx=8, pady=4, sticky="e") self.gui = StringVar(self, CONFIG.get("General", "trayicon").capitalize()) menu_gui = Menu(frame, tearoff=False) Menubutton(frame, menu=menu_gui, width=9, textvariable=self.gui).grid(row=1, column=1, padx=8, pady=4, sticky="w") for toolkit, b in TOOLKITS.items(): if b: menu_gui.add_radiobutton(label=toolkit.capitalize(), value=toolkit.capitalize(), variable=self.gui, command=self.change_gui) # --- Font self.preview_path = tempfile.mktemp(".png", "checkmails_preview") w = max([len(f) for f in TTF_FONTS]) self.fonts = sorted(TTF_FONTS) self.font = Combobox(frame, values=self.fonts, width=(w * 2) // 3, exportselection=False, state="readonly") current_font = CONFIG.get("General", "font") if current_font in self.fonts: i = self.fonts.index(current_font) else: i = 0 self.font.current(i) self.img_prev = PhotoImage(master=self, file=IMAGE) Label(frame, text=_("Font")).grid(row=2, column=0, padx=8, pady=4, sticky="e") self.font.grid(row=2, column=1, padx=8, pady=4, sticky="w") self.prev = Label(frame, image=self.img_prev) self.prev.grid(row=2, column=2, padx=8, pady=4) self.update_preview() self.font.bind('<<ComboboxSelected>>', self.update_preview) self.font.bind_class("ComboboxListbox", '<KeyPress>', self.key_nav) # --- Ok/Cancel frame_button = Frame(self) frame_button.grid(row=3, columnspan=3, padx=6, pady=(0, 6)) Button(frame_button, text="Ok", command=self.ok).grid(row=2, column=0, padx=8, pady=4) Button(frame_button, text=_("Cancel"), command=self.destroy).grid(row=2, column=1, padx=4, pady=4)
def __init__(self): Tk.__init__(self, className="CheckMails") self.withdraw() logging.info('Starting checkmails') # icon that will show up in the taskbar for every toplevel self.im_icon = PhotoImage(master=self, file=ICON_48) self.iconphoto(True, self.im_icon) # system tray icon self.icon = TrayIcon(IMAGE) self.icon.add_menu_item(label=_("Details"), command=self.display) self.icon.add_menu_item(label=_("Check"), command=self.check_mails) self.icon.add_menu_item(label=_("Reconnect"), command=self.reconnect) self.icon.add_menu_item(label=_("Suspend"), command=self.start_stop) self.icon.add_menu_separator() self.icon.add_menu_item(label=_("Change password"), command=self.change_password) self.icon.add_menu_item(label=_("Reset password"), command=self.reset_password) self.icon.add_menu_separator() self.icon.add_menu_item(label=_("Manage mailboxes"), command=self.manage_mailboxes) self.icon.add_menu_item(label=_("Preferences"), command=self.config) self.icon.add_menu_separator() self.icon.add_menu_item(label=_("Check for updates"), command=lambda: UpdateChecker(self, True)) self.icon.add_menu_item(label=_("About"), command=lambda: About(self)) self.icon.add_menu_separator() self.icon.add_menu_item(label=_("Quit"), command=self.quit) self.icon.loop(self) self.icon.bind_left_click(self.display) self.style = Style(self) self.style.theme_use('clam') bg = self.cget("background") self.style.configure("TLabel", background=bg) self.style.configure("TFrame", background=bg) self.style.configure("TButton", background=bg) self.style.configure("TCheckbutton", background=bg) self.style.configure("TMenubutton", background=bg) self.style.map('TCheckbutton', indicatorbackground=[ ('pressed', '#dcdad5'), ('!disabled', 'alternate', 'white'), ('disabled', 'alternate', '#a0a0a0'), ('disabled', '#dcdad5') ]) # master password self.pwd = None # login info self.info_conn = {} # time between two checks self.time = CONFIG.getint("General", "time") # maximum time for login / check before the connection is reset self.timeout = CONFIG.getint("General", "timeout") self.boxes = {} # number of unread mails for each mailbox self.nb_unread = {box: 0 for box in self.info_conn} # connection, logout and check are done in separate threads for each # mailbox so that the system tray icon does not become unresponsive if # the process takes some time self.threads_connect = {} self.threads_logout = {} self.threads_check = {} self.login_err_queue = Queue() # after callbacks id self.check_id = '' self.timer_id = '' self.notif_id = '' self.internet_id = '' self.notify_no_internet = True # avoid multiple notification of No Internet connection # notification displayed when clicking on the icon self.notif = '' # retrieve mailbox login information from encrypted files self.get_info_conn() if CONFIG.getboolean("General", "check_update"): UpdateChecker(self) # replace Ctrl+A binding by select all for all entries self.bind_class("TEntry", "<Control-a>", self.select_all_entry)
def connect_mailbox(self, box): """Connect to the mailbox box and select the folder.""" try: logging.info("Connecting to %s" % box) serveur, loginfo, folder = self.info_conn[box] # reinitialize the connection if it takes too long timeout_id = self.after(self.timeout, self.timed_out, box, False, True) self.boxes[box] = IMAP4_SSL(serveur) self.boxes[box].login(*loginfo) self.boxes[box].select(folder) try: self.after_cancel(timeout_id) except ValueError: pass logging.info("Connected to %s" % box) except (IMAP4.error, ConnectionResetError, TimeoutError) as e: try: self.after_cancel(timeout_id) except ValueError: pass if e.args[0] in [ b'Invalid login or password', b'Authenticate error', b'Login failed: authentication failure', b'LOGIN failed' ]: # Identification error logging.error("Incorrect login or password for %(mailbox)s" % {"mailbox": box}) self.login_err_queue.put(box) else: # try to reconnect logging.error('%s: %s' % (box, e)) self.logout(box, reconnect=True) except gaierror as e: if e.errno == -2: # Either there is No Internet connection or the IMAP server is wrong if internet_on(): run([ "notify-send", "-i", "dialog-error", _("Error"), _("Wrong IMAP server for %(mailbox)s.") % { "mailbox": box } ]) # remove box from the active mailboxes active = CONFIG.get("Mailboxes", "active").split(", ") inactive = CONFIG.get("Mailboxes", "inactive").split(", ") while "" in active: active.remove("") while "" in inactive: inactive.remove("") active.remove(box) inactive.append(box) CONFIG.set("Mailboxes", "active", ", ".join(active)) CONFIG.set("Mailboxes", "inactive", ", ".join(inactive)) logging.error("Wrong IMAP server for %(mailbox)s." % {"mailbox": box}) else: if self.notify_no_internet: run([ "notify-send", "-i", "dialog-error", _("Error"), _("No Internet connection.") ]) self.notify_no_internet = False logging.warning("No Internet connection") # cancel everything try: self.after_cancel(self.check_id) except ValueError: pass try: self.after_cancel(self.timer_id) except ValueError: pass try: self.after_cancel(self.notif_id) except ValueError: pass try: self.after_cancel(self.internet_id) except ValueError: pass # periodically checks if the internet connection is turned on self.internet_id = self.after(self.timeout, self.test_connection) else: # try to reconnect logging.exception(str(type(e))) run([ "notify-send", "-i", "dialog-error", _("Error"), traceback.format_exc() ]) self.logout(box, reconnect=True) except ValueError: # Error sometimes raised when a connection process is interrupted by a logout process pass