~indicator-applet-developers/indicator-messages/trunk.13.10

« back to all changes in this revision

Viewing changes to src/im-application-list.c

  • Committer: Tarmac
  • Author(s): Lars Uebernickel, Ted Gould, sergio.schvezov at canonical, Sergio Schvezov, Ken VanDine, Renato Araujo Oliveira Filho, Ricardo Mendoza
  • Date: 2013-08-19 16:51:38 UTC
  • mfrom: (327.1.83 consolidate)
  • Revision ID: tarmac-20130819165138-rtvgstpdkoysfshf
Merge in phablet branch.

Approved by PS Jenkins bot, Pete Woods.

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
/*
 
2
 * Copyright 2012 Canonical Ltd.
 
3
 *
 
4
 * This program is free software: you can redistribute it and/or modify it
 
5
 * under the terms of the GNU General Public License version 3, as published
 
6
 * by the Free Software Foundation.
 
7
 *
 
8
 * This program is distributed in the hope that it will be useful, but
 
9
 * WITHOUT ANY WARRANTY; without even the implied warranties of
 
10
 * MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
 
11
 * PURPOSE.  See the GNU General Public License for more details.
 
12
 *
 
13
 * You should have received a copy of the GNU General Public License along
 
14
 * with this program.  If not, see <http://www.gnu.org/licenses/>.
 
15
 *
 
16
 * Authors:
 
17
 *     Lars Uebernickel <lars.uebernickel@canonical.com>
 
18
 */
 
19
 
 
20
#include "im-application-list.h"
 
21
 
 
22
#include "indicator-messages-application.h"
 
23
#include "gactionmuxer.h"
 
24
 
 
25
#include <gio/gdesktopappinfo.h>
 
26
#include <string.h>
 
27
 
 
28
typedef GObjectClass ImApplicationListClass;
 
29
 
 
30
struct _ImApplicationList
 
31
{
 
32
  GObject parent;
 
33
 
 
34
  GHashTable *applications;
 
35
  GActionMuxer *muxer;
 
36
 
 
37
  GSimpleActionGroup * globalactions;
 
38
  GSimpleAction * statusaction;
 
39
 
 
40
  GHashTable *app_status;
 
41
};
 
42
 
 
43
G_DEFINE_TYPE (ImApplicationList, im_application_list, G_TYPE_OBJECT);
 
44
G_DEFINE_QUARK (draws_attention, message_action_draws_attention);
 
45
 
 
46
enum
 
47
{
 
48
  SOURCE_ADDED,
 
49
  SOURCE_CHANGED,
 
50
  SOURCE_REMOVED,
 
51
  MESSAGE_ADDED,
 
52
  MESSAGE_REMOVED,
 
53
  APP_ADDED,
 
54
  APP_STOPPED,
 
55
  REMOVE_ALL,
 
56
  STATUS_SET,
 
57
  N_SIGNALS
 
58
};
 
59
 
 
60
static guint signals[N_SIGNALS];
 
61
 
 
62
typedef struct
 
63
{
 
64
  ImApplicationList *list;
 
65
  GDesktopAppInfo *info;
 
66
  gchar *id;
 
67
  IndicatorMessagesApplication *proxy;
 
68
  GActionMuxer *muxer;
 
69
  GSimpleActionGroup *actions;
 
70
  GSimpleActionGroup *source_actions;
 
71
  GSimpleActionGroup *message_actions;
 
72
  GActionMuxer *message_sub_actions;
 
73
  GCancellable *cancellable;
 
74
  gboolean draws_attention;
 
75
} Application;
 
76
 
 
77
 
 
78
/* Prototypes */
 
79
static void         status_activated           (GSimpleAction *    action,
 
80
                                                GVariant *         param,
 
81
                                                gpointer           user_data);
 
82
 
 
83
static void
 
84
application_free (gpointer data)
 
85
{
 
86
  Application *app = data;
 
87
 
 
88
  if (!app)
 
89
    return;
 
90
 
 
91
  g_object_unref (app->info);
 
92
  g_free (app->id);
 
93
 
 
94
  if (app->cancellable)
 
95
    {
 
96
      g_cancellable_cancel (app->cancellable);
 
97
      g_clear_object (&app->cancellable);
 
98
    }
 
99
 
 
100
  if (app->proxy)
 
101
    g_object_unref (app->proxy);
 
102
 
 
103
  if (app->muxer)
 
104
    {
 
105
      g_object_unref (app->muxer);
 
106
      g_object_unref (app->source_actions);
 
107
      g_object_unref (app->message_actions);
 
108
      g_object_unref (app->message_sub_actions);
 
109
    }
 
110
 
 
111
  g_slice_free (Application, app);
 
112
}
 
113
 
 
114
static gboolean
 
115
application_draws_attention (gpointer key,
 
116
                             gpointer value,
 
117
                             gpointer user_data)
 
118
{
 
119
  Application *app = value;
 
120
 
 
121
  return app->draws_attention;
 
122
}
 
123
 
 
124
static void
 
125
im_application_list_update_draws_attention (ImApplicationList *list)
 
126
{
 
127
  const gchar *icon_name;
 
128
  GVariant *state;
 
129
  GActionGroup *main_actions;
 
130
 
 
131
  if (g_hash_table_find (list->applications, application_draws_attention, NULL))
 
132
    icon_name = "indicator-messages-new";
 
133
  else
 
134
    icon_name = "indicator-messages";
 
135
 
 
136
  main_actions = g_action_muxer_get_group (list->muxer, NULL);
 
137
  state = g_variant_new ("(sssb)", "", icon_name, "Messages", TRUE);
 
138
  g_action_group_change_action_state (main_actions, "messages", state);
 
139
}
 
140
 
 
141
/* Check a source action to see if it draws */
 
142
static gboolean
 
143
app_source_action_check_draw (Application * app, const gchar * action_name)
 
144
{
 
145
  gboolean retval = FALSE;
 
146
  GVariant * state;
 
147
  GVariant * draw;
 
148
 
 
149
  state = g_action_group_get_action_state (G_ACTION_GROUP(app->source_actions), action_name);
 
150
 
 
151
  /* uxsb */
 
152
  draw = g_variant_get_child_value(state, 3);
 
153
  retval = g_variant_get_boolean(draw);
 
154
 
 
155
  g_variant_unref(draw);
 
156
  g_variant_unref(state);
 
157
 
 
158
  return retval;
 
159
}
 
