julienblanchard.com $home

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:

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.

mfatray screenshot