messenger-gtk

Gtk+3 graphical user interfaces for GNUnet Messenger
Log | Files | Refs | Submodules | README | LICENSE

event.c (29083B)


      1 /*
      2    This file is part of GNUnet.
      3    Copyright (C) 2021--2026 GNUnet e.V.
      4 
      5    GNUnet is free software: you can redistribute it and/or modify it
      6    under the terms of the GNU Affero General Public License as published
      7    by the Free Software Foundation, either version 3 of the License,
      8    or (at your option) any later version.
      9 
     10    GNUnet is distributed in the hope that it will be useful, but
     11    WITHOUT ANY WARRANTY; without even the implied warranty of
     12    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
     13    Affero General Public License for more details.
     14 
     15    You should have received a copy of the GNU Affero General Public License
     16    along with this program.  If not, see <http://www.gnu.org/licenses/>.
     17 
     18    SPDX-License-Identifier: AGPL3.0-or-later
     19  */
     20 /*
     21  * @author Tobias Frisch
     22  * @file event.c
     23  */
     24 
     25 #include "event.h"
     26 
     27 #include <glib-2.0/glib.h>
     28 #include <gnunet/gnunet_chat_lib.h>
     29 #include <gnunet/gnunet_common.h>
     30 #include <stdio.h>
     31 #include <string.h>
     32 
     33 #include "account.h"
     34 #include "application.h"
     35 #include "contact.h"
     36 #include "discourse.h"
     37 #include "file.h"
     38 #include "secret.h"
     39 #include "ui.h"
     40 
     41 #include "ui/chat_entry.h"
     42 #include "ui/chat_title.h"
     43 #include "ui/message.h"
     44 
     45 static void
     46 _close_notification(NotifyNotification* notification,
     47                     gpointer user_data)
     48 {
     49   g_assert((notification) && (user_data));
     50 
     51   MESSENGER_Application *app = (MESSENGER_Application*) user_data;
     52 
     53   app->notifications = g_list_remove(app->notifications, notification);
     54 
     55   notify_notification_clear_actions(notification);
     56   notify_notification_clear_hints(notification);
     57 
     58   g_object_unref(notification);
     59 }
     60 
     61 static void
     62 _show_notification(MESSENGER_Application *app,
     63                    UNUSED struct GNUNET_CHAT_Context *context,
     64                    const struct GNUNET_CHAT_Contact *contact,
     65                    const char *text,
     66                    const char *icon,
     67                    const char *category)
     68 {
     69   g_assert(
     70     (app) &&
     71     (text) &&
     72     (icon) &&
     73     (category)
     74   );
     75 
     76   if (app->settings.disable_notifications)
     77     return;
     78 
     79   const char *sender = GNUNET_CHAT_contact_get_name(contact);
     80 
     81   NotifyNotification *notification = notify_notification_new(
     82     sender? sender : "(unknown)", text, icon
     83   );
     84 
     85   if (!notification)
     86     return;
     87 
     88   app->notifications = g_list_append(app->notifications, notification);
     89 
     90   notify_notification_set_category(notification, category);
     91 
     92   g_signal_connect(
     93     notification,
     94     "closed",
     95     G_CALLBACK(_close_notification),
     96     app
     97   );
     98 
     99   notify_notification_show(notification, NULL);
    100 
    101   if (app->settings.play_notification_sounds)
    102   {
    103     fprintf(stdout, "\a");
    104     fflush(stdout);
    105   }
    106 }
    107 
    108 void
    109 event_handle_warning(MESSENGER_Application *app,
    110                      struct GNUNET_CHAT_Context *context,
    111                      struct GNUNET_CHAT_Message *msg)
    112 {
    113   g_assert((app) && (msg));
    114 
    115   const char *text = GNUNET_CHAT_message_get_text(msg);
    116 
    117   const struct GNUNET_CHAT_Contact *contact = GNUNET_CHAT_message_get_sender(
    118     msg
    119   );
    120 
    121   g_printerr("ERROR: %s\n", text);
    122 
    123   _show_notification(
    124     app,
    125     context,
    126     contact,
    127     text,
    128     "dialog-warning-symbolic",
    129     "im.error"
    130   );
    131 }
    132 
    133 static enum GNUNET_GenericReturnValue
    134 _iterate_reload_account(void *cls,
    135                         UNUSED struct GNUNET_CHAT_Handle *handle,
    136                         struct GNUNET_CHAT_Account *account)
    137 {
    138   g_assert((cls) && (account));
    139 
    140   MESSENGER_Application *app = (MESSENGER_Application*) cls;
    141 
    142   account_create_info(account);
    143   account_update_attributes(account, app);
    144 
    145   return GNUNET_YES;
    146 }
    147 
    148 void
    149 event_refresh_accounts(MESSENGER_Application *app)
    150 {
    151   g_assert(app);
    152 
    153   UI_MESSENGER_Handle *ui = &(app->ui.messenger);
    154   CHAT_MESSENGER_Handle *chat = &(app->chat.messenger);
    155   const struct GNUNET_CHAT_Account *account;
    156 
    157   GNUNET_CHAT_iterate_accounts(
    158     chat->handle,
    159     _iterate_reload_account,
    160     app
    161   );
    162 
    163   switch (app->ui.state)
    164   {
    165     case MESSENGER_STATE_ACCOUNTS:
    166       ui_accounts_dialog_refresh(app, &(app->ui.accounts));
    167       break;
    168     case MESSENGER_STATE_MAIN_WINDOW:
    169       ui_messenger_refresh(app, ui);
    170       break;
    171     default:
    172       break;
    173   }
    174 
    175   account = GNUNET_CHAT_get_connected(chat->handle);
    176 
    177   if (account)
    178     application_show_window(app);
    179 }
    180 
    181 static gboolean
    182 _select_chat_to_activate(gpointer user_data)
    183 {
    184   g_assert(user_data);
    185 
    186   UI_CHAT_ENTRY_Handle *entry = (UI_CHAT_ENTRY_Handle*) user_data;
    187 
    188   if ((!entry) || (!(entry->chat)) || (!(entry->entry_box)))
    189     return FALSE;
    190 
    191   MESSENGER_Application *app = entry->chat->app;
    192 
    193   if (!app)
    194     return FALSE;
    195 
    196   GtkListBoxRow *row = GTK_LIST_BOX_ROW(
    197     gtk_widget_get_parent(entry->entry_box)
    198   );
    199 
    200   if (!row)
    201     return FALSE;
    202 
    203   UI_MESSENGER_Handle *ui = &(app->ui.messenger);
    204 
    205   gtk_list_box_select_row(ui->chats_listbox, row);
    206   gtk_list_box_invalidate_filter(ui->chats_listbox);
    207 
    208   gtk_widget_activate(GTK_WIDGET(row));
    209 
    210   ui->chat_selection = 0;
    211   return FALSE;
    212 }
    213 
    214 static gboolean
    215 _idle_chat_entry_update(gpointer user_data)
    216 {
    217   g_assert(user_data);
    218 
    219   UI_CHAT_ENTRY_Handle *entry = (UI_CHAT_ENTRY_Handle*) user_data;
    220 
    221   if ((!(entry->chat)) || (!(entry->chat->app)) ||
    222       (!(entry->chat->send_text_view)))
    223     goto update_exit;
    224 
    225   ui_chat_entry_update(entry, entry->chat->app);
    226 
    227 update_exit:
    228   entry->update = 0;
    229   return FALSE;
    230 }
    231 
    232 static void
    233 enqueue_chat_entry_update(UI_CHAT_ENTRY_Handle *entry)
    234 {
    235   g_assert(entry);
    236 
    237   if (entry->update)
    238     util_source_remove(entry->update);
    239 
    240   entry->update = util_idle_add(
    241     G_SOURCE_FUNC(_idle_chat_entry_update),
    242     entry
    243   );
    244 }
    245 
    246 static void
    247 _add_new_chat_entry(MESSENGER_Application *app,
    248                     struct GNUNET_CHAT_Context *context)
    249 {
    250   g_assert((app) && (context));
    251 
    252   UI_MESSENGER_Handle *ui = &(app->ui.messenger);
    253   UI_CHAT_ENTRY_Handle *entry = ui_chat_entry_new(app, context);
    254 
    255   enqueue_chat_entry_update(entry);
    256 
    257   gtk_container_add(GTK_CONTAINER(ui->chats_listbox), entry->entry_box);
    258 
    259   gtk_container_add(
    260     GTK_CONTAINER(ui->chats_stack),
    261     entry->chat->chat_box
    262   );
    263 
    264   gtk_container_add(
    265     GTK_CONTAINER(ui->chat_title_stack),
    266     entry->chat->title->chat_title_box
    267   );
    268 
    269   ui->chat_entries = g_list_append(ui->chat_entries, entry);
    270 
    271   GtkWidget *row = gtk_widget_get_parent(entry->entry_box);
    272 
    273   g_object_set_qdata(
    274     G_OBJECT(row),
    275     app->quarks.ui,
    276     entry
    277   );
    278 
    279   if (ui->chat_selection)
    280     util_source_remove(ui->chat_selection);
    281 
    282   ui->chat_selection = util_idle_add(
    283     G_SOURCE_FUNC(_select_chat_to_activate),
    284     entry
    285   );
    286 }
    287 
    288 static int
    289 _iterate_profile_contacts(void *cls,
    290                           UNUSED struct GNUNET_CHAT_Handle *handle,
    291                           struct GNUNET_CHAT_Contact *contact)
    292 {
    293   g_assert((cls) && (contact));
    294 
    295   MESSENGER_Application *app = (MESSENGER_Application*) cls;
    296 
    297   struct GNUNET_CHAT_Context *context = GNUNET_CHAT_contact_get_context(
    298     contact
    299   );
    300 
    301   if (GNUNET_SYSERR == GNUNET_CHAT_context_get_status(context))
    302     return GNUNET_YES;
    303 
    304   _add_new_chat_entry(app, context);
    305   return GNUNET_YES;
    306 }
    307 
    308 static int
    309 _iterate_profile_groups(void *cls,
    310                         UNUSED struct GNUNET_CHAT_Handle *handle,
    311                         UNUSED struct GNUNET_CHAT_Group *group)
    312 {
    313   g_assert(cls);
    314 
    315   MESSENGER_Application *app = (MESSENGER_Application*) cls;
    316 
    317   struct GNUNET_CHAT_Context *context = GNUNET_CHAT_group_get_context(
    318     group
    319   );
    320 
    321   if (GNUNET_SYSERR == GNUNET_CHAT_context_get_status(context))
    322     return GNUNET_YES;
    323 
    324   _add_new_chat_entry(app, GNUNET_CHAT_group_get_context(group));
    325   return GNUNET_YES;
    326 }
    327 
    328 void
    329 event_update_profile(MESSENGER_Application *app)
    330 {
    331   g_assert(app);
    332 
    333   if (MESSENGER_STATE_NEW_ACCOUNT == app->ui.state)
    334   {
    335     app->ui.state = MESSENGER_STATE_MAIN_WINDOW;
    336 
    337     ui_new_account_dialog_update(app, &(app->ui.new_account));
    338   }
    339 
    340   UI_MESSENGER_Handle *ui = &(app->ui.messenger);
    341   CHAT_MESSENGER_Handle *chat = &(app->chat.messenger);
    342 
    343   event_refresh_accounts(app);
    344 
    345   const char *name = GNUNET_CHAT_get_name(chat->handle);
    346 
    347   account_switch_name_avatar_to_info(
    348     GNUNET_CHAT_get_connected(chat->handle),
    349     ui->profile_avatar
    350   );
    351 
    352   ui_label_set_text(ui->profile_label, name);
    353 
    354   const char *key = GNUNET_CHAT_get_key(chat->handle);
    355 
    356   ui_label_set_text(ui->profile_key_label, key);
    357 
    358   gtk_stack_set_visible_child(ui->chats_stack, ui->no_chat_box);
    359   
    360   GList *children = gtk_container_get_children(GTK_CONTAINER(ui->leaflet_chat));
    361 
    362   if (children) {
    363     hdy_leaflet_set_visible_child(ui->leaflet_chat, GTK_WIDGET(children->data));
    364     g_list_free(children);
    365   }
    366 
    367   GNUNET_CHAT_iterate_contacts(chat->handle, _iterate_profile_contacts, app);
    368   GNUNET_CHAT_iterate_groups(chat->handle, _iterate_profile_groups, app);
    369 }
    370 
    371 static void
    372 _clear_chat_entry(GtkWidget *widget,
    373                   gpointer user_data)
    374 {
    375   g_assert((widget) && (user_data));
    376 
    377   MESSENGER_Application *app = (MESSENGER_Application*) user_data;
    378   UI_MESSENGER_Handle *ui = &(app->ui.messenger);
    379 
    380   GtkListBoxRow *row = GTK_LIST_BOX_ROW(widget);
    381 
    382   if (!gtk_list_box_row_get_selectable(row))
    383     return;
    384 
    385   UI_CHAT_ENTRY_Handle *entry = (UI_CHAT_ENTRY_Handle*) g_object_get_qdata(
    386     G_OBJECT(row),
    387     app->quarks.ui
    388   );
    389 
    390   ui->chat_entries = g_list_remove(ui->chat_entries, entry);
    391 
    392   ui_chat_entry_dispose(entry, app);
    393 }
    394 
    395 static int
    396 _cleanup_profile_contacts(void *cls,
    397                           UNUSED struct GNUNET_CHAT_Handle *handle,
    398                           struct GNUNET_CHAT_Contact *contact)
    399 {
    400   if (contact)
    401     contact_destroy_info(contact);
    402   return GNUNET_YES;
    403 }
    404 
    405 static int
    406 _cleanup_profile_files(void *cls,
    407                        UNUSED struct GNUNET_CHAT_Handle *handle,
    408                        struct GNUNET_CHAT_File *file)
    409 {
    410   if (file)
    411     file_destroy_info(file);
    412   return GNUNET_YES;
    413 }
    414 
    415 void
    416 event_cleanup_profile(MESSENGER_Application *app)
    417 {
    418   g_assert(app);
    419 
    420   UI_MESSENGER_Handle *ui = &(app->ui.messenger);
    421   CHAT_MESSENGER_Handle *chat = &(app->chat.messenger);
    422 
    423   account_remove_name_avatar_from_info(
    424     GNUNET_CHAT_get_connected(chat->handle),
    425     ui->profile_avatar
    426   );
    427 
    428   GList *entries = gtk_container_get_children(
    429     GTK_CONTAINER(ui->chats_listbox)
    430   );
    431 
    432   g_list_foreach(entries, (GFunc) _clear_chat_entry, app);
    433   g_list_free(entries);
    434 
    435   GNUNET_CHAT_iterate_contacts(chat->handle, _cleanup_profile_contacts, NULL);
    436   GNUNET_CHAT_iterate_files(chat->handle, _cleanup_profile_files, NULL);
    437 }
    438 
    439 static void
    440 _account_secret_lookup(MESSENGER_Application *app,
    441                        const char *secret,
    442                        uint32_t secret_len,
    443                        gboolean success,
    444                        gboolean error,
    445                        gpointer user_data)
    446 {
    447   g_assert((app) && (user_data));
    448 
    449   struct GNUNET_CHAT_Account *account = user_data;
    450 
    451   if (error)
    452   {
    453     fprintf(stderr, "ERROR: Looking up secret failed\n");
    454   }
    455   else if ((success) && (secret) && (secret_len > 0))
    456   {
    457     application_chat_lock(app);
    458     GNUNET_CHAT_connect(app->chat.messenger.handle, account, secret, secret_len);
    459     application_chat_unlock(app);
    460   }
    461   else
    462   {
    463     const char *name;
    464 
    465     application_chat_lock(app);
    466     name = GNUNET_CHAT_account_get_name(account);
    467     application_chat_unlock(app);
    468 
    469     secret_operation_generate(
    470       app,
    471       name,
    472       &_account_secret_lookup,
    473       account
    474     );
    475   }
    476 }
    477 
    478 static void
    479 _account_secret_transferred(MESSENGER_Application *app,
    480                             const char *secret,
    481                             uint32_t secret_len,
    482                             gboolean success,
    483                             gboolean error,
    484                             gpointer user_data)
    485 {
    486   g_assert(app);
    487 
    488   char *names = user_data;
    489 
    490   if (error)
    491   {
    492     fprintf(stderr, "ERROR: Storing secret failed\n");
    493   }
    494   else if ((success) && (secret) && (secret_len > 0))
    495   {
    496     const char *old_name;
    497 
    498     old_name = names;
    499     
    500     if (old_name)
    501       secret_operation_delete(app, old_name, NULL, NULL);
    502   }
    503 }
    504 
    505 static void
    506 _account_secret_transfer(MESSENGER_Application *app,
    507                          const char *secret,
    508                          uint32_t secret_len,
    509                          gboolean success,
    510                          gboolean error,
    511                          gpointer user_data)
    512 {
    513   g_assert((app) && (user_data));
    514 
    515   char *names = user_data;
    516 
    517   if (error)
    518   {
    519     fprintf(stderr, "ERROR: Looking up secret failed\n");
    520   }
    521   else if ((success) && (secret) && (secret_len > 0))
    522   {
    523     MESSENGER_SecretOperation *op;
    524     const char *name;
    525     char *old_name;
    526 
    527     name = names;
    528     old_name = strchr(names, '\0');
    529 
    530     if (old_name)
    531       old_name = g_strdup(old_name + 1);
    532 
    533     op = secret_operation_store(
    534       app,
    535       name,
    536       secret,
    537       secret_len,
    538       &_account_secret_transferred,
    539       old_name
    540     );
    541 
    542     if (op)
    543       secret_operation_own_user_data(op);
    544   }
    545 }
    546 
    547 void
    548 event_select_profile(MESSENGER_Application *app,
    549                      struct GNUNET_CHAT_Context *context,
    550                      struct GNUNET_CHAT_Message *msg)
    551 {
    552   g_assert((app) && (!context) && (msg));
    553 
    554   struct GNUNET_CHAT_Account *account = GNUNET_CHAT_message_get_account(msg);
    555 
    556   switch (GNUNET_CHAT_message_get_kind(msg))
    557   {
    558     case GNUNET_CHAT_KIND_CREATED_ACCOUNT:
    559     {
    560       const char *name;
    561       
    562       application_chat_lock(app);
    563       name = GNUNET_CHAT_account_get_name(account);
    564       application_chat_unlock(app);
    565 
    566       secret_operation_lookup(app, name, &_account_secret_lookup, account);
    567       break;
    568     }
    569     case GNUNET_CHAT_KIND_UPDATE_ACCOUNT:
    570     {
    571       MESSENGER_SecretOperation *op;
    572       const char *old_name;
    573       const char *name;
    574       char *names;
    575 
    576       application_chat_lock(app);
    577       old_name = GNUNET_CHAT_message_get_text(msg);
    578       name = GNUNET_CHAT_account_get_name(account);
    579       application_chat_unlock(app);
    580 
    581       if ((!old_name) || (!name))
    582         break;
    583 
    584       names = g_malloc(strlen(name) + strlen(old_name) + 2);
    585 
    586       if (names)
    587       {
    588         strcpy(names, name);
    589         strcpy(names + strlen(name) + 1, old_name);
    590       }
    591 
    592       op = secret_operation_lookup(app, old_name, &_account_secret_transfer, names);
    593 
    594       if (op)
    595         secret_operation_own_user_data(op);
    596       break;
    597     }
    598     default:
    599       break;
    600   }
    601 }
    602 
    603 gboolean
    604 _delayed_context_drop(gpointer user_data)
    605 {
    606   g_assert(user_data);
    607 
    608   struct GNUNET_CHAT_Context *context = (struct GNUNET_CHAT_Context*) user_data;
    609 
    610   // TODO: schedule_sync_lock(&(app->chat.schedule));
    611 
    612   struct GNUNET_CHAT_Group *group = GNUNET_CHAT_context_get_group(context);
    613   struct GNUNET_CHAT_Contact *contact = GNUNET_CHAT_context_get_contact(context);
    614 
    615   if (group)
    616     GNUNET_CHAT_group_leave(group);
    617   else if (contact)
    618     GNUNET_CHAT_contact_delete(contact);
    619 
    620   // TODO: schedule_sync_unlock(&(app->chat.schedule));
    621 
    622   return FALSE;
    623 }
    624 
    625 void
    626 event_update_chats(MESSENGER_Application *app,
    627                    struct GNUNET_CHAT_Context *context,
    628                    struct GNUNET_CHAT_Message *msg)
    629 {
    630   g_assert((app) && (context) && (msg));
    631 
    632   UI_CHAT_ENTRY_Handle *handle = GNUNET_CHAT_context_get_user_pointer(context);
    633 
    634   const enum GNUNET_CHAT_MessageKind kind = GNUNET_CHAT_message_get_kind(
    635     msg
    636   );
    637 
    638   if (GNUNET_CHAT_KIND_JOIN == kind)
    639   {
    640     if (!handle)
    641       _add_new_chat_entry(app, context);
    642     else
    643       enqueue_chat_entry_update(handle);
    644 
    645     if (app->settings.leave_chats_delay > 0)
    646       util_timeout_add_seconds(
    647         app->settings.leave_chats_delay,
    648         _delayed_context_drop,
    649         context
    650       );
    651   }
    652   else if ((handle) && (handle->entry_box))
    653     _clear_chat_entry(gtk_widget_get_parent(handle->entry_box), app);
    654 
    655   struct GNUNET_CHAT_Contact *contact = GNUNET_CHAT_message_get_sender(
    656     msg
    657   );
    658 
    659   if (!contact)
    660     return;
    661 
    662   if (GNUNET_YES == contact_create_info(contact))
    663     contact_update_attributes(contact, app);
    664 }
    665 
    666 static void
    667 _update_contact_context(struct GNUNET_CHAT_Contact *contact)
    668 {
    669   g_assert(contact);
    670 
    671   struct GNUNET_CHAT_Context *context = GNUNET_CHAT_contact_get_context(
    672     contact
    673   );
    674 
    675   if (!context)
    676     return;
    677 
    678   UI_CHAT_ENTRY_Handle *handle = GNUNET_CHAT_context_get_user_pointer(context);
    679 
    680   if (!handle)
    681     return;
    682 
    683   enqueue_chat_entry_update(handle);
    684 }
    685 
    686 void
    687 event_presence_contact(MESSENGER_Application *app,
    688                        struct GNUNET_CHAT_Context *context,
    689                        struct GNUNET_CHAT_Message *msg)
    690 {
    691   g_assert((app) && (context) && (msg));
    692 
    693   UI_CHAT_ENTRY_Handle *handle = GNUNET_CHAT_context_get_user_pointer(context);
    694 
    695   struct GNUNET_CHAT_Contact *contact = GNUNET_CHAT_message_get_sender(
    696     msg
    697   );
    698 
    699   if (!contact)
    700     return;
    701 
    702   contact_create_info(contact);
    703 
    704   if (!handle)
    705     return;
    706 
    707   struct GNUNET_CHAT_Group *group = GNUNET_CHAT_context_get_group(context);
    708 
    709   UI_MESSAGE_Handle *message = NULL;
    710   gboolean previous_presence = FALSE;
    711 
    712   if (group)
    713     message = (UI_MESSAGE_Handle*) (
    714 	    GNUNET_CHAT_member_get_user_pointer(group, contact)
    715     );
    716   else
    717     message = (UI_MESSAGE_Handle*) contact_get_last_message_from_info(contact);
    718 
    719   if (message)
    720   {
    721     ui_chat_remove_message(handle->chat, app, message);
    722     previous_presence = TRUE;
    723   }
    724 
    725   message = ui_message_new(app, UI_MESSAGE_STATUS);
    726   ui_message_update(message, app, msg);
    727 
    728   contact_update_attributes(contact, app);
    729   _update_contact_context(contact);
    730 
    731   ui_message_set_contact(message, contact);
    732 
    733   const enum GNUNET_CHAT_MessageKind kind = GNUNET_CHAT_message_get_kind(
    734     msg
    735   );
    736 
    737   const char *text = (
    738     GNUNET_CHAT_KIND_JOIN == kind? _("joined the chat") : _("left the chat")
    739   );
    740 
    741   if ((!ui_messenger_is_context_active(&(app->ui.messenger), context)) &&
    742       ((!previous_presence) || (GNUNET_CHAT_KIND_LEAVE == kind)) &&
    743       (GNUNET_YES == GNUNET_CHAT_message_is_recent(msg)))
    744     _show_notification(
    745       app,
    746       context,
    747       contact,
    748       text,
    749       "avatar-default-symbolic",
    750       "presence.online"
    751     );
    752 
    753   char time [20];
    754   time_t timestamp = GNUNET_CHAT_message_get_timestamp(
    755     msg
    756   );
    757 
    758   strftime(time, 20, "%Y-%m-%d %H:%M:%S", localtime(&timestamp));
    759 
    760   ui_label_set_text(message->text_label, text);
    761   ui_label_set_text(message->timestamp_label, time);
    762 
    763   ui_chat_add_message(handle->chat, app, message);
    764 
    765   if (group)
    766     GNUNET_CHAT_member_set_user_pointer(group, contact, message);
    767   else
    768     contact_set_last_message_to_info(contact, message);
    769 
    770   enqueue_chat_entry_update(handle);
    771 }
    772 
    773 void
    774 event_update_contacts(UNUSED MESSENGER_Application *app,
    775                       struct GNUNET_CHAT_Context *context,
    776                       struct GNUNET_CHAT_Message *msg)
    777 {
    778   g_assert((app) && (msg));
    779 
    780   struct GNUNET_CHAT_Contact *contact = GNUNET_CHAT_message_get_sender(
    781     msg
    782   );
    783 
    784   if (!contact)
    785     return;
    786 
    787   if (GNUNET_CHAT_KIND_SHARED_ATTRIBUTES == GNUNET_CHAT_message_get_kind(msg))
    788     contact_update_attributes(contact, app);
    789 
    790   contact_update_info(contact);
    791   _update_contact_context(contact);
    792 
    793   if (!context)
    794     return;
    795 
    796   UI_CHAT_ENTRY_Handle *handle = GNUNET_CHAT_context_get_user_pointer(context);
    797 
    798   if (!handle)
    799     return;
    800 
    801   enqueue_chat_entry_update(handle);
    802 }
    803 
    804 static void
    805 _event_invitation_action(MESSENGER_Application *app,
    806                          gboolean status,
    807                          gpointer user_data)
    808 {
    809   g_assert((app) && (user_data));
    810 
    811   struct GNUNET_CHAT_Invitation *invitation = (
    812     (struct GNUNET_CHAT_Invitation*) user_data
    813   );
    814 
    815   application_chat_lock(app);
    816 
    817   if (status)
    818     GNUNET_CHAT_invitation_accept(invitation);
    819   else
    820     GNUNET_CHAT_invitation_reject(invitation);
    821 
    822   application_chat_unlock(app);
    823 }
    824 
    825 void
    826 event_invitation(MESSENGER_Application *app,
    827                  struct GNUNET_CHAT_Context *context,
    828                  struct GNUNET_CHAT_Message *msg)
    829 {
    830   g_assert((app) && (context) && (msg));
    831 
    832   UI_CHAT_ENTRY_Handle *handle = GNUNET_CHAT_context_get_user_pointer(context);
    833 
    834   if (!handle)
    835     return;
    836 
    837   struct GNUNET_CHAT_Invitation *invitation;
    838   invitation = GNUNET_CHAT_message_get_invitation(msg);
    839 
    840   if (!invitation)
    841     return;
    842 
    843   UI_MESSAGE_Handle *message = GNUNET_CHAT_message_get_user_pointer(msg);
    844   enum GNUNET_GenericReturnValue new_message = GNUNET_NO;
    845 
    846   if (!message)
    847   {
    848     if (app->settings.delete_invitations_delay > 0)
    849       GNUNET_CHAT_message_delete(
    850         msg,
    851         app->settings.delete_invitations_delay
    852       );
    853 
    854     const enum GNUNET_GenericReturnValue sent =
    855       GNUNET_CHAT_message_is_sent(msg);
    856 
    857     if ((GNUNET_YES != sent) && (app->settings.send_read_receipts))
    858       GNUNET_CHAT_context_send_read_receipt(context, msg);
    859 
    860     message = ui_message_new(app, UI_MESSAGE_STATUS);
    861     new_message = GNUNET_YES;
    862   }
    863 
    864   if (!message)
    865     return;
    866 
    867   ui_message_update(message, app, msg);
    868 
    869   if (GNUNET_YES == new_message)
    870   {
    871     struct GNUNET_CHAT_Contact *sender = GNUNET_CHAT_message_get_sender(
    872       msg
    873     );
    874 
    875     struct GNUNET_CHAT_Contact *recipient = GNUNET_CHAT_message_get_recipient(
    876       msg
    877     );
    878 
    879     ui_message_set_contact(message, sender);
    880 
    881     const char *invite_message = (
    882       GNUNET_YES != GNUNET_CHAT_invitation_is_direct(invitation)
    883     )? _("invited %s to a chat") : _("requested %s to chat");
    884 
    885     const char *recipient_name = (
    886       (recipient) && 
    887       (GNUNET_YES != GNUNET_CHAT_contact_is_owned(recipient))
    888     )? GNUNET_CHAT_contact_get_name(recipient) : _("you");
    889 
    890     GString *message_string = g_string_new(NULL);
    891     g_string_printf(message_string, invite_message, recipient_name);
    892 
    893     if ((!ui_messenger_is_context_active(&(app->ui.messenger), context)) &&
    894         (GNUNET_YES == GNUNET_CHAT_message_is_recent(msg)))
    895       _show_notification(
    896         app,
    897         context,
    898         sender,
    899         message_string->str,
    900         "mail-message-new-symbolic",
    901         "im.received"
    902       );
    903 
    904     ui_label_set_text(message->text_label, message_string->str);
    905     g_string_free(message_string, TRUE);
    906 
    907     ui_message_set_status_callback(
    908       message, _event_invitation_action, invitation
    909     );
    910 
    911     ui_chat_add_message(handle->chat, app, message);
    912   }
    913 
    914   enqueue_chat_entry_update(handle);
    915 }
    916 
    917 void
    918 event_receive_message(MESSENGER_Application *app,
    919                       struct GNUNET_CHAT_Context *context,
    920                       struct GNUNET_CHAT_Message *msg)
    921 {
    922   g_assert((app) && (context) && (msg));
    923 
    924   UI_CHAT_ENTRY_Handle *handle = GNUNET_CHAT_context_get_user_pointer(context);
    925 
    926   if (!handle)
    927     return;
    928 
    929   const int sent = GNUNET_CHAT_message_is_sent(msg);
    930 
    931   if ((sent) && (app->settings.auto_delete_delay > 0))
    932     GNUNET_CHAT_message_delete(
    933       msg,
    934       app->settings.auto_delete_delay
    935     );
    936 
    937   const gchar *text = GNUNET_CHAT_message_get_text(msg);
    938 
    939   if ((text) && (!(*text)))
    940     goto skip_message;
    941 
    942   if ((GNUNET_YES != sent) && (app->settings.send_read_receipts))
    943     GNUNET_CHAT_context_send_read_receipt(context, msg);
    944 
    945   const UI_MESSAGE_Type type = (
    946     GNUNET_YES == sent? UI_MESSAGE_SENT : UI_MESSAGE_DEFAULT
    947   );
    948 
    949   UI_MESSAGE_Handle *message = ui_message_new(app, type);
    950 
    951   struct GNUNET_CHAT_File *file = GNUNET_CHAT_message_get_file(msg);
    952 
    953   if (file)
    954   {
    955     file_create_info(file);
    956     file_add_ui_message_to_info(file, message);
    957 
    958     if (app->settings.delete_files_delay > 0)
    959       GNUNET_CHAT_message_delete(
    960         msg,
    961         app->settings.delete_files_delay
    962       );
    963   }
    964 
    965   ui_message_update(message, app, msg);
    966 
    967   struct GNUNET_CHAT_Contact *contact = GNUNET_CHAT_message_get_sender(
    968     msg
    969   );
    970 
    971   ui_message_set_contact(message, contact);
    972 
    973   char time [20];
    974   time_t timestamp = GNUNET_CHAT_message_get_timestamp(
    975     msg
    976   );
    977 
    978   strftime(time, 20, "%Y-%m-%d %H:%M:%S", localtime(&timestamp));
    979 
    980   if ((!ui_messenger_is_context_active(&(app->ui.messenger), context)) &&
    981       (GNUNET_YES == GNUNET_CHAT_message_is_recent(msg)) &&
    982       (GNUNET_YES != sent))
    983     _show_notification(
    984       app,
    985       context,
    986       contact,
    987       text,
    988       "mail-unread-symbolic",
    989       "im.received"
    990     );
    991 
    992   ui_label_set_markup_text(message->text_label, text);
    993   ui_label_set_text(message->timestamp_label, time);
    994 
    995   ui_chat_add_message(handle->chat, app, message);
    996 
    997 skip_message:
    998   enqueue_chat_entry_update(handle);
    999 }
   1000 
   1001 static UI_MESSAGE_Handle*
   1002 _find_ui_message_handle(MESSENGER_Application *app,
   1003                         struct GNUNET_CHAT_Context *context,
   1004                         struct GNUNET_CHAT_Message *msg)
   1005 {
   1006   g_assert((app) && (context) && (msg));
   1007 
   1008   UI_CHAT_ENTRY_Handle *handle = GNUNET_CHAT_context_get_user_pointer(context);
   1009 
   1010   if ((!handle) || (!(handle->chat)))
   1011     return NULL;
   1012 
   1013   UI_MESSAGE_Handle *message = GNUNET_CHAT_message_get_user_pointer(msg);
   1014 
   1015   if (!message)
   1016   {
   1017     GList *rows = gtk_container_get_children(
   1018       GTK_CONTAINER(handle->chat->messages_listbox)
   1019     );
   1020 
   1021     for (GList *row = rows; row; row = row->next)
   1022     {
   1023       message = (UI_MESSAGE_Handle*) g_object_get_qdata(
   1024         G_OBJECT(row->data), app->quarks.ui
   1025       );
   1026 
   1027       if ((message) && (message->msg == msg))
   1028         break;
   1029 
   1030       message = NULL;
   1031     }
   1032 
   1033     if (rows)
   1034       g_list_free(rows);
   1035   }
   1036 
   1037   return message;
   1038 }
   1039 
   1040 static void
   1041 _event_update_tag_message_state(MESSENGER_Application *app,
   1042                                 struct GNUNET_CHAT_Context *context,
   1043                                 struct GNUNET_CHAT_Message *msg)
   1044 {
   1045   g_assert((msg) && (GNUNET_CHAT_KIND_TAG == GNUNET_CHAT_message_get_kind(msg)));
   1046 
   1047   struct GNUNET_CHAT_Message *target;
   1048   target = GNUNET_CHAT_message_get_target(msg);
   1049 
   1050   if (!target)
   1051     return;
   1052 
   1053   struct GNUNET_CHAT_Contact *contact;
   1054   contact = GNUNET_CHAT_message_get_sender(target);
   1055 
   1056   if (contact)
   1057     contact_update_info(contact);
   1058 
   1059   UI_CHAT_ENTRY_Handle *handle = GNUNET_CHAT_context_get_user_pointer(context);
   1060 
   1061   if ((!handle) || (!(handle->chat)))
   1062     return;
   1063 
   1064   UI_MESSAGE_Handle *message = _find_ui_message_handle(app, context, target);
   1065 
   1066   if (!message)
   1067     return;
   1068 
   1069   if (GNUNET_YES == GNUNET_CHAT_message_is_deleted(msg))
   1070     ui_message_remove_tag(message, app, msg);
   1071   else
   1072     ui_message_add_tag(message, app, msg);
   1073 }
   1074 
   1075 void
   1076 event_delete_message(MESSENGER_Application *app,
   1077                      struct GNUNET_CHAT_Context *context,
   1078                      struct GNUNET_CHAT_Message *msg)
   1079 {
   1080   g_assert((app) && (context) && (msg));
   1081 
   1082   UI_CHAT_ENTRY_Handle *handle = GNUNET_CHAT_context_get_user_pointer(context);
   1083 
   1084   if ((!handle) || (!(handle->chat)))
   1085     return;
   1086 
   1087   UI_MESSAGE_Handle *message = _find_ui_message_handle(app, context, msg);
   1088 
   1089   if (message)
   1090     ui_chat_remove_message(handle->chat, app, message);
   1091 
   1092   if (GNUNET_CHAT_KIND_TAG == GNUNET_CHAT_message_get_kind(msg))
   1093     _event_update_tag_message_state(app, context, msg);
   1094 
   1095   enqueue_chat_entry_update(handle);
   1096 }
   1097 
   1098 void
   1099 event_tag_message(MESSENGER_Application *app,
   1100                   struct GNUNET_CHAT_Context *context,
   1101                   struct GNUNET_CHAT_Message *msg)
   1102 {
   1103   g_assert((app) && (context) && (msg));
   1104 
   1105   UI_CHAT_ENTRY_Handle *handle = GNUNET_CHAT_context_get_user_pointer(context);
   1106 
   1107   struct GNUNET_CHAT_Message *target = GNUNET_CHAT_message_get_target(msg);
   1108 
   1109   _event_update_tag_message_state(app, context, msg);
   1110 
   1111   if ((!handle) || (!(handle->chat)))
   1112     return;
   1113 
   1114   UI_MESSAGE_Handle *message = _find_ui_message_handle(app, context, target);
   1115 
   1116   if (message)
   1117     ui_message_update(message, app, message->msg);
   1118 
   1119   enqueue_chat_entry_update(handle);
   1120 }
   1121 
   1122 static enum GNUNET_GenericReturnValue
   1123 _iterate_contacts_update_own(void *cls,
   1124                              UNUSED struct GNUNET_CHAT_Handle *handle,
   1125                              struct GNUNET_CHAT_Contact *contact)
   1126 {
   1127   g_assert((cls) && (contact));
   1128 
   1129   MESSENGER_Application *app = (MESSENGER_Application*) cls;
   1130 
   1131   if (GNUNET_YES != GNUNET_CHAT_contact_is_owned(contact))
   1132     return GNUNET_YES;
   1133 
   1134   contact_update_attributes(contact, app);
   1135   return GNUNET_YES;
   1136 }
   1137 
   1138 void
   1139 event_update_attributes(MESSENGER_Application *app)
   1140 {
   1141   g_assert(app);
   1142 
   1143   CHAT_MESSENGER_Handle *chat = &(app->chat.messenger);
   1144 
   1145   struct GNUNET_CHAT_Account *account = GNUNET_CHAT_get_connected(
   1146     chat->handle
   1147   );
   1148 
   1149   if (account)
   1150     account_update_attributes(account, app);
   1151 
   1152   GNUNET_CHAT_iterate_contacts(
   1153     chat->handle,
   1154     _iterate_contacts_update_own,
   1155     app
   1156   );
   1157 }
   1158 
   1159 void
   1160 event_discourse(MESSENGER_Application *app,
   1161                 struct GNUNET_CHAT_Context *context,
   1162                 struct GNUNET_CHAT_Message *msg)
   1163 {
   1164   g_assert((app) && (context) && (msg));
   1165 
   1166   struct GNUNET_CHAT_Discourse *discourse = GNUNET_CHAT_message_get_discourse(
   1167     msg
   1168   );
   1169 
   1170   if (!discourse)
   1171     return;
   1172 
   1173   if (GNUNET_YES == GNUNET_CHAT_message_is_sent(msg))
   1174   {
   1175     if (GNUNET_YES == GNUNET_CHAT_discourse_is_open(discourse))
   1176       discourse_create_info(discourse);
   1177     else
   1178       discourse_destroy_info(discourse);
   1179   }
   1180 
   1181   discourse_update_subscriptions(discourse);
   1182 
   1183   if (context == app->ui.discourse.context)
   1184     ui_discourse_window_update(&(app->ui.discourse), context);
   1185 }
   1186 
   1187 void
   1188 event_discourse_data(MESSENGER_Application *app,
   1189                      struct GNUNET_CHAT_Context *context,
   1190                      struct GNUNET_CHAT_Message *msg)
   1191 {
   1192   g_assert((app) && (context) && (msg));
   1193 
   1194   struct GNUNET_CHAT_Discourse *discourse = GNUNET_CHAT_message_get_discourse(
   1195     msg
   1196   );
   1197 
   1198   if (!discourse)
   1199     return;
   1200 
   1201   discourse_stream_message(discourse, msg);
   1202 }