Making a tray icon tool
2024-08-23 18:19:00
I love making tools for myself, after all creating stuff is what got me into programming. There’s something uniquely satisfying about using your own creations, that feeling of having created something useful and that is somewhat done never gets old.
The other day I was tired of opening a terminal to get some TOTP (Time-Based One-Time Password) codes for multi-factor authentication (MFA) so I thought about how could I make it seamless? My answer is a tray icon that I could click and I would get my TOTP code copied to my clipboard. Nifty, right?
Like every tool I write I try to make it a learning opportunity to fiddle with different languages and/or libs. This time it’s Python and QT. Python I know a bit but haven’t practiced in a long time and QT I think I havent’t used before.
So here is the quick python script I made using PyQT5:
#!/usr/bin/python3
import sys
import subprocess
from pathlib import Path
from PyQt5.QtGui import QIcon
from PyQt5.QtWidgets import QApplication, QMenu, QSystemTrayIcon
class TrayApp:
def __init__(self):
self.app = QApplication(sys.argv)
self.tray = QSystemTrayIcon()
self.setup_tray_icon()
def setup_tray_icon(self):
"""Sets up the tray icon with an icon, tooltip, and context menu."""
icon_path = Path.home() / ".local/share/mfa/key.png"
icon = QIcon(str(icon_path))
menu = QMenu()
exit_action = menu.addAction("Exit")
exit_action.triggered.connect(self.exit_app)
self.tray.setIcon(icon)
self.tray.setContextMenu(menu)
self.tray.setToolTip("mfa!")
self.tray.activated.connect(self.handle_tray_icon_click)
self.tray.show()
def exit_app(self):
"""Exits the application."""
QApplication.quit()
sys.exit()
def run(self):
"""Runs the QApplication event loop."""
self.app.exec_()
sys.exit()
def handle_tray_icon_click(self, reason):
"""Handles system tray icon activation events."""
if reason == QSystemTrayIcon.Trigger: # Left click
self.copy_mfa_to_clipboard()
def copy_mfa_to_clipboard(self):
"""Reads MFA secret, generates TOTP, and copies it to the clipboard."""
mfa_file_path = Path.home() / "path/to/my_mfa_secret_file"
try:
with open(mfa_file_path, 'r') as mfa_file:
mfa_secret = mfa_file.read().strip()
except FileNotFoundError:
self.tray.showMessage("Error", "MFA file not found.", QSystemTrayIcon.Warning, 3000)
return
except Exception as e:
self.tray.showMessage("Error", f"Failed to read MFA file: {e}", QSystemTrayIcon.Warning, 3000)
return
try:
cmd = ['oathtool', '--base32', '--totp', mfa_secret]
totp_code = subprocess.check_output(cmd).decode("utf-8").strip()
QApplication.clipboard().setText(totp_code)
self.tray.showMessage("MFA", f"Copied {totp_code}", QSystemTrayIcon.Information, 3000)
except subprocess.CalledProcessError:
self.tray.showMessage("Error", "Failed to generate TOTP code.", QSystemTrayIcon.Warning, 3000)
except Exception as e:
self.tray.showMessage("Error", f"Unexpected error: {e}", QSystemTrayIcon.Warning, 3000)
if __name__ == "__main__":
tray_app = TrayApp()
tray_app.run()
I guess the program is pretty straight forward past the QT specific setup. What it does is:
- First initialize the application with a QApplication object. It is required for any PyQt application.
- Setup the tray icon with a custom icon image, tooltip, and context menu. Add “Exit” option to close the application.
- The handle_tray_icon_click function is connected to the tray icon’s click event. On left click it calls copy_mfa_to_clipboard.
- copy_mfa_to_clipboard generates a TOTP code using the oathtool command and copy teh result to the clipboard. It also shows the generated code.
That’s it for today, made a fun little tool that saves me some time daily while learning some bits, good time!
A little screenshot.