/* This file is part of GNUnet. Copyright (C) 2021--2022 GNUnet e.V. GNUnet is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. GNUnet is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. You should have received a copy of the GNU Affero General Public License along with this program. If not, see . SPDX-License-Identifier: AGPL3.0-or-later */ /* * @author Tobias Frisch * @file event.c */ #include "event.h" #include "contact.h" #include "file.h" #include "ui.h" #include "ui/account_entry.h" #include "ui/chat_entry.h" #include "ui/contact_entry.h" #include "ui/message.h" static void _close_notification(NotifyNotification* notification, gpointer user_data) { MESSENGER_Application *app = (MESSENGER_Application*) user_data; app->notifications = g_list_remove(app->notifications, notification); notify_notification_clear_actions(notification); notify_notification_clear_hints(notification); g_object_unref(notification); } static void _show_notification(MESSENGER_Application *app, UNUSED struct GNUNET_CHAT_Context *context, const struct GNUNET_CHAT_Contact *contact, const char *text, const char *icon, const char *category) { if (app->settings.disable_notifications) return; const char *sender = GNUNET_CHAT_contact_get_name(contact); NotifyNotification *notification = notify_notification_new( sender? sender : "(unknown)", text, icon ); if (!notification) return; app->notifications = g_list_append(app->notifications, notification); notify_notification_set_category(notification, category); g_signal_connect( notification, "closed", G_CALLBACK(_close_notification), app ); notify_notification_show(notification, NULL); } void event_handle_warning(MESSENGER_Application *app, struct GNUNET_CHAT_Context *context, const struct GNUNET_CHAT_Message *msg) { const char *text = GNUNET_CHAT_message_get_text(msg); const struct GNUNET_CHAT_Contact *contact = GNUNET_CHAT_message_get_sender( msg ); g_printerr("ERROR: %s\n", text); _show_notification( app, context, contact, text, "dialog-warning-symbolic", "im.error" ); } static gboolean _idle_refresh_accounts(gpointer user_data) { MESSENGER_Application *app = (MESSENGER_Application*) user_data; if (!(app->ui.messenger.main_window)) goto refresh_exit; if (gtk_widget_is_visible(GTK_WIDGET(app->ui.messenger.main_window))) ui_messenger_refresh(app, &(app->ui.messenger)); else ui_accounts_dialog_refresh(app, &(app->ui.accounts)); refresh_exit: app->ui.messenger.account_refresh = 0; return FALSE; } void event_refresh_accounts(MESSENGER_Application *app) { if (app->ui.messenger.account_refresh) g_source_remove(app->ui.messenger.account_refresh); if (app->ui.messenger.main_window) app->ui.messenger.account_refresh = g_idle_add( G_SOURCE_FUNC(_idle_refresh_accounts), app ); else app->ui.messenger.account_refresh = 0; } static gboolean _select_chat_to_activate(gpointer user_data) { UI_CHAT_ENTRY_Handle *entry = (UI_CHAT_ENTRY_Handle*) user_data; if ((!entry) || (!(entry->chat)) || (!(entry->entry_box))) return FALSE; MESSENGER_Application *app = entry->chat->app; if (!app) return FALSE; GtkListBoxRow *row = GTK_LIST_BOX_ROW( gtk_widget_get_parent(entry->entry_box) ); if (!row) return FALSE; UI_MESSENGER_Handle *ui = &(app->ui.messenger); gtk_list_box_select_row(ui->chats_listbox, row); gtk_list_box_invalidate_filter(ui->chats_listbox); gtk_widget_activate(GTK_WIDGET(row)); ui->chat_selection = 0; return FALSE; } static gboolean _idle_chat_entry_update(gpointer user_data) { UI_CHAT_ENTRY_Handle *entry = (UI_CHAT_ENTRY_Handle*) user_data; if ((!(entry->chat)) || (!(entry->chat->app)) || (!(entry->chat->send_text_view))) goto update_exit; struct GNUNET_CHAT_Context *context = (struct GNUNET_CHAT_Context*) ( g_object_get_qdata( G_OBJECT(entry->chat->send_text_view), entry->chat->app->quarks.data ) ); ui_chat_entry_update(entry, entry->chat->app, context); update_exit: entry->update = 0; return FALSE; } static void enqueue_chat_entry_update(UI_CHAT_ENTRY_Handle *entry) { if (entry->update) g_source_remove(entry->update); entry->update = g_idle_add( G_SOURCE_FUNC(_idle_chat_entry_update), entry ); } static void _add_new_chat_entry(MESSENGER_Application *app, struct GNUNET_CHAT_Context *context) { UI_MESSENGER_Handle *ui = &(app->ui.messenger); UI_CHAT_ENTRY_Handle *entry = ui_chat_entry_new(app); enqueue_chat_entry_update(entry); gtk_container_add(GTK_CONTAINER(ui->chats_listbox), entry->entry_box); GNUNET_CHAT_context_set_user_pointer(context, entry); gtk_container_add( GTK_CONTAINER(ui->chats_stack), entry->chat->chat_box ); g_object_set_qdata( G_OBJECT(entry->chat->send_text_view), app->quarks.data, context ); ui->chat_entries = g_list_append(ui->chat_entries, entry); GtkWidget *row = gtk_widget_get_parent(entry->entry_box); g_object_set_qdata( G_OBJECT(row), app->quarks.ui, entry ); if (ui->chat_selection) g_source_remove(ui->chat_selection); ui->chat_selection = g_idle_add( G_SOURCE_FUNC(_select_chat_to_activate), entry ); } static int _iterate_profile_contacts(void *cls, UNUSED struct GNUNET_CHAT_Handle *handle, struct GNUNET_CHAT_Contact *contact) { MESSENGER_Application *app = (MESSENGER_Application*) cls; struct GNUNET_CHAT_Context *context = GNUNET_CHAT_contact_get_context( contact ); if (GNUNET_SYSERR == GNUNET_CHAT_context_get_status(context)) return GNUNET_YES; _add_new_chat_entry(app, context); return GNUNET_YES; } static int _iterate_profile_groups(void *cls, UNUSED struct GNUNET_CHAT_Handle *handle, UNUSED struct GNUNET_CHAT_Group *group) { MESSENGER_Application *app = (MESSENGER_Application*) cls; struct GNUNET_CHAT_Context *context = GNUNET_CHAT_group_get_context( group ); if (GNUNET_SYSERR == GNUNET_CHAT_context_get_status(context)) return GNUNET_YES; _add_new_chat_entry(app, GNUNET_CHAT_group_get_context(group)); return GNUNET_YES; } static void _clear_chat_entry(GtkWidget *widget, gpointer user_data) { MESSENGER_Application *app = (MESSENGER_Application*) user_data; GtkListBoxRow *row = GTK_LIST_BOX_ROW(widget); if (!gtk_list_box_row_get_selectable(row)) return; UI_CHAT_ENTRY_Handle *entry = (UI_CHAT_ENTRY_Handle*) g_object_get_qdata( G_OBJECT(row), app->quarks.ui ); ui_chat_entry_dispose(entry, app); } void event_update_profile(MESSENGER_Application *app) { UI_MESSENGER_Handle *ui = &(app->ui.messenger); CHAT_MESSENGER_Handle *chat = &(app->chat.messenger); const char *name = GNUNET_CHAT_get_name(chat->handle); ui_avatar_set_text(ui->profile_avatar, name); ui_label_set_text(ui->profile_label, name); const char *key = GNUNET_CHAT_get_key(chat->handle); ui_label_set_text(ui->profile_key_label, key); gtk_container_foreach( GTK_CONTAINER(ui->chats_listbox), _clear_chat_entry, app ); gtk_stack_set_visible_child(ui->chats_stack, ui->no_chat_box); GNUNET_CHAT_iterate_contacts(chat->handle, _iterate_profile_contacts, app); GNUNET_CHAT_iterate_groups(chat->handle, _iterate_profile_groups, app); } gboolean _delayed_context_drop(gpointer user_data) { struct GNUNET_CHAT_Context *context = (struct GNUNET_CHAT_Context*) user_data; struct GNUNET_CHAT_Group *group = GNUNET_CHAT_context_get_group(context); struct GNUNET_CHAT_Contact *contact = GNUNET_CHAT_context_get_contact(context); if (group) GNUNET_CHAT_group_leave(group); else if (contact) GNUNET_CHAT_contact_delete(contact); return FALSE; } void event_update_chats(MESSENGER_Application *app, struct GNUNET_CHAT_Context *context, const struct GNUNET_CHAT_Message *msg) { UI_CHAT_ENTRY_Handle *handle = GNUNET_CHAT_context_get_user_pointer(context); const enum GNUNET_CHAT_MessageKind kind = GNUNET_CHAT_message_get_kind( msg ); if (GNUNET_CHAT_KIND_JOIN == kind) { if (!handle) _add_new_chat_entry(app, context); else enqueue_chat_entry_update(handle); if (app->settings.leave_chats_delay > 0) g_timeout_add_seconds( app->settings.leave_chats_delay, _delayed_context_drop, context ); } else if (handle) _clear_chat_entry(gtk_widget_get_parent(handle->entry_box), app); contact_create_info(GNUNET_CHAT_message_get_sender(msg)); } static void _update_contact_context(struct GNUNET_CHAT_Contact *contact) { struct GNUNET_CHAT_Context *context = GNUNET_CHAT_contact_get_context( contact ); if (!context) return; UI_CHAT_ENTRY_Handle *handle = GNUNET_CHAT_context_get_user_pointer(context); if (!handle) return; enqueue_chat_entry_update(handle); } void event_presence_contact(MESSENGER_Application *app, struct GNUNET_CHAT_Context *context, const struct GNUNET_CHAT_Message *msg) { UI_CHAT_ENTRY_Handle *handle = GNUNET_CHAT_context_get_user_pointer(context); if (!handle) return; struct GNUNET_CHAT_Contact *contact = GNUNET_CHAT_message_get_sender( msg ); if (!contact) return; const enum GNUNET_CHAT_MessageKind kind = GNUNET_CHAT_message_get_kind( msg ); struct GNUNET_CHAT_Group *group = GNUNET_CHAT_context_get_group(context); UI_MESSAGE_Handle *message = NULL; contact_create_info(contact); if (group) message = (UI_MESSAGE_Handle*) ( GNUNET_CHAT_member_get_user_pointer(group, contact) ); else message = (UI_MESSAGE_Handle*) contact_get_last_message_from_info(contact); if (message) ui_chat_remove_message(handle->chat, app, message); message = ui_message_new(app, UI_MESSAGE_STATUS); ui_message_update(message, app, msg); _update_contact_context(contact); contact_add_name_avatar_to_info(contact, message->sender_avatar); contact_add_name_label_to_info(contact, message->sender_label); const char *text = ( GNUNET_CHAT_KIND_JOIN == kind? _("joined the chat") : _("left the chat") ); if (!ui_messenger_is_context_active(&(app->ui.messenger), context)) _show_notification( app, context, contact, text, "avatar-default-symbolic", "presence.online" ); struct GNUNET_TIME_Absolute timestamp = GNUNET_CHAT_message_get_timestamp( msg ); const char *time = GNUNET_STRINGS_absolute_time_to_string(timestamp); ui_label_set_text(message->text_label, text); ui_label_set_text(message->timestamp_label, time); ui_chat_add_message(handle->chat, app, message); if (group) GNUNET_CHAT_member_set_user_pointer(group, contact, message); else contact_set_last_message_to_info(contact, message); enqueue_chat_entry_update(handle); } void event_update_contacts(UNUSED MESSENGER_Application *app, struct GNUNET_CHAT_Context *context, const struct GNUNET_CHAT_Message *msg) { struct GNUNET_CHAT_Contact *contact = GNUNET_CHAT_message_get_sender( msg ); if (!contact) return; contact_update_info(contact); _update_contact_context(contact); UI_CHAT_ENTRY_Handle *handle = GNUNET_CHAT_context_get_user_pointer(context); if (!handle) return; enqueue_chat_entry_update(handle); } static void _event_invitation_accept_click(UNUSED GtkButton *button, gpointer user_data) { struct GNUNET_CHAT_Invitation *invitation = ( (struct GNUNET_CHAT_Invitation*) user_data ); GNUNET_CHAT_invitation_accept(invitation); } void event_invitation(MESSENGER_Application *app, struct GNUNET_CHAT_Context *context, const struct GNUNET_CHAT_Message *msg) { UI_CHAT_ENTRY_Handle *handle = GNUNET_CHAT_context_get_user_pointer(context); if (!handle) return; struct GNUNET_CHAT_Invitation *invitation; invitation = GNUNET_CHAT_message_get_invitation(msg); if (!invitation) return; if (app->settings.delete_invitations_delay > 0) GNUNET_CHAT_message_delete(msg, GNUNET_TIME_relative_multiply( GNUNET_TIME_relative_get_second_(), app->settings.delete_invitations_delay )); const int sent = GNUNET_CHAT_message_is_sent(msg); if ((GNUNET_YES != sent) && (app->settings.send_read_receipts)) GNUNET_CHAT_context_send_read_receipt(context, msg); UI_MESSAGE_Handle *message = ui_message_new(app, UI_MESSAGE_STATUS); ui_message_update(message, app, msg); const struct GNUNET_CHAT_Contact *contact = GNUNET_CHAT_message_get_sender( msg ); contact_add_name_avatar_to_info(contact, message->sender_avatar); contact_add_name_label_to_info(contact, message->sender_label); const char *invite_message = _("invited you to a chat"); if (!ui_messenger_is_context_active(&(app->ui.messenger), context)) _show_notification( app, context, contact, invite_message, "mail-message-new-symbolic", "im.received" ); ui_label_set_text(message->text_label, invite_message); g_signal_connect( message->accept_button, "clicked", G_CALLBACK(_event_invitation_accept_click), invitation ); gtk_widget_show(GTK_WIDGET(message->deny_button)); gtk_widget_show(GTK_WIDGET(message->accept_button)); ui_chat_add_message(handle->chat, app, message); if (app->settings.accept_all_invitations) gtk_button_clicked(message->accept_button); enqueue_chat_entry_update(handle); } void event_receive_message(MESSENGER_Application *app, struct GNUNET_CHAT_Context *context, const struct GNUNET_CHAT_Message *msg) { UI_CHAT_ENTRY_Handle *handle = GNUNET_CHAT_context_get_user_pointer(context); if (!handle) return; const int sent = GNUNET_CHAT_message_is_sent(msg); if ((sent) && (app->settings.auto_delete_delay > 0)) GNUNET_CHAT_message_delete(msg, GNUNET_TIME_relative_multiply( GNUNET_TIME_relative_get_second_(), app->settings.auto_delete_delay )); const gboolean whispering = ( GNUNET_CHAT_KIND_WHISPER == GNUNET_CHAT_message_get_kind(msg) ); if ((whispering) && (!(app->settings.show_whispering))) return; const gchar *text = GNUNET_CHAT_message_get_text(msg); if (whispering) text = _("whispers..."); if ((text) && (!(*text))) goto skip_message; if ((GNUNET_YES != sent) && (app->settings.send_read_receipts)) GNUNET_CHAT_context_send_read_receipt(context, msg); const UI_MESSAGE_Type type = ( GNUNET_YES == sent? UI_MESSAGE_SENT : UI_MESSAGE_DEFAULT ); UI_MESSAGE_Handle *message = ui_message_new(app, type); struct GNUNET_CHAT_File *file = GNUNET_CHAT_message_get_file(msg); if (file) { file_create_info(file); file_add_ui_message_to_info(file, message); if (app->settings.delete_files_delay > 0) GNUNET_CHAT_message_delete(msg, GNUNET_TIME_relative_multiply( GNUNET_TIME_relative_get_second_(), app->settings.delete_files_delay )); } ui_message_update(message, app, msg); const struct GNUNET_CHAT_Contact *contact = GNUNET_CHAT_message_get_sender( msg ); contact_add_name_avatar_to_info(contact, message->sender_avatar); contact_add_name_label_to_info(contact, message->sender_label); struct GNUNET_TIME_Absolute timestamp = GNUNET_CHAT_message_get_timestamp( msg ); const char *time = GNUNET_STRINGS_absolute_time_to_string(timestamp); if ((!ui_messenger_is_context_active(&(app->ui.messenger), context)) && (GNUNET_YES != sent)) _show_notification( app, context, contact, text, "mail-unread-symbolic", "im.received" ); ui_label_set_text(message->text_label, text); ui_label_set_text(message->timestamp_label, time); ui_chat_add_message(handle->chat, app, message); skip_message: enqueue_chat_entry_update(handle); } void event_delete_message(MESSENGER_Application *app, struct GNUNET_CHAT_Context *context, const struct GNUNET_CHAT_Message *msg) { UI_CHAT_ENTRY_Handle *handle = GNUNET_CHAT_context_get_user_pointer(context); if ((!handle) || (!(handle->chat))) return; GList *messages = handle->chat->messages; while (messages) { UI_MESSAGE_Handle *message = (UI_MESSAGE_Handle*) (messages->data); if ((message) && (message->msg == GNUNET_CHAT_message_get_target(msg))) { ui_chat_remove_message(handle->chat, app, message); break; } messages = messages->next; } enqueue_chat_entry_update(handle); }