Skip to content

Instantly share code, notes, and snippets.

@fabiolimace
Last active April 11, 2026 13:41
Show Gist options
  • Select an option

  • Save fabiolimace/7e6c1c51fffb461420946db07d6365c1 to your computer and use it in GitHub Desktop.

Select an option

Save fabiolimace/7e6c1c51fffb461420946db07d6365c1 to your computer and use it in GitHub Desktop.
Notification Speaker
#!/usr/bin/awk -f
#
# Parses DBus Notifications
#
# DBus Protocol: https://specifications.freedesktop.org/notification/1.3/protocol.html
#
# Notification Structure:
# UINT32 org.freedesktop.Notifications.Notify (
# STRING app_name,
# UINT32 replaces_id,
# STRING app_icon,
# STRING summary,
# STRING body,
# as actions,
# a{sv} hints,
# INT32 expire_timeout);
#
# Usage:
# dbus-monitor "destination=org.freedesktop.Notifications, interface='org.freedesktop.Notifications', member='Notify'" --monitor | gawk -f notification-parser.awk
# dbus-monitor "destination=org.freedesktop.Notifications, interface='org.freedesktop.Notifications', member='Notify'" --monitor | mawk -Winteractive -f notification-parser.awk
#
BEGIN {
EXCLUDE_SURNAMES=1
n_exclude = split(EXCLUDE_LIST, EXCLUDE_ARRAY, ",");
}
function flush() {
time = systime();
duration = 5 * 60;
summary = fields[3] "\n";
body = fields[4] "\n";
# avoid to anounce the same summary again during a short period
if (summary == old_summary && time < (old_time + duration)) summary = "";
else old_summary = summary;
message = summary body;
gsub(/[\042\047]/, "", message);
if (n_exclude) for (exclude in EXCLUDE_ARRAY) sub(EXCLUDE_ARRAY[exclude], " ", message);
# prevent duplicate messages
if (message != old_message) {
printf "\n%s", message;
old_message = message;
old_time = time;
fflush();
}
clear();
}
function clear() {
f = 0;
q = 0;
delete fields;
}
EXCLUDE_SURNAMES {
# Remove surnames from @'s
# Mr @John Doe -> Mr @John
# Remove surnames from ~'s
# Dr ~Mary Fox -> Dr ~Mary
for (i = 1; i < NF; i++) {
if ($i ~ /^[,.:;!?\042\047\050\051]*[@~]+[A-ZÁÂÉÊÍÓÔÚ][A-Za-zÇÁÂÃÉÊÍÓÔÕÚÜçáâãéêíóôõúü]+$/) {
while ((i+1) <= NF && $(i+1) ~ /^([A-ZÁÂÉÊÍÓÔÚ][A-Za-zÇÁÂÃÉÊÍÓÔÕÚÜçáâãéêíóôõúü]+|[d][eao][s]?)[,.:;!?\042\047\050\051]*$/) {
i++; gsub(/[^,.:;!?\042\047\050\051]+/,"\032", $i); # Flag the field as blank, using ASCII's <SUB> character.
}
}
}
# Erase blank fields
gsub(/[ ]\032/, "");
}
/^method call.*interface=org.freedesktop.Notifications; member=Notify/ {
f = 1
}
f > 0 && q == 0 && /^[ ]*string[ ]*/ {
q = 1
sub(/^[ ]*string[ ]*\042/, "");
}
f > 0 && q > 0 {
if ($0 ~ /\042$/) {
q = 0;
sub(/\042$/, "");
}
fields[f] = (fields[f] ? fields[f] "\n" : "") $0;
if (q == 0) {
f++;
}
}
f > 4 { flush(); }
#!/bin/bash
#
# Monitor and speak new notifications.
#
# Usage:
#
# ./notification-speaker.sh
# ./notification-speaker.sh en
# ./notification-speaker.sh pt-br # default language
# ./notification-speaker.sh pt-br "chat.google.com,mail.google.com" # comma-separated list of words to be excluded
#
ESPEAK_LANG="${1-pt-br}"
EXCLUDE_LIST="${2}"
BASEDIR=`dirname "$0"`
function fail() {
echo "${1}"; exit 1;
}
[ `which espeak-ng` ] || fail "Dependency not found: espeak-ng";
[ -f "${BASEDIR}/notification-parser.awk" ] || fail "Dependency not found in the working directory: notification-parser.awk";
DBUS_STRING="destination=org.freedesktop.Notifications, interface='org.freedesktop.Notifications', member='Notify'"
if [ `which gawk` ]; then
dbus-monitor "${DBUS_STRING}" | gawk -v EXCLUDE_LIST="${EXCLUDE_LIST}" -f ${BASEDIR}/notification-parser.awk | tee >(espeak-ng -v ${ESPEAK_LANG})
elif [ `which mawk` ]; then
dbus-monitor "${DBUS_STRING}" | mawk -Winteractive -f ${BASEDIR}/notification-parser.awk | tee >(espeak-ng -v ${ESPEAK_LANG})
else
dbus-monitor "${DBUS_STRING}" | awk -f ${BASEDIR}/notification-parser.awk | tee >(espeak-ng -v ${ESPEAK_LANG})
fi;
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment