trayer


Nametrayer JSON
Version 0.1.0 PyPI version JSON
download
home_pageNone
SummarySystem tray icons for GTK4 applications - betray GNOME 3's philosophy with style!
upload_time2025-10-21 11:24:54
maintainerNone
docs_urlNone
authorenne2
requires_python>=3.8
licenseMIT
keywords gtk4 tray system-tray statusnotifier dbusmenu gnome linux
VCS
bugtrack_url
requirements No requirements were recorded.
Travis-CI No Travis.
coveralls test coverage No coveralls.
            # 🗂️ trayer - System Tray Icons for GTK4

[![PyPI version](https://badge.fury.io/py/trayer.svg)](https://pypi.org/project/trayer/)
[![Python Versions](https://img.shields.io/pypi/pyversions/trayer.svg)](https://pypi.org/project/trayer/)
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/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

```bash
pip install trayer
```

**System Requirements:**
```bash
# 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

```python
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

```python
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

```python
# 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

```python
# 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

```python
# 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/`](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:

```bash
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:

```python
# ✅ 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
   - Spec: https://www.freedesktop.org/wiki/Specifications/StatusNotifierItem/

2. **DBusMenu** - The context menu
   - Spec: https://github.com/AyatanaIndicators/libdbusmenu

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

- 🐛 **Bug Reports:** [GitHub Issues](https://github.com/enne2/trayer/issues)
- 📚 **Documentation:** [GitHub Wiki](https://github.com/enne2/trayer/wiki)
- 💡 **Feature Requests:** [GitHub Discussions](https://github.com/enne2/trayer/discussions)

---

**Happy betraying!** 😈🗂️

            

Raw data

            {
    "_id": null,
    "home_page": null,
    "name": "trayer",
    "maintainer": null,
    "docs_url": null,
    "requires_python": ">=3.8",
    "maintainer_email": null,
    "keywords": "gtk4, tray, system-tray, statusnotifier, dbusmenu, gnome, linux",
    "author": "enne2",
    "author_email": null,
    "download_url": "https://files.pythonhosted.org/packages/1d/7a/216d4a6d5771612b51f278c7271f89365a4aebbd8a3c89f7f29361d364bf/trayer-0.1.0.tar.gz",
    "platform": null,
    "description": "# \ud83d\uddc2\ufe0f trayer - System Tray Icons for GTK4\n\n[![PyPI version](https://badge.fury.io/py/trayer.svg)](https://pypi.org/project/trayer/)\n[![Python Versions](https://img.shields.io/pypi/pyversions/trayer.svg)](https://pypi.org/project/trayer/)\n[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)\n\n**Etymology:** From \"tray\" + \"-er\" (one who creates trays), and coincidentally from Middle English \"traitor\" \u2014 because this library gleefully betrays GNOME 3's philosophy of hiding tray icons. \ud83d\ude08\n\nAdd system tray icons with context menus to your GTK4 applications with just a few lines of code!\n\n---\n\n## \u2728 Features\n\n- \ud83c\udfaf **Simple API** - Add tray icons in 3 lines of code\n- \ud83d\uddb1\ufe0f **Full Click Support** - Left, right, and middle-click actions\n- \ud83d\udccb **Context Menus** - Easy menu creation with separators\n- \ud83d\udd04 **Dynamic Updates** - Change icons and menus at runtime\n- \ud83c\udfa8 **Theme Integration** - Uses system icon themes\n- \ud83d\udc27 **Linux Desktop Support** - Works on GNOME (with extension), KDE, XFCE, Cinnamon\n- \ud83d\udce6 **Zero Config** - Implements StatusNotifierItem + DBusMenu protocols\n\n---\n\n## \ud83d\ude80 Quick Start\n\n### Installation\n\n```bash\npip install trayer\n```\n\n**System Requirements:**\n```bash\n# On Debian/Ubuntu:\nsudo apt install python3-gi gir1.2-gtk-4.0 python3-dbus\n\n# On GNOME, you also need:\nsudo apt install gnome-shell-extension-appindicator\ngnome-extensions enable appindicatorsupport@ubuntu.com\n# Then logout/login\n```\n\n### Basic Usage\n\n```python\nfrom trayer import TrayIcon\nimport gi\ngi.require_version('Gtk', '4.0')\nfrom gi.repository import Gtk\n\nclass MyApp(Gtk.Application):\n    def do_activate(self):\n        self.window = Gtk.ApplicationWindow(application=self)\n        self.window.set_title(\"My App\")\n        self.window.present()\n    \n    def toggle_window(self):\n        if self.window.is_visible():\n            self.window.hide()\n        else:\n            self.window.present()\n\n# Create app\napp = MyApp(application_id=\"com.example.myapp\")\n\n# Create tray icon\ntray = TrayIcon(\n    app_id=\"com.example.myapp\",\n    title=\"My Application\",\n    icon_name=\"application-x-executable\"\n)\n\n# Add click action\ntray.set_left_click(app.toggle_window)\n\n# Add menu items\ntray.add_menu_item(\"Show Window\", callback=lambda: app.window.present())\ntray.add_menu_item(\"Hide Window\", callback=lambda: app.window.hide())\ntray.add_menu_separator()\ntray.add_menu_item(\"Quit\", callback=app.quit)\n\n# Setup and run (IMPORTANT: setup() before run()!)\ntray.setup()\napp.run()\n```\n\nThat's it! \ud83c\udf89\n\n---\n\n## \ud83d\udcd6 Documentation\n\n### Create Tray Icon\n\n```python\nfrom trayer import TrayIcon\n\ntray = TrayIcon(\n    app_id=\"com.example.myapp\",      # Your application ID\n    title=\"My Application\",           # Tooltip text\n    icon_name=\"application-x-executable\"  # Icon from theme\n)\n```\n\n### Click Actions\n\n```python\n# Left-click\ntray.set_left_click(lambda: print(\"Left clicked!\"))\n\n# Middle-click\ntray.set_middle_click(lambda: print(\"Middle clicked!\"))\n\n# Right-click automatically shows the menu\n```\n\n### Menu Items\n\n```python\n# Add menu item\ntray.add_menu_item(\"Show\", callback=show_window)\n\n# Add disabled item\ntray.add_menu_item(\"Premium Feature\", callback=None, enabled=False)\n\n# Add separator\ntray.add_menu_separator()\n\n# Add quit button\ntray.add_menu_item(\"Quit\", callback=app.quit)\n```\n\n### Dynamic Updates\n\n```python\n# Change icon\ntray.change_icon(\"mail-unread\")\n\n# Change status\ntray.change_status(\"NeedsAttention\")  # Active, Passive, or NeedsAttention\n\n# Update menu dynamically\ntray.menu_items.clear()\ntray.add_menu_item(\"New Item\", callback=some_function)\ntray.update_menu()\n```\n\n### Complete Example\n\nSee [`examples/`](examples/) directory for full working examples:\n- `example_minimal.py` - Minimal integration\n- `example_hide_to_tray.py` - Hide window to tray\n- `example_dynamic_icon.py` - Dynamic icon changes\n- `example_dynamic_menu.py` - Dynamic menu updates\n\n---\n\n## \ud83c\udfa8 Icon Names\n\nCommon icon names from system themes:\n\n**Applications:**\n- `application-x-executable`\n- `applications-internet`\n- `applications-multimedia`\n\n**Status:**\n- `user-available` (green)\n- `user-busy` (red)\n- `user-away` (yellow)\n\n**Mail:**\n- `mail-unread`\n- `mail-read`\n\n**Symbols:**\n- `face-smile`\n- `emblem-important`\n- `dialog-information`\n\nFind more: Look in `/usr/share/icons/` or use `gtk4-icon-browser`\n\n---\n\n## \ud83d\udd27 API Reference\n\n### `TrayIcon(app_id, title, icon_name=\"application-x-executable\")`\n\nCreate a new tray icon.\n\n**Parameters:**\n- `app_id` (str): Application ID\n- `title` (str): Tooltip text\n- `icon_name` (str): Icon name from system theme\n\n### `tray.set_left_click(callback)`\n\nSet action for left-clicking the tray icon.\n\n### `tray.set_middle_click(callback)`\n\nSet action for middle-clicking the tray icon.\n\n### `tray.add_menu_item(label, callback, enabled=True, visible=True)`\n\nAdd a menu item.\n\n### `tray.add_menu_separator()`\n\nAdd a separator line to the menu.\n\n### `tray.setup()`\n\nInitialize the tray icon. **Must be called before `app.run()`!**\n\n### `tray.change_icon(icon_name)`\n\nChange the tray icon dynamically.\n\n### `tray.change_status(status)`\n\nChange status: \"Active\", \"Passive\", or \"NeedsAttention\".\n\n### `tray.update_menu()`\n\nUpdate the menu after modifying items dynamically.\n\n---\n\n## \ud83d\udc1b Troubleshooting\n\n### Tray icon doesn't appear on GNOME\n\nInstall and enable the AppIndicator extension:\n\n```bash\nsudo apt install gnome-shell-extension-appindicator\ngnome-extensions enable appindicatorsupport@ubuntu.com\n# Then logout/login\n```\n\n### Menu doesn't show\n\nMake sure you called `tray.setup()` **before** `app.run()`\n\n### Callbacks don't work\n\nEnsure callbacks don't take arguments or use lambda:\n\n```python\n# \u2705 Correct\ntray.add_menu_item(\"Show\", lambda: app.show_window(True))\n\n# \u274c Wrong\ntray.add_menu_item(\"Show\", app.show_window(True))\n```\n\n---\n\n## \ud83e\udd1d How It Works\n\nThis library implements two D-Bus protocols:\n\n1. **StatusNotifierItem** - The tray icon itself\n   - Spec: https://www.freedesktop.org/wiki/Specifications/StatusNotifierItem/\n\n2. **DBusMenu** - The context menu\n   - Spec: https://github.com/AyatanaIndicators/libdbusmenu\n\nYour application communicates with the desktop environment via D-Bus to display the icon and menu.\n\n---\n\n## \ud83d\udccb Requirements\n\n- Python 3.8+\n- PyGObject (GTK4 bindings)\n- dbus-python\n- A desktop environment with StatusNotifierItem support (GNOME with extension, KDE, XFCE, Cinnamon)\n\n---\n\n## \ud83e\udd14 Why \"trayer\"?\n\nThe name has a double meaning:\n\n1. **\"Tray-er\"** - One who creates trays (like \"player\", \"baker\")\n2. **\"Traitor\"** (Middle English) - Because we gleefully betray GNOME 3's philosophy of removing tray icons!\n\nThe 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! \ud83d\ude0e\n\n---\n\n## \ud83d\udcc4 License\n\nMIT License - Use freely in your projects!\n\n---\n\n## \ud83d\ude4f Contributing\n\nContributions are welcome! Please feel free to submit a Pull Request.\n\n---\n\n## \ud83d\udcac Support\n\n- \ud83d\udc1b **Bug Reports:** [GitHub Issues](https://github.com/enne2/trayer/issues)\n- \ud83d\udcda **Documentation:** [GitHub Wiki](https://github.com/enne2/trayer/wiki)\n- \ud83d\udca1 **Feature Requests:** [GitHub Discussions](https://github.com/enne2/trayer/discussions)\n\n---\n\n**Happy betraying!** \ud83d\ude08\ud83d\uddc2\ufe0f\n",
    "bugtrack_url": null,
    "license": "MIT",
    "summary": "System tray icons for GTK4 applications - betray GNOME 3's philosophy with style!",
    "version": "0.1.0",
    "project_urls": {
        "Bug Tracker": "https://github.com/enne2/trayer/issues",
        "Homepage": "https://github.com/enne2/trayer",
        "Repository": "https://github.com/enne2/trayer"
    },
    "split_keywords": [
        "gtk4",
        " tray",
        " system-tray",
        " statusnotifier",
        " dbusmenu",
        " gnome",
        " linux"
    ],
    "urls": [
        {
            "comment_text": null,
            "digests": {
                "blake2b_256": "22ec11038f1706f48c64340361f2af15266e6f5788cf4f8ab8377366af51773e",
                "md5": "cc21da33abac963023a45f2f79e9a5f5",
                "sha256": "5b3dc8aff7de0cb9977c43bbedd4ca4edee1ab60155fd12c12a3749ca40bb276"
            },
            "downloads": -1,
            "filename": "trayer-0.1.0-py3-none-any.whl",
            "has_sig": false,
            "md5_digest": "cc21da33abac963023a45f2f79e9a5f5",
            "packagetype": "bdist_wheel",
            "python_version": "py3",
            "requires_python": ">=3.8",
            "size": 9246,
            "upload_time": "2025-10-21T11:24:52",
            "upload_time_iso_8601": "2025-10-21T11:24:52.692315Z",
            "url": "https://files.pythonhosted.org/packages/22/ec/11038f1706f48c64340361f2af15266e6f5788cf4f8ab8377366af51773e/trayer-0.1.0-py3-none-any.whl",
            "yanked": false,
            "yanked_reason": null
        },
        {
            "comment_text": null,
            "digests": {
                "blake2b_256": "1d7a216d4a6d5771612b51f278c7271f89365a4aebbd8a3c89f7f29361d364bf",
                "md5": "f832fbe9f94be6a12670cb6973b0600b",
                "sha256": "32bd40ec222632a91d89cb0e34b1524fd598aae5d84a1dfa7d5c3d8a53b3323e"
            },
            "downloads": -1,
            "filename": "trayer-0.1.0.tar.gz",
            "has_sig": false,
            "md5_digest": "f832fbe9f94be6a12670cb6973b0600b",
            "packagetype": "sdist",
            "python_version": "source",
            "requires_python": ">=3.8",
            "size": 11363,
            "upload_time": "2025-10-21T11:24:54",
            "upload_time_iso_8601": "2025-10-21T11:24:54.122654Z",
            "url": "https://files.pythonhosted.org/packages/1d/7a/216d4a6d5771612b51f278c7271f89365a4aebbd8a3c89f7f29361d364bf/trayer-0.1.0.tar.gz",
            "yanked": false,
            "yanked_reason": null
        }
    ],
    "upload_time": "2025-10-21 11:24:54",
    "github": true,
    "gitlab": false,
    "bitbucket": false,
    "codeberg": false,
    "github_user": "enne2",
    "github_project": "trayer",
    "travis_ci": false,
    "coveralls": false,
    "github_actions": false,
    "lcname": "trayer"
}
        
Elapsed time: 3.05145s