1
/*******************************************************************************
2
**3456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789
3
** 10 20 30 40 50 60 70 80
7
** stack.c - manages the stack/queue of incoming notifications
9
** Copyright 2009 Canonical Ltd.
12
** Mirco "MacSlow" Mueller <mirco.mueller@canonical.com>
13
** David Barth <david.barth@canonical.com>
16
** Abhishek Mukherjee <abhishek.mukher.g@gmail.com> (append fixes, rev. 280)
17
** Aurélien Gâteau <aurelien.gateau@canonical.com> (0.10 spec, rev. 348)
19
** This program is free software: you can redistribute it and/or modify it
20
** under the terms of the GNU General Public License version 3, as published
21
** by the Free Software Foundation.
23
** This program is distributed in the hope that it will be useful, but
24
** WITHOUT ANY WARRANTY; without even the implied warranties of
25
** MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
26
** PURPOSE. See the GNU General Public License for more details.
28
** You should have received a copy of the GNU General Public License along
29
** with this program. If not, see <http://www.gnu.org/licenses/>.
31
*******************************************************************************/
35
#include <dbus/dbus-glib-lowlevel.h>
36
#include <glib-object.h>
44
G_DEFINE_TYPE (Stack, stack, G_TYPE_OBJECT);
46
#define FORCED_SHUTDOWN_THRESHOLD 500
49
void close_handler (GObject* n, Stack* stack);
51
/*-- internal API ------------------------------------------------------------*/
54
stack_dispose (GObject* gobject)
56
/* chain up to the parent class */
57
G_OBJECT_CLASS (stack_parent_class)->dispose (gobject);
62
disconnect_bubble (gpointer data,
65
Bubble* bubble = BUBBLE(data);
66
Stack* stack = STACK(user_data);
67
g_signal_handlers_disconnect_by_func (G_OBJECT(bubble), G_CALLBACK (close_handler), stack);
72
stack_finalize (GObject* gobject)
74
if (STACK(gobject)->list != NULL)
75
g_list_foreach (STACK(gobject)->list, disconnect_bubble, gobject);
76
if (STACK(gobject)->defaults != NULL)
77
g_object_unref (STACK(gobject)->defaults);
78
if (STACK(gobject)->observer != NULL)
79
g_object_unref (STACK(gobject)->observer);
81
/* chain up to the parent class */
82
G_OBJECT_CLASS (stack_parent_class)->finalize (gobject);
86
stack_init (Stack* self)
88
/* If you need specific construction properties to complete
89
** initialization, delay initialization completion until the
90
** property is set. */
96
stack_get_property (GObject* gobject,
104
G_OBJECT_WARN_INVALID_PROPERTY_ID (gobject, prop, spec);
109
#include "stack-glue.h"
112
stack_class_init (StackClass* klass)
114
GObjectClass* gobject_class = G_OBJECT_CLASS (klass);
116
gobject_class->dispose = stack_dispose;
117
gobject_class->finalize = stack_finalize;
118
gobject_class->get_property = stack_get_property;
120
dbus_g_object_type_install_info (G_TYPE_FROM_CLASS (klass),
121
&dbus_glib_stack_object_info);
125
compare_id (gconstpointer a,
138
id_1 = bubble_get_id ((Bubble*) a);
139
id_2 = *((guint*) b);
154
compare_append (gconstpointer a,
169
if (!bubble_is_append_allowed((Bubble*) a))
171
if (!bubble_is_append_allowed((Bubble*) b))
174
str_1 = bubble_get_title ((Bubble*) a);
175
str_2 = bubble_get_title ((Bubble*) b);
177
cmp = g_strcmp0(str_1, str_2);
183
str_1 = bubble_get_sender ((Bubble*) a);
184
str_2 = bubble_get_sender ((Bubble*) b);
185
return g_strcmp0(str_1, str_2);
189
find_entry_by_id (Stack* self,
198
entry = g_list_find_custom (self->list,
208
find_bubble_by_id (Stack* self,
217
entry = g_list_find_custom (self->list,
223
return (Bubble*) entry->data;
227
find_bubble_for_append(Stack* self,
236
entry = g_list_find_custom(self->list,
237
(gconstpointer) bubble,
243
return (Bubble*) entry->data;
247
_weak_notify_cb (gpointer data,
248
GObject* former_object)
250
Stack* stack = STACK (data);
252
stack->list = g_list_remove (stack->list, former_object);
256
_trigger_bubble_redraw (gpointer data,
264
bubble = BUBBLE (data);
265
if (!IS_BUBBLE (bubble))
268
bubble_recalc_size (bubble);
269
bubble_refresh (bubble);
273
value_changed_handler (Defaults* defaults,
276
if (stack->list != NULL)
277
g_list_foreach (stack->list, _trigger_bubble_redraw, NULL);
280
static Bubble *sync_bubble = NULL;
284
/*-- public API --------------------------------------------------------------*/
287
stack_new (Defaults* defaults,
292
if (!defaults || !observer)
295
this = g_object_new (STACK_TYPE, NULL);
299
this->defaults = defaults;
300
this->observer = observer;
303
this->slots[SLOT_TOP] = NULL;
304
this->slots[SLOT_BOTTOM] = NULL;
306
/* hook up handler to act on changes of defaults/settings */
307
g_signal_connect (G_OBJECT (defaults),
309
G_CALLBACK (value_changed_handler),
316
stack_del (Stack* self)
321
g_object_unref (self);
325
close_handler (GObject *n,
328
/* TODO: use weak-refs to dispose the bubble.
329
Meanwhile, do nothing here to avoid segfaults
330
and rely on the stack_purge_old_bubbles() call
331
later on in the thread.
337
stack_free_slot (stack, BUBBLE (n));
340
&& bubble_is_synchronous (BUBBLE (n)))
344
} if (IS_BUBBLE (n)) {
345
stack_pop_bubble_by_id (stack, bubble_get_id ((Bubble*) n));
346
/* Fix for a tricky race condition
347
where a bubble fades out in sync
348
with a synchronous bubble: the symc.
349
one is still considered visible while
350
the normal one has triggered this signal.
351
This ensures the display slot of the
352
sync. bubble is recycled, and no gap is
353
left on the screen */
356
/* Fix for a tricky race condition
357
where a bubble fades out in sync
358
with a synchronous bubble: the symc.
359
one is still considered visible while
360
the normal one has triggered this signal.
361
This ensures the display slot of the
362
sync. bubble is recycled, and no gap is
363
left on the screen */
367
stack_layout (stack);
373
/* since notification-ids are unsigned integers the first id index is 1, 0 is
374
** used to indicate an error */
376
stack_push_bubble (Stack* self,
379
guint notification_id = -1;
382
if (!self || !IS_BUBBLE (bubble))
385
/* check if this is just an update */
386
if (find_bubble_by_id (self, bubble_get_id (bubble)))
388
bubble_start_timer (bubble, TRUE);
389
bubble_refresh (bubble);
391
/* resync the synchronous bubble if it's at the top */
392
if (sync_bubble != NULL
393
&& bubble_is_visible (sync_bubble))
394
if (stack_is_at_top_corner (self, sync_bubble))
395
bubble_sync_with (sync_bubble, bubble);
397
return bubble_get_id (bubble);
400
/* add bubble/id to stack */
401
notification_id = self->next_id++;
403
// FIXME: migrate stack to use abstract notification object and don't
404
// keep heavy bubble objects around, at anyone time at max. only two
405
// bubble-objects will be in memory... this will also reduce leak-
407
bubble_set_id (bubble, notification_id);
408
self->list = g_list_append (self->list, (gpointer) bubble);
410
g_signal_connect (G_OBJECT (bubble),
412
G_CALLBACK (close_handler),
415
/* return current/new id to caller (usually our DBus-dispatcher) */
416
return notification_id;
420
stack_pop_bubble_by_id (Stack* self,
429
/* find bubble corresponding to id */
430
bubble = find_bubble_by_id (self, id);
434
/* close/hide/fade-out bubble */
435
bubble_hide (bubble);
437
/* find entry in list corresponding to id and remove it */
438
self->list = g_list_delete_link (self->list,
439
find_entry_by_id (self, id));
440
g_object_unref (bubble);
442
/* immediately refresh the layout of the stack */
447
process_dbus_icon_data (GValue *data)
451
int width, height, rowstride, bits_per_sample, n_channels, size;
454
GdkPixbuf *pixbuf = NULL;
456
g_return_val_if_fail (data != NULL, NULL);
458
dbus_icon_t = dbus_g_type_get_struct ("GValueArray",
465
dbus_g_type_get_collection ("GArray",
469
if (G_VALUE_HOLDS (data, dbus_icon_t))
471
dbus_g_type_struct_get (data,
481
size = (height - 1) * rowstride + width *
482
((n_channels * bits_per_sample + 7) / 8);
483
copy = (guchar *) g_memdup (pixels->data, size);
484
pixbuf = gdk_pixbuf_new_from_data(copy, GDK_COLORSPACE_RGB,
489
(GdkPixbufDestroyNotify)g_free,
496
// control if there are non-default actions requested with this notification
498
dialog_check_actions_and_timeout (gchar** actions,
502
gboolean turn_into_dialog = FALSE;
506
for (i = 0; actions[i] != NULL; i += 2)
508
if (actions[i+1] == NULL)
510
g_debug ("incorrect action callback "
515
turn_into_dialog = TRUE;
516
g_debug ("notification request turned into a dialog "
517
"box, because it contains at least one action "
518
"callback (%s: \"%s\")",
525
turn_into_dialog = TRUE;
526
g_debug ("notification request turned into a dialog "
527
"box, because of its infinite timeout");
531
return turn_into_dialog;
534
// FIXME: a intnernal function used for the forcefully-shutdown work-around
535
// regarding mem-leaks
537
_arm_forced_quit (gpointer data)
541
// sanity check, "disarming" this forced quit
545
stack = STACK (data);
547
// only forcefully quit if the queue is empty
548
if (g_list_length (stack->list) == 0)
552
// I don't think this is ever reached :)
560
stack_notify_handler (Stack* self,
561
const gchar* app_name,
564
const gchar* summary,
569
DBusGMethodInvocation* context)
571
Bubble* bubble = NULL;
572
Bubble* app_bubble = NULL;
574
GValue* compat = NULL;
575
GdkPixbuf* pixbuf = NULL;
576
gboolean new_bubble = FALSE;
577
gboolean turn_into_dialog;
579
// check max. allowed limit queue-size
580
if (g_list_length (self->list) > MAX_STACK_SIZE)
582
GError* error = NULL;
584
error = g_error_new (g_quark_from_string ("notify-osd"),
586
"Reached stack-limit of %d",
588
dbus_g_method_return_error (context, error);
589
g_error_free (error);
595
// see if pathological actions or timeouts are used by an app issuing a
597
turn_into_dialog = dialog_check_actions_and_timeout (actions, timeout);
598
if (turn_into_dialog)
600
// TODO: apport_report (app_name, summary, actions, timeout);
601
gchar* sender = dbus_g_method_get_sender (context);
603
fallback_dialog_show (self->defaults,
611
dbus_g_method_return (context, id);
616
// check if a bubble exists with same id
617
bubble = find_bubble_by_id (self, id);
622
bubble = bubble_new (self->defaults);
623
g_object_weak_ref (G_OBJECT (bubble),
627
sender = dbus_g_method_get_sender (context);
628
bubble_set_sender (bubble, sender);
632
if (new_bubble && hints)
634
data = (GValue*) g_hash_table_lookup (hints, "x-canonical-append");
635
compat = (GValue*) g_hash_table_lookup (hints, "append");
637
if ((data && G_VALUE_HOLDS_STRING (data)) ||
638
(compat && G_VALUE_HOLDS_STRING (compat)))
639
bubble_set_append (bubble, TRUE);
641
bubble_set_append (bubble, FALSE);
645
bubble_set_title (bubble, summary);
647
bubble_set_message_body (bubble, body);
649
if (new_bubble && bubble_is_append_allowed(bubble)) {
650
app_bubble = find_bubble_for_append(self, bubble);
652
/* Appending to an old bubble */
653
if (app_bubble != NULL) {
654
g_object_unref(bubble);
657
bubble_append_message_body (bubble, "\n");
658
bubble_append_message_body (bubble, body);
665
data = (GValue*) g_hash_table_lookup (hints, "x-canonical-private-synchronous");
666
compat = (GValue*) g_hash_table_lookup (hints, "synchronous");
667
if ((data && G_VALUE_HOLDS_STRING (data)) || (compat && G_VALUE_HOLDS_STRING (compat)))
669
if (sync_bubble != NULL
670
&& IS_BUBBLE (sync_bubble))
672
g_object_unref (bubble);
673
bubble = sync_bubble;
676
if (data && G_VALUE_HOLDS_STRING (data))
677
bubble_set_synchronous (bubble, g_value_get_string (data));
679
if (compat && G_VALUE_HOLDS_STRING (compat))
680
bubble_set_synchronous (bubble, g_value_get_string (compat));
686
data = (GValue*) g_hash_table_lookup (hints, "value");
687
if (data && G_VALUE_HOLDS_INT (data))
688
bubble_set_value (bubble, g_value_get_int (data));
693
data = (GValue*) g_hash_table_lookup (hints, "urgency");
694
if (data && G_VALUE_HOLDS_UCHAR (data))
695
bubble_set_urgency (bubble,
696
g_value_get_uchar (data));
697
/* Note: urgency was defined as an enum: LOW, NORMAL, CRITICAL
704
data = (GValue*) g_hash_table_lookup (hints, "x-canonical-private-icon-only");
705
compat = (GValue*) g_hash_table_lookup (hints, "icon-only");
706
if ((data && G_VALUE_HOLDS_STRING (data)) || (compat && G_VALUE_HOLDS_STRING (compat)))
707
bubble_set_icon_only (bubble, TRUE);
709
bubble_set_icon_only (bubble, FALSE);
714
if ((data = (GValue*) g_hash_table_lookup (hints, "image_data")))
716
g_debug("Using image_data hint\n");
717
pixbuf = process_dbus_icon_data (data);
718
bubble_set_icon_from_pixbuf (bubble, pixbuf);
720
else if ((data = (GValue*) g_hash_table_lookup (hints, "image_path")))
722
g_debug("Using image_path hint\n");
723
if ((data && G_VALUE_HOLDS_STRING (data)))
724
bubble_set_icon_from_path (bubble, g_value_get_string(data));
726
g_warning ("image_path hint is not a string\n");
728
else if (icon && *icon != '\0')
730
g_debug("Using icon parameter\n");
731
bubble_set_icon (bubble, icon);
733
else if ((data = (GValue*) g_hash_table_lookup (hints, "icon_data")))
735
g_debug("Using deprecated icon_data hint\n");
736
pixbuf = process_dbus_icon_data (data);
737
bubble_set_icon_from_pixbuf (bubble, pixbuf);
741
log_bubble_debug (bubble, app_name,
742
(*icon == '\0' && data != NULL) ?
745
bubble_determine_layout (bubble);
747
bubble_recalc_size (bubble);
749
if (bubble_is_synchronous (bubble))
751
stack_display_sync_bubble (self, bubble);
753
stack_push_bubble (self, bubble);
755
if (! new_bubble && bubble_is_append_allowed (bubble))
756
log_bubble (bubble, app_name, "appended");
757
else if (! new_bubble)
758
log_bubble (bubble, app_name, "replaced");
760
log_bubble (bubble, app_name, "");
762
/* make sure the sync. bubble is positioned correctly
763
even for the append case
765
// no longer needed since we have the two-slots mechanism now
766
//if (sync_bubble != NULL
767
// && bubble_is_visible (sync_bubble))
768
// stack_display_position_sync_bubble (self, sync_bubble);
770
/* update the layout of the stack;
771
* this will also open the new bubble */
776
dbus_g_method_return (context, bubble_get_id (bubble));
778
// FIXME: this is a temporary work-around, I do not like at all, until
779
// the heavy memory leakage of notify-osd is fully fixed...
780
// after a threshold-value is reached, "arm" a forceful shutdown of
781
// notify-osd (still allowing notifications in the queue, and coming in,
782
// to be displayed), in order to get the leaked memory freed again, any
783
// new notifications, coming in after the shutdown, will instruct the
784
// session to restart notify-osd
785
if (bubble_get_id (bubble) == FORCED_SHUTDOWN_THRESHOLD)
786
g_timeout_add (defaults_get_on_screen_timeout (self->defaults),
794
stack_close_notification_handler (Stack* self,
799
g_warning ("%s(): notification id == 0, likely wrong\n",
802
Bubble* bubble = find_bubble_by_id (self, id);
804
// exit but pretend it's ok, for applications
805
// that call us after an action button was clicked
809
dbus_send_close_signal (bubble_get_sender (bubble),
810
bubble_get_id (bubble),
813
// do not trigger any closure of a notification-bubble here, as
814
// this kind of control from outside (DBus) does not comply with
815
// the notify-osd specification
816
//bubble_hide (bubble);
817
//g_object_unref (bubble);
818
//stack_layout (self);
824
stack_get_capabilities (Stack* self,
827
*out_caps = g_malloc0 (13 * sizeof(char *));
829
(*out_caps)[0] = g_strdup ("body");
830
(*out_caps)[1] = g_strdup ("body-markup");
831
(*out_caps)[2] = g_strdup ("icon-static");
832
(*out_caps)[3] = g_strdup ("image/svg+xml");
833
(*out_caps)[4] = g_strdup ("x-canonical-private-synchronous");
834
(*out_caps)[5] = g_strdup ("x-canonical-append");
835
(*out_caps)[6] = g_strdup ("x-canonical-private-icon-only");
836
(*out_caps)[7] = g_strdup ("x-canonical-truncation");
838
/* a temp. compatibility-check for the transition time to allow apps a
839
** grace-period to catch up with the capability- and hint-name-changes
840
** introduced with notify-osd rev. 224 */
841
(*out_caps)[8] = g_strdup ("private-synchronous");
842
(*out_caps)[9] = g_strdup ("append");
843
(*out_caps)[10] = g_strdup ("private-icon-only");
844
(*out_caps)[11] = g_strdup ("truncation");
846
(*out_caps)[12] = NULL;
852
stack_get_server_information (Stack* self,
856
gchar** out_spec_ver)
858
*out_name = g_strdup ("notify-osd");
859
*out_vendor = g_strdup ("Canonical Ltd");
860
*out_version = g_strdup ("1.0");
861
*out_spec_ver = g_strdup ("1.1");
867
stack_is_slot_vacant (Stack* self,
871
if (!self || !IS_STACK (self))
874
if (slot != SLOT_TOP && slot != SLOT_BOTTOM)
877
return self->slots[slot] == NULL ? VACANT : OCCUPIED;
880
// return values of -1 for x and y indicate an error by the caller
882
stack_get_slot_position (Stack* self,
892
if (!self || !IS_STACK (self))
899
if (slot != SLOT_TOP && slot != SLOT_BOTTOM)
906
// initialize x and y
907
defaults_get_top_corner (self->defaults, x, y);
909
// differentiate returned top-left corner for top and bottom slot
910
// depending on the placement
911
switch (defaults_get_gravity (self->defaults))
918
// the position for the sync./feedback bubble
919
if (slot == SLOT_TOP)
920
*y += defaults_get_desktop_height (d) / 2 -
921
EM2PIXELS (defaults_get_bubble_vert_gap (d) / 2.0f, d) -
923
EM2PIXELS (defaults_get_bubble_shadow_size (d), d);
924
// the position for the async. bubble
925
else if (slot == SLOT_BOTTOM)
926
*y += defaults_get_desktop_height (d) / 2 +
927
EM2PIXELS (defaults_get_bubble_vert_gap (d) / 2.0f, d) -
928
EM2PIXELS (defaults_get_bubble_shadow_size (d), d);
931
case GRAVITY_NORTH_EAST:
934
// there's nothing to do for slot == SLOT_TOP as we
935
// already have correct x and y from the call to
936
// defaults_get_top_corner() earlier
938
// this needs to look at the height of the bubble in the
940
if (slot == SLOT_BOTTOM)
942
switch (defaults_get_slot_allocation (d))
944
case SLOT_ALLOCATION_FIXED:
945
*y += EM2PIXELS (defaults_get_icon_size (d), d) +
946
2 * EM2PIXELS (defaults_get_margin_size (d), d) +
947
EM2PIXELS (defaults_get_bubble_vert_gap (d), d); /* +
948
2 * EM2PIXELS (defaults_get_bubble_shadow_size (d), d);*/
951
case SLOT_ALLOCATION_DYNAMIC:
952
g_assert (stack_is_slot_vacant (self, SLOT_TOP) == OCCUPIED);
953
*y += bubble_get_height (self->slots[SLOT_TOP]) +
954
EM2PIXELS (defaults_get_bubble_vert_gap (d), d) -
955
2 * EM2PIXELS (defaults_get_bubble_shadow_size (d), d);
966
g_warning ("Unhandled placement!\n");
971
// call this _before_ the fade-in animation of the bubble starts
973
stack_allocate_slot (Stack* self,
978
if (!self || !IS_STACK (self))
981
if (!bubble || !IS_BUBBLE (bubble))
984
if (slot != SLOT_TOP && slot != SLOT_BOTTOM)
987
if (stack_is_slot_vacant (self, slot))
988
self->slots[slot] = BUBBLE (g_object_ref ((gpointer) bubble));
995
// call this _after_ the fade-out animation of the bubble is finished
997
stack_free_slot (Stack* self,
1001
if (!self || !IS_STACK (self))
1004
if (!bubble || !IS_BUBBLE (bubble))
1007
// check top and bottom slots for bubble pointer equality
1008
if (bubble == self->slots[SLOT_TOP])
1010
g_object_unref (self->slots[SLOT_TOP]);
1011
self->slots[SLOT_TOP] = NULL;
1013
else if (bubble == self->slots[SLOT_BOTTOM])
1015
g_object_unref (self->slots[SLOT_BOTTOM]);
1016
self->slots[SLOT_BOTTOM] = NULL;