160
 
 
161
/* Check a message action to see if it draws */
 
162
static gboolean
 
163
app_message_action_check_draw (Application * app, const gchar * action_name)
 
164
{
 
165
  GAction * action = NULL;
 
166
  action = g_simple_action_group_lookup (app->message_actions, action_name);
 
167
  return GPOINTER_TO_INT(g_object_get_qdata(G_OBJECT(action), message_action_draws_attention_quark()));
 
168
}
 
169
 
 
170
/* Regenerate the draw attention flag based on the sources and messages
 
171
   that we have in the action groups */
 
172
static void
 
173
app_check_draw_attention (Application * app)
 
174
{
 
175
  gchar **source_actions = NULL;
 
176
  gchar **message_actions = NULL;
 
177
  gchar **it;
 
178
 
 
179
  source_actions = g_action_group_list_actions (G_ACTION_GROUP (app->source_actions));
 
180
  for (it = source_actions; *it && !app->draws_attention; it++)
 
181
    app->draws_attention = app_source_action_check_draw (app, *it);
 
182
 
 
183
  message_actions = g_action_group_list_actions (G_ACTION_GROUP (app->message_actions));
 
184
  for (it = message_actions; *it; it++)
 
185
    app->draws_attention = app_message_action_check_draw (app, *it);
 
186
 
 
187
  g_strfreev (source_actions);
 
188
  g_strfreev (message_actions);
 
189
 
 
190
  return;
 
191
}
 
192
 
 
193
/* Remove a source from an application, signal up and update the status
 
194
   of the draws attention flag. */
 
195
static void
 
196
im_application_list_source_removed (Application *app,
 
197
                                    const gchar *id)
 
198
{
 
199
  g_simple_action_group_remove (app->source_actions, id);
 
200
 
 
201
  g_signal_emit (app->list, signals[SOURCE_REMOVED], 0, app->id, id);
 
202
 
 
203
  if (app->draws_attention)
 
204
    {
 
205
      app->draws_attention = FALSE;
 
206
      app_check_draw_attention(app);
 
207
    }
 
208
 
 
209
  im_application_list_update_draws_attention (app->list);
 
210
}
 
211
 
 
212
static void
 
213
im_application_list_source_activated (GSimpleAction *action,
 
214
                                      GVariant      *parameter,
 
215
                                      gpointer       user_data)
 
216
{
 
217
  Application *app = user_data;
 
218
  const gchar *source_id;
 
219
 
 
220
  source_id = g_action_get_name (G_ACTION (action));
 
221
 
 
222
  if (g_variant_get_boolean (parameter))
 
223
    {
 
224
      indicator_messages_application_call_activate_source (app->proxy,
 
225
                                                           source_id,
 
226
                                                           app->cancellable,
 
227
                                                           NULL, NULL);
 
228
    }
 
229
  else
 
230
    {
 
231
      const gchar *sources[] = { source_id, NULL };
 
232
      const gchar *messages[] = { NULL };
 
233
      indicator_messages_application_call_dismiss (app->proxy, sources, messages,
 
234
                                                   app->cancellable, NULL, NULL);
 
235
    }
 
236
 
 
237
  im_application_list_source_removed (app, source_id);
 
238
}
 
239
 
 
240
static void
 
241
im_application_list_message_removed (Application *app,
 
242
                                     const gchar *id)
 
243
{
 
244
  g_simple_action_group_remove (app->message_actions, id);
 
245
  g_action_muxer_remove (app->message_sub_actions, id);
 
246
 
 
247
  im_application_list_update_draws_attention (app->list);
 
248
 
 
249
  g_signal_emit (app->list, signals[MESSAGE_REMOVED], 0, app->id, id);
 
250
}
 
251
 
 
252
static void
 
253
im_application_list_message_activated (GSimpleAction *action,
 
254
                                       GVariant      *parameter,
 
255
                                       gpointer       user_data)
 
256
{
 
257
  Application *app = user_data;
 
258
  const gchar *message_id;
 
259
 
 
260
  message_id = g_action_get_name (G_ACTION (action));
 
261
 
 
262
  if (g_variant_get_boolean (parameter))
 
263
    {
 
264
      indicator_messages_application_call_activate_message (app->proxy,
 
265
                                                            message_id,
 
266
                                                            "",
 
267
                                                            g_variant_new_array (G_VARIANT_TYPE_VARIANT, NULL, 0),
 
268
                                                            app->cancellable,
 
269
                                                            NULL, NULL);
 
270
    }
 
271
  else
 
272
    {
 
273
      const gchar *sources[] = { NULL };
 
274
      const gchar *messages[] = { message_id, NULL };
 
275
      indicator_messages_application_call_dismiss (app->proxy, sources, messages,
 
276
                                                   app->cancellable, NULL, NULL);
 
277
    }
 
278
 
 
279
  im_application_list_message_removed (app, message_id);
 
280
}
 
281
 
 
282
static void
 
283
im_application_list_sub_message_activated (GSimpleAction *action,
 
284
                                           GVariant      *parameter,
 
285
                                           gpointer       user_data)
 
286
{
 
287
  Application *app = user_data;
 
288
  const gchar *message_id;
 
289
  const gchar *action_id;
 
290
  GVariantBuilder builder;
 
291
 
 
292
  message_id = g_object_get_data (G_OBJECT (action), "message");
 
293
  action_id = g_action_get_name (G_ACTION (action));
 
294
 
 
295
  g_variant_builder_init (&builder, G_VARIANT_TYPE ("av"));
 
296
  if (parameter)
 
297
    g_variant_builder_add (&builder, "v", parameter);
 
298
 
 
299
  indicator_messages_application_call_activate_message (app->proxy,
 
300
                                                        message_id,
 
301
                                                        action_id,
 
302
                                                        g_variant_builder_end (&builder),
 
303
                                                        app->cancellable,
 
304
                                                        NULL, NULL);
 
305
 
 
306
  im_application_list_message_removed (app, message_id);
 
307
}
 
