How to read desktop notifications in the terminal
https://askubuntu.com/questions/1560426/how-to-read-desktop-notifications-in-the-terminal
I am trying to create a script that listens to the desktop notifications dbus and print the sender and message to the command line.... the final goal is to run a specific function when I get a specific notification.
I tried using dbus-monitor --session which prints stuff out when I get a notification, but I don't see the actual notification. I had Gemini AI help me write some python code, this code runs, but nothing happens when I get a notification.
import asyncio
from dbus_next.aio import MessageBus
from dbus_next.constants import BusType, MessageType
from dbus_next import Message, Variant
import logging
# Set up basic logging
# Note: We are only using INFO level for connection status,
# message processing is handled via print() for clarity.
logging.basicConfig(level=logging.INFO, format='%(levelname)s: %(message)s')
# --- DBus Interface Constants ---
# The D-Bus service name for notifications
NOTIF_SERVICE = 'org.freedesktop.Notifications'
# The D-Bus object path for notifications
NOTIF_PATH = '/org/freedesktop/Notifications'
# The D-Bus interface for notifications
NOTIF_INTERFACE = NOTIF_SERVICE
# ANSI escape codes for bold text
BOLD_START = '\033[1m'
BOLD_END = '\033[0m'
def message_handler(message):
"""
Generic handler for D-Bus messages, focusing only on new notifications
(Notify method calls) and printing the sender, summary, and message body.
"""
if message.interface == NOTIF_INTERFACE:
# --- Handle New Notification (Method Call) ---
if message.message_type == MessageType.METHOD_CALL and message.member == 'Notify':
# Notify arguments: (app_name, replaces_id, app_icon, summary, body, actions, hints, expire_timeout)
# Ensure we have enough arguments (at least app_name and body)
if len(message.body) >= 8:
app_name = message.body[0] # App Name (Sender)
summary = message.body[3] # Notification Summary (Title)
body_text = message.body[4] # Notification Body (Main message)
# Combine summary and body for the message
# If both exist, use "Summary: Body". If only one exists, use that.
if summary and body_text:
message_content = f"{summary}: {body_text}"
elif summary:
message_content = summary
else:
message_content = body_text
output = f"[{app_name}] {message_content}"
# Apply bold formatting if the sender is Discord (case-insensitive check)
if app_name.lower() == 'discord':
output = f"{BOLD_START}{output}{BOLD_END}"
print(output)
# Log other relevant messages (like replies to GetCapabilities)
else:
logging.debug(f"[D-Bus Message] Type: {message.message_type.name}, Member: {message.member}, Body: {message.body}")
async def notification_listener(bus):
"""
Configures the bus to listen for all messages related to the
org.freedesktop.Notifications interface.
"""
# 1. Add the generic message handler
bus.add_message_handler(message_handler)
# 2. Use AddMatch to filter messages directed to this interface
# This is crucial for catching the 'Notify' method call.
# The rule is updated to match on the specific method call ('Notify') rather than
# relying solely on the destination service, which is a more robust way to capture
# new notification requests.
match_rule = f"type='method_call', interface='{NOTIF_INTERFACE}', member='Notify'"
await bus.call(
Message(
destination='org.freedesktop.DBus',
path='/org/freedesktop/DBus',
interface='org.freedesktop.DBus',
member='AddMatch',
signature='s',
body=[match_rule]
)
)
logging.info("Listening for ALL D-Bus Messages on org.freedesktop.Notifications interface.")
logging.info("To test, send a notification, e.g., 'notify-send Hello World'")
# Keep the asyncio loop running indefinitely
await asyncio.get_running_loop().create_future()
async def main():
"""
The main entry point for the script.
"""
try:
logging.info(f"Attempting to connect to the D-Bus session bus...")
bus = await MessageBus(bus_type=BusType.SESSION).connect()
logging.info("Successfully connected to the D-Bus session bus.")
# Attempt to call the GetCapabilities method to ensure the service is running
reply = await bus.call(
Message(
destination=NOTIF_SERVICE,
path=NOTIF_PATH,
interface=NOTIF_INTERFACE,
member='GetCapabilities',
signature='',
body=[]
)
)
if reply.message_type == MessageType.METHOD_RETURN:
caps = reply.body[0]
logging.info(f"Notification service capabilities retrieved: {caps}")
await notification_listener(bus)
else:
# This often means no notification daemon (like dunst or a desktop environment's service) is running.
logging.error("Could not retrieve notification service capabilities. Is a notification daemon running?")
except Exception as e:
logging.error(f"Error during D-Bus setup or initial capability check: {e}")
if __name__ == "__main__":
try:
# Run the main coroutine
asyncio.run(main())
except KeyboardInterrupt:
logging.info("Notification listener stopped by user.")
except Exception as e:
logging.error(f"An unexpected error occurred: {e}")
It seems like this should be a simple task.... I would rather use a bash script if I could, but python would be fine too.
I did find this post which had a python script that I had to be update to work with py3. This script runs but nothing happens when I get a notification or send one via notify-send
import gi
gi.require_version("Gtk", "3.0") # or "4.0" depending on your target GTK version
from gi.repository import Gtk
import dbus
from dbus.mainloop.glib import DBusGMainLoop
def filter_cb(bus, message):
# the NameAcquired message comes through before match string gets applied
if message.get_member() != "Notify":
return
args = message.get_args_list()
# args are
# (app_name, notification_id, icon, summary, body, actions, hints, timeout)
print("Notification from app '%s'" % args[0])
print("Summary: %s" % args[3])
print("Body: %s", args[4])
DBusGMainLoop(set_as_default=True)
bus = dbus.SessionBus()
bus.add_match_string(
"type='method_call',interface='org.freedesktop.Notifications',member='Notify'")
bus.add_message_filter(filter_cb)
Gtk.main()
Thanks.