Skip to content

Status icons

Usage

Although the usual user interface for an app is a window, some apps will augment - or even replace - a window-base interface with an icon in the system tray or status bar provided by the operating system. This is especially common for apps that primarily run in the background.

Toga supports two types of status icons - simple status icons, and menu status icons.

Simple status icons

A simple status icon is a bare icon in the status bar. You can set and change the icon as required to reflect changes in application state; by default, the status icon will use the app's icon. The text associated with the icon will be used as a tooltip; again, the app's formal name will be used as default text. The icon can respond to mouse clicks by defining an on_press handler.

To define a simple status icon, declare an instance of toga.SimpleStatusIcon, and add it to your app's status_icons set:

import toga

# Define a status icon that uses default values for icon and tooltip,
# and doesn't respond to mouse clicks.
status_icon_1 = toga.SimpleStatusIcon()

# Define a second status icon that provides explicit values for the id, icon and
# tooltip, and responds to mouse clicks.
def my_handler(widget, **kwargs):
    print("Second status icon pressed!")

status_icon_2 = toga.SimpleStatusIcon(
    id="second",
    text="Hello!",
    icon="icons/red",
    on_press=my_handler
)

# Add both status icons to the app
app.status_icons.add(status_icon_1, status_icon_2)

Once a status icon has been added to the app, it can be retrieved by ID or by index; and it can be removed from the app:

# Change the icon of the first status icon, retrieved by index:
app.status_icons[0].icon = "icons/green"

# Change the icon of the second status icon, retrieved by id:
app.status_icons["second"].icon = "icons/blue"

# Remove the first status icon from the app:
app.status_icons.remove(status_icon_1)

A menu-based status icon is defined by adding a toga.MenuStatusIcon instance. A toga.MenuStatusIcon behaves almost the same as SimpleStatusIcon, except that it cannot have an on_click handler - but it can be used to register Commands that will be displayed in a menu when the icon is clicked.

The MenuStatusIcon is a Group for command sorting purposes. To put a command in a menu associated with a MenuStatusIcon, set the group associated with that command, then add the command to the CommandSet associated with status icons. The following example will create a MenuStatusIcon that has a single top-level menu item, plus a sub-menu that itself has a single menu item:

# Create a MenuStatusIcon
status_icon = toga.MenuStatusIcon(icon="icons/blue")

# Create some commands that are associated with the menu status icon's group.
def callback(sender, **kwargs):
    print("Command activated")

cmd1 = toga.Command(
    callback,
    text='Example command',
    group=status_icon,
)

# Create a sub-group of the status icon. This will appear as a submenu.
stuff_group = toga.Group('Stuff', parent=status_icon)

cmd2 = toga.Command(
    callback,
    text='Stuff sub-command',
    group=stuff_group
)

# Add the status icon to the app
app.status_icons.add(status_icon)

# Add the commands to the status icons command set
app.status_icons.commands.add(cmd1, cmd2)

If you add at least one MenuStatusIcon instance to your app, Toga will add some standard commands to the app's status icon command set. These items will appear at the end of the first MenuStatusIcon's menu. To remove these items, clear the app's status icon command set before adding your own commands.

If you add a command to the app's status icon command set that doesn't belong to a MenuStatusIcon group, or that belongs to a MenuStatusIcon group that hasn't been registered with the app as a status icon, a ValueError will be raised. An error will also be raised if you remove a status icon while there status icon commands referencing that command.

Notes

  • Status icons on GTK are implemented using the XApp library. This requires that the user has installed the system packages for libxapp, plus the GObject Introspection bindings for that library. The name of the system package required is distribution dependent:
    • Ubuntu: gir1.2-xapp-1.0
    • Fedora: xapps
  • The GNOME desktop environment does not provide built-in support for status icons. This is an explicit design decision on their part, and they advise against using status icons as part of your app design. However, there are GNOME shell extensions that can add support for status icons. Other GTK-based desktop environments (such as Cinnamon) do support status icons.

Reference

