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