messenger-gtk

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

event.c (25701B)


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