2

I'm trying to update a previous notification (which displays the screen's brightness after being changed using a progress bar) with gdbus, as notify-send does not support updating.

Previously I use a workaround by killing the previous notification, but it makes the notification disappear and then reappear, instead of smoothly updating the progress bar:

#!/bin/bash
for i in {0..100..10}
    do
        kill $(pgrep ^xfce4-notifyd$)
        notify-send "Brightness" -h int:value:$(xbacklight -get) -h string:synchronous:volume -i weather-clear -t 1
    done

I tried dbus-send, but no notification showed up even if I remove the hints part. After some googling, I found out gdbus and got it working, but only without hints.

Basically, this is where I've got so far with gdbus:

#!/bin/bash
gdbus call --session --dest org.freedesktop.Notifications \
--object-path /org/freedesktop/Notifications \
--method org.freedesktop.Notifications.Notify \
brightness \
42 \
notification-display-brightness-full \
"Message" "Body" [] \
"{'value':i, 'name':'value', 'value':'$(xbacklight -get)'}" "{'type':'string', 'name':'synchronous', 'value':'volume'}" \
1

However I'm stuck at this error:

Error parsing parameter 7 of type 'a{sv}': expected value:
  {'value':i, 'name':'value', 'value':'0.000000'}
           ^

Can anyone help me with the syntax for notification hints sent by gdbus?

I read somewhere that I can use some custom patched version of notify-send, however I don't like the idea of introducing unofficial binaries into my stable system.

Btw, I'm using xfce4-notifyd in xubuntu.

UPDATE:

I tried to be less pig-headed and I tried python's dbus module for the job. And again, I'm stuck at hints. It only worked if I remove the hints. Here's the new code:

#!/usr/bin/env python3
"""Creates a Notification pop-up bubble"""
import dbus
item              = "org.freedesktop.Notifications"
path              = "/org/freedesktop/Notifications"
interface         = "org.freedesktop.Notifications"
app_name          = "brightness"
id_num_to_replace = 0
icon              = "weather-clear"
title             = "Message"
text              = "Body"
actions_list      = ''
hint              = '"{'type':i, 'name':'value', 'value':'$(xbacklight -get)'}" "{'type':'string', 'name':'synchronous', 'value':'volume'}"'
time              = 5000   # Use seconds x 1000

bus = dbus.SessionBus()
notif = bus.get_object(item, path)
notify = dbus.Interface(notif, interface)
notify.Notify(app_name, id_num_to_replace, icon, title, text, actions_list, hint, time)

And the error:

File "/home/pygeek03/bin/brightness.py", line 13
 hint              = '"{'type':i, 'name':'value', 'value':'$(xbacklight -get)'}" "{'type':'string', 'name':'synchronous', 'value':'volume'}"'
                           ^
SyntaxError: invalid syntax
PyGeek03
  • 168

2 Answers2

-1

https://github.com/bkw777/notify-send.sh does it
Pure bash other than using gdbus call to do the actual dbus transaction.
It's main reason for existing is actually to replace existing notification, so the readme shows exactly that example.

Aside from reading the code, you can enable debugging and read the final gdbus command out of the debug log.

