Skip to content

Commit 0886f92

Browse files
committed
Compare raw icons by their checksums
Currently, we just skipped the notification comparison, if the notification had a raw icon attached. This is a bit counterintuitive. Calculating a checksum of the raw icon's data is the solution. For that we cache the pixel buffer and introduce a field, which saves the current icon's id. The icon_id may be a path or a hash. So you can compare two notifications by their icon_id field regardless of their icon type by their icon_id field.
1 parent 8a46b88 commit 0886f92

File tree

8 files changed

+214
-124
lines changed

8 files changed

+214
-124
lines changed

src/dbus.c

Lines changed: 1 addition & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -86,8 +86,6 @@ struct dbus_method {
8686
GVariant *parameters, \
8787
GDBusMethodInvocation *invocation)
8888

89-
static struct raw_image *get_raw_image_from_data_hint(GVariant *icon_data);
90-
9189
int cmp_methods(const void *vkey, const void *velem)
9290
{
9391
const char *key = (const char*)vkey;
@@ -241,7 +239,7 @@ static struct notification *dbus_message_to_notification(const gchar *sender, GV
241239
if (!dict_value)
242240
dict_value = g_variant_lookup_value(hints, "icon_data", G_VARIANT_TYPE("(iiibiiay)"));
243241
if (dict_value) {
244-
n->raw_icon = get_raw_image_from_data_hint(dict_value);
242+
notification_icon_replace_data(n, dict_value);
245243
g_variant_unref(dict_value);
246244
}
247245

@@ -577,42 +575,6 @@ static void dbus_cb_name_lost(GDBusConnection *connection,
577575
exit(1);
578576
}
579577

580-
static struct raw_image *get_raw_image_from_data_hint(GVariant *icon_data)
581-
{
582-
struct raw_image *image = g_malloc(sizeof(struct raw_image));
583-
GVariant *data_variant;
584-
gsize expected_len;
585-
586-
g_variant_get(icon_data,
587-
"(iiibii@ay)",
588-
&image->width,
589-
&image->height,
590-
&image->rowstride,
591-
&image->has_alpha,
592-
&image->bits_per_sample,
593-
&image->n_channels,
594-
&data_variant);
595-
596-
expected_len = (image->height - 1) * image->rowstride + image->width
597-
* ((image->n_channels * image->bits_per_sample + 7) / 8);
598-
599-
if (expected_len != g_variant_get_size (data_variant)) {
600-
LOG_W("Expected image data to be of length %" G_GSIZE_FORMAT
601-
" but got a length of %" G_GSIZE_FORMAT,
602-
expected_len,
603-
g_variant_get_size(data_variant));
604-
g_free(image);
605-
g_variant_unref(data_variant);
606-
return NULL;
607-
}
608-
609-
image->data = (guchar *) g_memdup(g_variant_get_data(data_variant),
610-
g_variant_get_size(data_variant));
611-
g_variant_unref(data_variant);
612-
613-
return image;
614-
}
615-
616578
int dbus_init(void)
617579
{
618580
guint owner_id;

src/icon.c

Lines changed: 110 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -205,19 +205,120 @@ GdkPixbuf *get_pixbuf_from_icon(const char *iconname)
205205
return pixbuf;
206206
}
207207

208-
GdkPixbuf *get_pixbuf_from_raw_image(const struct raw_image *raw_image)
208+
GdkPixbuf *icon_get_for_name(const char *name, char **id)
209209
{
210+
ASSERT_OR_RET(name, NULL);
211+
ASSERT_OR_RET(id, NULL);
212+
213+
GdkPixbuf *pb = get_pixbuf_from_icon(name);
214+
if (pb)
215+
*id = g_strdup(name);
216+
return pb;
217+
}
218+
219+
GdkPixbuf *icon_get_for_data(GVariant *data, char **id)
220+
{
221+
ASSERT_OR_RET(data, NULL);
222+
ASSERT_OR_RET(id, NULL);
223+
224+
if (!STR_EQ("(iiibiiay)", g_variant_get_type_string(data))) {
225+
LOG_W("Invalid data for pixbuf given.");
226+
return NULL;
227+
}
228+
229+
/* The raw image is a big array of char data.
230+
*
231+
* The image is serialised rowwise pixel by pixel. The rows are aligned
232+
* by a spacer full of garbage. The overall data length of data + garbage
233+
* is called the rowstride.
234+
*
235+
* Mind the missing spacer at the last row.
236+
*
237+
* len: |<--------------rowstride---------------->|
238+
* len: |<-width*pixelstride->|
239+
* row 1: | data for row 1 | spacer of garbage |
240+
* row 2: | data for row 2 | spacer of garbage |
241+
* | . | spacer of garbage |
242+
* | . | spacer of garbage |
243+
* | . | spacer of garbage |
244+
* row n-1: | data for row n-1 | spacer of garbage |
245+
* row n: | data for row n |
246+
*/
247+
210248
GdkPixbuf *pixbuf = NULL;
249+
GVariant *data_variant = NULL;
250+
unsigned char *data_pb;
251+
252+
gsize len_expected;
253+
gsize len_actual;
254+
gsize pixelstride;
255+
256+
int width;
257+
int height;
258+
int rowstride;
259+
int has_alpha;
260+
int bits_per_sample;
261+
int n_channels;
262+
263+
g_variant_get(data,
264+
"(iiibii@ay)",
265+
&width,
266+
&height,
267+
&rowstride,
268+
&has_alpha,
269+
&bits_per_sample,
270+
&n_channels,
271+
&data_variant);
272+
273+
// note: (A+7)/8 rounds up A to the next byte boundary
274+
pixelstride = (n_channels * bits_per_sample + 7)/8;
275+
len_expected = (height - 1) * rowstride + width * pixelstride;
276+
len_actual = g_variant_get_size(data_variant);
277+
278+
if (len_actual != len_expected) {
279+
LOG_W("Expected image data to be of length %" G_GSIZE_FORMAT
280+
" but got a length of %" G_GSIZE_FORMAT,
281+
len_expected,
282+
len_actual);
283+
g_variant_unref(data_variant);
284+
return NULL;
285+
}
286+
287+
data_pb = (guchar *) g_memdup(g_variant_get_data(data_variant), len_actual);
211288

212-
pixbuf = gdk_pixbuf_new_from_data(raw_image->data,
289+
pixbuf = gdk_pixbuf_new_from_data(data_pb,
213290
GDK_COLORSPACE_RGB,
214-
raw_image->has_alpha,
215-
raw_image->bits_per_sample,
216-
raw_image->width,
217-
raw_image->height,
218-
raw_image->rowstride,
219-
NULL,
220-
NULL);
291+
has_alpha,
292+
bits_per_sample,
293+
width,
294+
height,
295+
rowstride,
296+
(GdkPixbufDestroyNotify) g_free,
297+
data_pb);
298+
if (!pixbuf) {
299+
/* Dear user, I'm sorry, I'd like to give you a more specific
300+
* error message. But sadly, I can't */
301+
LOG_W("Cannot serialise raw icon data into pixbuf.");
302+
return NULL;
303+
}
304+
305+
/* To calculate a checksum of the current image, we have to remove
306+
* all excess spacers, so that our checksummed memory only contains
307+
* real data. */
308+
size_t data_chk_len = pixelstride * width * height;
309+
unsigned char *data_chk = g_malloc(data_chk_len);
310+
size_t rowstride_short = pixelstride * width;
311+
312+
for (int i = 0; i < height; i++) {
313+
memcpy(data_chk + (i*rowstride_short),
314+
data_pb + (i*rowstride),
315+
rowstride_short);
316+
}
317+
318+
*id = g_compute_checksum_for_data(G_CHECKSUM_MD5, data_chk, data_chk_len);
319+
320+
g_free(data_chk);
321+
g_variant_unref(data_variant);
221322

222323
return pixbuf;
223324
}

src/icon.h

Lines changed: 27 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -37,9 +37,34 @@ GdkPixbuf *get_pixbuf_from_file(const char *filename);
3737
*/
3838
GdkPixbuf *get_pixbuf_from_icon(const char *iconname);
3939

40-
/** Convert a struct raw_image to a `GdkPixbuf`
40+
/** Read an icon from disk and convert it to a GdkPixbuf.
41+
*
42+
* The returned id will be a unique identifier. To check if two given
43+
* GdkPixbufs are equal, it's sufficient to just compare the id strings.
44+
*
45+
* @param name A string describing and icon. May be a full path, a file path or
46+
* just a simple name. If it's a name without a slash, the icon will
47+
* get searched in the folders of the icon_path setting.
48+
* @param id (necessary) A unique identifier of the returned pixbuf. Only filled,
49+
* if the return value is non-NULL.
50+
* @return a pixbuf representing name's image.
51+
* If an invalid path given, it will return NULL.
52+
*/
53+
GdkPixbuf *icon_get_for_name(const char *name, char **id);
54+
55+
/** Convert a GVariant like described in GdkPixbuf
56+
*
57+
* The returned id will be a unique identifier. To check if two given
58+
* GdkPixbufs are equal, it's sufficient to just compare the id strings.
59+
*
60+
* @param data A GVariant in the format "(iiibii@ay)" filled with values
61+
* like described in the notification spec.
62+
* @param id (necessary) A unique identifier of the returned pixbuf.
63+
* Only filled, if the return value is non-NULL.
64+
* @return a pixbuf representing name's image.
65+
* If an invalid GVariant is passed, it will return NULL.
4166
*/
42-
GdkPixbuf *get_pixbuf_from_raw_image(const struct raw_image *raw_image);
67+
GdkPixbuf *icon_get_for_data(GVariant *data, char **id);
4368

4469
#endif
4570
/* vim: set tabstop=8 shiftwidth=8 expandtab textwidth=0: */

src/notification.c

Lines changed: 37 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,6 @@
2424
#include "settings.h"
2525
#include "utils.h"
2626

27-
static void notification_update_icon(struct notification *n);
2827
static void notification_extract_urls(struct notification *n);
2928
static void notification_format_message(struct notification *n);
3029

@@ -55,7 +54,8 @@ void notification_print(const struct notification *n)
5554
printf("\tsummary: '%s'\n", n->summary);
5655
printf("\tbody: '%s'\n", n->body);
5756
printf("\ticon: '%s'\n", n->iconname);
58-
printf("\traw_icon set: %s\n", (n->raw_icon ? "true" : "false"));
57+
printf("\traw_icon set: %s\n", (n->icon_id && !STR_EQ(n->iconname, n->icon_id)) ? "true" : "false");
58+
printf("\ticon_id: '%s'\n", n->icon_id);
5959
printf("\tcategory: %s\n", n->category);
6060
printf("\ttimeout: %ld\n", n->timeout/1000);
6161
printf("\turgency: %s\n", notification_urgency_to_string(n->urgency));
@@ -175,29 +175,15 @@ int notification_cmp_data(const void *va, const void *vb, void *data)
175175
return notification_cmp(a, b);
176176
}
177177

178-
int notification_is_duplicate(const struct notification *a, const struct notification *b)
178+
bool notification_is_duplicate(const struct notification *a, const struct notification *b)
179179
{
180-
//Comparing raw icons is not supported, assume they are not identical
181-
if (settings.icon_position != ICON_OFF
182-
&& (a->raw_icon || b->raw_icon))
183-
return false;
184-
185180
return STR_EQ(a->appname, b->appname)
186181
&& STR_EQ(a->summary, b->summary)
187182
&& STR_EQ(a->body, b->body)
188-
&& (settings.icon_position != ICON_OFF ? STR_EQ(a->iconname, b->iconname) : 1)
183+
&& (settings.icon_position != ICON_OFF ? STR_EQ(a->icon_id, b->icon_id) : 1)
189184
&& a->urgency == b->urgency;
190185
}
191186

192-
/* see notification.h */
193-
void rawimage_free(struct raw_image *i)
194-
{
195-
ASSERT_OR_RET(i,);
196-
197-
g_free(i->data);
198-
g_free(i);
199-
}
200-
201187
static void notification_private_free(NotificationPrivate *p)
202188
{
203189
g_free(p);
@@ -241,13 +227,41 @@ void notification_unref(struct notification *n)
241227
g_free(n->stack_tag);
242228

243229
g_hash_table_unref(n->actions);
244-
rawimage_free(n->raw_icon);
230+
231+
if (n->icon)
232+
g_object_unref(n->icon);
233+
g_free(n->icon_id);
245234

246235
notification_private_free(n->priv);
247236

248237
g_free(n);
249238
}
250239

240+
void notification_icon_replace_path(struct notification *n, const char *new_icon)
241+
{
242+
ASSERT_OR_RET(n,);
243+
ASSERT_OR_RET(new_icon,);
244+
245+
g_free(n->iconname);
246+
n->iconname = g_strdup(new_icon);
247+
248+
g_clear_object(&n->icon);
249+
g_clear_pointer(&n->icon_id, g_free);
250+
251+
n->icon = icon_get_for_name(new_icon, &n->icon_id);
252+
}
253+
254+
void notification_icon_replace_data(struct notification *n, GVariant *new_icon)
255+
{
256+
ASSERT_OR_RET(n,);
257+
ASSERT_OR_RET(new_icon,);
258+
259+
g_clear_object(&n->icon);
260+
g_clear_pointer(&n->icon_id, g_free);
261+
262+
n->icon = icon_get_for_data(new_icon, &n->icon_id);
263+
}
264+
251265
/* see notification.h */
252266
void notification_replace_single_field(char **haystack,
253267
char **needle,
@@ -332,8 +346,10 @@ void notification_init(struct notification *n)
332346
/* Icon handling */
333347
if (STR_EMPTY(n->iconname))
334348
g_clear_pointer(&n->iconname, g_free);
335-
if (!n->raw_icon && !n->iconname)
336-
n->iconname = g_strdup(settings.icons[n->urgency]);
349+
if (!n->icon && n->iconname)
350+
n->icon = icon_get_for_name(n->iconname, &n->icon_id);
351+
if (!n->icon && !n->iconname)
352+
notification_icon_replace_path(n, settings.icons[n->urgency]);
337353

338354
/* Color hints */
339355
struct notification_colors defcolors;
@@ -365,25 +381,10 @@ void notification_init(struct notification *n)
365381
rule_apply_all(n);
366382

367383
/* UPDATE derived fields */
368-
notification_update_icon(n);
369384
notification_extract_urls(n);
370385
notification_format_message(n);
371386
}
372387

373-
static void notification_update_icon(struct notification *n)
374-
{
375-
g_return_if_fail(n);
376-
377-
g_clear_object(&n->icon);
378-
379-
if (n->raw_icon)
380-
n->icon = get_pixbuf_from_raw_image(n->raw_icon);
381-
else if (n->iconname)
382-
n->icon = get_pixbuf_from_icon(n->iconname);
383-
384-
n->icon = icon_pixbuf_scale(n->icon);
385-
}
386-
387388
static void notification_format_message(struct notification *n)
388389
{
389390
g_clear_pointer(&n->msg, g_free);

0 commit comments

Comments
 (0)