308
 
 
309
static void
 
310
im_application_list_remove_all (GSimpleAction *action,
 
311
                                GVariant      *parameter,
 
312
                                gpointer       user_data)
 
313
{
 
314
  ImApplicationList *list = user_data;
 
315
  GHashTableIter iter;
 
316
  Application *app;
 
317
 
 
318
  g_signal_emit (list, signals[REMOVE_ALL], 0);
 
319
 
 
320
  g_hash_table_iter_init (&iter, list->applications);
 
321
  while (g_hash_table_iter_next (&iter, NULL, (gpointer *) &app))
 
322
    {
 
323
      gchar **source_actions;
 
324
      gchar **message_actions;
 
325
      gchar **it;
 
326
 
 
327
      app->draws_attention = FALSE;
 
328
 
 
329
      source_actions = g_action_group_list_actions (G_ACTION_GROUP (app->source_actions));
 
330
      for (it = source_actions; *it; it++)
 
331
        im_application_list_source_removed (app, *it);
 
332
 
 
333
      message_actions = g_action_group_list_actions (G_ACTION_GROUP (app->message_actions));
 
334
      for (it = message_actions; *it; it++)
 
335
        im_application_list_message_removed (app, *it);
 
336
 
 
337
      if (app->proxy != NULL) /* If it is remote, we tell the app we've cleared */
 
338
        {
 
339
          indicator_messages_application_call_dismiss (app->proxy, 
 
340
                                                       (const gchar * const *) source_actions,
 
341
                                                       (const gchar * const *) message_actions,
 
342
                                                       app->cancellable, NULL, NULL);
 
343
        }
 
344
 
 
345
      g_strfreev (source_actions);
 
346
      g_strfreev (message_actions);
 
347
    }
 
348
 
 
349
  im_application_list_update_draws_attention (list);
 
350
}
 
351
 
 
352
static void
 
353
im_application_list_dispose (GObject *object)
 
354
{
 
355
  ImApplicationList *list = IM_APPLICATION_LIST (object);
 
356
 
 
357
  g_clear_object (&list->statusaction);
 
358
  g_clear_object (&list->globalactions);
 
359
  g_clear_pointer (&list->app_status, g_hash_table_unref);
 
360
 
 
361
  g_clear_pointer (&list->applications, g_hash_table_unref);
 
362
  g_clear_object (&list->muxer);
 
363
 
 
364
  G_OBJECT_CLASS (im_application_list_parent_class)->dispose (object);
 
365
}
 
366
 
 
367
static void
 
368
im_application_list_finalize (GObject *object)
 
369
{
 
370
  G_OBJECT_CLASS (im_application_list_parent_class)->finalize (object);
 
371
}
 
372
 
 
373
static void
 
374
im_application_list_class_init (ImApplicationListClass *klass)
 
375
{
 
376
  GObjectClass *object_class = G_OBJECT_CLASS (klass);
 
377
 
 
378
  object_class->dispose = im_application_list_dispose;
 
379
  object_class->finalize = im_application_list_finalize;
 
380
 
 
381
  signals[SOURCE_ADDED] = g_signal_new ("source-added",
 
382
                                        IM_TYPE_APPLICATION_LIST,
 
383
                                        G_SIGNAL_RUN_FIRST,
 
384
                                        0,
 
385
                                        NULL, NULL,
 
386
                                        g_cclosure_marshal_generic,
 
387
                                        G_TYPE_NONE,
 
388
                                        4,
 
389
                                        G_TYPE_STRING,
 
390
                                        G_TYPE_STRING,
 
391
                                        G_TYPE_STRING,
 
392
                                        G_TYPE_STRING);
 
393
 
 
394
  signals[SOURCE_CHANGED] = g_signal_new ("source-changed",
 
395
                                          IM_TYPE_APPLICATION_LIST,
 
396
                                          G_SIGNAL_RUN_FIRST,
 
397
                                          0,
 
398
                                          NULL, NULL,
 
399
                                          g_cclosure_marshal_generic,
 
400
                                          G_TYPE_NONE,
 
401
                                          4,
 
402
                                          G_TYPE_STRING,
 
403
                                          G_TYPE_STRING,
 
404
                                          G_TYPE_STRING,
 
405
                                          G_TYPE_STRING);
 
406
 
 
407
  signals[SOURCE_REMOVED] = g_signal_new ("source-removed",
 
408
                                          IM_TYPE_APPLICATION_LIST,
 
409
                                          G_SIGNAL_RUN_FIRST,
 
410
                                          0,
 
411
                                          NULL, NULL,
 
412
                                          g_cclosure_marshal_generic,
 
413
                                          G_TYPE_NONE,
 
414
                                          2,
 
415
                                          G_TYPE_STRING,
 
416
                                          G_TYPE_STRING);
 
417
 
 
418
  signals[MESSAGE_ADDED] = g_signal_new ("message-added",
 
419
                                         IM_TYPE_APPLICATION_LIST,
 
420
                                         G_SIGNAL_RUN_FIRST,
 
421
                                         0,
 
422
                                         NULL, NULL,
 
423
                                         g_cclosure_marshal_generic,
 
424
                                         G_TYPE_NONE,
 
425
                                         10,
 
426
                                         G_TYPE_STRING,
 
427
                                         G_TYPE_STRING,
 
428
                                         G_TYPE_STRING,
 
429
                                         G_TYPE_STRING,
 
430
                                         G_TYPE_STRING,
 
431
                                         G_TYPE_STRING,
 
432
                                         G_TYPE_STRING,
 
433
                                         G_TYPE_VARIANT,
 
434
                                         G_TYPE_INT64,
 
435
                                         G_TYPE_BOOLEAN);
 
436
 
 
437
  signals[MESSAGE_REMOVED] = g_signal_new ("message-removed",
 
438
                                           IM_TYPE_APPLICATION_LIST,
 
439
                                           G_SIGNAL_RUN_FIRST,
 
440
                                           0,
 
441
                                           NULL, NULL,
 
442
                                           g_cclosure_marshal_generic,
 
443
                                           G_TYPE_NONE,
 
444
                                           2,
 
445
                                           G_TYPE_STRING,
 
446
                                           G_TYPE_STRING);
 
447
 
 
448
  signals[APP_ADDED] = g_signal_new ("app-added",
 
449
                                     IM_TYPE_APPLICATION_LIST,
 
450
                                     G_SIGNAL_RUN_FIRST,
 
451
                                     0,
 
452
                                     NULL, NULL,
 
453
                                     g_cclosure_marshal_generic,
 
454
                                     G_TYPE_NONE,
 
455
                                     2,
 
456
                                     G_TYPE_STRING,
 
457
                                     G_TYPE_DESKTOP_APP_INFO);
 
458
 
 
459
  signals[APP_STOPPED] = g_signal_new ("app-stopped",
 
460
                                       IM_TYPE_APPLICATION_LIST,
 
461
                                       G_SIGNAL_RUN_FIRST,
 
462
                                       0,
 
463
                                       NULL, NULL,
 
464
                                       g_cclosure_marshal_VOID__OBJECT,
 
465
                                       G_TYPE_NONE,
 
466
                                       1,
 
467
                                       G_TYPE_STRING);
 
468
 
 
469
  signals[REMOVE_ALL] = g_signal_new ("remove-all",
 
470
                                      IM_TYPE_APPLICATION_LIST,
 
471
                                      G_SIGNAL_RUN_FIRST,
 
472
                                      0,
 
473
                                      NULL, NULL,
 
474
                                      g_cclosure_marshal_VOID__VOID,
 
475
                                      G_TYPE_NONE,
 
476
                                      0);
 
477
 
 
478
  signals[STATUS_SET] = g_signal_new ("status-set",
 
479
                                      IM_TYPE_APPLICATION_LIST,
 
480
                                      G_SIGNAL_RUN_FIRST,
 
481
                                      0,
 
482
                                      NULL, NULL,
 
483
                                      g_cclosure_marshal_generic,
 
484
                                      G_TYPE_NONE,
 
485
                                      1,
 
486
                                      G_TYPE_STRING);
 
487
}
 
488
 
 
489
static void
 
490
im_application_list_init (ImApplicationList *list)
 
491
{
 
492
  const GActionEntry action_entries[] = {
 
493
    { "messages", NULL, NULL, "('', 'indicator-messages', 'Messages', true)", NULL },
 
494
    { "remove-all", im_application_list_remove_all }
 
495
  };
 
496
 
 
497
  list->applications = g_hash_table_new_full (g_str_hash, g_str_equal, NULL, application_free);
 
498
  list->app_status = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_free);
 
499
 
 
500
  list->globalactions = g_simple_action_group_new ();
 
501
  g_simple_action_group_add_entries (list->globalactions, action_entries, G_N_ELEMENTS (action_entries), list);
 
502
 
 
503
  list->statusaction = g_simple_action_new_stateful("status", G_VARIANT_TYPE_STRING, g_variant_new_string("offline"));
 
504
  g_signal_connect(list->statusaction, "activate", G_CALLBACK(status_activated), list);
 
505
  g_simple_action_group_insert(list->globalactions, G_ACTION(list->statusaction));
 
506
 
 
507
  list->muxer = g_action_muxer_new ();
 
508
  g_action_muxer_insert (list->muxer, NULL, G_ACTION_GROUP (list->globalactions));
 
509
 
 
510
}
 
511
 
 
512
ImApplicationList *
 
513
im_application_list_new (void)
 
514
{
 
515
  return g_object_new (IM_TYPE_APPLICATION_LIST, NULL);
 
516
}
 
517
 
 
518
static gchar *
 
519
im_application_list_canonical_id (const gchar *id)
 
520
{
 
521
  gchar *str;
 
522
  gchar *p;
 
523
  int len;
 
524
 
 
525
  len = strlen (id);
 
526
  if (g_str_has_suffix (id, ".desktop"))
 
527
    len -= 8;
 
528
 
 
529
  str = g_strndup (id, len);
 
530
 
 
531
  for (p = str; *p; p++)
 
532
    {
 
533
      if (*p == '.')
 
534
        *p = '_';
 
535
    }
 
536
 
 
537
  return str;
 
538
}
 
539
 
 
540
static Application *
 
541
im_application_list_lookup (ImApplicationList *list,
 
542
                            const gchar       *desktop_id)
 
543
{
 
544
  gchar *id;
 
545
  Application *app;
 
546
 
 
547
  id = im_application_list_canonical_id (desktop_id);
 
548
  app = g_hash_table_lookup (list->applications, id);
 
549
 
 
550
  g_free (id);
 
551
  return app;
 
552
}
 
553
 
 
554
void
 
555
im_application_list_activate_launch (GSimpleAction *action,
 
556
                                     GVariant      *parameter,
 
557
                                     gpointer       user_data)
 
558
{
 
559
  Application *app = user_data;
 
560
  GError *error = NULL;
 
561
 
 
562
  if (!g_app_info_launch (G_APP_INFO (app->info), NULL, NULL, &error))
 
563
    {
 
564
      g_warning ("unable to launch application: %s", error->message);
 
565
      g_error_free (error);
 
566
    }
 
567
}
 
568
 
 
569
void
 
570
im_application_list_activate_app_action (GSimpleAction *action,
 
571
                                         GVariant      *parameter,
 
572
                                         gpointer       user_data)
 
573
{
 
574
  Application *app = user_data;
 
575
 
 
576
  g_desktop_app_info_launch_action (app->info, g_action_get_name (G_ACTION (action)), NULL);
 
577
}
 
578
 
 
579
void
 
580
im_application_list_add (ImApplicationList  *list,
 
581
                         const gchar        *desktop_id)
 
