Jordan Schilling

Logo

View my SDLC journey. github.com/jschilling12

11 January 2026

Implementing a Pomodoro Timer GUI with Tkinter

by Jordan Schilling

Devlog — 2026-01-11

Overview

Implemented a Pomodoro Timer application with a graphical user interface using Tkinter. This marked a significant step into GUI development and desktop application architecture.

Since this was new territory, the goal was to learn Tkinter properly—understanding how its event loop works, how timers behave, and how GUI-driven applications differ from traditional scripts. The Pomodoro Timer served as a practical theme to explore these concepts.

This effort also became the integration point for running time tracking in the background, tying together multiple systems into a single cohesive application.

What Happened

This development session packed in a large number of concepts and architectural decisions:

Initially, almost nothing worked correctly. This led directly into learning about threading.

Threading and Background Processing

To keep the GUI responsive while tracking application usage in the background, threading was required.

Using threads allowed:

The background worker thread used for time tracking is a daemon thread.

GUI frameworks are not thread-safe — UI logic must stay on the main thread.

Common Threading Pitfall (Avoided)

What Not To Do

Thread(target=self.root.mainloop())  # WRONG

Why this fails:

Correct Pattern

Thread(target=tracker.timed_process)

Separation of Concerns

This refactor clearly separated responsibilities across components:

Component Responsibility
PomodoroTimer UI + countdown logic
timeTracker Background window time tracking
saveFiles File system persistence

This separation eliminated a large class of bugs and made reasoning about the system much easier.

Application Shutdown and Thread Cleanup

Closing a Tkinter window does not automatically stop background threads.

What Was Learned

Solution: WM_DELETE_WINDOW

def on_close():
    tracker.stop()
    saves.save_time_tracking(txt, tracker.time_tracking)
    app.root.destroy()

app.root.protocol("WM_DELETE_WINDOW", on_close)

This guarantees:

Active Window Edge Cases

Windows process queries are not always valid. Defensive checks were required to handle None and invalid process states.

def activeWindow():
    try:
        window = win32gui.GetForegroundWindow()
        if not window:
            return None
        _, pid = win32process.GetWindowThreadProcessId(window)
        if not pid or pid == 0:
            return None
        handle = win32api.OpenProcess(
            win32con.PROCESS_QUERY_LIMITED_INFORMATION,
            False,
            pid
        )
        if not handle:
            return None

This was necessary to prevent crashes when a foreground window could not be resolved.

Configuration Storage (AppData)

Configuration storage was rewritten to follow a more industry-standard approach.

New Config Location

CONFIG_PATH = Path(os.environ["APPDATA"]) / "pomodoro_tracker" / "empty.txt"
CONFIG_PATH.parent.mkdir(parents=True, exist_ok=True)

Why This Matters

Final Application Architecture

  1. Application Launch
  2. Load config from %APPDATA%
  3. Prompt for save folder if missing
  4. Start background tracker thread
  5. Start Tkinter mainloop

While Running

On Window Close

  1. Stop tracker thread
  2. Flush last timing segment
  3. Save CSV automatically
  4. Exit cleanly

This is production-grade desktop application behavior.

Key Lessons Learned

This was a full coding marathon day with a steep learning curve. One AI assist was used specifically for background threading concepts, but the knowledge gained will carry forward into future projects.

Sources

  1. https://docs.python.org/3/library/tkinter.html
  2. https://tkdocs.com/tutorial/threads.html
  3. https://stackoverflow.com/questions/459083/how-do-you-run-your-own-code-alongside-tkinters-event-loop
  4. https://docs.python.org/3/library/threading.html#threading.Thread
  5. https://docs.python.org/3/library/threading.html#daemon-threads
  6. https://tkdocs.com/tutorial/windows.html#close
  7. https://www.tcl.tk/man/tcl/TkCmd/wm.htm
  8. https://pyinstaller.org/en/stable/runtime-information.html
  9. https://pyinstaller.org/en/stable/spec-files.html#adding-data-files
  10. https://pyinstaller.org/en/stable/faq.html#how-to-access-data-files
  11. https://docs.python.org/3/library/pathlib.html#pathlib.Path.read_text
  12. https://docs.python.org/3/library/csv.html#csv.DictWriter
  13. https://learn.microsoft.com/en-us/windows/win32/shell/appdata
  14. https://docs.python.org/3/library/os.html#os.environ
  15. https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.core/about/about_operators
  16. https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.core/about/about_quoting_rules

tags: