mika's open learning notebook

field notes, essays, and things learned by building


Desktop Linux

GTK Application Lifecycle, Gio & Libadwaita

How a Python GTK application starts, activates, owns windows, and uses Gio and Libadwaita.

One of the most useful shifts in my GTK learning was understanding that a desktop app is not just a script that creates a window. GTK applications have a lifecycle.

Start with an application object

A minimal GTK application usually starts with an application object:

app = Gtk.Application(application_id="com.example.App")

For a GNOME-style app using Libadwaita:

app = Adw.Application(
    application_id="com.example.App",
    flags=Gio.ApplicationFlags.DEFAULT_FLAGS,
)

The application object owns the process lifecycle and coordinates activation, windows, actions, and application identity.

Why the application ID matters

The application ID is more than a label. It gives the app a stable identity that can be used by the desktop environment and related GNOME systems.

A common reverse-DNS style looks like:

com.example.MyApp

For a real project, the ID should stay stable once users depend on it.

do_activate() is where the app becomes visible

GTK calls the application’s activation handler when the app should present its UI.

A useful pattern is:

def do_activate(self) -> None:
    if self._window is None:
        self._window = MainWindow(self)
    self._window.present()

This taught me two things:

  1. application startup and window construction are related but separate responsibilities
  2. activation can happen more than once, so blindly creating a new window every time is usually wrong

Own one main window deliberately

Keeping a reference such as:

self._window: MainWindow | None = None

makes ownership explicit.

The application owns the main window, and the window can own its child views and widgets.

This is much easier to reason about than creating objects with no clear owner.

present() instead of assuming creation is enough

Creating a window object does not express the full intention. present() tells GTK to show and focus the window appropriately.

That makes this lifecycle clearer:

process starts
→ application runs
→ activation occurs
→ create window if needed
→ present window
→ GTK main loop handles events

What Gio is doing

Gio is part of GLib’s application and I/O stack. In GTK apps it commonly appears around:

  • application flags
  • actions
  • menus
  • files and resources
  • settings
  • D-Bus communication
  • application services

I used to think of Gio as optional background machinery. I now understand that it is a normal part of serious GTK/GNOME application architecture.

What Libadwaita adds

Adw.Application builds on GTK/Gio application behavior while integrating Libadwaita’s GNOME application patterns.

Libadwaita is especially useful for:

  • GNOME-native adaptive layouts
  • standard window patterns
  • preferences UI
  • header bars and navigation
  • consistent GNOME visual behavior

GTK is still the widget toolkit underneath it.

Separate startup from feature code

A clean application module should stay small.

A useful responsibility split is:

app.py
  application lifecycle
  activation
  top-level dependency wiring

window.py
  main window structure

views/
  individual screens or views

providers/
  external services or integrations

This keeps the application object from turning into a giant controller that knows everything.

Imports can communicate architecture

An import list such as:

from gi.repository import Adw, Gio
from myapp.window import MainWindow
from myapp.providers.service import ServiceProvider

already tells me a lot about the architecture:

  • Adw/Gio own app lifecycle concerns
  • the window is its own type
  • external behavior is behind a provider abstraction

Learning to read imports this way helps me understand unfamiliar code faster.

Main lesson

A GTK application is a long-lived event-driven program with explicit ownership and lifecycle. The application object is the root, activation creates or presents UI, and Gio/Libadwaita provide the surrounding GNOME application infrastructure.