582
{
 
583
  GDesktopAppInfo *info;
 
584
  Application *app;
 
585
  const gchar *id;
 
586
  GSimpleActionGroup *actions;
 
587
  GSimpleAction *launch_action;
 
588
 
 
589
  g_return_if_fail (IM_IS_APPLICATION_LIST (list));
 
590
  g_return_if_fail (desktop_id != NULL);
 
591
 
 
592
  if (im_application_list_lookup (list, desktop_id))
 
593
    return;
 
594
 
 
595
  info = g_desktop_app_info_new (desktop_id);
 
596
  if (!info)
 
597
    {
 
598
      g_warning ("an application with id '%s' is not installed", desktop_id);
 
599
      return;
 
600
    }
 
601
 
 
602
  id = g_app_info_get_id (G_APP_INFO (info));
 
603
  g_return_if_fail (id != NULL);
 
604
 
 
605
  app = g_slice_new0 (Application);
 
606
  app->info = info;
 
607
  app->id = im_application_list_canonical_id (id);
 
608
  app->list = list;
 
609
  app->muxer = g_action_muxer_new ();
 
610
  app->source_actions = g_simple_action_group_new ();
 
611
  app->message_actions = g_simple_action_group_new ();
 
612
  app->message_sub_actions = g_action_muxer_new ();
 
613
  app->draws_attention = FALSE;
 
614
 
 
615
  actions = g_simple_action_group_new ();
 
616
 
 
617
  launch_action = g_simple_action_new_stateful ("launch", NULL, g_variant_new_boolean (FALSE));
 
618
  g_signal_connect (launch_action, "activate", G_CALLBACK (im_application_list_activate_launch), app);
 
619
  g_action_map_add_action (G_ACTION_MAP (actions), G_ACTION (launch_action));
 
620
 
 
621
  {
 
622
    const gchar *const *app_actions;
 
623
 
 
624
    for (app_actions = g_desktop_app_info_list_actions (app->info); *app_actions; app_actions++)
 
625
      {
 
626
        GSimpleAction *action;
 
627
 
 
628
        action = g_simple_action_new (*app_actions, NULL);
 
629
        g_signal_connect (action, "activate", G_CALLBACK (im_application_list_activate_app_action), app);
 
630
        g_action_map_add_action (G_ACTION_MAP (actions), G_ACTION (action));
 
631
 
 
632
        g_object_unref (action);
 
633
      }
 
634
  }
 
635
 
 
636
  g_action_muxer_insert (app->muxer, NULL, G_ACTION_GROUP (actions));
 
637
  g_action_muxer_insert (app->muxer, "src", G_ACTION_GROUP (app->source_actions));
 
638
  g_action_muxer_insert (app->muxer, "msg", G_ACTION_GROUP (app->message_actions));
 
639
  g_action_muxer_insert (app->muxer, "msg-actions", G_ACTION_GROUP (app->message_sub_actions));
 
640
 
 
641
  g_hash_table_insert (list->applications, (gpointer) app->id, app);
 
642
  g_action_muxer_insert (list->muxer, app->id, G_ACTION_GROUP (app->muxer));
 
643
 
 
644
  g_signal_emit (app->list, signals[APP_ADDED], 0, app->id, app->info);
 
645
 
 
646
  g_object_unref (launch_action);
 
647
  g_object_unref (actions);
 
648
}
 
649
 
 
650
void
 
651
im_application_list_remove (ImApplicationList *list,
 
652
                            const gchar       *id)
 
653
{
 
654
  Application *app;
 
655
 
 
656
  g_return_if_fail (IM_IS_APPLICATION_LIST (list));
 
657
 
 
658
  app = im_application_list_lookup (list, id);
 
659
  if (app)
 
660
    {
 
661
      if (app->proxy || app->cancellable)
 
662
        g_signal_emit (app->list, signals[APP_STOPPED], 0, app->id);
 
663
 
 
664
      g_hash_table_remove (list->applications, id);
 
665
      g_action_muxer_remove (list->muxer, id);
 
666
 
 
667
      im_application_list_update_draws_attention (list);
 
668
    }
 
669
}
 
670
 
 
671
static void
 
672
im_application_list_source_added (Application *app,
 
673
                                  guint        position,
 
674
                                  GVariant    *source)
 
675
{
 
676
  const gchar *id;
 
677
  const gchar *label;
 
678
  const gchar *iconstr;
 
679
  guint32 count;
 
680
  gint64 time;
 
681
  const gchar *string;
 
682
  gboolean draws_attention;
 
683
  GVariant *state;
 
684
  GSimpleAction *action;
 
685
 
 
686
  g_variant_get (source, "(&s&s&sux&sb)",
 
687
                 &id, &label, &iconstr, &count, &time, &string, &draws_attention);
 
688
 
 
689
  state = g_variant_new ("(uxsb)", count, time, string, draws_attention);
 
690
  action = g_simple_action_new_stateful (id, G_VARIANT_TYPE_BOOLEAN, state);
 
691
  g_signal_connect (action, "activate", G_CALLBACK (im_application_list_source_activated), app);
 
692
 
 
693
  g_simple_action_group_insert (app->source_actions, G_ACTION (action));
 
694
 
 
695
  g_signal_emit (app->list, signals[SOURCE_ADDED], 0, app->id, id, label, iconstr);
 
696
 
 
697
  if (draws_attention)
 
698
    app->draws_attention = TRUE;
 
699
 
 
700
  im_application_list_update_draws_attention (app->list);
 
701
 
 
702
  g_object_unref (action);
 
703
}
 
704
 
 
705
static void
 
706
im_application_list_source_changed (Application *app,
 
707
                                    GVariant    *source)
 
708
{
 
709
  const gchar *id;
 
710
  const gchar *label;
 
711
  const gchar *iconstr;
 
712
  guint32 count;
 
713
  gint64 time;
 
714
  const gchar *string;
 
715
  gboolean draws_attention;
 
716
 
 
717
  g_variant_get (source, "(&s&s&sux&sb)",
 
718
                 &id, &label, &iconstr, &count, &time, &string, &draws_attention);
 
719
 
 
720
  g_action_group_change_action_state (G_ACTION_GROUP (app->source_actions), id,
 
721
                                      g_variant_new ("(uxsb)", count, time, string, draws_attention));
 
722
 
 
723
  g_signal_emit (app->list, signals[SOURCE_CHANGED], 0, app->id, id, label, iconstr);
 
724
 
 
725
  im_application_list_update_draws_attention (app->list);
 
726
}
 
727
 
 
728
static void
 
