Skip to content

Commit 3d7e901

Browse files
committed
Merge branch 'tray_icons'
Add support for several system tray icon gui toolkits: increase the number of compatible desktop environments.
2 parents da8fc79 + dc9b249 commit 3d7e901

20 files changed

Lines changed: 744 additions & 309 deletions

File tree

README.rst

Lines changed: 15 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -12,13 +12,6 @@ for each mailbox is stored in an encrypted file using a master password.
1212
CheckMails is designed for Linux. It is written in Python 3 and relies upon
1313
Tk GUI toolkit.
1414

15-
Warning
16-
-------
17-
18-
CheckMails is a system tray unread mail checker, so it does not work on
19-
desktop environments with no system tray (like Unity). Furthermore, it relies
20-
on tktray which is an old library, so some desktop environment system trays
21-
are not fully compatible with it (e.g. in KDE/plasma).
2215

2316
Install
2417
-------
@@ -30,9 +23,9 @@ Install
3023
- Ubuntu
3124

3225
CheckMails is available in the PPA `ppa:j-4321-i/ppa`.
33-
26+
3427
::
35-
28+
3629
$ sudo add-apt-repository ppa:j-4321-i/ppa
3730
$ sudo apt-get update
3831
$ sudo apt-get install checkmails
@@ -41,12 +34,17 @@ Install
4134

