roundabout,
created on Sunday, 17 August 2025, 20:31:04 (1755462664),
received on Sunday, 17 August 2025, 20:31:34 (1755462694)
Author identity: Vlad <vlad.muntoiu@gmail.com>
999274bbebf9fe9571eb5dfff72859e7df0f4ebb
applets/notifier/__init__.py
@@ -0,0 +1,144 @@
""" D-Bus notifier applet for the Panorama panel. Copyright 2025, roundabout-host.com <vlad@roundabout-host.com> This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public Licence as published by the Free Software Foundation, either version 3 of the Licence, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public Licence for more details. You should have received a copy of the GNU General Public Licence along with this program. If not, see <https://www.gnu.org/licenses/>. """ from __future__ import annotations import panorama_panel import locale from pathlib import Path from pydbus import SessionBus import gi gi.require_version("Gtk", "4.0") from gi.repository import Gtk, Gdk, GLib module_directory = Path(__file__).resolve().parent locale.bindtextdomain("panorama-app-menu", module_directory / "locale") _ = lambda x: locale.dgettext("panorama-app-menu", x) custom_css = """ .notification-button { padding: 0; } """ css_provider = Gtk.CssProvider() css_provider.load_from_data(custom_css) Gtk.StyleContext.add_provider_for_display( Gdk.Display.get_default(), css_provider, Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION ) class NotificationsService: """ <node> <interface name="org.freedesktop.Notifications"> <method name="GetCapabilities"> <arg type="as" name="caps" direction="out"/> </method> <method name="GetServerInformation"> <arg type="s" name="name" direction="out"/> <arg type="s" name="vendor" direction="out"/> <arg type="s" name="version" direction="out"/> <arg type="s" name="spec_version" direction="out"/> </method> <method name="Notify"> <arg type="s" name="app_name" direction="in"/> <arg type="u" name="replaces_id" direction="in"/> <arg type="s" name="app_icon" direction="in"/> <arg type="s" name="summary" direction="in"/> <arg type="s" name="body" direction="in"/> <arg type="as" name="actions" direction="in"/> <arg type="a{sv}" name="hints" direction="in"/> <arg type="i" name="expire_timeout" direction="in"/> <arg type="u" name="id" direction="out"/> </method> </interface> </node> """ def __init__(self, applet: NotifierApplet): self._next_id = 1 self.applet = applet def GetCapabilities(self): return ["body"] def GetServerInformation(self): # name, vendor, version, spec_version return "panorama-panel", "panorama", "0.1", "1.2" def Notify(self, *args): notification_id = self._next_id self._next_id += 1 self.applet.push_notification(*args) return notification_id class NotifierApplet(panorama_panel.Applet): name = _("Notification centre") description = _("Get desktop notifications") def __init__(self, orientation=Gtk.Orientation.HORIZONTAL, config=None): super().__init__(orientation=orientation, config=config) if config is None: config = {} self.button = Gtk.MenuButton() self.button.set_has_frame(False) self.button.add_css_class("notification-button") self.stack = Gtk.Stack() self.button.set_child(self.stack) self.notification_indicator = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL) self.notification_indicator.append(Gtk.Image.new_from_icon_name("")) self.stack.add_child(self.notification_indicator) self.stack.set_transition_type(Gtk.StackTransitionType.SLIDE_UP_DOWN) self.stack.set_transition_duration(500) # TODO: support the other parameters; add a popover with a history self.append(self.button) self.service = NotificationsService(self) bus = SessionBus() self.publishing = bus.publish("org.freedesktop.Notifications", self.service) def push_notification(self, app_name, replaces_id, app_icon, summary, body, actions, hints, expire_timeout): new_child = Gtk.Label.new(_(summary)) self.stack.add_child(new_child) self.stack.set_visible_child(new_child) def remove_notification(): self.stack.set_visible_child(self.stack.get_visible_child().get_prev_sibling()) def remove_child(): self.stack.remove(new_child) GLib.timeout_add(self.stack.get_transition_duration(), remove_child) return False if expire_timeout <= 0: expire_timeout = 2500 GLib.timeout_add(expire_timeout, remove_notification) # TODO: notify that it was closed def get_config(self): return {} def shutdown(self): self.publishing.unpublish()