1 /*******************************************************************************
2 **3456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789
3 **      10        20        30        40        50        60        70        80
4 **
5 ** notify-osd
6 **
7 ** stack.c - manages the stack/queue of incoming notifications
8 **
9 ** Copyright 2009 Canonical Ltd.
10 **
11 ** Authors:
12 **    Mirco "MacSlow" Mueller <mirco.mueller@canonical.com>
13 **    David Barth <david.barth@canonical.com>
14 **
15 ** Contributor(s):
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)
18 **
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.
22 **
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.
27 **
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/>.
30 **
31 *******************************************************************************/
32 
33 #include <assert.h>
34 #include "dbus.h"
35 #include <dbus/dbus-glib-lowlevel.h>
36 #include <glib-object.h>
37 #include "stack.h"
38 #include "bubble.h"
39 #include "apport.h"
40 #include "dialog.h"
41 #include "dnd.h"
42 #include "log.h"
43 
44 G_DEFINE_TYPE (Stack, stack, G_TYPE_OBJECT);
45 
46 #define FORCED_SHUTDOWN_THRESHOLD 500
47 
48 /* fwd declaration */
49 void close_handler (GObject* n, Stack*  stack);
50 
51 /*-- internal API ------------------------------------------------------------*/
52 
53 static void
stack_dispose(GObject * gobject)54 stack_dispose (GObject* gobject)
55 {
56 	/* chain up to the parent class */
57 	G_OBJECT_CLASS (stack_parent_class)->dispose (gobject);
58 }
59 
60 static
61 void
disconnect_bubble(gpointer data,gpointer user_data)62 disconnect_bubble (gpointer data,
63 	      gpointer user_data)
64 {
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);
68 }
69 
70 
71 static void
stack_finalize(GObject * gobject)72 stack_finalize (GObject* gobject)
73 {
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);
80 
81 	/* chain up to the parent class */
82 	G_OBJECT_CLASS (stack_parent_class)->finalize (gobject);
83 }
84 
85 static void
stack_init(Stack * self)86 stack_init (Stack* self)
87 {
88 	/* If you need specific construction properties to complete
89 	** initialization, delay initialization completion until the
90 	** property is set. */
91 
92 	self->list = NULL;
93 }
94 
95 static void
stack_get_property(GObject * gobject,guint prop,GValue * value,GParamSpec * spec)96 stack_get_property (GObject*    gobject,
97 		    guint       prop,
98 		    GValue*     value,
99 		    GParamSpec* spec)
100 {
101 	switch (prop)
102 	{
103 		default :
104 			G_OBJECT_WARN_INVALID_PROPERTY_ID (gobject, prop, spec);
105 		break;
106 	}
107 }
108 
109 #include "stack-glue.h"
110 
111 static void
stack_class_init(StackClass * klass)112 stack_class_init (StackClass* klass)
113 {
114 	GObjectClass* gobject_class = G_OBJECT_CLASS (klass);
115 
116 	gobject_class->dispose      = stack_dispose;
117 	gobject_class->finalize     = stack_finalize;
118 	gobject_class->get_property = stack_get_property;
119 
120 	dbus_g_object_type_install_info (G_TYPE_FROM_CLASS (klass),
121 					 &dbus_glib_stack_object_info);
122 }
123 
124 gint
compare_id(gconstpointer a,gconstpointer b)125 compare_id (gconstpointer a,
126 	    gconstpointer b)
127 {
128 	gint  result;
129 	guint id_1;
130 	guint id_2;
131 
132 	if (!a || !b)
133 		return -1;
134 
135 	if (!IS_BUBBLE (a))
136 		return -1;
137 
138 	id_1 = bubble_get_id ((Bubble*) a);
139 	id_2 = *((guint*) b);
140 
141 	if (id_1 < id_2)
142 		result = -1;
143 
144 	if (id_1 == id_2)
145 		result = 0;
146 
147 	if (id_1 > id_2)
148 		result = 1;
149 
150 	return result;
151 }
152 
153 static gint
compare_append(gconstpointer a,gconstpointer b)154 compare_append (gconstpointer a,
155 	        gconstpointer b)
156 {
157 	const gchar* str_1;
158 	const gchar* str_2;
159 	gint cmp;
160 
161 	if (!a || !b)
162 		return -1;
163 
164 	if (!IS_BUBBLE (a))
165 		return -1;
166 	if (!IS_BUBBLE (b))
167 		return 1;
168 
169 	if (!bubble_is_append_allowed((Bubble*) a))
170 		return -1;
171 	if (!bubble_is_append_allowed((Bubble*) b))
172 		return 1;
173 
174 	str_1 = bubble_get_title ((Bubble*) a);
175 	str_2 = bubble_get_title ((Bubble*) b);
176 
177 	cmp = g_strcmp0(str_1, str_2);
178 	if (cmp < 0)
179 		return -1;
180 	if (cmp > 0)
181 		return 1;
182 
183 	str_1 = bubble_get_sender ((Bubble*) a);
184 	str_2 = bubble_get_sender ((Bubble*) b);
185 	return g_strcmp0(str_1, str_2);
186 }
187 
188 GList*
find_entry_by_id(Stack * self,guint id)189 find_entry_by_id (Stack* self,
190 		  guint  id)
191 {
192 	GList* entry;
193 
194 	/* sanity check */
195 	if (!self)
196 		return NULL;
197 
198 	entry = g_list_find_custom (self->list,
199 				    (gconstpointer) &id,
200 				    compare_id);
201 	if (!entry)
202 		return NULL;
203 
204 	return entry;
205 }
206 
207 static Bubble*
find_bubble_by_id(Stack * self,guint id)208 find_bubble_by_id (Stack* self,
209 		   guint  id)
210 {
211 	GList* entry;
212 
213 	/* sanity check */
214 	if (!self)
215 		return NULL;
216 
217 	entry = g_list_find_custom (self->list,
218 				    (gconstpointer) &id,
219 				    compare_id);
220 	if (!entry)
221 		return NULL;
222 
223 	return (Bubble*) entry->data;
224 }
225 
226 static Bubble*
find_bubble_for_append(Stack * self,Bubble * bubble)227 find_bubble_for_append(Stack* self,
228 		       Bubble *bubble)
229 {
230 	GList* entry;
231 
232 	/* sanity check */
233 	if (!self)
234 		return NULL;
235 
236 	entry = g_list_find_custom(self->list,
237 				   (gconstpointer) bubble,
238 				   compare_append);
239 
240 	if (!entry)
241 		return NULL;
242 
243 	return (Bubble*) entry->data;
244 }
245 
246 static void
_weak_notify_cb(gpointer data,GObject * former_object)247 _weak_notify_cb (gpointer data,
248 		 GObject* former_object)
249 {
250 	Stack* stack = STACK (data);
251 
252 	stack->list = g_list_remove (stack->list, former_object);
253 }
254 
255 static void
_trigger_bubble_redraw(gpointer data,gpointer user_data)256 _trigger_bubble_redraw (gpointer data,
257 			gpointer user_data)
258 {
259 	Bubble* bubble;
260 
261 	if (!data)
262 		return;
263 
264 	bubble = BUBBLE (data);
265 	if (!IS_BUBBLE (bubble))
266 		return;
267 
268 	bubble_recalc_size (bubble);
269 	bubble_refresh (bubble);
270 }
271 
272 static void
value_changed_handler(Defaults * defaults,Stack * stack)273 value_changed_handler (Defaults* defaults,
274 		       Stack*    stack)
275 {
276 	if (stack->list != NULL)
277 		g_list_foreach (stack->list, _trigger_bubble_redraw, NULL);
278 }
279 
280 static Bubble *sync_bubble = NULL;
281 
282 #include "display.c"
283 
284 /*-- public API --------------------------------------------------------------*/
285 
286 Stack*
stack_new(Defaults * defaults,Observer * observer)287 stack_new (Defaults* defaults,
288 	   Observer* observer)
289 {
290 	Stack* this;
291 
292 	if (!defaults || !observer)
293 		return NULL;
294 
295 	this = g_object_new (STACK_TYPE, NULL);
296 	if (!this)
297 		return NULL;
298 
299 	this->defaults           = defaults;
300 	this->observer           = observer;
301 	this->list               = NULL;
302 	this->next_id            = 1;
303 	this->slots[SLOT_TOP]    = NULL;
304 	this->slots[SLOT_BOTTOM] = NULL;
305 
306 	/* hook up handler to act on changes of defaults/settings */
307 	g_signal_connect (G_OBJECT (defaults),
308 			  "value-changed",
309 			  G_CALLBACK (value_changed_handler),
310 			  this);
311 
312 	return this;
313 }
314 
315 void
stack_del(Stack * self)316 stack_del (Stack* self)
317 {
318 	if (!self)
319 		return;
320 
321 	g_object_unref (self);
322 }
323 
324 void
close_handler(GObject * n,Stack * stack)325 close_handler (GObject *n,
326 	       Stack*  stack)
327 {
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.
332 	*/
333 
334 	if (n != NULL)
335 	{
336 		if (IS_BUBBLE (n))
337 			stack_free_slot (stack, BUBBLE (n));
338 
339 		if (IS_BUBBLE (n)
340 		    && bubble_is_synchronous (BUBBLE (n)))
341 		{
342 			g_object_unref (n);
343 			sync_bubble = NULL;
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 */
354 			sync_bubble = NULL;
355 		} else {
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 */
364 			sync_bubble = NULL;
365 		}
366 
367 		stack_layout (stack);
368 	}
369 
370 	return;
371 }
372 
373 /* since notification-ids are unsigned integers the first id index is 1, 0 is
374 ** used to indicate an error */
375 guint
stack_push_bubble(Stack * self,Bubble * bubble)376 stack_push_bubble (Stack*  self,
377 		   Bubble* bubble)
378 {
379 	guint notification_id = -1;
380 
381 	/* sanity check */
382 	if (!self || !IS_BUBBLE (bubble))
383 		return -1;
384 
385 	/* check if this is just an update */
386 	if (find_bubble_by_id (self, bubble_get_id (bubble)))
387 	{
388 		bubble_start_timer (bubble, TRUE);
389 		bubble_refresh (bubble);
390 
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);
396 
397 		return bubble_get_id (bubble);
398 	}
399 
400 	/* add bubble/id to stack */
401 	notification_id = self->next_id++;
402 
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-
406 	// potential
407 	bubble_set_id (bubble, notification_id);
408 	self->list = g_list_append (self->list, (gpointer) bubble);
409 
410 	g_signal_connect (G_OBJECT (bubble),
411 			  "timed-out",
412 			  G_CALLBACK (close_handler),
413 			  self);
414 
415 	/* return current/new id to caller (usually our DBus-dispatcher) */
416 	return notification_id;
417 }
418 
419 void
stack_pop_bubble_by_id(Stack * self,guint id)420 stack_pop_bubble_by_id (Stack* self,
421 			guint  id)
422 {
423 	Bubble* bubble;
424 
425 	/* sanity check */
426 	if (!self)
427 		return;
428 
429 	/* find bubble corresponding to id */
430 	bubble = find_bubble_by_id (self, id);
431 	if (!bubble)
432 		return;
433 
434 	/* close/hide/fade-out bubble */
435 	bubble_hide (bubble);
436 
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);
441 
442 	/* immediately refresh the layout of the stack */
443 	stack_layout (self);
444 }
445 
446 static GdkPixbuf*
process_dbus_icon_data(GValue * data)447 process_dbus_icon_data (GValue *data)
448 {
449 	GType dbus_icon_t;
450 	GArray *pixels;
451 	int width, height, rowstride, bits_per_sample, n_channels, size;
452 	gboolean has_alpha;
453 	guchar *copy;
454 	GdkPixbuf *pixbuf = NULL;
455 
456 	g_return_val_if_fail (data != NULL, NULL);
457 
458 	dbus_icon_t = dbus_g_type_get_struct ("GValueArray",
459 					      G_TYPE_INT,
460 					      G_TYPE_INT,
461 					      G_TYPE_INT,
462 					      G_TYPE_BOOLEAN,
463 					      G_TYPE_INT,
464 					      G_TYPE_INT,
465 					      dbus_g_type_get_collection ("GArray",
466 									  G_TYPE_UCHAR),
467 					      G_TYPE_INVALID);
468 
469 	if (G_VALUE_HOLDS (data, dbus_icon_t))
470 	{
471 		dbus_g_type_struct_get (data,
472 					0, &width,
473 					1, &height,
474 					2, &rowstride,
475 					3, &has_alpha,
476 					4, &bits_per_sample,
477 					5, &n_channels,
478 					6, &pixels,
479 					G_MAXUINT);
480 
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,
485 						  has_alpha,
486 						  bits_per_sample,
487 						  width, height,
488 						  rowstride,
489 						  (GdkPixbufDestroyNotify)g_free,
490 						  NULL);
491 	}
492 
493 	return pixbuf;
494 }
495 
496 // control if there are non-default actions requested with this notification
497 static gboolean
dialog_check_actions_and_timeout(gchar ** actions,gint timeout)498 dialog_check_actions_and_timeout (gchar** actions,
499 				  gint    timeout)
500 {
501 	int      i                = 0;
502 	gboolean turn_into_dialog = FALSE;
503 
504 	if (actions != NULL)
505 	{
506 		for (i = 0; actions[i] != NULL; i += 2)
507 		{
508 			if (actions[i+1] == NULL)
509 			{
510 				g_debug ("incorrect action callback "
511 					 "with no label");
512 				break;
513 			}
514 
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\")",
519 				 actions[i],
520 				 actions[i+1]);
521 		}
522 
523 		if (timeout == 0)
524 		{
525 			turn_into_dialog = TRUE;
526 			g_debug ("notification request turned into a dialog "
527 				 "box, because of its infinite timeout");
528 		}
529 	}
530 
531 	return turn_into_dialog;
532 }
533 
534 // FIXME: a intnernal function used for the forcefully-shutdown work-around
535 // regarding mem-leaks
536 gboolean
_arm_forced_quit(gpointer data)537 _arm_forced_quit (gpointer data)
538 {
539 	Stack* stack = NULL;
540 
541 	// sanity check, "disarming" this forced quit
542 	if (!data)
543 		return FALSE;
544 
545 	stack = STACK (data);
546 
547 	// only forcefully quit if the queue is empty
548 	if (g_list_length (stack->list) == 0)
549 	{
550 		gtk_main_quit ();
551 
552 		// I don't think this is ever reached :)
553 		return FALSE;
554 	}
555 
556 	return TRUE;
557 }
558 
559 gboolean
stack_notify_handler(Stack * self,const gchar * app_name,guint id,const gchar * icon,const gchar * summary,const gchar * body,gchar ** actions,GHashTable * hints,gint timeout,DBusGMethodInvocation * context)560 stack_notify_handler (Stack*                 self,
561 		      const gchar*           app_name,
562 		      guint                  id,
563 		      const gchar*           icon,
564 		      const gchar*           summary,
565 		      const gchar*           body,
566 		      gchar**                actions,
567 		      GHashTable*            hints,
568 		      gint                   timeout,
569 		      DBusGMethodInvocation* context)
570 {
571 	Bubble*    bubble     = NULL;
572 	Bubble*    app_bubble = NULL;
573 	GValue*    data       = NULL;
574 	GValue*    compat     = NULL;
575 	GdkPixbuf* pixbuf     = NULL;
576 	gboolean   new_bubble = FALSE;
577 	gboolean   turn_into_dialog;
578 
579 	// check max. allowed limit queue-size
580 	if (g_list_length (self->list) > MAX_STACK_SIZE)
581 	{
582 		GError* error = NULL;
583 
584 		error = g_error_new (g_quark_from_string ("notify-osd"),
585 		                     1,
586 		                     "Reached stack-limit of %d",
587 		                     MAX_STACK_SIZE);
588 		dbus_g_method_return_error (context, error);
589 		g_error_free (error);
590 		error = NULL;
591 
592 		return TRUE;
593 	}
594 
595 	// see if pathological actions or timeouts are used by an app issuing a
596 	// notification
597 	turn_into_dialog = dialog_check_actions_and_timeout (actions, timeout);
598 	if (turn_into_dialog)
599 	{
600 		// TODO: apport_report (app_name, summary, actions, timeout);
601 		gchar* sender = dbus_g_method_get_sender (context);
602 
603 		fallback_dialog_show (self->defaults,
604 				      sender,
605 				      app_name,
606 				      id,
607 				      summary,
608 				      body,
609 				      actions);
610 		g_free (sender);
611 		dbus_g_method_return (context, id);
612 
613 		return TRUE;
614 	}
615 
616         // check if a bubble exists with same id
617 	bubble = find_bubble_by_id (self, id);
618 	if (bubble == NULL)
619 	{
620 		gchar *sender;
621 		new_bubble = TRUE;
622 		bubble = bubble_new (self->defaults);
623 		g_object_weak_ref (G_OBJECT (bubble),
624 				   _weak_notify_cb,
625 				   (gpointer) self);
626 
627 		sender = dbus_g_method_get_sender (context);
628 		bubble_set_sender (bubble, sender);
629 		g_free (sender);
630 	}
631 
632 	if (new_bubble && hints)
633 	{
634 		data   = (GValue*) g_hash_table_lookup (hints, "x-canonical-append");
635 		compat = (GValue*) g_hash_table_lookup (hints, "append");
636 
637 		if ((data && G_VALUE_HOLDS_STRING (data)) ||
638 		    (compat && G_VALUE_HOLDS_STRING (compat)))
639 			bubble_set_append (bubble, TRUE);
640 		else
641 			bubble_set_append (bubble, FALSE);
642 	}
643 
644 	if (summary)
645 		bubble_set_title (bubble, summary);
646 	if (body)
647 		bubble_set_message_body (bubble, body);
648 
649 	if (new_bubble && bubble_is_append_allowed(bubble)) {
650 		app_bubble = find_bubble_for_append(self, bubble);
651 
652 		/* Appending to an old bubble */
653 		if (app_bubble != NULL) {
654 			g_object_unref(bubble);
655 			bubble = app_bubble;
656 			if (body) {
657 				bubble_append_message_body (bubble, "\n");
658 				bubble_append_message_body (bubble, body);
659 			}
660 		}
661 	}
662 
663 	if (hints)
664 	{
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)))
668 		{
669 			if (sync_bubble != NULL
670 			    && IS_BUBBLE (sync_bubble))
671 			{
672 				g_object_unref (bubble);
673 				bubble = sync_bubble;
674 			}
675 
676 			if (data && G_VALUE_HOLDS_STRING (data))
677 				bubble_set_synchronous (bubble, g_value_get_string (data));
678 
679 			if (compat && G_VALUE_HOLDS_STRING (compat))
680 				bubble_set_synchronous (bubble, g_value_get_string (compat));
681 		}
682 	}
683 
684 	if (hints)
685 	{
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));
689 	}
690 
691 	if (hints)
692 	{
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
698 		   So, 2 means CRITICAL
699 		*/
700 	}
701 
702 	if (hints)
703 	{
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);
708 		else
709 			bubble_set_icon_only (bubble, FALSE);
710 	}
711 
712 	if (hints)
713 	{
714 		if ((data = (GValue*) g_hash_table_lookup (hints, "image_data")))
715 		{
716 			g_debug("Using image_data hint\n");
717 			pixbuf = process_dbus_icon_data (data);
718 			bubble_set_icon_from_pixbuf (bubble, pixbuf);
719 		}
720 		else if ((data = (GValue*) g_hash_table_lookup (hints, "image_path")))
721 		{
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));
725 			else
726 				g_warning ("image_path hint is not a string\n");
727 		}
728 		else if (icon && *icon != '\0')
729 		{
730 			g_debug("Using icon parameter\n");
731 			bubble_set_icon (bubble, icon);
732 		}
733 		else if ((data = (GValue*) g_hash_table_lookup (hints, "icon_data")))
734 		{
735 			g_debug("Using deprecated icon_data hint\n");
736 			pixbuf = process_dbus_icon_data (data);
737 			bubble_set_icon_from_pixbuf (bubble, pixbuf);
738 		}
739 	}
740 
741 	log_bubble_debug (bubble, app_name,
742 			  (*icon == '\0' && data != NULL) ?
743 			  "..." : icon);
744 
745 	bubble_determine_layout (bubble);
746 
747 	bubble_recalc_size (bubble);
748 
749 	if (bubble_is_synchronous (bubble))
750 	{
751 		stack_display_sync_bubble (self, bubble);
752 	} else {
753 		stack_push_bubble (self, bubble);
754 
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");
759 		else
760 			log_bubble (bubble, app_name, "");
761 
762 		/* make sure the sync. bubble is positioned correctly
763 		   even for the append case
764 		*/
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);
769 
770 		/* update the layout of the stack;
771 		 * this will also open the new bubble */
772 		stack_layout (self);
773 	}
774 
775 	if (bubble)
776 		dbus_g_method_return (context, bubble_get_id (bubble));
777 
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),
787 			       _arm_forced_quit,
788 			       (gpointer) self);
789 
790 	return TRUE;
791 }
792 
793 gboolean
stack_close_notification_handler(Stack * self,guint id,GError ** error)794 stack_close_notification_handler (Stack*   self,
795 				  guint    id,
796 				  GError** error)
797 {
798 	if (id == 0)
799 		g_warning ("%s(): notification id == 0, likely wrong\n",
800 			   G_STRFUNC);
801 
802 	Bubble* bubble = find_bubble_by_id (self, id);
803 
804 	// exit but pretend it's ok, for applications
805 	// that call us after an action button was clicked
806 	if (bubble == NULL)
807 		return TRUE;
808 
809 	dbus_send_close_signal (bubble_get_sender (bubble),
810 				bubble_get_id (bubble),
811 				3);
812 
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);
819 
820 	return TRUE;
821 }
822 
823 gboolean
stack_get_capabilities(Stack * self,gchar *** out_caps)824 stack_get_capabilities (Stack*   self,
825 			gchar*** out_caps)
826 {
827 	*out_caps = g_malloc0 (13 * sizeof(char *));
828 
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");
837 
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");
845 
846 	(*out_caps)[12] = NULL;
847 
848 	return TRUE;
849 }
850 
851 gboolean
stack_get_server_information(Stack * self,gchar ** out_name,gchar ** out_vendor,gchar ** out_version,gchar ** out_spec_ver)852 stack_get_server_information (Stack*  self,
853 			      gchar** out_name,
854 			      gchar** out_vendor,
855 			      gchar** out_version,
856 			      gchar** out_spec_ver)
857 {
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");
862 
863 	return TRUE;
864 }
865 
866 gboolean
stack_is_slot_vacant(Stack * self,Slot slot)867 stack_is_slot_vacant (Stack* self,
868                       Slot   slot)
869 {
870 	// sanity checks
871 	if (!self || !IS_STACK (self))
872 		return FALSE;
873 
874 	if (slot != SLOT_TOP && slot != SLOT_BOTTOM)
875 		return FALSE;
876 
877 	return self->slots[slot] == NULL ? VACANT : OCCUPIED;
878 }
879 
880 // return values of -1 for x and y indicate an error by the caller
881 void
stack_get_slot_position(Stack * self,Slot slot,gint bubble_height,gint * x,gint * y)882 stack_get_slot_position (Stack* self,
883                          Slot   slot,
884                          gint   bubble_height,
885                          gint*  x,
886                          gint*  y)
887 {
888 	// sanity checks
889 	if (!x && !y)
890 		return;
891 
892 	if (!self || !IS_STACK (self))
893 	{
894 		*x = -1;
895 		*y = -1;
896 		return;
897 	}
898 
899 	if (slot != SLOT_TOP && slot != SLOT_BOTTOM)
900 	{
901 		*x = -1;
902 		*y = -1;
903 		return;
904 	}
905 
906 	// initialize x and y
907 	defaults_get_top_corner (self->defaults, x, y);
908 
909 	// differentiate returned top-left corner for top and bottom slot
910 	// depending on the placement
911 	switch (defaults_get_gravity (self->defaults))
912 	{
913 		Defaults* d;
914 
915 		case GRAVITY_EAST:
916 			d = self->defaults;
917 
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) -
922 				      bubble_height +
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);
929 		break;
930 
931 		case GRAVITY_NORTH_EAST:
932 			d = self->defaults;
933 
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
937 
938 			// this needs to look at the height of the bubble in the
939 			// top slot
940 			if (slot == SLOT_BOTTOM)
941 			{
942 				switch (defaults_get_slot_allocation (d))
943 				{
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);*/
949 					break;
950 
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);
956 					break;
957 
958 					default:
959 					break;
960 				}
961 
962 			}
963 		break;
964 
965 		default:
966 			g_warning ("Unhandled placement!\n");
967 		break;
968 	}
969 }
970 
971 // call this _before_ the fade-in animation of the bubble starts
972 gboolean
stack_allocate_slot(Stack * self,Bubble * bubble,Slot slot)973 stack_allocate_slot (Stack*  self,
974 		     Bubble* bubble,
975                      Slot    slot)
976 {
977 	// sanity checks
978 	if (!self || !IS_STACK (self))
979 		return FALSE;
980 
981 	if (!bubble || !IS_BUBBLE (bubble))
982 		return FALSE;
983 
984 	if (slot != SLOT_TOP && slot != SLOT_BOTTOM)
985 		return FALSE;
986 
987 	if (stack_is_slot_vacant (self, slot))
988 		self->slots[slot] = BUBBLE (g_object_ref ((gpointer) bubble));
989 	else
990 		return FALSE;
991 
992 	return TRUE;
993 }
994 
995 // call this _after_ the fade-out animation of the bubble is finished
996 gboolean
stack_free_slot(Stack * self,Bubble * bubble)997 stack_free_slot (Stack*  self,
998 		 Bubble* bubble)
999 {
1000 	// sanity checks
1001 	if (!self || !IS_STACK (self))
1002 		return FALSE;
1003 
1004 	if (!bubble || !IS_BUBBLE (bubble))
1005 		return FALSE;
1006 
1007 	// check top and bottom slots for bubble pointer equality
1008 	if (bubble == self->slots[SLOT_TOP])
1009 	{
1010 		g_object_unref (self->slots[SLOT_TOP]);
1011 		self->slots[SLOT_TOP] = NULL;
1012 	}
1013 	else if (bubble == self->slots[SLOT_BOTTOM])
1014 	{
1015 		g_object_unref (self->slots[SLOT_BOTTOM]);
1016 		self->slots[SLOT_BOTTOM] = NULL;
1017 	}
1018 	else
1019 		return FALSE;
1020 
1021 	return TRUE;
1022 }
1023