~elementary-os/elementaryos/os-patch-notify-osd-precise

« back to all changes in this revision

Viewing changes to src/stack.c

  • Committer: Sergey "Shnatsel" Davidoff
  • Date: 2012-06-18 21:08:31 UTC
  • Revision ID: shnatsel@gmail.com-20120618210831-g6k5y7vecjdylgic
Initial import, version 0.9.34-0ubuntu2

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
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
 
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
 
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
 
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
 
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
 
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
 
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
 
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
 
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*
 
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*
 
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*
 
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
 
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
 
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
 
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*
 
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
 
316
stack_del (Stack* self)
 
317
{
 
318
        if (!self)
 
319
                return;
 
320
 
 
321
        g_object_unref (self);
 
322
}
 
323
 
 
324
void
 
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
 
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
 
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*
 
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
 
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
 
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
 
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
 
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
 
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
 
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
 
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
 
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
 
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
 
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
}