Closed dharmx closed 2 years ago
#!/usr/bin/env python
import contextlib
import datetime
import dbus
import gi
gi.require_version("GdkPixbuf", "2.0")
from gi.repository import GdkPixbuf, GLib
from dbus.mainloop.glib import DBusGMainLoop
def unwrap(value):
# Try to trivially translate a dictionary's elements into nice string
# formatting.
if isinstance(value, dbus.ByteArray):
return "".join([str(byte) for byte in value])
if isinstance(value, (dbus.Array, list, tuple)):
return [unwrap(item) for item in value]
if isinstance(value, (dbus.Dictionary, dict)):
return dict([(unwrap(x), unwrap(y)) for x, y in value.items()])
if isinstance(value, (dbus.Signature, dbus.String)):
return str(value)
if isinstance(value, dbus.Boolean):
return bool(value)
if isinstance(
value,
(dbus.Int16, dbus.UInt16, dbus.Int32, dbus.UInt32, dbus.Int64, dbus.UInt64),
):
return int(value)
if isinstance(value, dbus.Byte):
return bytes([int(value)])
return value
def save_image_bytes(px_args):
# gets image data and saves it to file
save_path = f"/tmp/image-{datetime.datetime.now().strftime('%s')}.png"
# https://specifications.freedesktop.org/notification-spec/latest/ar01s08.html
# https://specifications.freedesktop.org/notification-spec/latest/ar01s05.html
GdkPixbuf.Pixbuf.new_from_data(
width=px_args[0],
height=px_args[1],
has_alpha=px_args[3],
data=px_args[6],
colorspace=GdkPixbuf.Colorspace.RGB,
rowstride=px_args[2],
bits_per_sample=px_args[4],
).savev(save_path, "png")
print(len(px_args[6])) # DEBUG: The sizes are the same! Although, DUNST shows images changes.
return save_path
def message_callback(_, message):
if type(message) != dbus.lowlevel.MethodCallMessage:
return
args_list = message.get_args_list()
args_list = [unwrap(item) for item in args_list]
details = {
"appname": args_list[0],
"summary": args_list[3],
"body": args_list[4],
"urgency": args_list[6]["urgency"],
"iconpath": None,
}
if args_list[2]:
details["iconpath"] = args_list[2]
with contextlib.suppress(KeyError):
# for some reason args_list[6]["icon_data"][6] i.e. the byte data
# does not change unless I restart spotify but, the song title
# (body / summary) change gets picked up.
details["iconpath"] = save_image_bytes(args_list[6]["icon_data"])
print(details) # DEBUG
DBusGMainLoop(set_as_default=True)
rules = {
"interface": "org.freedesktop.Notifications",
"member": "Notify",
"eavesdrop": "true", # https://bugs.freedesktop.org/show_bug.cgi?id=39450
}
bus = dbus.SessionBus() # TODO: Use proxy.
# is there a better way of doing this? Like a adding some sort of signal?
# I could not figure out the bus.add_signal_receiver thing. How do I use this?
# Is this needed at all?
bus.add_match_string(",".join([f"{key}={value}" for key, value in rules.items()]))
bus.add_message_filter(message_callback)
loop = GLib.MainLoop()
try:
loop.run()
except KeyboardInterrupt:
loop.quit()
bus.close()
# vim:filetype=python
Cooked this but the image_data never changes any idea why? Demo:
I cannot believe I am so stupid. How am I even alive. In the above script I have verified that icon_data
is deprecated on favor of image-data
. So, I just needed to change that.
Additionally, I have also reviewed this issue #804 and decided it would be a headache to maintain this.
Following is the corrected script if someone needs it!
#!/usr/bin/env python
import contextlib
import datetime
import dbus
import gi
gi.require_version("GdkPixbuf", "2.0")
from gi.repository import GdkPixbuf, GLib
from dbus.mainloop.glib import DBusGMainLoop
def unwrap(value):
# Try to trivially translate a dictionary's elements into nice string
# formatting.
if isinstance(value, dbus.ByteArray):
return "".join([str(byte) for byte in value])
if isinstance(value, (dbus.Array, list, tuple)):
return [unwrap(item) for item in value]
if isinstance(value, (dbus.Dictionary, dict)):
return dict([(unwrap(x), unwrap(y)) for x, y in value.items()])
if isinstance(value, (dbus.Signature, dbus.String)):
return str(value)
if isinstance(value, dbus.Boolean):
return bool(value)
if isinstance(
value,
(dbus.Int16, dbus.UInt16, dbus.Int32, dbus.UInt32, dbus.Int64, dbus.UInt64),
):
return int(value)
if isinstance(value, dbus.Byte):
return bytes([int(value)])
return value
def save_img_byte(px_args):
# gets image data and saves it to file
save_path = f"/tmp/image-{datetime.datetime.now().strftime('%s')}.png"
# https://specifications.freedesktop.org/notification-spec/latest/ar01s08.html
# https://specifications.freedesktop.org/notification-spec/latest/ar01s05.html
GdkPixbuf.Pixbuf.new_from_bytes(
width=px_args[0],
height=px_args[1],
has_alpha=px_args[3],
data=GLib.Bytes(px_args[6]),
colorspace=GdkPixbuf.Colorspace.RGB,
rowstride=px_args[2],
bits_per_sample=px_args[4],
).savev(save_path, "png")
return save_path
def message_callback(_, message):
if type(message) != dbus.lowlevel.MethodCallMessage:
return
args_list = message.get_args_list()
args_list = [unwrap(item) for item in args_list]
details = {
"appname": args_list[0],
"summary": args_list[3],
"body": args_list[4],
"urgency": args_list[6]["urgency"],
"iconpath": None,
}
if args_list[2]:
details["iconpath"] = args_list[2]
with contextlib.suppress(KeyError):
# for some reason args_list[6]["icon_data"][6] i.e. the byte data
# does not change unless I restart spotify but, the song title
# (body / summary) change gets picked up.
details["iconpath"] = save_img_byte(args_list[6]["image-data"])
print(details) # DEBUG
DBusGMainLoop(set_as_default=True)
rules = {
"interface": "org.freedesktop.Notifications",
"member": "Notify",
"eavesdrop": "true", # https://bugs.freedesktop.org/show_bug.cgi?id=39450
}
bus = dbus.SessionBus()
bus.add_match_string(",".join([f"{key}={value}" for key, value in rules.items()]))
bus.add_message_filter(message_callback)
loop = GLib.MainLoop()
try:
loop.run()
except KeyboardInterrupt:
bus.close()
# vim:filetype=python
Closing!
Great that you've solved it and thanks for providing your solution!
@dharmx I cannot believe you are so smart! So if I run this script while I receive notifs then the image-data
goes into /tmp
? What if I had dbus-monitor "interface='org.freedesktop.Notifications', member='Notify'"
running previously and I have some raw data available such as:
dict entry(
string "image-data"
variant struct {
int32 58
int32 58
int32 232
boolean true
int32 8
int32 4
array of bytes [
00 00 00 00 00 00 00 07 00 00 00 3e 00 00 00 76 02 02 02 9d
01 01 01 c4 00 00 00 df 00 00 00 ed 00 00 00 fa 00 00 00 fa
00 00 00 ed 00 00 00 df 00 00 00 c4 00 00 00 9d 00 00 00 76
[...]
00 00 00 9c 00 00 00 c3 01 00 00 df 01 00 01 ec 00 01 00 f9
00 01 00 f9 00 01 00 ec 02 00 01 df 01 01 01 c3 00 00 02 9c
00 00 02 75 00 00 00 3d 00 00 00 07 00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
]
}
)
Could your script be used to parse the image-data
and decode/save? If so, how? or, you think there's a better way to decode/save the image-data
from this dbus-monitor
output? thx!
Could your script be used to parse the image-data and
decode/save
?
The script does exactly that. You Just execute that and forget. It'll save the images to /tmp/
and prints a JSON metadata of that song. If you want to know when the value is sent then you'd read using a while loop. Note that this script depends on Gio bindings for python.
./notify | while read -r metadata; do
# read using jq or whatever
done
@dharmx I can't figure it out, for sure I'm the stupid one here. 😁 I do have the dbus-monitor
command running as a user systemd service, so when I get a notif, the raw data gets saved in the system journal and I can retrieve it with journalctl
. I tried launching your script and waited for the next notif to come in, plus several more, but it's not picking up the data and nothing is saved to /tmp
. I also tried copying the data for a raw notif from the journal and tried cat notif.txt | yourScript.py
but that doesn't do anything either, it just seems to hang actually. Running on Debian here and python3-gi
is installed. Any idea what's wrong? I guess it's me tbh, do I need to be running Dunst for this to work? 😅 I just want to decode the image-data
somehow back into an image and this thread came up in a search...
do I need to be running Dunst for this to work
Any, notification daemon should work. Like dunst, tiramisu, naughty (AwesomeWM), etc. But yes, you would need to have any one of these installed.
Here's a demo and code.
https://github.com/dunst-project/dunst/assets/80379926/d835f1ad-ec8d-4646-a4a3-813ba1e88f51
I mean I just have stock Gnome with default FreeDesktop notifications I guess. /usr/bin/dbus-monitor "interface='org.freedesktop.Notifications'"
is running as a systemd service, so in addition to seeing the actual notif, the raw data is saved in the journal.
Issue description
How do I get the icon_data through a script. For example, Discord sends the profile picture in byte format and Spotify sends the album art in byte format. I am making a notification manager so I was wondering if getting these hints would be possible or not and if so how? Also, the
dunstctl history
part also doesn't have them. The icon data path is empty!However, upon monitoring the DBUS interface(?) I do see that the icon data is being returned. Note that the notifications itself work fine! The images do show when I am pinged from Discord. I just want the icon bytes so I can parse it in my script.
Installation info
Version: Dunst - A customizable and lightweight notification-daemon v1.8.1-60-g3bba0b0 Install type: From AUR from here Window manager: BSPWM
Minimal dunstrc
```ini # Dunstrc here # Not applicable. ```Please, ask if you need any additional info.