Create a macOS Menu Bar App with Python (Pomodoro Timer)

Create a macOS Menu Bar App with Python (Pomodoro Timer)

Create your very own macOS Menu Bar App using Python, rumps and py2app – A simple Pomodoro Timer

Published on June 12, 2019

On my Mac, I use the menu bar constantly. This post explains how to create a custom macOS menu bar app using Python. As an example, let's create a 🍅 pomodoro app, which you can use to boost your productivity and manage your time from the convenience of your menu bar. It serves as a starting point for customization to suit your individual needs – you can use this code as a boilerplate to create a radically different application!

We'll use...

  • Python and PyCharm as an IDE
  • Rumps → Ridiculously Uncomplicated macOS Python Statusbar apps
  • py2app → For creating standalone macOS apps from Python code (how cool is that?)

Project Setup

Before we can start, let's install all requirements on our local machine. Run the following commands to set up the project directory:

  Terminal
~$export PROJECT_NAME=pomodoro
~$mkdir ~/PycharmProjects/$PROJECT_NAME
~$cd ~/PycharmProjects/$PROJECT_NAME
~$touch pomodoro.py setup.py

Be sure to install both rumps and py2app via pip (on my macOS). Enter the following in your terminal to install them globally (alternatively, set up a virtual environment):

  Terminal
~$pip3 install -U py2app
~$pip3 install -U rumps

Basic Example

Open the project directory in your favorite IDE. First, set up the following boilerplate code in order to get started:

  pomodoro.py
1import rumps

2class PomodoroApp(object):
3    def __init__(self):
4        self.app = rumps.App("Pomodoro", "🍅")

5    def run(self):
6        self.app.run()

7if __name__ == '__main__':
8    app = PomodoroApp()
9    app.run()

If you execute the python program using python pomodoro.py, you will notice a new addition to your menu bar – albeit with limited functionality, as you can see…

A barely functional menu bar app...

A barely functional menu bar app...

Full Implementation

Now let's implement the actual functionality of our pomodoro menu bar app – see the complete code below:

  pomodoro.py
1import rumps

2class PomodoroApp(object):
3    def __init__(self):
4        self.config = {
5            "app_name": "Pomodoro",
6            "start": "Start Timer",
7            "pause": "Pause Timer",
8            "continue": "Continue Timer",
9            "stop": "Stop Timer",
10            "break_message": "Time is up! Take a break :)",
11            "interval": 1500
12        }
13        self.app = rumps.App(self.config["app_name"])
14        self.timer = rumps.Timer(self.on_tick, 1)
15        self.interval = self.config["interval"]
16        self.set_up_menu()
17        self.start_pause_button = rumps.MenuItem(title=self.config["start"], callback=self.start_timer)
18        self.stop_button = rumps.MenuItem(title=self.config["stop"], callback=None)
19        self.app.menu = [self.start_pause_button, self.stop_button]

20    def set_up_menu(self):
21        self.timer.stop()
22        self.timer.count = 0
23        self.app.title = "🍅"

24    def on_tick(self, sender):
25        time_left = sender.end - sender.count
26        mins = time_left // 60 if time_left >= 0 else time_left // 60 + 1
27        secs = time_left % 60 if time_left >= 0 else (-1 * time_left) % 60
28        if mins == 0 and time_left < 0:
29            rumps.notification(title=self.config["app_name"], subtitle=self.config["break_message"], message='')
30            self.stop_timer()
31            self.stop_button.set_callback(None)
32        else:
33            self.stop_button.set_callback(self.stop_timer)
34            self.app.title = '{:2d}:{:02d}'.format(mins, secs)
35        sender.count += 1

36    def start_timer(self, sender):
37        if sender.title.lower().startswith(("start", "continue")):
38            if sender.title == self.config["start"]:
39                self.timer.count = 0
40                self.timer.end = self.interval
41            sender.title = self.config["pause"]
42            self.timer.start()
43        else:
44            sender.title = self.config["continue"]
45            self.timer.stop()

46    def stop_timer(self, sender):
47        self.set_up_menu()
48        self.stop_button.set_callback(None)
49        self.start_pause_button.title = self.config["start"]

50    def run(self):
51        self.app.run()

52if __name__ == '__main__':
53    app = PomodoroApp()
54    app.run()

By executing the python program again by executing python pomodoro.py, you can discover the menu bar app in its full glory:

Now we're getting there!

Now we're getting there!

Time's ticking...

Time's ticking...

And as soon as the timer reaches zero, it will remind you to take a break via a notification!

And as soon as the timer reaches zero, it will remind you to take a break via a notification!

Let's bundle our code into a macOS application using py2app so you don’t have to execute it via Python every single time. It needs to be ready for every day use, and that means adding it to the login items of your Mac!

Creating macOS Apps from Python Code

Let's create a second file and add the following code, which provides all the necessary instructions to create the application bundle (app name, app version, app icon, etc.) to py2app in form of setup arguments:

  setup.py
1from setuptools import setup

2APP = ['pomodoro.py']
3DATA_FILES = []
4OPTIONS = {
5    'argv_emulation': True,
6    'iconfile': 'icon.icns',
7    'plist': {
8        'CFBundleShortVersionString': '0.2.0',
9        'LSUIElement': True,
10    },
11    'packages': ['rumps'],
12}

13setup(
14    app=APP,
15    name='Pomodoro',
16    data_files=DATA_FILES,
17    options={'py2app': OPTIONS},
18    setup_requires=['py2app'], install_requires=['rumps']
19)

Are you missing the icon file? You can download the file I used from the GitHub repository.

Now you can go ahead and create your Pomodoro.app bundle using py2app. Type the following in your terminal:

  Terminal
~$python setup.py py2app

The application bundle will be created in your project directory under ./dist/*.

Conclusion

I’ve implemented a similar menu bar app with some additional functionality. It’s called Timebox and integrates with Things 3. Check it out and let me know what you think!

Here's how it looks like:

Timebox – A more advanced implementation

Timebox – A more advanced implementation

I hope you enjoyed this article –  please let me know if you have any questions or if you run into any issues. Have some custom functionality in mind to improve your Pomodoro workflow? Just fork the repository and have a go at it – I'm curious to see what you'll come up with!

Let's stay in touch!

If you'd like to get notified about updates, feel free to

Enter your email address below to receive an email whenever I write a new post.

Note: You can unsubscribe at any time by clicking on the unsubscribe link included at the bottom of every email. No Spam!