Source code in core/src/toga/statusicons.py
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
class StatusIcon:
    def __init__(self, icon: IconContentT | None = None):
        """An abstract base class for all status icons."""
        self.factory = get_factory()
        self._impl = getattr(self.factory, self.__class__.__name__)(interface=self)

        self.icon = icon

    @property
    @abstractmethod
    def id(self) -> str:
        """A unique identifier for the status icon."""

    @property
    @abstractmethod
    def text(self) -> str:
        """A text label for the status icon."""

    @property
    def icon(self) -> Icon | None:
        """The Icon to display in the status bar.

        When setting the icon, you can provide either an [`Icon`][toga.Icon] instance,
        or a path that will be passed to the `Icon` constructor.
        """
        return self._icon

    @icon.setter
    def icon(self, icon_or_name: IconContentT | None):
        if isinstance(icon_or_name, Icon) or icon_or_name is None:
            self._icon = icon_or_name
        else:
            self._icon = Icon(icon_or_name)

        self._impl.set_icon(self._icon)

icon property writable

The Icon to display in the status bar.

When setting the icon, you can provide either an Icon instance, or a path that will be passed to the Icon constructor.

id abstractmethod property

A unique identifier for the status icon.

text abstractmethod property

A text label for the status icon.

__init__(icon=None)

An abstract base class for all status icons.

Source code in core/src/toga/statusicons.py
21
22
23
24
25
26
def __init__(self, icon: IconContentT | None = None):
    """An abstract base class for all status icons."""
    self.factory = get_factory()
    self._impl = getattr(self.factory, self.__class__.__name__)(interface=self)

    self.icon = icon

Bases: StatusIcon

Source code in core/src/toga/statusicons.py
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
class SimpleStatusIcon(StatusIcon):
    def __init__(
        self,
        id: str | None = None,
        icon: IconContentT | None = None,
        text: str | None = None,
        on_press: toga.widgets.button.OnPressHandler | None = None,
    ):
        """
        An button in a status bar or system tray.

        When pressed, the `on_press` handler will be activated.

        :param id: An identifier for the status icon.
        :param icon: The icon, or icon resource, that will be displayed in the status
            bar or system tray.
        :param text: A text label for the status icon. Defaults to the formal name of
            the app.
        :param on_press: The handler to invoke when the status icon is pressed.
        """
        super().__init__(icon=icon)
        self.on_press = on_press

        self._id = f"statusicon-{_py_id(self)}" if id is None else id
        self._text = text if text is not None else toga.App.app.formal_name

    def __repr__(self):
        return f"<SimpleStatusIcon {self.text!r}: {self.id}>"

    @property
    def id(self) -> str:
        return self._id

    @property
    def text(self) -> str:
        return self._text

    @property
    def on_press(self) -> toga.widgets.button.OnPressHandler:
        """The handler to invoke when the status icon is pressed."""
        return self._on_press

    @on_press.setter
    def on_press(self, handler: toga.widgets.button.OnPressHandler) -> None:
        self._on_press = wrapped_handler(self, handler)

on_press property writable

The handler to invoke when the status icon is pressed.

__init__(id=None, icon=None, text=None, on_press=None)

An button in a status bar or system tray.

When pressed, the on_press handler will be activated.

:param id: An identifier for the status icon. :param icon: The icon, or icon resource, that will be displayed in the status bar or system tray. :param text: A text label for the status icon. Defaults to the formal name of the app. :param on_press: The handler to invoke when the status icon is pressed.

Source code in core/src/toga/statusicons.py
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
def __init__(
    self,
    id: str | None = None,
    icon: IconContentT | None = None,
    text: str | None = None,
    on_press: toga.widgets.button.OnPressHandler | None = None,
):
    """
    An button in a status bar or system tray.

    When pressed, the `on_press` handler will be activated.

    :param id: An identifier for the status icon.
    :param icon: The icon, or icon resource, that will be displayed in the status
        bar or system tray.
    :param text: A text label for the status icon. Defaults to the formal name of
        the app.
    :param on_press: The handler to invoke when the status icon is pressed.
    """
    super().__init__(icon=icon)
    self.on_press = on_press

    self._id = f"statusicon-{_py_id(self)}" if id is None else id
    self._text = text if text is not None else toga.App.app.formal_name

