Skip to content

Instantly share code, notes, and snippets.

@weirane
Created May 30, 2020 01:41
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save weirane/192a1254a4854d351085e6a105f4a82a to your computer and use it in GitHub Desktop.
Save weirane/192a1254a4854d351085e6a105f4a82a to your computer and use it in GitHub Desktop.
System tray for email syncing state
#!/usr/bin/python3
# System tray icon displaying email syncing state.
# Tell the tray that email is syncing:
# kill -USR1 $(cat $XDG_RUNTIME_DIR/mailtray.pid)
# Sync has completed and recalculate new emails:
# kill -USR2 $(cat $XDG_RUNTIME_DIR/mailtray.pid)
#
# However, the `Gtk.StatusIcon` used in this script has been deprecated and I haven't found a
# replacement.
import gi
gi.require_version('Gtk', '3.0')
from gi.repository import Gtk
from signal import signal, SIGUSR1, SIGUSR2
import os
MAILDIR = os.environ.get('MAILDIR', os.path.expandvars("$HOME/Mail"))
class MailTray:
def __init__(self):
self.icon = Gtk.StatusIcon()
self.menu = Gtk.Menu()
cn = Gtk.MenuItem.new_with_label('Update count')
cn.connect('activate', self.state_idle)
self.menu.append(cn)
quit = Gtk.MenuItem.new_with_label('Quit')
quit.connect('activate', self.cleanup)
self.menu.append(quit)
self.icon.connect('popup-menu', self.popup_menu_cb)
self.new_count = {}
self.state_idle()
try:
self.pidfile = os.path.join(os.environ['XDG_RUNTIME_DIR'], 'mailtray.pid')
except KeyError:
print('XDG_RUNTIME_DIR is not set')
with open(self.pidfile, 'w') as f:
f.write(str(os.getpid()))
signal(SIGUSR1, self.state_receiving)
signal(SIGUSR2, self.state_idle)
def check_new(self, _=None):
self.new_count = {
acc: len(list(filter(lambda x: x, os.popen(
f"""sh -c 'find "{MAILDIR}/{acc}/Inbox/new/" -type f -print0'"""
).read().split('\0'))))
for acc in filter(lambda s: not s.startswith('.'), os.listdir(MAILDIR))
}
def state_idle(self, *args):
self.check_new()
msg = ', '.join(f'{a} ({n})' for a, n in
filter(lambda it: it[1] != 0, self.new_count.items()))
if msg:
self.icon.set_tooltip_text(msg)
self.icon.set_from_icon_name('mail_new')
else:
self.icon.set_tooltip_text('Mail')
self.icon.set_from_icon_name('mail_generic')
def state_receiving(self, *args):
self.icon.set_tooltip_text('Mail')
self.icon.set_from_icon_name('mail-send-receive')
def cleanup(self, _=None):
try:
os.remove(self.pidfile)
except Exception:
pass
Gtk.main_quit()
def popup_menu_cb(self, widget, button, time, data=None):
self.menu.show_all()
self.menu.popup(None, None, Gtk.StatusIcon.position_menu, self.icon, button, time)
if __name__ == '__main__':
MailTray()
Gtk.main()
#!/usr/bin/python3
# System tray icon displaying email syncing state.
# Tell the tray that email is syncing:
# echo 'syncing' > $XDG_RUNTIME_DIR/mailtray.fifo
# Sync has completed and recalculate new emails:
# echo 'idle' > $XDG_RUNTIME_DIR/mailtray.fifo
#
# Because in Qt, it's hard to use UNIX signals to tell the tray what's going on, we use a fifo to
# inform the tray of the state.
from PyQt5.QtWidgets import QApplication, QSystemTrayIcon, QMenu
from PyQt5.QtGui import QIcon
import sys
import threading
import os
MAILDIR = os.environ.get('MAILDIR', os.path.expandvars("$HOME/Mail"))
class MailTray:
def __init__(self):
self.app = QApplication([])
self.app.setQuitOnLastWindowClosed(False)
self.tray = QSystemTrayIcon()
self.menu = QMenu()
uc = self.menu.addAction('Update count')
uc.triggered.connect(self.state_idle)
quit = self.menu.addAction('Quit')
quit.triggered.connect(self.cleanup)
self.tray.setContextMenu(self.menu)
self.icon_clean = QIcon.fromTheme('mail_generic')
self.icon_new = QIcon.fromTheme('mail_new')
self.icon_recieve = QIcon.fromTheme('mail-send-receive')
self.new_count = {}
self.state_idle()
self.tray.setVisible(True)
try:
self.fifofile = os.path.join(os.environ['XDG_RUNTIME_DIR'], 'mailtray.fifo')
except KeyError:
print('XDG_RUNTIME_DIR is not set')
sys.exit(1)
try:
os.mkfifo(self.fifofile)
except FileExistsError:
pass
self.thread = threading.Thread(target=self.listen, daemon=True)
self.thread.start()
def listen(self):
while True:
with open(self.fifofile) as f:
line = f.readline().strip()
if line == 'syncing':
self.state_receiving()
elif line == 'idle':
self.state_idle()
elif line == 'exit':
self.cleanup()
def check_new(self, _=None):
self.new_count = {
acc: len(list(filter(bool, os.popen(
f"""sh -c 'find "{MAILDIR}/{acc}/Inbox/new/" -type f -print0'"""
).read().split('\0'))))
for acc in filter(lambda s: not s.startswith('.'), os.listdir(MAILDIR))
}
def state_idle(self, *args):
self.check_new()
msg = ', '.join(f'{a} ({n})'
for a, n in filter(lambda it: it[1] != 0, self.new_count.items()))
if msg:
self.tray.setToolTip(msg)
self.tray.setIcon(self.icon_new)
else:
self.tray.setToolTip('Mail')
self.tray.setIcon(self.icon_clean)
def state_receiving(self, *args):
self.tray.setToolTip('Syncing')
self.tray.setIcon(self.icon_recieve)
def cleanup(self):
try:
os.remove(self.fifofile)
except Exception:
pass
self.app.quit()
if __name__ == '__main__':
os.environ['QT_LOGGING_RULES'] = 'qt5ct.debug=false'
mailtray = MailTray()
mailtray.app.exec_()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment