notification-hub

distraction-free notification daemon for simple linux desktops.
git clone https://git.ce9e.org/notification-hub.git

commit
6e4a6f8cca4fec346eeb38b8218e12af50a60e25
parent
0d5a46f349db48f9e6b8f98a29318b2ef0811920
Author
Tobias Bengfort <tobias.bengfort@posteo.de>
Date
2025-09-07 15:23
port to AyatanaAppIndicatorGlib and Gio.Menu

AyatanaAppIndicator had a breaking change:
https://github.com/AyatanaIndicators/libayatana-appindicator-glib/pull/82

The new version removes a lot of legacy and only uses gio, not gtk, so
it should be compatible with both gtk3 and gtk4. However, it also
removes compatibility with dbusmenu (see
https://github.com/AyatanaIndicators/libayatana-appindicator-glib/issues/87)

Diffstat

M PKGBUILD 2 +-
M README.md 3 +--
M notification_hub.py 35 ++++++++++++++++++++++-------------

3 files changed, 24 insertions, 16 deletions


diff --git a/PKGBUILD b/PKGBUILD

@@ -4,7 +4,7 @@ pkgdesc='Distraction-free notification daemon for simple linux desktops'
    4     4 arch=('all')
    5     5 url='https://github.com/xi/notification-hub'
    6     6 license='MIT'
    7    -1 depends=(python3-gi gir1.2-gtk-3.0 gir1.2-ayatanaappindicator3-0.1)
   -1     7 depends=(python3-gi gir1.2-ayatanaappindicatorglib-2.0)
    8     8 
    9     9 package() {
   10    10 	install -Dm 755 notification_hub.py "$pkgdir/usr/bin/notification-hub"

diff --git a/README.md b/README.md

@@ -12,8 +12,7 @@ Distraction-free notification daemon for simple linux desktops.
   12    12 ## Dependencies
   13    13 
   14    14 -	[python3-gi](https://docs.gtk.org/gio/)
   15    -1 -	gir1.2-gtk-3.0
   16    -1 -	[gir1.2-ayatanaappindicator3-0.1](https://lazka.github.io/pgi-docs/AyatanaAppIndicator3-0.1/classes/Indicator.html)
   -1    15 -	[gir1.2-ayatanaappindicatorglib-2.0](https://lazka.github.io/pgi-docs/AyatanaAppIndicatorGlib-2.0/classes/Indicator.html)
   17    16 
   18    17 ## Known Limitations
   19    18 

diff --git a/notification_hub.py b/notification_hub.py

@@ -4,13 +4,11 @@ import sys
    4     4 
    5     5 import gi
    6     6 
    7    -1 gi.require_version('AyatanaAppIndicator3', '0.1')
    8    -1 gi.require_version('Gtk', '3.0')
   -1     7 gi.require_version('AyatanaAppIndicatorGlib', '2.0')
    9     8 
   10     9 from gi.repository import Gio  # noqa
   11    10 from gi.repository import GLib  # noqa
   12    -1 from gi.repository import Gtk  # noqa
   13    -1 from gi.repository import AyatanaAppIndicator3 as AppIndicator3  # noqa
   -1    11 from gi.repository import AyatanaAppIndicatorGlib as AppIndicator3  # noqa
   14    12 
   15    13 __version__ = '0.0.0'
   16    14 
@@ -52,10 +50,19 @@ menu = None
   52    50 threads = {}
   53    51 
   54    52 
   55    -1 def clear_thread(item, key):
   -1    53 def remove_menu_item(menu, key):
   -1    54     # menu items can only be removed by position
   -1    55     for i in range(menu.get_n_items()):
   -1    56         if menu.get_item_attribute_value(0, 'app-name').unpack() == key:
   -1    57             menu.remove(i)
   -1    58             return
   -1    59 
   -1    60 
   -1    61 def clear_thread(_action, _, key):
   56    62     del threads[key]
   57    -1     menu.remove(item)
   58    -1     if len(menu) == 0:
   -1    63     remove_menu_item(menu, key)
   -1    64     actions.remove_action(key)
   -1    65     if menu.get_n_items() == 0:
   59    66         indicator.set_status(AppIndicator3.IndicatorStatus.PASSIVE)
   60    67 
   61    68 
@@ -85,10 +92,10 @@ def on_add_notification(params, id):
   85    92         thread['id'] = id
   86    93         thread['menuitem'].set_label(label)
   87    94     else:
   88    -1         item = Gtk.MenuItem(label=label)
   89    -1         item.connect('activate', clear_thread, app_name)
   90    -1         menu.append(item)
   91    -1         menu.show_all()
   -1    95         item = Gio.MenuItem.new(label, app_name)
   -1    96         actions.add_action_entries([(app_name, clear_thread)], app_name)
   -1    97         item.set_attribute([('app-name', 's', app_name)])
   -1    98         menu.append_item(item)
   92    99         indicator.set_status(AppIndicator3.IndicatorStatus.ATTENTION)
   93   100 
   94   101         threads[app_name] = {
@@ -100,7 +107,7 @@ def on_add_notification(params, id):
  100   107 def on_close_notification(id):
  101   108     for key, thread in list(threads.items()):
  102   109         if id == thread['id']:
  103    -1             clear_thread(thread['menuitem'], key)
   -1   110             clear_thread(None, None, key)
  104   111 
  105   112 
  106   113 def on_call(
@@ -169,8 +176,10 @@ if __name__ == '__main__':
  169   176     )
  170   177     indicator.set_status(AppIndicator3.IndicatorStatus.PASSIVE)
  171   178     indicator.set_title('Notifications')
  172    -1     menu = Gtk.Menu()
   -1   179     menu = Gio.Menu()
  173   180     indicator.set_menu(menu)
   -1   181     actions = Gio.SimpleActionGroup()
   -1   182     indicator.set_actions(actions)
  174   183 
  175   184     try:
  176   185         loop = GLib.MainLoop()