729
im_application_list_sources_listed (GObject      *source_object,
 
730
                                    GAsyncResult *result,
 
731
                                    gpointer      user_data)
 
732
{
 
733
  Application *app = user_data;
 
734
  GVariant *sources;
 
735
  GError *error = NULL;
 
736
 
 
737
  if (indicator_messages_application_call_list_sources_finish (app->proxy, &sources, result, &error))
 
738
    {
 
739
      GVariantIter iter;
 
740
      GVariant *source;
 
741
      guint i = 0;
 
742
 
 
743
      g_variant_iter_init (&iter, sources);
 
744
      while ((source = g_variant_iter_next_value (&iter)))
 
745
        {
 
746
          im_application_list_source_added (app, i++, source);
 
747
          g_variant_unref (source);
 
748
        }
 
749
 
 
750
      g_variant_unref (sources);
 
751
    }
 
752
  else
 
753
    {
 
754
      g_warning ("could not fetch the list of sources: %s", error->message);
 
755
      g_error_free (error);
 
756
    }
 
757
}
 
758
 
 
759
static gchar *
 
760
get_symbolic_app_icon_string (GIcon *icon)
 
761
{
 
762
  const gchar * const *names;
 
763
  gchar *symbolic_name;
 
764
  GIcon *symbolic_icon;
 
765
  gchar *str;
 
766
 
 
767
  if (!G_IS_THEMED_ICON (icon))
 
768
    return NULL;
 
769
 
 
770
  names = g_themed_icon_get_names (G_THEMED_ICON (icon));
 
771
  if (!names || !names[0])
 
772
    return NULL;
 
773
 
 
774
  symbolic_icon = g_themed_icon_new_from_names ((gchar **) names, -1);
 
775
 
 
776
  symbolic_name = g_strconcat (names[0], "-symbolic", NULL);
 
777
  g_themed_icon_prepend_name (G_THEMED_ICON (symbolic_icon), symbolic_name);
 
778
 
 
779
  str = g_icon_to_string (symbolic_icon);
 
780
 
 
781
  g_free (symbolic_name);
 
782
  g_object_unref (symbolic_icon);
 
783
  return str;
 
784
}
 
785
 
 
786
static void
 
787
im_application_list_message_added (Application *app,
 
788
                                   GVariant    *message)
 
789
{
 
790
  const gchar *id;
 
791
  const gchar *iconstr;
 
792
  const gchar *title;
 
793
  const gchar *subtitle;
 
794
  const gchar *body;
 
795
  gint64 time;
 
796
  GVariantIter *action_iter;
 
797
  gboolean draws_attention;
 
798
  GSimpleAction *action;
 
799
  GIcon *app_icon;
 
800
  gchar *app_iconstr = NULL;
 
801
  GVariant *actions = NULL;
 
802
 
 
803
  g_variant_get (message, "(&s&s&s&s&sxaa{sv}b)",
 
804
                 &id, &iconstr, &title, &subtitle, &body, &time, &action_iter, &draws_attention);
 
805
 
 
806
  app_icon = g_app_info_get_icon (G_APP_INFO (app->info));
 
807
  if (app_icon)
 
808
    app_iconstr = get_symbolic_app_icon_string (app_icon);
 
809
 
 
810
  action = g_simple_action_new (id, G_VARIANT_TYPE_BOOLEAN);
 
811
  g_object_set_qdata(G_OBJECT(action), message_action_draws_attention_quark(), GINT_TO_POINTER(draws_attention));
 
812
  g_signal_connect (action, "activate", G_CALLBACK (im_application_list_message_activated), app);
 
813
  g_simple_action_group_insert (app->message_actions, G_ACTION (action));
 
814
 
 
815
  {
 
816
    GVariant *entry;
 
817
    GSimpleActionGroup *action_group;
 
818
    GVariantBuilder actions_builder;
 
819
 
 
820
    g_variant_builder_init (&actions_builder, G_VARIANT_TYPE ("aa{sv}"));
 
821
    action_group = g_simple_action_group_new ();
 
822
 
 
823
    while ((entry = g_variant_iter_next_value (action_iter)))
 
824
      {
 
825
        const gchar *name;
 
826
        GSimpleAction *action;
 
827
        GVariant *label;
 
828
        const gchar *type = NULL;
 
829
        GVariant *hint;
 
830
        GVariantBuilder dict_builder;
 
831
        gchar *prefixed_name;
 
832
 
 
833
        if (!g_variant_lookup (entry, "name", "&s", &name))
 
834
          {
 
835
            g_warning ("action dictionary for message '%s' is missing 'name' key", id);
 
836
            continue;
 
837
          }
 
838
 
 
839
        label = g_variant_lookup_value (entry, "label", G_VARIANT_TYPE_STRING);
 
840
        g_variant_lookup (entry, "parameter-type", "&g", &type);
 
841
        hint = g_variant_lookup_value (entry, "parameter-hint", NULL);
 
842
 
 
843
        action = g_simple_action_new (name, type ? G_VARIANT_TYPE (type) : NULL);
 
844
        g_object_set_data_full (G_OBJECT (action), "message", g_strdup (id), g_free);
 
845
        g_signal_connect (action, "activate", G_CALLBACK (im_application_list_sub_message_activated), app);
 
846
        g_simple_action_group_insert (action_group, G_ACTION (action));
 
847
 
 
848
        g_variant_builder_init (&dict_builder, G_VARIANT_TYPE ("a{sv}"));
 
849
 
 
850
        prefixed_name = g_strjoin (".", app->id, "msg-actions", id, name, NULL);
 
851
        g_variant_builder_add (&dict_builder, "{sv}", "name", g_variant_new_string (prefixed_name));
 
852
 
 
853
        if (label)
 
854
          {
 
855
            g_variant_builder_add (&dict_builder, "{sv}", "label", label);
 
856
            g_variant_unref (label);
 
857
          }
 
858
 
 
859
        if (type)
 
860
          g_variant_builder_add (&dict_builder, "{sv}", "parameter-type", g_variant_new_string (type));
 
861
 
 
862
        if (hint)
 
863
          {
 
864
            g_variant_builder_add (&dict_builder, "{sv}", "parameter-hint", hint);
 
865
            g_variant_unref (hint);
 
866
          }
 
867
 
 
868
        g_variant_builder_add (&actions_builder, "a{sv}", &dict_builder);
 
869
 
 
870
        g_object_unref (action);
 
871
        g_variant_unref (entry);
 
872
        g_free (prefixed_name);
 
873
      }
 
874
 
 
875
    g_action_muxer_insert (app->message_sub_actions, id, G_ACTION_GROUP (action_group));
 
876
    actions = g_variant_builder_end (&actions_builder);
 
877
 
 
878
    g_object_unref (action_group);
 
879
  }
 
880
 
 
881
  if (draws_attention)
 
882
    app->draws_attention = TRUE;
 
883
 
 
884
  im_application_list_update_draws_attention (app->list);
 
885
 
 
886
  g_signal_emit (app->list, signals[MESSAGE_ADDED], 0,
 
887
                 app->id, app_iconstr, id, iconstr, title,
 
888
                 subtitle, body, actions, time, draws_attention);
 
889
 
 
890
  g_variant_iter_free (action_iter);
 
891
  g_free (app_iconstr);
 
892
  g_object_unref (action);
 
893
}
 
894
 
 
895
static void
 
896
im_application_list_messages_listed (GObject      *source_object,
 
897
                                     GAsyncResult *result,
 
898
                                     gpointer      user_data)
 
899
{
 
900
  Application *app = user_data;
 
901
  GVariant *messages;
 
902
  GError *error = NULL;
 
903
 
 
904
  if (indicator_messages_application_call_list_messages_finish (app->proxy, &messages, result, &error))
 
905
    {
 
906
      GVariantIter iter;
 
907
      GVariant *message;
 
908
 
 
909
      g_variant_iter_init (&iter, messages);
 
910
      while ((message = g_variant_iter_next_value (&iter)))
 
911
        {
 
912
          im_application_list_message_added (app, message);
 
913
          g_variant_unref (message);
 
914
        }
 
915
 
 
916
      g_variant_unref (messages);
 
917
    }
 
918
  else
 
919
    {
 
920
      g_warning ("could not fetch the list of messages: %s", error->message);
 
921
      g_error_free (error);
 
922
    }
 
923
}
 
924
 
 
925
static void
 
926
im_application_list_unset_remote (Application *app)
 
927
{
 
928
  gboolean was_running;
 
929
 
 
930
  was_running = app->proxy || app->cancellable;
 
931
 
 
932
  if (app->cancellable)
 
933
    {
 
934
      g_cancellable_cancel (app->cancellable);
 
935
      g_clear_object (&app->cancellable);
 
936
    }
 
937
  g_clear_object (&app->proxy);
 
938
 
 
939
  /* clear actions by creating a new action group and overriding it in
 
940
   * the muxer */
 
941
  g_object_unref (app->source_actions);
 
942
  g_object_unref (app->message_actions);
 
943
  g_object_unref (app->message_sub_actions);
 
944
  app->source_actions = g_simple_action_group_new ();
 
945
  app->message_actions = g_simple_action_group_new ();
 
946
  app->message_sub_actions = g_action_muxer_new ();
 
947
  g_action_muxer_insert (app->muxer, "src", G_ACTION_GROUP (app->source_actions));
 
948
  g_action_muxer_insert (app->muxer, "msg", G_ACTION_GROUP (app->message_actions));
 
949
  g_action_muxer_insert (app->muxer, "msg-actions", G_ACTION_GROUP (app->message_sub_actions));
 
950
 
 
951
  im_application_list_update_draws_attention (app->list);
 
952
  g_action_group_change_action_state (G_ACTION_GROUP (app->muxer), "launch", g_variant_new_boolean (FALSE));
 
953
 
 
954
  if (was_running)
 
955
    g_signal_emit (app->list, signals[APP_STOPPED], 0, app->id);
 
956
}
 
957
 
 
958
static void
 
959
im_application_list_app_vanished (GDBusConnection *connection,
 
960
                                  const gchar     *name,
 
961
                                  gpointer         user_data)
 
962
{
 
963
  Application *app = user_data;
 
964
 
 
965
  im_application_list_unset_remote (app);
 
966
}
 
967
 
 
968
static void
 
969
im_application_list_proxy_created (GObject      *source_object,
 
970
                                   GAsyncResult *result,
 
971
                                   gpointer      user_data)
 
972
{
 
973
  Application *app = user_data;
 
974
  GError *error = NULL;
 
975
 
 
976
  app->proxy = indicator_messages_application_proxy_new_finish (result, &error);
 
977
  if (!app->proxy)
 
978
    {
 
979
      if (error->code != G_IO_ERROR_CANCELLED)
 
980
        g_warning ("could not create application proxy: %s", error->message);
 
981
      g_error_free (error);
 
982
      return;
 
983
    }
 
984
 
 
985
  indicator_messages_application_call_list_sources (app->proxy, app->cancellable,
 
986
                                                    im_application_list_sources_listed, app);
 
987
  indicator_messages_application_call_list_messages (app->proxy, app->cancellable,
 
988
                                                     im_application_list_messages_listed, app);
 
989
 
 
990
  g_signal_connect_swapped (app->proxy, "source-added", G_CALLBACK (im_application_list_source_added), app);
 
991
  g_signal_connect_swapped (app->proxy, "source-changed", G_CALLBACK (im_application_list_source_changed), app);
 
992
  g_signal_connect_swapped (app->proxy, "source-removed", G_CALLBACK (im_application_list_source_removed), app);
 
993
  g_signal_connect_swapped (app->proxy, "message-added", G_CALLBACK (im_application_list_message_added), app);
 
994
  g_signal_connect_swapped (app->proxy, "message-removed", G_CALLBACK (im_application_list_message_removed), app);
 
995
 
 
996
  g_action_group_change_action_state (G_ACTION_GROUP (app->muxer), "launch", g_variant_new_boolean (TRUE));
 
997
 
 
998
  g_bus_watch_name_on_connection (g_dbus_proxy_get_connection (G_DBUS_PROXY (app->proxy)),
 
999
                                  g_dbus_proxy_get_name (G_DBUS_PROXY (app->proxy)),
 
1000
                                  G_BUS_NAME_WATCHER_FLAGS_NONE,
 
1001
                                  NULL, im_application_list_app_vanished,
 
1002
                                  app, NULL);
 
1003
}
 
1004
 
 
1005
void
 
1006
im_application_list_set_remote (ImApplicationList *list,
 
1007
                                const gchar       *id,
 
1008
                                GDBusConnection   *connection,
 
1009
                                const gchar       *unique_bus_name,
 
1010
                                const gchar       *object_path)
 
1011
{
 
1012
  Application *app;
 
1013
 
 
1014
  g_return_if_fail (IM_IS_APPLICATION_LIST (list));
 
1015
 
 
1016
  app = im_application_list_lookup (list, id);
 
1017
  if (!app)
 
1018
    {
 
1019
      g_warning ("'%s' is not a registered application", id);
 
1020
      return;
 
1021
    }
 
1022
 
 
1023
  if (app->cancellable)
 
1024
    {
 
1025
      gchar *name_owner = NULL;
 
1026
 
 
1027
      if (app->proxy)
 
1028
        name_owner = g_dbus_proxy_get_name_owner (G_DBUS_PROXY (app->proxy));
 
1029
      g_warning ("replacing '%s' at %s with %s", id, name_owner, unique_bus_name);
 
1030
 
 
1031
      im_application_list_unset_remote (app);
 
1032
 
 
1033
      g_free (name_owner);
 
1034
    }
 
1035
 
 
1036
  app->cancellable = g_cancellable_new ();
 
1037
  indicator_messages_application_proxy_new (connection, G_DBUS_PROXY_FLAGS_NONE,
 
1038
                                            unique_bus_name, object_path, app->cancellable,
 
1039
                                            im_application_list_proxy_created, app);
 
1040
}
 
1041
 
 
1042
GActionGroup *
 
1043
im_application_list_get_action_group (ImApplicationList *list)
 
1044
{
 
1045
  g_return_val_if_fail (IM_IS_APPLICATION_LIST (list), NULL);
 
1046
 
 
1047
  return G_ACTION_GROUP (list->muxer);
 
1048
}
 
1049
 
 
1050
GList *
 
1051
im_application_list_get_applications (ImApplicationList *list)
 
1052
{
 
1053
  g_return_val_if_fail (IM_IS_APPLICATION_LIST (list), NULL);
 
1054
 
 
1055
  return g_hash_table_get_keys (list->applications);
 
1056
}
 
1057
 
 
1058
GDesktopAppInfo *
 
1059
im_application_list_get_application (ImApplicationList *list,
 
1060
                                     const gchar       *id)
 
1061
{
 
1062
  Application *app;
 
1063
 
 
1064
  g_return_val_if_fail (IM_IS_APPLICATION_LIST (list), NULL);
 
1065
 
 
1066
  app = g_hash_table_lookup (list->applications, id);
 
1067
  return app ? app->info : NULL;
 
1068
}
 
1069
 
 
1070
static void
 
1071
status_activated (GSimpleAction * action, GVariant * param, gpointer user_data)
 
1072
{
 
1073
  g_return_if_fail (IM_IS_APPLICATION_LIST(user_data));
 
1074
  ImApplicationList * list = IM_APPLICATION_LIST(user_data);
 
1075
  const gchar * status = g_variant_get_string(param, NULL);
 
1076
 
 
1077
  g_simple_action_set_state(action, param);
 
1078
 
 
1079
  GList * appshash = g_hash_table_get_keys(list->app_status);
 
1080
  GList * appsfree = g_list_copy_deep(appshash, (GCopyFunc)g_strdup, NULL);
 
1081
  GList * app;
 
1082
 
 
1083
  for (app = appsfree; app != NULL; app = g_list_next(app)) {
 
1084
    g_hash_table_insert(list->app_status, app->data, g_strdup(status));
 
1085
  }
 
1086
 
 
1087
  g_list_free(appshash);
 
1088
  g_list_free(appsfree);
 
1089
 
 
1090
  g_signal_emit (list, signals[STATUS_SET], 0, status);
 
1091
 
 
1092
  return;
 
1093
}
 
1094
 
 
1095
#define STATUS_ID_OFFLINE  (G_N_ELEMENTS(status_ids) - 1)
 
1096
static const gchar *status_ids[] = { "available", "away", "busy", "invisible", "offline" };
 
1097
 
 
1098
static guint
 
1099
status2val (const gchar * string)
 
1100
{
 
1101
        if (string == NULL) return STATUS_ID_OFFLINE;
 
1102
 
 
1103
        guint i;
 
1104
        for (i = 0; i < G_N_ELEMENTS(status_ids); i++) {
 
1105
                if (g_strcmp0(status_ids[i], string) == 0) {
 
1106
                        break;
 
1107
                }
 
1108
        }
 
1109
 
 
1110
        if (i > STATUS_ID_OFFLINE)
 
1111
                i = STATUS_ID_OFFLINE;
 
1112
 
 
1113
        return i;
 
1114
}
 
1115
 
 
1116
void
 
1117
im_application_list_set_status (ImApplicationList * list, const gchar * id, const gchar *status)
 
1118
{
 
1119
        g_return_if_fail (IM_IS_APPLICATION_LIST (list));
 
1120
 
 
1121
        g_hash_table_insert(list->app_status, im_application_list_canonical_id(id), g_strdup(status));
 
1122
 
 
1123
        guint final_status = STATUS_ID_OFFLINE;
 
1124
 
 
1125
        GList * statuses = g_hash_table_get_values(list->app_status);
 
1126
        GList * statusentry;
 
1127
 
 
1128
        for (statusentry = statuses; statusentry != NULL; statusentry = g_list_next(statusentry)) {
 
1129
                guint statusval = status2val((gchar *)statusentry->data);
 
1130
                final_status = MIN(final_status, statusval);
 
1131
        }
 
1132
 
 
1133
        g_list_free(statuses);
 
1134
 
 
1135
        g_simple_action_set_state(list->statusaction, g_variant_new_string(status_ids[final_status]));
 
1136
 
 
1137
        return;
 
1138
}
 
1139