4235
First, install the missing dependencies among:
4336
- Tkinter (Python wrapper for Tk)
44-
- Tktray https://code.google.com/archive/p/tktray/downloads
4537
- libnotify and a notification server if your desktop environment does not provide one.
4638
(see https://wiki.archlinux.org/index.php/Desktop_notifications for more details)
4739
- PyCrypto https://pypi.python.org/pypi/pycrypto
4840
- Pillow https://pypi.python.org/pypi/Pillow
4941

42+
You also need to have at least one of the following GUI toolkits for the system tray icon:
43+
- Tktray https://code.google.com/archive/p/tktray/downloads
44+
- PyGTK http://www.pygtk.org/downloads.html
45+
- PyQt5, PyQt4 or PySide
46+
47+
5048
For instance, in Ubuntu/Debian you will need to install the following packages:
5149
python3-tk, tk-tktray, libnotify and the notification server of your choice,
5250
python3-crypto, python3-pil
@@ -64,10 +62,15 @@ it from the command line with `checkmails`. In this last case, you will see
6462
the messages printed every time a process is lauched or finished and when
6563
an error is encountered. Therefore you can check that everything works fine.
6664

65+
Troubleshooting
66+
---------------
6767

68-
If there is a black border around the system tray icon, you can try to
69-
restart the panel (e.g. xfce4-panel), it should disappear (at least in xfce).
68+
Several gui toolkits are available to display the system tray icon, so if the
69+
icon does not behave properly, try to change toolkit, they are not all fully
70+
compatible with every desktop environment.
7071

72+
If there is a problem with the font of the number of unread mails, try to change
73+
the font from the settings.
7174

7275
If you encounter bugs or if you have suggestions, please open an issue on
7376
`GitHub <https://github.com/j4321/CheckMails/issues>`__ or write me an email

changelog

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,12 @@ Copyright 2016 Juliette Monsel <j_4321@protonmail.com>
55
Changelog
66
---------
77

8+
- Version 1.2.0
9+
* Fix font settings
10+
* Add support for several system tray icon gui toolkits:
11+
increase the number of compatible desktop environments
12+
* Improve style consistency
13+
814
- Version 1.1.5
915
* Add log file ~/.checkmails/checkmails.log
1016
* Fix setup.py so that install works

checkmailslib/about.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,6 @@ class About(Toplevel):
3434
def __init__(self, master):
3535
""" créer le Toplevel 'À propos de CheckMails' """
3636
Toplevel.__init__(self, master)
37-
3837
self.title(_("About CheckMails"))
3938
self.image = PhotoImage(file=ICON_48, master=self)
4039
Label(self, image=self.image).grid(row=0, columnspan=2, pady=10)

checkmailslib/check.py

Lines changed: 80 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -36,9 +36,9 @@
3636
from tkinter.messagebox import showerror, askokcancel
3737
from tkinter.ttk import Entry, Label, Button, Style
3838
from PIL import Image, ImageDraw, ImageFont
39-
from checkmailslib.tktray import Icon
40-
from checkmailslib.constants import IMAGE, ICON, IMAGE2, save_config, ICON_48
41-
from checkmailslib.constants import encrypt, decrypt, LOCAL_PATH, CONFIG, internet_on
39+
from checkmailslib.trayicon import TrayIcon
40+
from checkmailslib.constants import IMAGE, ICON, IMAGE2, save_config, FONTSIZE,\
41+
encrypt, decrypt, LOCAL_PATH, CONFIG, internet_on, TTF_FONTS, ICON_48
4242
from checkmailslib.manager import Manager
4343
from checkmailslib.config import Config
4444
from checkmailslib.about import About
@@ -54,33 +54,41 @@ def __init__(self):
5454
# icon that will show up in the taskbar for every toplevel
5555
self.im_icon = PhotoImage(master=self, file=ICON_48)
5656
self.iconphoto(True, self.im_icon)
57+
5758
# system tray icon
58-
self.img = PhotoImage(file=IMAGE)
59-
self.icon = Icon(self, image=self.img)
60-
self.icon.menu.add_command(label=_("Check"), command=self.check_mails)
61-
self.icon.menu.add_command(label=_("Reconnect"),
59+
self.icon = TrayIcon(IMAGE)
60+
self.icon.add_menu_item(label=_("Details"), command=self.display)
61+
self.icon.add_menu_item(label=_("Check"), command=self.check_mails)
62+
self.icon.add_menu_item(label=_("Reconnect"),
6263
command=self.reconnect)
63-
self.icon.menu.add_command(label=_("Suspend"), command=self.start_stop)
64-
self.icon.menu.add_separator()
65-
self.icon.menu.add_command(label=_("Change password"),
64+
self.icon.add_menu_item(label=_("Suspend"), command=self.start_stop)
65+
self.icon.add_menu_separator()
66+
self.icon.add_menu_item(label=_("Change password"),
6667
command=self.change_password)
67-
self.icon.menu.add_command(label=_("Reset password"),
68+
self.icon.add_menu_item(label=_("Reset password"),
6869
command=self.reset_password)
69-
self.icon.menu.add_separator()
70-
self.icon.menu.add_command(label=_("Manage mailboxes"),
70+
self.icon.add_menu_separator()
71+
self.icon.add_menu_item(label=_("Manage mailboxes"),
7172
command=self.manage_mailboxes)
72-
self.icon.menu.add_command(label=_("Preferences"), command=self.config)
73-
self.icon.menu.add_separator()
74-
self.icon.menu.add_command(label=_("Check for updates"),
73+
self.icon.add_menu_item(label=_("Preferences"), command=self.config)
74+
self.icon.add_menu_separator()
75+
self.icon.add_menu_item(label=_("Check for updates"),
7576
command=lambda: UpdateChecker(self, True))
76-
self.icon.menu.add_command(label=_("About"),
77+
self.icon.add_menu_item(label=_("About"),
7778
command=lambda: About(self))
78-
self.icon.menu.add_separator()
79-
self.icon.menu.add_command(label=_("Quit"), command=self.quit)
80-
self.icon.bind('<Button-1>', self.display)
79+
self.icon.add_menu_separator()
80+
self.icon.add_menu_item(label=_("Quit"), command=self.quit)
81+
self.icon.loop(self)
82+
self.icon.bind_left_click(self.display)
8183

8284
self.style = Style(self)
8385
self.style.theme_use('clam')
86+
bg = self.cget("background")
87+
self.style.configure("TLabel", background=bg)
88+
self.style.configure("TFrame", background=bg)
89+
self.style.configure("TButton", background=bg)
90+
self.style.configure("TCheckbutton", background=bg)
91+
self.style.configure("TMenubutton", background=bg)
8492
self.style.map('TCheckbutton',
8593
indicatorbackground=[('pressed', '#dcdad5'),
8694
('!disabled', 'alternate', 'white'),
@@ -118,37 +126,43 @@ def __init__(self):
118126
if CONFIG.getboolean("General", "check_update"):
119127
UpdateChecker(self)
120128

129+
# replace Ctrl+A binding by select all for all entries
130+
self.bind_class("TEntry", "<Control-a>", self.select_all_entry)
131+
132+
def select_all_entry(self, event):
133+
event.widget.selection_range(0, "end")
134+
121135
def report_callback_exception(self, *args):
122136
"""Log exceptions."""
123137
err = "".join(traceback.format_exception(*args))
124138
logging.error(err)
125139

126140
def start_stop(self):
127141
"""Suspend checks."""
128-
if self.icon.menu.entrycget(2, "label") == _("Suspend"):
129-
self.icon.after_cancel(self.check_id)
130-
self.icon.after_cancel(self.timer_id)
131-
self.icon.after_cancel(self.notif_id)
132-
self.icon.after_cancel(self.internet_id)
133-
self.img.configure(file=IMAGE)
134-
self.icon.menu.entryconfigure(2, label=_("Restart"))
135-
self.icon.menu.entryconfigure(0, state="disabled")
136-
self.icon.menu.entryconfigure(1, state="disabled")
142+
if self.icon.get_item_label(3) == _("Suspend"):
143+
self.after_cancel(self.check_id)
144+
self.after_cancel(self.timer_id)
145+
self.after_cancel(self.notif_id)
146+
self.after_cancel(self.internet_id)
147+
self.icon.change_icon(IMAGE, "checkmails suspended")
148+
self.icon.set_item_label(3, _("Restart"))
149+
self.icon.disable_item(1)
150+
self.icon.disable_item(2)
137151
else:
138-
self.icon.menu.entryconfigure(2, label=_("Suspend"))
139-
self.icon.menu.entryconfigure(0, state="normal")
140-
self.icon.menu.entryconfigure(1, state="normal")
152+
self.icon.set_item_label(3, _("Suspend"))
153+
self.icon.enable_item(1)
154+
self.icon.enable_item(2)
141155
self.reconnect()
142156

143157
def reconnect(self):
144-
self.icon.after_cancel(self.check_id)
158+
self.after_cancel(self.check_id)
145159
self.nb_unread = {box: 0 for box in self.info_conn}
146160
for box in self.boxes:
147161
self.logout(box, True, True)
148-
self.check_id = self.icon.after(20000, self.launch_check, False)
162+
self.check_id = self.after(20000, self.launch_check, False)
149163

150-
def display(self, event):
151-
if self.icon.menu.entrycget(2, "label") == _("Suspend"):
164+
def display(self):
165+
if self.icon.get_item_label(3) == _("Suspend"):
152166
notif = self.notif
153167
if not notif:
154168
notif = _("Checking...")
@@ -165,9 +179,9 @@ def reset_conn(self):
165179
self.threads_connect = {}
166180
self.threads_reconnect = {}
167181
self.threads_check = {}
168-
self.icon.after_cancel(self.timer_id)
169-
self.icon.after_cancel(self.check_id)
170-
self.icon.after_cancel(self.notif_id)
182+
self.after_cancel(self.timer_id)
183+
self.after_cancel(self.check_id)
184+
self.after_cancel(self.notif_id)
171185
self.get_info_conn()
172186

173187
def get_info_conn(self):
@@ -192,36 +206,37 @@ def get_info_conn(self):
192206
if not self.info_conn:
193207
self.notif = _("No active mailbox")
194208
run(["notify-send", "-i", IMAGE2, _("No active mailbox"), _("Use the mailbox manager to configure a mailbox.")])
195-
elif self.icon.menu.entrycget(2, "label") == _("Suspend"):
209+
elif self.icon.get_item_label(3) == _("Suspend"):
196210
self.notif = ""
197211
for box in self.info_conn:
198212
self.connect(box)
199-
self.icon.after_cancel(self.check_id)
213+
self.after_cancel(self.check_id)
200214
self.check_id = self.after(20000, self.launch_check, False)
201215

202216
def change_icon(self, nbmail):
203217
"""Display the number of unread mails nbmail in the system tray icon."""
204218
nb = "%i" % nbmail
205219
im = Image.open(IMAGE)
206-
draw = ImageDraw.Draw(im)
207-
font_path = CONFIG.get("General", "font")
208220
W, H = im.size
221+
draw = ImageDraw.Draw(im)
222+
font_path = TTF_FONTS[CONFIG.get("General", "font")]
209223
try:
210-
font = ImageFont.truetype(font_path, 10)
224+
font = ImageFont.truetype(font_path, FONTSIZE)
211225
w, h = draw.textsize(nb, font=font)
212-
draw.text(((W - w) / 2, (H - h) / 2), nb, fill=(255, 0, 0), font=font)
226+
draw.text(((W - w) / 2, (H - h) / 2), nb, fill=(255, 0, 0),
227+
font=font)
213228
except OSError:
214229
w, h = draw.textsize(nb)
215230
draw.text(((W - w) / 2, (H - h) / 2), nb, fill=(255, 0, 0))
216231
im.save(ICON)
217-
self.img.configure(file=ICON)
232+
self.icon.change_icon(ICON, "checkmails %s" % nb)
218233

219234
def config(self):
220235
"""Open config dialog to set times and language."""
221236
Config(self)
222237
self.time = CONFIG.getint("General", "time")
223238
self.timeout = CONFIG.getint("General", "timeout")
224-
if self.icon.menu.entrycget(2, "label") == _("Suspend"):
239+
if self.icon.get_item_label(3) == _("Suspend"):
225240
self.check_mails(False)
226241

227242
def manage_mailboxes(self):
@@ -299,10 +314,10 @@ def connect_mailbox(self, box):
299314
run(["notify-send", "-i", "dialog-error", _("Error"),
300315
_("No internet connection.")])
301316
# cancel everything
302-
self.icon.after_cancel(self.check_id)
303-
self.icon.after_cancel(self.timer_id)
304-
self.icon.after_cancel(self.notif_id)
305-
self.icon.after_cancel(self.internet_id)
317+
self.after_cancel(self.check_id)
318+
self.after_cancel(self.timer_id)
319+
self.after_cancel(self.notif_id)
320+
self.after_cancel(self.internet_id)
306321
# periodically checks if the internet connection is turned on
307322
self.internet_id = self.after(self.timeout, self.test_connection)
308323
else:
@@ -324,7 +339,7 @@ def test_connection(self):
324339
if internet_on():
325340
self.reset_conn()
326341
else:
327-
self.internet_id = self.icon.after(self.timeout, self.test_connection)
342+
self.internet_id = self.after(self.timeout, self.test_connection)
328343

329344
def logout_mailbox(self, box, reconnect):
330345
"""
@@ -373,8 +388,8 @@ def launch_check(self, force_notify=False):
373388
b = [self.threads_connect[box].isAlive() for box in self.threads_connect]
374389
if len(b) < len(self.info_conn) or True in b:
375390
logging.info("Waiting for connexion ...")
376-
self.icon.after_cancel(self.check_id)
377-
self.check_id = self.icon.after(20000, self.launch_check, force_notify)
391+
self.after_cancel(self.check_id)
392+
self.check_id = self.after(20000, self.launch_check, force_notify)
378393
else:
379394
logging.info("Launching check")
380395
self.check_mails(force_notify)
@@ -417,17 +432,17 @@ def check_mails(self, force_notify=True):
417432
display a notification even if there is no unread mail.
418433
"""
419434
self.notif = _("Checking...") + "\n"
420-
self.icon.after_cancel(self.timer_id)
421-
self.icon.after_cancel(self.notif_id)
435+
self.after_cancel(self.timer_id)
436+
self.after_cancel(self.notif_id)
422437
for box, mail in self.boxes.items():
423438
if not self.threads_connect[box].isAlive() and (box not in self.threads_check or not self.threads_check[box].isAlive()):
424439
self.threads_check[box] = Thread(target=self.check_mailbox,
425440
name='check_' + box,
426441
daemon=True,
427442
args=(box,))
428443
self.threads_check[box].start()
429-
self.notif_id = self.icon.after(20000, self.notify_unread_mails, force_notify)
430-
self.timer_id = self.icon.after(self.time, self.check_mails, False)
444+
self.notif_id = self.after(20000, self.notify_unread_mails, force_notify)
445+
self.timer_id = self.after(self.time, self.check_mails, False)
431446

432447
def notify_unread_mails(self, force_notify=True):
433448
"""
@@ -438,11 +453,14 @@ def notify_unread_mails(self, force_notify=True):
438453
"""
439454
b = [self.threads_check[box].isAlive() for box in self.threads_check]
440455
if len(b) < len(self.info_conn) or True in b:
441-
self.notif_id = self.icon.after(20000, self.notify_unread_mails, force_notify)
456+
self.notif_id = self.after(20000, self.notify_unread_mails, force_notify)
442457
else:
443458
if self.notif != _("Checking...") + "\n":
444-
self.notif = self.notif[:-2].split("\n")[1]
445-
run(["notify-send", "-i", IMAGE2, _("Unread mails"), self.notif])
459+
try:
460+
self.notif = self.notif[:-2].split("\n")[1]
461+
run(["notify-send", "-i", IMAGE2, _("Unread mails"), self.notif])
462+
except IndexError:
463+
run(["notify-send", "-i", IMAGE2, _("Unread mails"), self.notif])
446464
elif force_notify:
447465
run(["notify-send", "-i", IMAGE2, _("Unread mails"), _("No unread mail")])
448466
self.notif = _("No unread mail")
@@ -458,6 +476,7 @@ def quit(self):
458476
for box in self.info_conn:
459477
self.logout(box)
460478
try:
479+
self.after_cancel(self.loop_id)
461480
self.destroy()
462481
except TclError:
463482
# depending on the pending processes when the app is destroyed

0 commit comments

Comments
 (0)