Skip to main content

🗂️ trayer - System Tray Icons for GTK4

PyPI version Python Versions License: MIT

Etymology: From "tray" + "-er" (one who creates trays), and coincidentally from Middle English "traitor" — because this library gleefully betrays GNOME 3's philosophy of hiding tray icons. 😈

Add system tray icons with context menus to your GTK4 applications with just a few lines of code!


✨ Features

  • 🎯 Simple API - Add tray icons in 3 lines of code
  • 🖱️ Full Click Support - Left, right, and middle-click actions
  • 📋 Context Menus - Easy menu creation with separators
  • 🔄 Dynamic Updates - Change icons and menus at runtime
  • 🎨 Theme Integration - Uses system icon themes
  • 🐧 Linux Desktop Support - Works on GNOME (with extension), KDE, XFCE, Cinnamon
  • 📦 Zero Config - Implements StatusNotifierItem + DBusMenu protocols

🚀 Quick Start

Installation

pip install trayer

System Requirements:

# On Debian/Ubuntu:
sudo apt install python3-gi gir1.2-gtk-4.0 python3-dbus

# On GNOME, you also need:
sudo apt install gnome-shell-extension-appindicator
gnome-extensions enable appindicatorsupport@ubuntu.com
# Then logout/login

Basic Usage

from trayer import TrayIcon
import gi
gi.require_version('Gtk', '4.0')
from gi.repository import Gtk

class MyApp(Gtk.Application):
    def do_activate(self):
        self.window = Gtk.ApplicationWindow(application=self)
        self.window.set_title("My App")
        self.window.present()
    
    def toggle_window(self):
        if self.window.is_visible():
            self.window.hide()
        else:
            self.window.present()

# Create app
app = MyApp(application_id="com.example.myapp")

# Create tray icon
tray = TrayIcon(
    app_id="com.example.myapp",
    title="My Application",
    icon_name="application-x-executable"
)

# Add click action
tray.set_left_click(app.toggle_window)

# Add menu items
tray.add_menu_item("Show Window", callback=lambda: app.window.present())
tray.add_menu_item("Hide Window", callback=lambda: app.window.hide())
tray.add_menu_separator()
tray.add_menu_item("Quit", callback=app.quit)

# Setup and run (IMPORTANT: setup() before run()!)
tray.setup()
app.run()

That's it! 🎉


📖 Documentation

Create Tray Icon

from trayer import TrayIcon

tray = TrayIcon(
    app_id="com.example.myapp",      # Your application ID
    title="My Application",           # Tooltip text
    icon_name="application-x-executable"  # Icon from theme
)

Click Actions

# Left-click
tray.set_left_click(lambda: print("Left clicked!"))

# Middle-click
tray.set_middle_click(lambda: print("Middle clicked!"))

# Right-click automatically shows the menu

Menu Items

# Add menu item
tray.add_menu_item("Show", callback=show_window)

# Add disabled item
tray.add_menu_item("Premium Feature", callback=None, enabled=False)

# Add separator
tray.add_menu_separator()

# Add quit button
tray.add_menu_item("Quit", callback=app.quit)

Dynamic Updates

# Change icon
tray.change_icon("mail-unread")

# Change status
tray.change_status("NeedsAttention")  # Active, Passive, or NeedsAttention

# Update menu dynamically
tray.menu_items.clear()
tray.add_menu_item("New Item", callback=some_function)
tray.update_menu()

Complete Example

See examples/ directory for full working examples:

  • example_minimal.py - Minimal integration
  • example_hide_to_tray.py - Hide window to tray
  • example_dynamic_icon.py - Dynamic icon changes
  • example_dynamic_menu.py - Dynamic menu updates

🎨 Icon Names

Common icon names from system themes:

Applications:

  • application-x-executable
  • applications-internet
  • applications-multimedia

Status:

  • user-available (green)
  • user-busy (red)
  • user-away (yellow)

Mail:

  • mail-unread
  • mail-read

Symbols:

  • face-smile
  • emblem-important
  • dialog-information

Find more: Look in /usr/share/icons/ or use gtk4-icon-browser


