diff --git a/configure.ac b/configure.ac index 8bf3d9e492..eea40a416f 100644 --- a/configure.ac +++ b/configure.ac @@ -733,7 +733,16 @@ AS_IF([test "${enable_gme}" != "no" -a "${HAVE_ZLIB}" = "yes"], [ ]) AS_IF([test "${HAVE_DBUS}" = "yes" -a "${enable_notify}" != "no"], [ - HAVE_NOTIFY=yes + AS_IF([test "${enable_staticlink}" != "no"], [ + HAVE_NOTIFY=yes + NOTIFY_DEPS_CFLAGS="${GTK3_310_CFLAGS}" + NOTIFY_DEPS_LIBS="${GTK3_310_LIBS}" + AC_SUBST(NOTIFY_DEPS_CFLAGS) + AC_SUBST(NOTIFY_DEPS_LIBS) + ], [ + PKG_CHECK_MODULES(NOTIFY_DEPS, glib-2.0 >= 2.26 gio-2.0 gdk-pixbuf-2.0, HAVE_NOTIFY=yes, HAVE_NOTIFY=no) + ]) + NOTIFY_LIBS="$DBUS_DEPS_LIBS" NOTIFY_CFLAGS="$DBUS_DEPS_CFLAGS" AC_SUBST(NOTIFY_LIBS) diff --git a/plugins/gtkui/covermanager/covermanager.c b/plugins/gtkui/covermanager/covermanager.c index 75c8ac4229..ea6b4f0515 100644 --- a/plugins/gtkui/covermanager/covermanager.c +++ b/plugins/gtkui/covermanager/covermanager.c @@ -157,7 +157,7 @@ static GdkPixbuf * _load_image_from_cover (covermanager_t *impl, ddb_cover_info_t *cover, int want_default) { GdkPixbuf *img = NULL; - if (!img && cover && cover->image_filename) { + if (cover && cover->image_filename) { long size = 0; char *buf = _buffer_from_file (cover->image_filename, &size); if (buf != NULL) { @@ -267,7 +267,8 @@ _cover_loaded_callback (int error, ddb_cover_query_t *query, ddb_cover_info_t *c gtkui_dispatch_on_main (^{ // Prevent spurious loading of the same image. The load is already scheduled, so we should just wait for it. char *key = _cache_key_for_track (impl, query->track); - gboolean should_wait = gobj_cache_get_should_wait (impl->cache, key) || (gobj_cache_get (impl->cache, key) != NULL); + gboolean should_wait = + gobj_cache_get_should_wait (impl->cache, key) || (gobj_cache_get (impl->cache, key) != NULL); if (should_wait) { // append to the end of loader queue @@ -378,7 +379,12 @@ covermanager_free (covermanager_t *impl) { } static GdkPixbuf * -_cover_for_track (covermanager_t *impl, int want_default, DB_playItem_t *track, int64_t source_id, covermanager_completion_block_t completion_block) { +_cover_for_track ( + covermanager_t *impl, + int want_default, + DB_playItem_t *track, + int64_t source_id, + covermanager_completion_block_t completion_block) { if (!impl->plugin) { completion_block (NULL); return NULL; @@ -418,12 +424,20 @@ _cover_for_track (covermanager_t *impl, int want_default, DB_playItem_t *track, } GdkPixbuf * -covermanager_cover_for_track_no_default (covermanager_t *impl, DB_playItem_t *track, int64_t source_id, covermanager_completion_block_t completion_block) { +covermanager_cover_for_track_no_default ( + covermanager_t *impl, + DB_playItem_t *track, + int64_t source_id, + covermanager_completion_block_t completion_block) { return _cover_for_track (impl, 0, track, source_id, completion_block); } GdkPixbuf * -covermanager_cover_for_track (covermanager_t *impl, DB_playItem_t *track, int64_t source_id, covermanager_completion_block_t completion_block) { +covermanager_cover_for_track ( + covermanager_t *impl, + DB_playItem_t *track, + int64_t source_id, + covermanager_completion_block_t completion_block) { return _cover_for_track (impl, 1, track, source_id, completion_block); } @@ -451,8 +465,13 @@ covermanager_create_scaled_image (covermanager_t *manager, GdkPixbuf *image, Gtk } GtkAllocation -covermanager_desired_size_for_image_size (covermanager_t *manager, GtkAllocation image_size, GtkAllocation availableSize) { - double scale = min ((double)availableSize.width / (double)image_size.width, (double)availableSize.height / (double)image_size.height); +covermanager_desired_size_for_image_size ( + covermanager_t *manager, + GtkAllocation image_size, + GtkAllocation availableSize) { + double scale = min ( + (double)availableSize.width / (double)image_size.width, + (double)availableSize.height / (double)image_size.height); GtkAllocation a = { 0 }; a.width = image_size.width * scale; diff --git a/plugins/notify/Makefile.am b/plugins/notify/Makefile.am index 53b692f850..cd73c33185 100644 --- a/plugins/notify/Makefile.am +++ b/plugins/notify/Makefile.am @@ -3,6 +3,6 @@ pkglib_LTLIBRARIES = notify.la notify_la_SOURCES = notify.c notify_la_LDFLAGS = -module -avoid-version -notify_la_LIBADD = $(LDADD) $(NOTIFY_LIBS) $(DISPATCH_LIBS) -notify_la_CFLAGS = -std=c99 $(CFLAGS) $(NOTIFY_CFLAGS) $(DISPATCH_CFLAGS) -I@top_srcdir@/include +notify_la_LIBADD = $(LDADD) $(NOTIFY_LIBS) $(NOTIFY_DEPS_LIBS) $(DISPATCH_LIBS) +notify_la_CFLAGS = -std=c99 $(CFLAGS) $(NOTIFY_CFLAGS) $(NOTIFY_DEPS_CFLAGS) $(DISPATCH_CFLAGS) -I@top_srcdir@/include endif diff --git a/plugins/notify/notify.c b/plugins/notify/notify.c index 9869f5f57a..29c3d3c94d 100644 --- a/plugins/notify/notify.c +++ b/plugins/notify/notify.c @@ -23,9 +23,12 @@ #include #include #include +#include #include "../../gettext.h" #include "../artwork/artwork.h" +#define min(x, y) ((x) < (y) ? (x) : (y)) +#define MAX_ALBUM_ART_FILE_SIZE (40 * 1024 * 1024) #define E_NOTIFICATION_BUS_NAME "org.freedesktop.Notifications" #define E_NOTIFICATION_INTERFACE "org.freedesktop.Notifications" #define E_NOTIFICATION_PATH "/org/freedesktop/Notifications" @@ -163,6 +166,122 @@ _cover_loaded_callback (int error, ddb_cover_query_t *query, ddb_cover_info_t *c Block_release (completion_block); } +static char * +_buffer_from_file (const char *fname, long *psize) { + char *buffer = NULL; + FILE *fp = fopen (fname, "rb"); + if (fp == NULL) { + return NULL; + } + if (fseek (fp, 0, SEEK_END) < 0) { + goto error; + } + long size = ftell (fp); + if (size <= 0 || size > MAX_ALBUM_ART_FILE_SIZE) { + goto error; // we don't really want to load ultra-high-res images + } + rewind (fp); + + buffer = malloc (size); + if (buffer == NULL) { + goto error; + } + + if (fread (buffer, 1, size, fp) != size) { + goto error; + } + + fclose (fp); + + *psize = size; + return buffer; + +error: + if (fp != NULL) { + fclose (fp); + } + free (buffer); + return NULL; +} + +static GdkPixbuf * +_create_scaled_image (GdkPixbuf *image, int width, int height) { + int originalWidth = gdk_pixbuf_get_width (image); + int originalHeight = gdk_pixbuf_get_height (image); + + if (originalWidth <= width && originalHeight <= height) { + g_object_ref (image); + return image; + } + + gboolean has_alpha = gdk_pixbuf_get_has_alpha (image); + int bits_per_sample = gdk_pixbuf_get_bits_per_sample (image); + + GdkPixbuf *scaled_image = gdk_pixbuf_new (GDK_COLORSPACE_RGB, has_alpha, bits_per_sample, width, height); + + double scale_x = (double)width / (double)originalWidth; + double scale_y = (double)height / (double)originalHeight; + + gdk_pixbuf_scale (image, scaled_image, 0, 0, width, height, 0, 0, scale_x, scale_y, GDK_INTERP_BILINEAR); + + return scaled_image; +} + +static void +_desired_size_for_image_size ( + int image_width, + int image_height, + int avail_width, + int avail_height, + int *result_width, + int *result_height) { + double scale = min ((double)avail_width / (double)image_width, (double)avail_height / (double)image_height); + + *result_width = image_width * scale; + *result_height = image_height * scale; +} + +static GdkPixbuf * +_load_image (const char *image_filename) { + GdkPixbuf *img = NULL; + + long size = 0; + char *buf = _buffer_from_file (image_filename, &size); + if (buf != NULL) { + GdkPixbufLoader *loader = gdk_pixbuf_loader_new (); + gdk_pixbuf_loader_write (loader, (const guchar *)buf, size, NULL); + gdk_pixbuf_loader_close (loader, NULL); + img = gdk_pixbuf_loader_get_pixbuf (loader); + free (buf); + } + + if (img == NULL) { + return NULL; + } + + const int max_image_size = conf_get_int ("notify.albumart_size", 64); + + // downscale + int orig_width = gdk_pixbuf_get_width (img); + int orig_height = gdk_pixbuf_get_height (img); + + if (orig_width > max_image_size || orig_height > max_image_size) { + int new_width = max_image_size; + int new_height = max_image_size; + + int result_width; + int result_height; + + _desired_size_for_image_size (orig_width, orig_height, new_width, new_height, &result_width, &result_height); + + GdkPixbuf *scaled_img = _create_scaled_image (img, result_width, result_height); + g_object_unref (img); + img = scaled_img; + } + + return img; +} + static dbus_uint32_t show_notification (DB_playItem_t *track, char *image_filename, dbus_uint32_t replaces_id, int force) { char title[1024]; @@ -205,14 +324,21 @@ show_notification (DB_playItem_t *track, char *image_filename, dbus_uint32_t rep bool should_wait_for_cover = !image_filename && deadbeef->conf_get_int ("notify.albumart", 0) && artwork_plugin; bool should_apply_kde_fix = deadbeef->conf_get_int ("notify.fix_kde_5_23_5", 0) ? true : false; - // KDE won't re-display notification via reuse, + // KDE won't re-display notification via reuse, // so don't show it now and wait for cover callback. if (!(should_wait_for_cover && should_apply_kde_fix)) { - char *v_iconname = image_filename ?: "deadbeef"; + char *v_iconname = ""; // image_filename ?: "deadbeef"; const char *v_summary = title; const char *v_body = esc_content; dbus_int32_t v_timeout = -1; + GdkPixbuf *img = _load_image (image_filename); + if (!img) { + v_iconname = "deadbeef"; + } + + DBusMessageIter iter, sub; + dbus_message_append_args ( msg, DBUS_TYPE_STRING, @@ -227,13 +353,78 @@ show_notification (DB_playItem_t *track, char *image_filename, dbus_uint32_t rep &v_body, DBUS_TYPE_INVALID); - DBusMessageIter iter, sub; // actions dbus_message_iter_init_append (msg, &iter); dbus_message_iter_open_container (&iter, DBUS_TYPE_ARRAY, "s", &sub); dbus_message_iter_close_container (&iter, &sub); + // hints dbus_message_iter_open_container (&iter, DBUS_TYPE_ARRAY, "{sv}", &sub); + + if (img != NULL) { + dbus_int32_t width = gdk_pixbuf_get_width (img); + dbus_int32_t height = gdk_pixbuf_get_height (img); + dbus_int32_t stride = gdk_pixbuf_get_rowstride (img); + dbus_bool_t has_alpha = gdk_pixbuf_get_has_alpha (img); + dbus_int32_t bits_per_sample = gdk_pixbuf_get_bits_per_sample (img); + dbus_int32_t channels = gdk_pixbuf_get_n_channels (img); + guchar *image_bytes = gdk_pixbuf_get_pixels (img); + + DBusMessageIter dict_entry_sub; + dbus_message_iter_open_container (&sub, DBUS_TYPE_DICT_ENTRY, 0, &dict_entry_sub); + + { + char *v_image_data = "image-data"; + dbus_message_iter_append_basic (&dict_entry_sub, DBUS_TYPE_STRING, &v_image_data); + + DBusMessageIter value_sub; + + dbus_message_iter_open_container (&dict_entry_sub, DBUS_TYPE_VARIANT, "(iiibiiay)", &value_sub); + + { + + DBusMessageIter image_sub; + dbus_message_iter_open_container (&value_sub, DBUS_TYPE_STRUCT, NULL, &image_sub); + + { + + dbus_message_iter_append_basic (&image_sub, DBUS_TYPE_INT32, &width); + dbus_message_iter_append_basic (&image_sub, DBUS_TYPE_INT32, &height); + dbus_message_iter_append_basic (&image_sub, DBUS_TYPE_INT32, &stride); + dbus_message_iter_append_basic (&image_sub, DBUS_TYPE_BOOLEAN, &has_alpha); + dbus_message_iter_append_basic (&image_sub, DBUS_TYPE_INT32, &bits_per_sample); + dbus_message_iter_append_basic (&image_sub, DBUS_TYPE_INT32, &channels); + + DBusMessageIter data_sub; + + dbus_message_iter_open_container ( + &image_sub, + DBUS_TYPE_ARRAY, + DBUS_TYPE_BYTE_AS_STRING, + &data_sub); + + { + dbus_message_iter_append_fixed_array ( + &data_sub, + DBUS_TYPE_BYTE, + &image_bytes, + stride * height); + } + + dbus_message_iter_close_container (&image_sub, &data_sub); + } + + dbus_message_iter_close_container (&value_sub, &image_sub); + } + + dbus_message_iter_close_container (&dict_entry_sub, &value_sub); + } + + dbus_message_iter_close_container (&sub, &dict_entry_sub); + + g_object_unref (img); + } + dbus_message_iter_close_container (&iter, &sub); dbus_message_iter_append_basic (&iter, DBUS_TYPE_INT32, &v_timeout); diff --git a/premake5.lua b/premake5.lua index a539391bec..3f2ddacbde 100644 --- a/premake5.lua +++ b/premake5.lua @@ -558,7 +558,7 @@ project "notify_plugin" files { "plugins/notify/notify.c" } - pkgconfig ("dbus-1") + pkgconfig ("dbus-1", "glib-2.0", "gio-2.0", "gdk-pixbuf-2.0") buildoptions {"-fblocks"} links {"dispatch", "BlocksRuntime"} end