The gdbus commandline 2nd to last arg contains $h, and backtrack from there $h is built from ${HINTS[*]}, which is built by one or more calls to process_hint and make_hint (returns it's output via global $_r to avoid subshells). There is always at least one to set the urgency level even if you don't supply any --hint options.

To replace a notification, you need to know it's ID, and to know it's ID you have to collect it when it was created the first time. (or you could try to just generte your own ID which you hope will be unique and never overwrite some other notification, but the clean way is to let the notification daemon generate the notification the first time, and then use that to update it after that.)

Send a notification and collect it's ID with debugging enabled so you can see the generated gdbus command

$ DEBUG_NOTIFY_SEND=true notify-send.sh --print-id --expire-time=0 --close-action="mainline-gtk --install 6.3.4" "New Kernel" "Install 6.3.4"
/usr/local/bin/notify-send.sh debug logging to /run/user/1000/.notify-send.sh.1044020.e
68
$

notification ID was 68

gdbus command was:

$ grep "gdbus call" /run/user/1000/.notify-send.sh.1044020.e
++ gdbus call --session --dest org.freedesktop.Notifications --object-path /org/freedesktop/Notifications --method org.freedesktop.Notifications.Notify -- notify-send.sh 0 '' 'New Kernel' 'Install 6.3.4' '[]' '{"urgency":<byte 1>}' 0
$

now replace that notification in-place, using --replace=68

$ DEBUG_NOTIFY_SEND=true notify-send.sh --replace=68 --expire-time=0 --close-action="mainline-gtk --install 6.3.5" "New Kernel" "Install 6.3.5"
/usr/local/bin/notify-send.sh debug logging to /run/user/1000/.notify-send.sh.1044042.e
$ grep "gdbus call" /run/user/1000/.notify-send.sh.1044042.e
++ gdbus call --session --dest org.freedesktop.Notifications --object-path /org/freedesktop/Notifications --method org.freedesktop.Notifications.Notify -- notify-send.sh 68 '' 'New Kernel' 'Install 6.3.5' '[]' '{"urgency":<byte 1>}' 0
$

So hints are of the form: {'name':<type value>}

with some non-obvious rules and allowed deviations:

  • the entire string has to be enclosed in quotes or escaped with backslashes since this is all on a bash command line, and the exact syntax is "whatever you want that results in a valid bash commandline..." since there's more than one way to quote things. Exept one really non-obvious, name must be individually quoted, single or double, even if it has no spaces, name's quotes must themselves be either escaped or quoted so that bash doesn't eat them so they become literals and part of the string.
    The script is generating: '{"urgency":<byte 1>}'
    also valid: "{'urgency':<byte 1>}"
    also valid: "{\"urgency\":<byte 1>}"
    NOT valid: \{'urgency':\<byte\ 1\>\}
    IS valid: \{\'urgency\':\<byte\ 1\>\}

  • type is optional, at least for some types, at least for byte and int32
    valid: "{'urgency':<byte 1>}"
    also valid: "{'urgency':<1>}"

  • https://specifications.freedesktop.org/notification-spec/notification-spec-latest.html#hints has a table that shows all the types as BYTE INT32 STRING etc, but they must actually be sent in lower-case.
    valid: "{'urgency':<byte 1>}"
    not valid: "{'urgency':<BYTE 1>}"

  • Not shown yet but multiple hint items are comma seperated within the braces
    "{\"urgency\":<byte 1>,\"category\":<string \"info\">}"

Here's another with more stuff filled in, added an icon name and another hint for category, and action buttons so all fields have stuff in them, and actions and hints are both multi-item lists:

$ notify-send.sh --expire-time=0 --force-expire --category=info --icon=mainline --action="Mainline App:mainline-gtk" --action="Install 6.3.4:mainline-gtk --install 6.3.4" "Mainlin Kernels" "New kernel available"
/usr/local/bin/notify-send.sh debug logging to /run/user/1000/.notify-send.sh.1148819.e
$ grep "gdbus call" /run/user/1000/.notify-send.sh.1148819.e
++ gdbus call --session --dest org.freedesktop.Notifications --object-path /org/freedesktop/Notifications --method org.freedesktop.Notifications.Notify -- notify-send.sh 0 mainline 'Mainlin Kernels' 'New kernel available' '["0","Mainline App","1","Install 6.3.4"]' '{"urgency":<byte 1>,"category":<string "info">}' 0
$

You may have noticed that these examples include an action that doesn't show up in the gdbus command.

Actions get handled by setting up a gdbus monitor command, and grepping the notification ID from the monitor process's output.
The gdbus monitor will output multiple messages, and you ignore any that don't have your ID. When you get a record with your ID in it, parse the action out of that line, and depending upon what the action was, maybe run the command you were previously given.

The action commandline is never written to dbus. What happens is notify-send.sh runs notify-action.sh and notify-action.sh waits for essentially "notification ID 68 closed" and when it sees that, it runs the command that notify-send.sh gave it previously.

If you use an action button instead of close-action, you give that button some unique key (you make it up, can be anything, simplest is just a number, one for each button, can start from the same 0 or 1 every time since it only needs to be unique per notification ID, so 3 buttons can be keys 1 2 3), and that goes onto dbus, and later the watcher process looks for essentially "notification 68 got action 2" and notify-action.sh has previously been given a list of button IDs and associated commands, so when it sees "got ID 68 action 2" it runs the command for action 2.

-1

It's just invalid python syntax. What you do with the strings for hint does not make any sense. Try seeing matching quotes for example.

hint should be: hint = {'type':'i', 'name':'value', 'value':int(os.system('xbacklight -get'))}