/* This file is part of GNUnet. Copyright (C) 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 ui/messages.c */ #include "messages.h" #include "list_input.h" #include "text_input.h" #include "../application.h" #include "../util.h" struct tm* _messages_new_day(time_t* current_time, const time_t* timestamp) { struct tm* ts = localtime(timestamp); ts->tm_sec = 0; ts->tm_min = 0; ts->tm_hour = 0; const time_t date_time = timelocal(ts); if (date_time <= *current_time) { return NULL; } *current_time = date_time; return ts; } void _messages_handle_message(UI_MESSAGES_Handle *messages) { switch (GNUNET_CHAT_message_get_kind(messages->selected)) { case GNUNET_CHAT_KIND_INVITATION: { struct GNUNET_CHAT_Invitation *invitation = ( GNUNET_CHAT_message_get_invitation(messages->selected) ); if (invitation) GNUNET_CHAT_invitation_accept(invitation); break; } case GNUNET_CHAT_KIND_FILE: { struct GNUNET_CHAT_File *file = GNUNET_CHAT_message_get_file( messages->selected ); if ((file) && (GNUNET_YES != GNUNET_CHAT_file_is_downloading(file))) GNUNET_CHAT_file_start_download(file, NULL, NULL); break; default: break; } } } void messages_event(UI_MESSAGES_Handle *messages, MESSENGER_Application *app, int key) { list_input_reset(messages); messages->line_time = 0; UI_MESSAGES_List *element = messages->head; while (element) { struct tm *ts = _messages_new_day( &(messages->line_time), &(element->timestamp) ); list_input_select(messages, ts? 2 : 1, element->message); element = element->next; } list_input_select(messages, 1, NULL); switch (key) { case 27: case KEY_EXIT: app->chat.context = NULL; break; case '\t': app->chat.show_members = TRUE; break; case '\n': case KEY_ENTER: if (messages->selected) _messages_handle_message(messages); else if (messages->text_len > 0) { if (0 != strncmp(messages->text, UI_MESSAGES_FILE_PREFIX, UI_MESSAGES_FILE_PREFIX_LEN)) goto write_text; const char* filename = messages->text + 5; if (0 != access(filename, R_OK | F_OK)) break; GNUNET_CHAT_context_send_file( app->chat.context, filename, NULL, NULL ); goto drop_text; write_text: GNUNET_CHAT_context_send_text( app->chat.context, messages->text ); drop_text: messages->text_len = 0; } break; case KEY_BACKSPACE: if (messages->selected) GNUNET_CHAT_message_delete( messages->selected, GNUNET_TIME_relative_get_zero_() ); break; default: break; } if (!(messages->selected)) text_input_event(messages->text, key); list_input_event(messages, key); } void _messages_iterate_print(UI_MESSAGES_Handle *messages, const time_t* timestamp, const struct GNUNET_CHAT_Message *message) { enum GNUNET_CHAT_MessageKind kind = GNUNET_CHAT_message_get_kind(message); struct GNUNET_CHAT_Contact *sender = GNUNET_CHAT_message_get_sender(message); const char *name = sender? GNUNET_CHAT_contact_get_name(sender) : NULL; const char *text = GNUNET_CHAT_message_get_text(message); const struct GNUNET_CHAT_File *file = GNUNET_CHAT_message_get_file(message); struct tm* ts = localtime(timestamp); char time_buf [255]; strftime(time_buf, sizeof(time_buf), "%H:%M", ts); ts = _messages_new_day(&(messages->line_time), timestamp); list_input_print(messages, ts? 2 : 1); wmove(messages->window, y, 0); if (ts) { char date_buf [255]; strftime(date_buf, sizeof(date_buf), "%x", ts); const int width = getmaxx(messages->window); whline(messages->window, '-', width); wmove(messages->window, y, 8); wprintw(messages->window, " %s ", date_buf); wmove(messages->window, y+1, 0); } const int attrs_select = A_BOLD; if (selected) wattron(messages->window, attrs_select); wprintw(messages->window, " %s | ", time_buf); switch (kind) { case GNUNET_CHAT_KIND_JOIN: wprintw( messages->window, "%s joins the room.", name ); break; case GNUNET_CHAT_KIND_LEAVE: wprintw( messages->window, "%s leaves the room.", name ); break; case GNUNET_CHAT_KIND_INVITATION: wprintw( messages->window, "%s invites you to a room.", name ); break; case GNUNET_CHAT_KIND_TEXT: wprintw( messages->window, "%s: %s", name, text ); break; case GNUNET_CHAT_KIND_FILE: { const char *filename = GNUNET_CHAT_file_get_name(file); const uint64_t localsize = GNUNET_CHAT_file_get_local_size(file); const uint64_t filesize = GNUNET_CHAT_file_get_size(file); wprintw( messages->window, "%s shares the file '%s' (%lu / %lu) with you.", name, filename, localsize, filesize ); break; } default: wprintw( messages->window, "[%d] %s: %s", (int) kind, name, text ); break; } if (selected) wattroff(messages->window, attrs_select); } void messages_print(UI_MESSAGES_Handle *messages) { if (!(messages->window)) return; list_input_reset(messages); messages->line_time = 0; werase(messages->window); UI_MESSAGES_List *element = messages->head; while (element) { _messages_iterate_print(messages, &(element->timestamp), element->message); element = element->next; } const int count = messages->line_index; const bool selected = (count == messages->line_selected); const int width = getmaxx(messages->window); const int height = getmaxy(messages->window); const int line_height = height - 2; wmove(messages->window, line_height, 0); whline(messages->window, '-', width); const bool is_file_text = (0 == strncmp( messages->text, UI_MESSAGES_FILE_PREFIX, UI_MESSAGES_FILE_PREFIX_LEN )); const int attrs_select = A_BOLD | (is_file_text? A_ITALIC : A_NORMAL); if (selected) wattron(messages->window, attrs_select); wmove(messages->window, height - 1, 0); wprintw(messages->window, "%s", messages->text); if (selected) wattroff(messages->window, attrs_select); wmove(messages->window, height - 1, messages->text_pos); if (selected) { wcursyncup(messages->window); curs_set(1); } } void messages_clear(UI_MESSAGES_Handle *messages) { UI_MESSAGES_List *element; while (messages->head) { element = messages->head; GNUNET_CONTAINER_DLL_remove( messages->head, messages->tail, element ); GNUNET_free(element); } } static int _message_compare_timestamps(UNUSED void *cls, UI_MESSAGES_List *list0, UI_MESSAGES_List *list1) { if ((!list0) || (!list1)) return 0; if (list0->timestamp > list1->timestamp) return -1; else if (list0->timestamp < list1->timestamp) return +1; else return 0; } void messages_add(UI_MESSAGES_Handle *messages, const struct GNUNET_CHAT_Message *message) { enum GNUNET_CHAT_MessageKind kind = GNUNET_CHAT_message_get_kind(message); switch (kind) { case GNUNET_CHAT_KIND_CONTACT: case GNUNET_CHAT_KIND_WHISPER: case GNUNET_CHAT_KIND_DELETION: return; default: break; } list_input_reset(messages); messages->line_time = 0; UI_MESSAGES_List *element = messages->head; while (element) { struct tm *ts = _messages_new_day( &(messages->line_time), &(element->timestamp) ); list_input_select(messages, ts? 2 : 1, element->message); element = element->next; } list_input_select(messages, 1, NULL); const struct GNUNET_TIME_Absolute abs_time = ( GNUNET_CHAT_message_get_timestamp(message) ); const struct GNUNET_TIME_Timestamp timestamp = ( GNUNET_TIME_absolute_to_timestamp(abs_time) ); element = GNUNET_new(UI_MESSAGES_List); element->timestamp = GNUNET_TIME_timestamp_to_s(timestamp); element->message = message; GNUNET_CONTAINER_DLL_insert_sorted( UI_MESSAGES_List, _message_compare_timestamps, NULL, messages->head, messages->tail, element ); list_input_select(messages, 1, NULL); if (!(messages->selected)) list_input_event(messages, KEY_DOWN); } void messages_remove(UI_MESSAGES_Handle *messages, const struct GNUNET_CHAT_Message *message) { UI_MESSAGES_List *element = messages->head; while (element) { if (element->message == message) break; element = element->next; } if (element) GNUNET_CONTAINER_DLL_remove( messages->head, messages->tail, element ); }