🔧 API Reference

TrayIcon(app_id, title, icon_name="application-x-executable")

Create a new tray icon.

Parameters:

  • app_id (str): Application ID
  • title (str): Tooltip text
  • icon_name (str): Icon name from system theme

tray.set_left_click(callback)

Set action for left-clicking the tray icon.

tray.set_middle_click(callback)

Set action for middle-clicking the tray icon.

tray.add_menu_item(label, callback, enabled=True, visible=True)

Add a menu item.

tray.add_menu_separator()

Add a separator line to the menu.

tray.setup()

Initialize the tray icon. Must be called before app.run()!

tray.change_icon(icon_name)

Change the tray icon dynamically.

tray.change_status(status)

Change status: "Active", "Passive", or "NeedsAttention".

tray.update_menu()

Update the menu after modifying items dynamically.


🐛 Troubleshooting

Tray icon doesn't appear on GNOME

Install and enable the AppIndicator extension:

sudo apt install gnome-shell-extension-appindicator
gnome-extensions enable appindicatorsupport@ubuntu.com
# Then logout/login

Menu doesn't show

Make sure you called tray.setup() before app.run()

Callbacks don't work

Ensure callbacks don't take arguments or use lambda:

# ✅ Correct
tray.add_menu_item("Show", lambda: app.show_window(True))

# ❌ Wrong
tray.add_menu_item("Show", app.show_window(True))

🤝 How It Works

This library implements two D-Bus protocols:

  1. StatusNotifierItem - The tray icon itself

  2. DBusMenu - The context menu

Your application communicates with the desktop environment via D-Bus to display the icon and menu.


📋 Requirements

  • Python 3.8+
  • PyGObject (GTK4 bindings)
  • dbus-python
  • A desktop environment with StatusNotifierItem support (GNOME with extension, KDE, XFCE, Cinnamon)

🤔 Why "trayer"?

The name has a double meaning:

  1. "Tray-er" - One who creates trays (like "player", "baker")
  2. "Traitor" (Middle English) - Because we gleefully betray GNOME 3's philosophy of removing tray icons!

The GNOME team decided tray icons were "legacy" and removed native support. This library brings them back through the StatusNotifierItem protocol. We're the rebels of the desktop world! 😎


📄 License

MIT License - Use freely in your projects!


🙏 Contributing

Contributions are welcome! Please feel free to submit a Pull Request.


💬 Support


Happy betraying! 😈🗂️


👨‍💻 Author

Matteo Benedetto (@Enne2)

Eclectic systems designer passionate about elegant solutions to complex problems.


📄 License

MIT License - Use freely in your projects! See LICENSE file for details.

© 2025 Matteo Benedetto

Metadata

Release files for trayer 0.1.1

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for trayer 0.1.1
File Size Uploaded
trayer-0.1.1.tar.gz 12.5 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for trayer 0.1.1
File Interpreter ABI Platform
trayer-0.1.1-py3-none-any.whl Python 3 none any Details

Total release size: 23.5 kB

Release files / trayer-0.1.1.tar.gz

Download URL trayer-0.1.1.tar.gz
Size 12.5 kB
Tags Source
SHA-256 checksum
How to use checksums
4bc2866e9e1cb3fc608f322c2e4a5cc36a1d6c14fc870d20cc47a0fa25b4e396
BLAKE2b-256 checksum
How to use checksums
a22cc35ad421fff880cec20a352fa31e0a16469f7971685689998c644107e036
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/6.2.0 CPython/3.13.5

Release files / trayer-0.1.1-py3-none-any.whl

Download URL trayer-0.1.1-py3-none-any.whl
Size 10.9 kB
Tags Python 3
SHA-256 checksum
How to use checksums
7b511a51ba1c08c5653bdbb91f008cb7dbba105ee5364ff831a20872fdb101de
BLAKE2b-256 checksum
How to use checksums
26447cca0538a40a1186b9b4240603526f4c0d4a6cd75995b2f98ee43d67abf0
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/6.2.0 CPython/3.13.5

Release history Release notifications | RSS feed

This release

0.1.1 This release

2 release files

0.1.0

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page