Bases: Group, StatusIcon

Source code in core/src/toga/statusicons.py
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
class MenuStatusIcon(Group, StatusIcon):
    def __init__(
        self,
        id: str | None = None,
        icon: IconContentT | None = None,
        text: str | None = None,
    ):
        """
        An item in a status bar or system tray that displays a menu when pressed.

        A `MenuStatusIcon` can be used as a [`Group`][toga.Group] when defining
        [`toga.Command`][] instances.

        :param id: An identifier for the status icon.
        :param icon: The icon, or icon resource, that will be displayed in the status
            bar or system tray.
        :param text: A text label for the status icon. Defaults to the formal name of
            the app.
        """
        Group.__init__(
            self,
            id=f"menustatusitem-{_py_id(self)}" if id is None else id,
            text=(text if text is not None else toga.App.app.formal_name),
        )
        StatusIcon.__init__(self, icon=icon)

    def __repr__(self):
        return f"<MenuStatusIcon {self.text!r}: {self.id}>"

__init__(id=None, icon=None, text=None)

An item in a status bar or system tray that displays a menu when pressed.

A MenuStatusIcon can be used as a Group when defining toga.Command instances.

:param id: An identifier for the status icon. :param icon: The icon, or icon resource, that will be displayed in the status bar or system tray. :param text: A text label for the status icon. Defaults to the formal name of the app.

Source code in core/src/toga/statusicons.py
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
def __init__(
    self,
    id: str | None = None,
    icon: IconContentT | None = None,
    text: str | None = None,
):
    """
    An item in a status bar or system tray that displays a menu when pressed.

    A `MenuStatusIcon` can be used as a [`Group`][toga.Group] when defining
    [`toga.Command`][] instances.

    :param id: An identifier for the status icon.
    :param icon: The icon, or icon resource, that will be displayed in the status
        bar or system tray.
    :param text: A text label for the status icon. Defaults to the formal name of
        the app.
    """
    Group.__init__(
        self,
        id=f"menustatusitem-{_py_id(self)}" if id is None else id,
        text=(text if text is not None else toga.App.app.formal_name),
    )
    StatusIcon.__init__(self, icon=icon)

Bases: Sequence[StatusIcon], Mapping[str, StatusIcon]

Source code in core/src/toga/statusicons.py
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
class StatusIconSet(Sequence[StatusIcon], Mapping[str, StatusIcon]):
    def __init__(self):
        """An ordered collection of status icons.

        The items in the set can be retrieved by instance, or by ID. When iterated, the
        items are returned in the order they were added.
        """
        self.factory = get_factory()
        self._impl = self.factory.StatusIconSet(interface=self)

        self.elements: dict[str, StatusIcon] = {}
        self.commands = CommandSet()

    @property
    def _menu_status_icons(self):
        """An iterator over the menu status icons that have been registered."""
        return (icon for icon in self if isinstance(icon, MenuStatusIcon))

    @property
    def _primary_menu_status_icon(self):
        """The first menu status icon that has been registered.

        Returns `None` if no menu status icons have been registered.
        """
        try:
            return next(self._menu_status_icons)
        except StopIteration:
            # No menu status icons registered.
            return None

    def _create_standard_commands(self):
        # Create the standard commands for the menu status icon. Use the standard
        # constructor, but force the commands into *last* section of the COMMANDS group
        # so they'll appear on the first MenuStatusIcon.
        for cmd_id in [
            Command.ABOUT,
            Command.EXIT,
        ]:
            self.commands.add(
                Command.standard(
                    toga.App.app,
                    cmd_id,
                    section=sys.maxsize,
                    group=Group.COMMANDS,
                )
            )

    def __iter__(self) -> Iterator[StatusIcon]:
        return iter(self.elements.values())

    def __contains__(self, value: object) -> bool:
        if isinstance(value, str):
            return value in self.elements
        else:
            return value in self.elements.values()

    def __len__(self) -> int:
        return len(self.elements)

    def __getitem__(self, index_or_id):
        if isinstance(index_or_id, int):
            return list(self.elements.values())[index_or_id]
        else:
            return self.elements[index_or_id]

    def add(self, *status_icons: StatusIcon):
        """Add one or more icons to the set.

        :param status_icons: The icon (or icons) to add to the set.
        """
        added = False
        for status_icon in status_icons:
            if status_icon.id not in self.elements:
                self.elements[status_icon.id] = status_icon
                status_icon._impl.create()
                added = True

        if added and self.commands.on_change:
            self.commands.on_change()

    def remove(self, status_icon: StatusIcon):
        """Remove a single icon from the set.

        :param status_icon: The status icon instance to remove.
        :raises ValueError: If the status icon commands include any commands that
            reference the icon that has been removed.
        """
        try:
            self.elements.pop(status_icon.id)
            status_icon._impl.remove()

            if self.commands.on_change:
                self.commands.on_change()
        except KeyError as exc:
            raise ValueError("Not a known status icon.") from exc

    def clear(self):
        """Remove all the icons from the set.

        :raises ValueError: If the status icon commands include any commands that
            reference an icon that has been removed.
        """
        # Convert into a list so that we're not deleting from a list while iterating.
        for status_icon in list(self):
            self.elements.pop(status_icon.id)
            status_icon._impl.remove()

        if self.commands.on_change:
            self.commands.on_change()

__init__()

An ordered collection of status icons.

The items in the set can be retrieved by instance, or by ID. When iterated, the items are returned in the order they were added.

Source code in core/src/toga/statusicons.py
135
136
137
138
139
140
141
142
143
144
145
def __init__(self):
    """An ordered collection of status icons.

    The items in the set can be retrieved by instance, or by ID. When iterated, the
    items are returned in the order they were added.
    """
    self.factory = get_factory()
    self._impl = self.factory.StatusIconSet(interface=self)

    self.elements: dict[str, StatusIcon] = {}
    self.commands = CommandSet()

add(*status_icons)

Add one or more icons to the set.

:param status_icons: The icon (or icons) to add to the set.

Source code in core/src/toga/statusicons.py
199
200
201
202
203
204
205
206
207
208
209
210
211
212
def add(self, *status_icons: StatusIcon):
    """Add one or more icons to the set.

    :param status_icons: The icon (or icons) to add to the set.
    """
    added = False
    for status_icon in status_icons:
        if status_icon.id not in self.elements:
            self.elements[status_icon.id] = status_icon
            status_icon._impl.create()
            added = True

    if added and self.commands.on_change:
        self.commands.on_change()

clear()

Remove all the icons from the set.

:raises ValueError: If the status icon commands include any commands that reference an icon that has been removed.

Source code in core/src/toga/statusicons.py
230
231
232
233
234
235
236
237
238
239
240
241
242
def clear(self):
    """Remove all the icons from the set.

    :raises ValueError: If the status icon commands include any commands that
        reference an icon that has been removed.
    """
    # Convert into a list so that we're not deleting from a list while iterating.
    for status_icon in list(self):
        self.elements.pop(status_icon.id)
        status_icon._impl.remove()

    if self.commands.on_change:
        self.commands.on_change()

remove(status_icon)

Remove a single icon from the set.

:param status_icon: The status icon instance to remove. :raises ValueError: If the status icon commands include any commands that reference the icon that has been removed.

Source code in core/src/toga/statusicons.py
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
def remove(self, status_icon: StatusIcon):
    """Remove a single icon from the set.

    :param status_icon: The status icon instance to remove.
    :raises ValueError: If the status icon commands include any commands that
        reference the icon that has been removed.
    """
    try:
        self.elements.pop(status_icon.id)
        status_icon._impl.remove()

        if self.commands.on_change:
            self.commands.on_change()
    except KeyError as exc:
        raise ValueError("Not a known status icon.") from exc