-
-
Notifications
You must be signed in to change notification settings - Fork 72
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
add previous or next on gnome notification #1228
Comments
Interesting request, ill look into it. For the one click to repeat there is a config key in the config file, set |
Spec - https://specifications.freedesktop.org/notification-spec/notification-spec-latest.html KDE implementation - https://invent.kde.org/frameworks/knotifications So, the state of this ability in Python is currently bad. The end result can look something similar to: We can (but REALLY shouldn't) yoink this script: notify-send.sh#!/usr/bin/env bash
# Merged:
# https://github.com/vlevit/notify-send.sh/blob/master/notify-send.sh
# https://github.com/vlevit/notify-send.sh/blob/master/notify-action.sh
# notify-send.sh - drop-in replacement for notify-send with more features
# Copyright (C) 2015-2021 notify-send.sh authors (see AUTHORS file)
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, 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 License for more details.
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
# Desktop Notifications Specification
# https://developer.gnome.org/notification-spec/
set -euo pipefail
#set -x
VERSION=1.2
NOTIFY_ARGS=(--session
--dest org.freedesktop.Notifications
--object-path /org/freedesktop/Notifications)
EXPIRE_TIME=-1
APP_NAME="${0##*/}"
REPLACE_ID=0
URGENCY=1
HINTS=()
SUMMARY_SET=n
help() {
cat <<EOF
Usage:
notify-send.sh [OPTION...] <SUMMARY> [BODY] - create a notification
Help Options:
-?|--help Show help options
Application Options:
-u, --urgency=LEVEL Specifies the urgency level (low, normal, critical).
-t, --expire-time=TIME Specifies the timeout in milliseconds at which to expire the notification.
-f, --force-expire Forcefully closes the notification when the notification has expired.
-a, --app-name=APP_NAME Specifies the app name for the icon.
-i, --icon=ICON[,ICON...] Specifies an icon filename or stock icon to display.
-c, --category=TYPE[,TYPE...] Specifies the notification category.
-h, --hint=TYPE:NAME:VALUE Specifies basic extra data to pass. Valid types are int, double, string and byte.
-o, --action=COMMAND=LABEL Specifies an action. Can be passed multiple times. LABEL is usually a button's label. COMMAND is a shell command executed when action is invoked.
-d, --default-action=COMMAND Specifies the default action which is usually invoked by clicking the notification.
-l, --close-action=COMMAND Specifies the action invoked when notification is closed.
-p, --print-id Print the notification ID to the standard output.
-r, --replace=ID Replace existing notification.
-R, --replace-file=FILE Store and load notification replace ID to/from this file.
-s, --close=ID Close notification.
-v, --version Version of the package.
EOF
}
cleanup() {
rm -f "$GDBUS_MONITOR_PID"
}
create_pid_file(){
rm -f "$GDBUS_MONITOR_PID"
umask 077
touch "$GDBUS_MONITOR_PID"
}
invoke_action() {
invoked_action_id="$1"
local action="" cmd=""
for index in "${!ACTION_COMMANDS[@]}"; do
if [[ $((index % 2)) == 0 ]]; then
action="${ACTION_COMMANDS[$index]}"
else
cmd="${ACTION_COMMANDS[$index]}"
if [[ "$action" == "$invoked_action_id" ]]; then
echo "${cmd}"
# bash -c "${cmd}" &
fi
fi
done
}
monitor() {
create_pid_file
( "${GDBUS_MONITOR[@]}" & echo $! >&3 ) 3>"$GDBUS_MONITOR_PID" | while read -r line; do
local closed_notification_id="$(sed '/^\/org\/freedesktop\/Notifications: org.freedesktop.Notifications.NotificationClosed (uint32 \([0-9]\+\), uint32 [0-9]\+)$/!d;s//\1/' <<< "$line")"
if [[ -n "$closed_notification_id" ]]; then
if [[ "$closed_notification_id" == "$NOTIFICATION_ID" ]]; then
invoke_action close
break
fi
else
local action_invoked="$(sed '/\/org\/freedesktop\/Notifications: org.freedesktop.Notifications.ActionInvoked (uint32 \([0-9]\+\), '\''\(.*\)'\'')$/!d;s//\1:\2/' <<< "$line")"
IFS=: read invoked_id action_id <<< "$action_invoked"
if [[ "$invoked_id" == "$NOTIFICATION_ID" ]]; then
invoke_action "$action_id"
break
fi
fi
done
kill $(<"$GDBUS_MONITOR_PID")
if [[ -n ${NOTIFY_PID-} ]]; then
# echo "killing ${NOTIFY_PID} and its children"
procChildren=$(cat /proc/${NOTIFY_PID}/task/${NOTIFY_PID}/children)
kill -SIGTERM "${NOTIFY_PID}" "${procChildren}"
fi
cleanup
}
actionTime() {
GDBUS_MONITOR_PID=/tmp/notify-action-dbus-monitor.$$.pid
GDBUS_MONITOR=(gdbus monitor --session --dest org.freedesktop.Notifications --object-path /org/freedesktop/Notifications)
NOTIFICATION_ID="$1"
if [[ -z "$NOTIFICATION_ID" ]]; then
echo "no notification id passed: $@"
exit 1
fi
shift
ACTION_COMMANDS=("$@")
if [[ -z "$ACTION_COMMANDS" ]]; then
echo "no action commands passed: $@"
exit 1
fi
monitor
}
convert_type() {
case "$1" in
int) echo int32 ;;
double|string|byte) echo "$1" ;;
*) echo error; return 1 ;;
esac
}
make_action_key() {
echo "$(tr -dc _A-Z-a-z-0-9 <<< \"$1\")${RANDOM}"
}
make_action() {
local action_key="$1"
printf -v text "%q" "$2"
echo "\"$action_key\", \"$text\""
}
make_hint() {
if ! type=$(convert_type "$1"); then
return 1
fi
name="$2"
[[ "$type" = string ]] && command="\"$3\"" || command="$3"
echo "\"$name\": <$type $command>"
}
concat_actions() {
local result="$1"
shift
for s in "$@"; do
result="$result, $s"
done
echo "[$result]"
}
concat_hints() {
local result="$1"
shift
for s in "$@"; do
result="$result, $s"
done
echo "{$result}"
}
parse_notification_id() {
sed 's/(uint32 \([0-9]\+\),)/\1/g'
}
notify() {
local actions
if [[ -z ${ACTIONS-} ]]; then
actions='[]'
else
actions="$(concat_actions "${ACTIONS[@]}")"
fi
local hints="$(concat_hints "${HINTS[@]}")"
NOTIFICATION_ID=$(gdbus call "${NOTIFY_ARGS[@]}" \
--method org.freedesktop.Notifications.Notify \
-- \
"$APP_NAME" "$REPLACE_ID" "$ICON" "$SUMMARY" "$BODY" \
"${actions}" "${hints}" "int32 $EXPIRE_TIME" \
| parse_notification_id)
if [[ -n "${STORE_ID-}" ]]; then
echo "$NOTIFICATION_ID" > "$STORE_ID"
fi
if [[ -n "$PRINT_ID" ]]; then
echo "$NOTIFICATION_ID"
fi
if [[ $FORCE_EXPIRE -eq 1 ]]; then
SLEEP_TIME="$( LC_NUMERIC=C printf %f "${EXPIRE_TIME}e-3" )"
( sleep "$SLEEP_TIME" ; notify_close "$NOTIFICATION_ID" ) &
NOTIFY_PID=$!
fi
maybe_run_action_handler
}
notify_close () {
gdbus call "${NOTIFY_ARGS[@]}" --method org.freedesktop.Notifications.CloseNotification "$1" >/dev/null
}
process_urgency() {
case "$1" in
low) URGENCY=0 ;;
normal) URGENCY=1 ;;
critical) URGENCY=2 ;;
*) echo "Unknown urgency $URGENCY specified. Known urgency levels: low, normal, critical."
exit 1
;;
esac
}
process_category() {
IFS=, read -a categories <<< "$1"
for category in "${categories[@]}"; do
hint="$(make_hint string category "$category")"
HINTS=("${HINTS[@]}" "$hint")
done
}
process_hint() {
IFS=: read type name command <<< "$1"
if [[ -z "$name" ]] || [[ -z "$command" ]]; then
echo "Invalid hint syntax specified. Use TYPE:NAME:VALUE."
exit 1
fi
hint="$(make_hint "$type" "$name" "$command")"
if [[ ! $? = 0 ]]; then
echo "Invalid hint type \"$type\". Valid types are int, double, string and byte."
exit 1
fi
HINTS=("${HINTS[@]}" "$hint")
}
maybe_run_action_handler() {
if [[ -n "$NOTIFICATION_ID" ]] && [[ -n "${ACTION_COMMANDS-}" ]]; then
actionTime "$NOTIFICATION_ID" "${ACTION_COMMANDS[@]}" &
exit 0
fi
}
process_action() {
IFS='=' read command name <<<"$1"
if [[ -z "$name" ]] || [[ -z "$command" ]]; then
echo "Invalid action syntax '${1}' specified. Use NAME=COMMAND."
exit 1
fi
local action_key="$(make_action_key "$name")"
ACTION_COMMANDS=("${ACTION_COMMANDS[@]}" "$action_key" "$command")
local action="$(make_action "$action_key" "$name")"
ACTIONS=("${ACTIONS[@]}" "$action")
}
process_special_action() {
action_key="$1"
command="$2"
if [[ -z "$action_key" ]] || [[ -z "$command" ]]; then
echo "Command must not be empty"
exit 1
fi
ACTION_COMMANDS=("${ACTION_COMMANDS[@]}" "$action_key" "$command")
if [[ "$action_key" != close ]]; then
local action="$(make_action "$action_key" "$name")"
ACTIONS=("${ACTIONS[@]}" "$action")
fi
}
process_posargs() {
if [[ "$1" = -* ]] && ! [[ "$positional" = yes ]]; then
echo "Unknown option $1"
exit 1
else
if [[ "$SUMMARY_SET" = n ]]; then
SUMMARY="$1"
SUMMARY_SET=y
else
BODY="$1"
fi
fi
}
FORCE_EXPIRE=0
while (( $# > 0 )) ; do
case "$1" in
-\?|--help)
help
exit 0
;;
-v|--version)
echo "${0##*/} $VERSION"
exit 0
;;
-u|--urgency|--urgency=*)
[[ "$1" = --urgency=* ]] && urgency="${1#*=}" || { shift; urgency="$1"; }
process_urgency "$urgency"
;;
-t|--expire-time|--expire-time=*)
[[ "$1" = --expire-time=* ]] && EXPIRE_TIME="${1#*=}" || { shift; EXPIRE_TIME="$1"; }
if ! [[ "$EXPIRE_TIME" =~ ^-?[0-9]+$ ]]; then
echo "Invalid expire time: ${EXPIRE_TIME}"
exit 1
fi
;;
-f|--force-expire)
FORCE_EXPIRE=1
;;
-a|--app-name|--app-name=*)
[[ "$1" = --app-name=* ]] && APP_NAME="${1#*=}" || { shift; APP_NAME="$1"; }
;;
-i|--icon|--icon=*)
[[ "$1" = --icon=* ]] && ICON="${1#*=}" || { shift; ICON="$1"; }
;;
-c|--category|--category=*)
[[ "$1" = --category=* ]] && category="${1#*=}" || { shift; category="$1"; }
process_category "$category"
;;
-h|--hint|--hint=*)
[[ "$1" = --hint=* ]] && hint="${1#*=}" || { shift; hint="$1"; }
process_hint "$hint"
;;
-o | --action | --action=*)
[[ "$1" == --action=* ]] && action="${1#*=}" || { shift; action="$1"; }
process_action "$action"
;;
-d | --default-action | --default-action=*)
[[ "$1" == --default-action=* ]] && default_action="${1#*=}" || { shift; default_action="$1"; }
process_special_action default "$default_action"
;;
-l | --close-action | --close-action=*)
[[ "$1" == --close-action=* ]] && close_action="${1#*=}" || { shift; close_action="$1"; }
process_special_action close "$close_action"
;;
-p|--print-id)
PRINT_ID=yes
;;
-r|--replace|--replace=*)
[[ "$1" = --replace=* ]] && REPLACE_ID="${1#*=}" || { shift; REPLACE_ID="$1"; }
;;
-R|--replace-file|--replace-file=*)
[[ "$1" = --replace-file=* ]] && filename="${1#*=}" || { shift; filename="$1"; }
if [[ -s "$filename" ]]; then
REPLACE_ID="$(< "$filename")"
fi
STORE_ID="$filename"
;;
-s|--close|--close=*)
[[ "$1" = --close=* ]] && close_id="${1#*=}" || { shift; close_id="$1"; }
# always check that --close provides a numeric value
if [[ -z "$close_id" || ! "$close_id" =~ ^[0-9]+$ ]]; then
echo "Invalid close id: '$close_id'"
exit 1
fi
notify_close "$close_id"
exit $?
;;
--)
positional=yes
;;
*)
process_posargs "$1"
;;
esac
shift
done
# always force --replace and --replace-file to provide a numeric value; 0 means no id provided
if [[ -z "$REPLACE_ID" || ! "$REPLACE_ID" =~ ^[0-9]+$ ]]; then
REPLACE_ID=0
fi
# urgency is always set
HINTS=("$(make_hint byte urgency "$URGENCY")" "${HINTS[@]}")
if [[ "$SUMMARY_SET" = n ]]; then
help
exit 1
else
notify
fi And do something like this on the Python side(shell_exec is from try:
command = ['./notify-send.sh', '--print-id', '--urgency=normal', '--app-name=Nyx', '--force-expire', '--expire-time=145000']
if task:
command.extend(['--action=doTomorrow=Do Tomorrow'])
command.extend(['--action=markTaskDone=Mark Done'])
command.extend(['--action=deleteTaskLocally=Delete (local)'])
command.extend([icon_string, title, body])
result, errors = shell_exec(args=command, timeout=config['notificationLifetimeSeconds'])
except ShellExecError:
logging.exception('ShellExecError, possible command timeout?')
return # Can this crash and lose us normal content?
except Exception:
logging.exception('Unhandled exception sending notification!')
return
newline_pos = result.stdOut.find('\n')
# Extracting the first line (task ID) using the position of the first newline
notificationID = result.stdOut[:newline_pos] if newline_pos != -1 else result.stdOut
# Everything after the first line - action
notificationAction = result.stdOut[newline_pos+1:] if newline_pos != -1 else ''
logging.debug(f"Action from notification ID '{notificationID}' is '{notificationAction}' because it had std_out '{result.stdOut}'. std_err was '{result.stdErr}'")
if notificationAction == 'default':
if taskURL != '':
result, errors = shell_exec(args=['xdg-open', taskURL], timeout=config['notificationLifetimeSeconds'])
elif notificationAction == 'markTaskDone':
if task is None:
logging.critical(f"{notificationAction}: This shouldn't happen…")
else:
try:
markTaskDone(task=task)
except Exception:
logging.exception("Failed to mark task done!")
elif notificationAction == 'deleteTaskLocally':
if task is None:
logging.critical(f"{notificationAction}: This shouldn't happen…")
else:
deleteTaskLocally(task=task)
elif notificationAction == 'doTomorrow':
if task is None:
logging.critical(f"{notificationAction}: This shouldn't happen…")
else:
setDueTomorrow(task=task)
elif notificationAction == '':
logging.debug('User closed notificaition')
else:
logging.error(f"Unknown action '{notificationAction}'")
if errors is not None and 'Timeout' in errors:
try:
command = ['./notify-send.sh', f'--close={notificationID}']
result, errors = shell_exec(args=command, timeout=config['notificationLifetimeSeconds'])
except Exception:
logging.exception('Unhandled exception when killing notif!') This works, but is terrible and issue prone, I plan to write a notif library for Python since all the existing ones frankly suck for one reason or another. |
would be nice to have the ability to go to previous song or skip to the next one directly in the notification that appear in gnome/mpris when a new song is played
(it would also be nice to have one click to repeat, two clicks for previous but i guess this is for another issue, right?)
The text was updated successfully, but these errors were encountered: