diff options
author | dec05eba <dec05eba@protonmail.com> | 2022-11-07 22:21:52 +0100 |
---|---|---|
committer | dec05eba <dec05eba@protonmail.com> | 2022-11-11 00:53:46 +0100 |
commit | e19a29c7e51860144f02d7e7b08ac5e430e1f78f (patch) | |
tree | f57adcf98b77a271b4df74a20e2389b73f495df7 /src | |
parent | 5d2a7d977f9b0a1604e106f4e2b0c2c9b89c3235 (diff) |
Support images in text, add custom emoji to matrix
Diffstat (limited to 'src')
-rw-r--r-- | src/Body.cpp | 6 | ||||
-rw-r--r-- | src/QuickMedia.cpp | 49 | ||||
-rw-r--r-- | src/Tabs.cpp | 1 | ||||
-rw-r--r-- | src/Text.cpp | 138 | ||||
-rw-r--r-- | src/main.cpp | 4 | ||||
-rw-r--r-- | src/plugins/Matrix.cpp | 595 |
6 files changed, 642 insertions, 151 deletions
diff --git a/src/Body.cpp b/src/Body.cpp index 5bc4f2a..7411a79 100644 --- a/src/Body.cpp +++ b/src/Body.cpp @@ -59,7 +59,7 @@ namespace QuickMedia { body_spacing[BODY_THEME_MINIMAL].body_padding_vertical = std::floor(10.0f * get_config().scale * get_config().spacing_scale); body_spacing[BODY_THEME_MINIMAL].reaction_background_padding_x = std::floor(7.0f * get_config().scale * get_config().spacing_scale); - body_spacing[BODY_THEME_MINIMAL].reaction_background_padding_y = std::floor(3.0f * get_config().scale * get_config().spacing_scale); + body_spacing[BODY_THEME_MINIMAL].reaction_background_padding_y = std::floor(5.0f * get_config().scale * get_config().spacing_scale); body_spacing[BODY_THEME_MINIMAL].reaction_spacing_x = std::floor(5.0f * get_config().scale * get_config().spacing_scale); body_spacing[BODY_THEME_MINIMAL].reaction_padding_y = std::floor(7.0f * get_config().scale * get_config().spacing_scale); body_spacing[BODY_THEME_MINIMAL].embedded_item_font_size = std::floor(get_config().body.embedded_load_font_size * get_config().scale * get_config().font_scale); @@ -76,7 +76,7 @@ namespace QuickMedia { body_spacing[BODY_THEME_MODERN_SPACIOUS].body_padding_vertical = std::floor(20.0f * get_config().scale * get_config().spacing_scale); body_spacing[BODY_THEME_MODERN_SPACIOUS].reaction_background_padding_x = std::floor(7.0f * get_config().scale * get_config().spacing_scale); - body_spacing[BODY_THEME_MODERN_SPACIOUS].reaction_background_padding_y = std::floor(3.0f * get_config().scale * get_config().spacing_scale); + body_spacing[BODY_THEME_MODERN_SPACIOUS].reaction_background_padding_y = std::floor(5.0f * get_config().scale * get_config().spacing_scale); body_spacing[BODY_THEME_MODERN_SPACIOUS].reaction_spacing_x = std::floor(5.0f * get_config().scale * get_config().spacing_scale); body_spacing[BODY_THEME_MODERN_SPACIOUS].reaction_padding_y = std::floor(7.0f * get_config().scale * get_config().spacing_scale); body_spacing[BODY_THEME_MODERN_SPACIOUS].embedded_item_font_size = std::floor(get_config().body.embedded_load_font_size * get_config().scale * get_config().font_scale); @@ -1519,7 +1519,7 @@ namespace QuickMedia { } if(reaction.text) { - reaction.text->set_position(reaction_background.get_position() + mgl::vec2f(body_spacing[body_theme].reaction_background_padding_x, - 4.0f + body_spacing[body_theme].reaction_background_padding_y)); + reaction.text->set_position(reaction_background.get_position() + mgl::vec2f(body_spacing[body_theme].reaction_background_padding_x, -6.0f + body_spacing[body_theme].reaction_background_padding_y)); reaction_background.draw(window); reaction.text->draw(window); diff --git a/src/QuickMedia.cpp b/src/QuickMedia.cpp index c528056..fc3cba6 100644 --- a/src/QuickMedia.cpp +++ b/src/QuickMedia.cpp @@ -71,7 +71,6 @@ extern "C" { static int FPS_IDLE; static const double IDLE_TIMEOUT_SEC = 2.0; static const mgl::vec2i AVATAR_THUMBNAIL_SIZE(std::floor(32), std::floor(32)); -static const float more_items_height = 2.0f; static const int FPS_SYNC_TO_VSYNC = 0; static const std::pair<const char*, const char*> valid_plugins[] = { @@ -5319,17 +5318,10 @@ namespace QuickMedia { return load_cached_related_embedded_item(body_item, message, me.get(), current_room->get_user_display_name(me), me->user_id, message_body_items); } - static std::string message_to_qm_text(Message *message) { - if(message->body_is_formatted) - return formatted_text_to_qm_text(message->body.c_str(), message->body.size(), true); - else - return message->body; - } - - static std::shared_ptr<BodyItem> message_to_body_item(RoomData *room, Message *message, const std::string &my_display_name, const std::string &my_user_id) { + static std::shared_ptr<BodyItem> message_to_body_item(Matrix *matrix, RoomData *room, Message *message, const std::string &my_display_name, const std::string &my_user_id) { auto body_item = BodyItem::create(""); body_item->set_author(extract_first_line_remove_newline_elipses(room->get_user_display_name(message->user), AUTHOR_MAX_LENGTH)); - body_item->set_description(strip(message_to_qm_text(message))); + body_item->set_description(strip(message_to_qm_text(matrix, message))); body_item->set_timestamp(message->timestamp); if(!message->thumbnail_url.empty()) { body_item->thumbnail_url = message->thumbnail_url; @@ -5364,10 +5356,10 @@ namespace QuickMedia { return body_item; } - static BodyItems messages_to_body_items(RoomData *room, const Messages &messages, const std::string &my_display_name, const std::string &my_user_id) { + static BodyItems messages_to_body_items(Matrix *matrix, RoomData *room, const Messages &messages, const std::string &my_display_name, const std::string &my_user_id) { BodyItems result_items(messages.size()); for(size_t i = 0; i < messages.size(); ++i) { - result_items[i] = message_to_body_item(room, messages[i].get(), my_display_name, my_user_id); + result_items[i] = message_to_body_item(matrix, room, messages[i].get(), my_display_name, my_user_id); } return result_items; } @@ -5659,10 +5651,10 @@ namespace QuickMedia { // TODO: Properly check reply message objects for mention of user instead of message data, but only when synapse fixes that notifications // are not triggered by reply to a message with our display name/user id. Message *edited_message_ref = static_cast<Message*>(body_item->userdata); - std::string qm_formatted_text = message_to_qm_text(message.get()); + std::string qm_formatted_text = message_to_qm_text(matrix, message.get()); body_item->set_description(std::move(qm_formatted_text)); - if(message->user != me && message_contains_user_mention(message.get(), my_display_name, me->user_id)) + if(message->user != me && message_contains_user_mention(matrix, message.get(), my_display_name, me->user_id)) body_item->set_description_color(get_theme().attention_alert_text_color, true); else body_item->set_description_color(get_theme().text_color); @@ -5700,10 +5692,10 @@ namespace QuickMedia { // TODO: Properly check reply message objects for mention of user instead of message data, but only when synapse fixes that notifications // are not triggered by reply to a message with our display name/user id. Message *edited_message_ref = static_cast<Message*>(body_item->userdata); - std::string qm_formatted_text = formatted_text_to_qm_text(message->body.c_str(), message->body.size(), true); + std::string qm_formatted_text = formatted_text_to_qm_text(matrix, message->body.c_str(), message->body.size(), true); body_item->set_description(std::move(qm_formatted_text)); - if(message->user != me && message_contains_user_mention(message.get(), my_display_name, me->user_id)) + if(message->user != me && message_contains_user_mention(matrix, message.get(), my_display_name, me->user_id)) body_item->set_description_color(get_theme().attention_alert_text_color, true); else body_item->set_description_color(get_theme().text_color); @@ -5832,7 +5824,7 @@ namespace QuickMedia { fetched_messages_set.insert(message->event_id); } auto me = matrix->get_me(current_room); - auto new_body_items = messages_to_body_items(current_room, all_messages, current_room->get_user_display_name(me), me->user_id); + auto new_body_items = messages_to_body_items(matrix, current_room, all_messages, current_room->get_user_display_name(me), me->user_id); messages_load_cached_related_embedded_item(new_body_items, tabs[MESSAGES_TAB_INDEX].body->get_items(), me, current_room); tabs[MESSAGES_TAB_INDEX].body->insert_items_by_timestamps(std::move(new_body_items)); modify_related_messages_in_current_room(all_messages); @@ -6143,7 +6135,7 @@ namespace QuickMedia { message->type = MessageType::REACTION; message->related_event_type = RelatedEventType::REACTION; message->related_event_id = static_cast<Message*>(related_to_message)->event_id; - auto body_item = message_to_body_item(current_room, message.get(), current_room->get_user_avatar_url(me), me->user_id); + auto body_item = message_to_body_item(matrix, current_room, message.get(), current_room->get_user_avatar_url(me), me->user_id); load_cached_related_embedded_item(body_item.get(), message.get(), me, current_room, tabs[MESSAGES_TAB_INDEX].body->get_items()); tabs[MESSAGES_TAB_INDEX].body->insert_items_by_timestamps({body_item}); Messages messages; @@ -6158,7 +6150,7 @@ namespace QuickMedia { return provisional_message; }); } else { - auto body_item = message_to_body_item(current_room, message.get(), current_room->get_user_avatar_url(me), me->user_id); + auto body_item = message_to_body_item(matrix, current_room, message.get(), current_room->get_user_avatar_url(me), me->user_id); body_item->set_description_color(get_theme().provisional_message_color); load_cached_related_embedded_item(body_item.get(), message.get(), me, current_room, tabs[MESSAGES_TAB_INDEX].body->get_items()); tabs[MESSAGES_TAB_INDEX].body->insert_items_by_timestamps({body_item}); @@ -6183,7 +6175,7 @@ namespace QuickMedia { void *related_to_message = currently_operating_on_item->userdata; message->related_event_type = RelatedEventType::REPLY; message->related_event_id = static_cast<Message*>(related_to_message)->event_id; - auto body_item = message_to_body_item(current_room, message.get(), current_room->get_user_avatar_url(me), me->user_id); + auto body_item = message_to_body_item(matrix, current_room, message.get(), current_room->get_user_avatar_url(me), me->user_id); body_item->set_description_color(get_theme().provisional_message_color); load_cached_related_embedded_item(body_item.get(), message.get(), me, current_room, tabs[MESSAGES_TAB_INDEX].body->get_items()); tabs[MESSAGES_TAB_INDEX].body->insert_items_by_timestamps({body_item}); @@ -6212,13 +6204,13 @@ namespace QuickMedia { auto body_item = find_body_item_by_event_id(tabs[MESSAGES_TAB_INDEX].body->get_items().data(), tabs[MESSAGES_TAB_INDEX].body->get_items().size(), message->related_event_id, &body_item_index); if(body_item) { const std::string formatted_text = matrix->body_to_formatted_body(current_room, text); - std::string qm_formatted_text = formatted_text_to_qm_text(formatted_text.c_str(), formatted_text.size(), true); + std::string qm_formatted_text = formatted_text_to_qm_text(matrix, formatted_text.c_str(), formatted_text.size(), true); auto body_item_shared_ptr = tabs[MESSAGES_TAB_INDEX].body->get_item_by_index(body_item_index); body_item_shared_ptr->set_description(std::move(qm_formatted_text)); body_item_shared_ptr->set_description_color(get_theme().provisional_message_color); - auto edit_body_item = message_to_body_item(current_room, message.get(), current_room->get_user_avatar_url(me), me->user_id); + auto edit_body_item = message_to_body_item(matrix, current_room, message.get(), current_room->get_user_avatar_url(me), me->user_id); edit_body_item->visible = false; load_cached_related_embedded_item(edit_body_item.get(), message.get(), me, current_room, tabs[MESSAGES_TAB_INDEX].body->get_items()); tabs[MESSAGES_TAB_INDEX].body->insert_items_by_timestamps({edit_body_item}); @@ -6466,7 +6458,7 @@ namespace QuickMedia { } all_messages.insert(all_messages.end(), message_list->begin(), message_list->end()); - auto new_body_items = messages_to_body_items(current_room, *message_list, current_room->get_user_display_name(me), me->user_id); + auto new_body_items = messages_to_body_items(matrix, current_room, *message_list, current_room->get_user_display_name(me), me->user_id); messages_load_cached_related_embedded_item(new_body_items, tabs[MESSAGES_TAB_INDEX].body->get_items(), me, current_room); tabs[MESSAGES_TAB_INDEX].body->insert_items_by_timestamps(std::move(new_body_items)); modify_related_messages_in_current_room(*message_list); @@ -6536,7 +6528,7 @@ namespace QuickMedia { } }; - auto add_new_messages_to_current_room = [&me, &tabs, &ui_tabs, ¤t_room, MESSAGES_TAB_INDEX, &after_token](Messages &messages) { + auto add_new_messages_to_current_room = [this, &me, &tabs, &ui_tabs, ¤t_room, MESSAGES_TAB_INDEX, &after_token](Messages &messages) { if(messages.empty()) return; @@ -6555,7 +6547,7 @@ namespace QuickMedia { if(!after_token.empty()) scroll_to_end = false; - auto new_body_items = messages_to_body_items(current_room, messages, current_room->get_user_display_name(me), me->user_id); + auto new_body_items = messages_to_body_items(matrix, current_room, messages, current_room->get_user_display_name(me), me->user_id); messages_load_cached_related_embedded_item(new_body_items, tabs[MESSAGES_TAB_INDEX].body->get_items(), me, current_room); tabs[MESSAGES_TAB_INDEX].body->insert_items_by_timestamps(std::move(new_body_items)); if(scroll_to_end) @@ -7489,7 +7481,7 @@ namespace QuickMedia { if(fetch_message_tab == PINNED_TAB_INDEX) { PinnedEventData *event_data = static_cast<PinnedEventData*>(fetch_body_item->userdata); if(fetch_message_result.message) { - *fetch_body_item = *message_to_body_item(current_room, fetch_message_result.message.get(), current_room->get_user_display_name(me), me->user_id); + *fetch_body_item = *message_to_body_item(matrix, current_room, fetch_message_result.message.get(), current_room->get_user_display_name(me), me->user_id); event_data->status = FetchStatus::FINISHED_LOADING; event_data->message = fetch_message_result.message.get(); fetch_body_item->userdata = event_data; @@ -7499,7 +7491,7 @@ namespace QuickMedia { } } else if(fetch_message_tab == MESSAGES_TAB_INDEX) { if(fetch_message_result.message) { - fetch_body_item->embedded_item = message_to_body_item(current_room, fetch_message_result.message.get(), current_room->get_user_display_name(me), me->user_id); + fetch_body_item->embedded_item = message_to_body_item(matrix, current_room, fetch_message_result.message.get(), current_room->get_user_display_name(me), me->user_id); fetch_body_item->embedded_item_status = FetchStatus::FINISHED_LOADING; if(fetch_message_result.message->user == me) fetch_body_item->set_description_color(get_theme().attention_alert_text_color, true); @@ -7846,6 +7838,9 @@ namespace QuickMedia { auto matrix_room_directory_page = std::make_unique<MatrixRoomDirectoryPage>(this, matrix); auto settings_body = create_body(); + auto custom_emoji_body_item = BodyItem::create("Custom emoji"); + custom_emoji_body_item->url = "emoji"; + settings_body->append_item(std::move(custom_emoji_body_item)); auto join_body_item = BodyItem::create("Join room"); join_body_item->url = "join"; settings_body->append_item(std::move(join_body_item)); diff --git a/src/Tabs.cpp b/src/Tabs.cpp index a5c371a..41554fb 100644 --- a/src/Tabs.cpp +++ b/src/Tabs.cpp @@ -8,6 +8,7 @@ #include <mglpp/window/Event.hpp> #include <mglpp/window/Window.hpp> #include <mglpp/graphics/Texture.hpp> +#include <assert.h> namespace QuickMedia { static float floor(float v) { diff --git a/src/Text.cpp b/src/Text.cpp index 3ecf24c..c43944b 100644 --- a/src/Text.cpp +++ b/src/Text.cpp @@ -4,10 +4,12 @@ #include "../include/Theme.hpp" #include "../include/AsyncImageLoader.hpp" #include "../include/StringUtils.hpp" +#include "../include/Scale.hpp" #include "../generated/Emoji.hpp" #include <string.h> #include <stack> #include <unistd.h> +#include <assert.h> #include <mglpp/graphics/Rectangle.hpp> #include <mglpp/window/Event.hpp> #include <mglpp/window/Window.hpp> @@ -45,6 +47,8 @@ namespace QuickMedia static const uint8_t FORMATTED_TEXT_START = '\x02'; static const uint8_t FORMATTED_TEXT_END = '\x03'; + static const mgl::vec2i MAX_IMAGE_SIZE(300, 300); + enum class FormattedTextType : uint8_t { TEXT, IMAGE @@ -92,6 +96,7 @@ namespace QuickMedia setString(std::move(_str)); } + // TODO: Validate |str|. Turn |str| into a valid utf-8 string void Text::setString(std::string str) { //if(str != this->str) @@ -119,6 +124,7 @@ namespace QuickMedia dirtyText = true; } + // TODO: Alt text. Helpful when copying the text. Or do we want to copy the url instead? // static std::string Text::formatted_image(const std::string &url, bool local, mgl::vec2i size) { const uint32_t str_size = url.size(); @@ -455,6 +461,7 @@ namespace QuickMedia return size; image_url.assign(str + offset, text_size); + image_size = clamp_to_size(image_size, MAX_IMAGE_SIZE); return std::min(offset + text_size + 1, size); // + 1 for FORMATTED_TEXT_END } @@ -518,7 +525,8 @@ namespace QuickMedia text_elements.push_back(std::move(text_element)); } else { text_element.text_num_bytes = 1; - text_elements.push_back(std::move(text_element)); + if(!text_element.url.empty()) + text_elements.push_back(std::move(text_element)); } } } else if(match_emoji_sequence((const unsigned char*)str.data() + index, size - index, emoji_sequence, emoji_sequence_length, emoji_byte_length)) { @@ -585,10 +593,18 @@ namespace QuickMedia return vertices[vertex_ref.vertices_index][vertex_ref.index + 5].position.x; } + float Text::get_text_quad_top_side(const VertexRef &vertex_ref) const { + return vertices[vertex_ref.vertices_index][vertex_ref.index + 1].position.y; + } + float Text::get_text_quad_bottom_side(const VertexRef &vertex_ref) const { return vertices[vertex_ref.vertices_index][vertex_ref.index + 4].position.y; } + float Text::get_text_quad_height(const VertexRef &vertex_ref) const { + return get_text_quad_bottom_side(vertex_ref) - get_text_quad_top_side(vertex_ref); + } + float Text::get_caret_offset_by_caret_index(int index) const { const int num_vertices = vertices_linear.size(); if(num_vertices == 0) @@ -636,6 +652,69 @@ namespace QuickMedia static mgl::vec2f vec2f_floor(mgl::vec2f value) { return mgl::vec2f((int)value.x, (int)value.y); } + + void Text::move_vertex_lines_by_largest_items(int vertices_linear_end) { + if(vertices_linear.empty() || vertices_linear_end == 0) + return; + + mgl::Font *latin_font; + if(bold_font) + latin_font = FontLoader::get_font(FontLoader::FontType::LATIN_BOLD, characterSize); + else + latin_font = FontLoader::get_font(FontLoader::FontType::LATIN, characterSize); + + const float vspace = font_get_real_height(latin_font); + const float vertex_height = get_text_quad_height(vertices_linear[0]); + float vertex_max_height = std::max(vertex_height, vspace); + float vertex_second_max_height = vspace; + int current_line = vertices_linear[0].line; + int current_line_vertices_linear_start = 0; + float move_y = 0.0f; + + for(int i = 0; i < vertices_linear_end; ++i) { + VertexRef &vertex_ref = vertices_linear[i]; + mgl::Vertex *vertex = &vertices[vertex_ref.vertices_index][vertex_ref.index]; + for(int v = 0; v < 6; ++v) { + vertex[v].position.y += move_y; + } + + if(vertices_linear[i].line != current_line) { + const float vertices_move_down_offset = vertex_max_height - vertex_second_max_height; + if(vertex_max_height > vspace/* && vertex_max_height - vertex_min_height > 2.0f*/) { + for(int j = current_line_vertices_linear_start; j <= i; ++j) { + VertexRef &vertex_ref = vertices_linear[j]; + mgl::Vertex *vertex = &vertices[vertex_ref.vertices_index][vertex_ref.index]; + for(int v = 0; v < 6; ++v) { + vertex[v].position.y += vertices_move_down_offset; + } + } + move_y += vertices_move_down_offset; + } + + vertex_max_height = vspace; + current_line = vertices_linear[i].line; + current_line_vertices_linear_start = i; + } + + const float vertex_height = std::max(get_text_quad_height(vertex_ref), vspace); + if(vertex_height > vertex_max_height) { + vertex_second_max_height = vertex_max_height; + vertex_max_height = vertex_height; + } + } + + const float vertices_move_down_offset = vertex_max_height - vertex_second_max_height; + if(vertex_max_height > vspace/* && vertex_max_height - vertex_min_height > 2.0f*/) { + // TODO: current_line_vertices_linear_start vs vertices_linear_end + for(int j = current_line_vertices_linear_start; j < vertices_linear_end; ++j) { + VertexRef &vertex_ref = vertices_linear[j]; + mgl::Vertex *vertex = &vertices[vertex_ref.vertices_index][vertex_ref.index]; + for(int v = 0; v < 6; ++v) { + vertex[v].position.y += vertices_move_down_offset; + } + } + } + } void Text::updateGeometry(bool update_even_if_not_dirty) { if(dirtyText) { @@ -668,8 +747,8 @@ namespace QuickMedia latin_font = FontLoader::get_font(FontLoader::FontType::LATIN, characterSize); const float latin_font_width = latin_font->get_glyph(' ').advance; - const float hspace_latin = latin_font_width + characterSpacing; const float vspace = font_get_real_height(latin_font); + const float hspace_latin = latin_font_width + characterSpacing; const float emoji_spacing = 2.0f; int hspace_monospace = 0; @@ -686,7 +765,7 @@ namespace QuickMedia mgl::vec2f glyphPos; uint32_t prevCodePoint = 0; // TODO: Only do this if dirtyText (then the Text object shouldn't be reset in Body. There should be a cleanup function in text instead) - for(usize textElementIndex = 0; textElementIndex < textElements.size(); ++textElementIndex) + for(int textElementIndex = 0; textElementIndex < (int)textElements.size(); ++textElementIndex) { TextElement &textElement = textElements[textElementIndex]; @@ -714,11 +793,11 @@ namespace QuickMedia if(prevCodePoint != 0) glyphPos.x += emoji_spacing; - const float font_height_offset = 0.0f;//floor(vspace * 0.6f); - mgl::vec2f vertexTopLeft(glyphPos.x, glyphPos.y + font_height_offset - textElement.size.y * 0.5f); - mgl::vec2f vertexTopRight(glyphPos.x + textElement.size.x, glyphPos.y + font_height_offset - textElement.size.y * 0.5f); - mgl::vec2f vertexBottomLeft(glyphPos.x, glyphPos.y + font_height_offset + textElement.size.y * 0.5f); - mgl::vec2f vertexBottomRight(glyphPos.x + textElement.size.x, glyphPos.y + font_height_offset + textElement.size.y * 0.5f); + const float font_height_offset = vspace; + mgl::vec2f vertexTopLeft(glyphPos.x, glyphPos.y + font_height_offset - textElement.size.y); + mgl::vec2f vertexTopRight(glyphPos.x + textElement.size.x, glyphPos.y + font_height_offset - textElement.size.y); + mgl::vec2f vertexBottomLeft(glyphPos.x, glyphPos.y + font_height_offset); + mgl::vec2f vertexBottomRight(glyphPos.x + textElement.size.x, glyphPos.y + font_height_offset); vertexTopLeft = vec2f_floor(vertexTopLeft); vertexTopRight = vec2f_floor(vertexTopRight); @@ -1018,6 +1097,7 @@ namespace QuickMedia } } vertices_linear_done:; + move_vertex_lines_by_largest_items(vertices_linear_index); // TODO: Optimize for(TextElement &textElement : textElements) { @@ -1052,19 +1132,19 @@ namespace QuickMedia boundingBox.size.y = 0.0f; for(VertexRef &vertex_ref : vertices_linear) { boundingBox.size.x = std::max(boundingBox.size.x, get_text_quad_right_side(vertex_ref)); - //boundingBox.size.y = std::max(boundingBox.size.y, get_text_quad_bottom_side(vertex_ref)); + boundingBox.size.y = std::max(boundingBox.size.y, get_text_quad_bottom_side(vertex_ref)); } - boundingBox.size.y = num_lines * line_height; + //boundingBox.size.y = num_lines * line_height; //boundingBox.size.y = text_offset_y; // TODO: - //if(vertices_linear.empty()) - // boundingBox.size.y = line_height; + if(vertices_linear.empty()) + boundingBox.size.y = line_height; - //if(editable) - // boundingBox.size.y = num_lines * line_height; + if(editable) + boundingBox.size.y = num_lines * line_height; // TODO: Clear |vertices| somehow even with editable text for(size_t i = 0; i < FONT_ARRAY_SIZE; ++i) { @@ -1458,16 +1538,21 @@ namespace QuickMedia target.draw(vertex_buffers[FONT_INDEX_EMOJI]); }*/ + // TODO: Use rounded rectangle for fallback image + // TODO: Use a new vector with only the image data instead of this. // TODO: Sprite mgl::Sprite sprite; - mgl::Rectangle fallback_emoji(mgl::vec2f(vspace, vspace)); - fallback_emoji.set_color(get_theme().shade_color); + mgl::Rectangle fallback_image(mgl::vec2f(vspace, vspace)); + fallback_image.set_color(get_theme().image_loading_background_color); for(const TextElement &textElement : textElements) { if(textElement.text_type == TextElement::TextType::EMOJI) { - if(textElement.pos.to_vec2f().y + vspace > boundingBox.size.y) + // TODO: + if(textElement.pos.to_vec2f().y + vspace > boundingBox.size.y + 10.0f) { + fprintf(stderr, "bounding box y: %f\n", boundingBox.size.y); continue; + } auto emoji_data = AsyncImageLoader::get_instance().get_thumbnail(textElement.url, textElement.local, { (int)vspace, (int)vspace }); if(emoji_data->loading_state == LoadingState::FINISHED_LOADING) { @@ -1480,18 +1565,17 @@ namespace QuickMedia if(emoji_data->loading_state == LoadingState::APPLIED_TO_TEXTURE && emoji_data->texture.get_size().x > 0) { sprite.set_texture(&emoji_data->texture); sprite.set_position(pos + textElement.pos.to_vec2f()); - sprite.set_size(textElement.size.to_vec2f()); + //sprite.set_size(textElement.size.to_vec2f()); target.draw(sprite); } else { - fallback_emoji.set_position(pos + textElement.pos.to_vec2f()); - target.draw(fallback_emoji); + fallback_image.set_position(pos + textElement.pos.to_vec2f()); + target.draw(fallback_image); } } } // TODO: Use a new vector with only the image data instead of this. // TODO: Sprite - #if 0 for(const TextElement &textElement : textElements) { if(textElement.type == TextElement::Type::IMAGE) { auto thumbnail_data = AsyncImageLoader::get_instance().get_thumbnail(textElement.url, textElement.local, textElement.size); @@ -1502,18 +1586,22 @@ namespace QuickMedia thumbnail_data->loading_state = LoadingState::APPLIED_TO_TEXTURE; } - if(thumbnail_data->loading_state == LoadingState::APPLIED_TO_TEXTURE) { - if(textElement.pos.to_vec2f().y + thumbnail_data->texture->get_size().y > boundingBox.size.y) + if(thumbnail_data->loading_state == LoadingState::APPLIED_TO_TEXTURE && thumbnail_data->texture.get_size().x > 0) { + // TODO: + if(textElement.pos.to_vec2f().y + thumbnail_data->texture.get_size().y > boundingBox.size.y + 10.0f) continue; sprite.set_texture(&thumbnail_data->texture); sprite.set_position(pos + textElement.pos.to_vec2f()); - sprite.set_size(textElement.size.to_vec2f()); + //sprite.set_size(textElement.size.to_vec2f()); target.draw(sprite); + } else { + fallback_image.set_size(textElement.size.to_vec2f()); + fallback_image.set_position(pos + textElement.pos.to_vec2f()); + target.draw(fallback_image); } } } - #endif if(!editable) return true; pos.y -= floor(vspace*1.25f); diff --git a/src/main.cpp b/src/main.cpp index 52cb374..ef0568b 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1,9 +1,7 @@ #include "../include/QuickMedia.hpp" -#include <unistd.h> -#include <X11/Xlib.h> +#include <locale.h> int main(int argc, char **argv) { - XInitThreads(); setlocale(LC_ALL, "C"); // Sigh... stupid C QuickMedia::Program program; return program.run(argc, argv); diff --git a/src/plugins/Matrix.cpp b/src/plugins/Matrix.cpp index f79b10c..28b4823 100644 --- a/src/plugins/Matrix.cpp +++ b/src/plugins/Matrix.cpp @@ -9,6 +9,7 @@ #include "../../include/AsyncImageLoader.hpp" #include "../../include/Config.hpp" #include "../../include/Theme.hpp" +#include "../../include/Scale.hpp" #include <rapidjson/document.h> #include <rapidjson/writer.h> #include <rapidjson/stringbuffer.h> @@ -27,14 +28,15 @@ namespace QuickMedia { static const mgl::vec2i thumbnail_max_size(600, 337); + static const mgl::vec2i custom_emoji_max_size(64, 64); static const char* SERVICE_NAME = "matrix"; static const char* OTHERS_ROOM_TAG = "tld.name.others"; // Filter without account data. TODO: We include pinned events but limit events to 1. That means if the last event is a pin, // then we cant see room message preview. TODO: Fix this somehow. // TODO: What about state events in initial sync in timeline? such as user display name change. - static const char* INITIAL_FILTER = "{\"presence\":{\"limit\":0,\"types\":[\"\"]},\"account_data\":{\"limit\":0,\"types\":[\"\"]},\"room\":{\"state\":{\"not_types\":[\"m.room.related_groups\",\"m.room.power_levels\",\"m.room.join_rules\",\"m.room.history_visibility\"],\"lazy_load_members\":true},\"timeline\":{\"types\":[\"m.room.message\"],\"limit\":1,\"lazy_load_members\":true},\"ephemeral\":{\"limit\":0,\"types\":[\"\"],\"lazy_load_members\":true},\"account_data\":{\"limit\":1,\"types\":[\"m.fully_read\",\"m.tag\",\"qm.last_read_message_timestamp\"],\"lazy_load_members\":true}}}"; - static const char* ADDITIONAL_MESSAGES_FILTER = "{\"presence\":{\"limit\":0,\"types\":[\"\"]},\"account_data\":{\"limit\":0,\"types\":[\"\"]},\"room\":{\"state\":{\"not_types\":[\"m.room.related_groups\",\"m.room.power_levels\",\"m.room.join_rules\",\"m.room.history_visibility\"],\"lazy_load_members\":true},\"timeline\":{\"limit\":20,\"lazy_load_members\":true},\"ephemeral\":{\"limit\":0,\"types\":[\"\"],\"lazy_load_members\":true},\"account_data\":{\"limit\":0,\"types\":[\"\"],\"lazy_load_members\":true}}}"; - static const char* CONTINUE_FILTER = "{\"presence\":{\"limit\":0,\"types\":[\"\"]},\"account_data\":{\"limit\":0,\"types\":[\"\"]},\"room\":{\"state\":{\"not_types\":[\"m.room.related_groups\",\"m.room.power_levels\",\"m.room.join_rules\",\"m.room.history_visibility\"],\"lazy_load_members\":true},\"timeline\":{\"lazy_load_members\":true},\"ephemeral\":{\"limit\":0,\"types\":[\"\"],\"lazy_load_members\":true},\"account_data\":{\"types\":[\"m.fully_read\",\"m.tag\",\"qm.last_read_message_timestamp\"],\"lazy_load_members\":true}}}"; + static const char* INITIAL_FILTER = "{\"presence\":{\"limit\":0,\"types\":[\"\"]},\"account_data\":{\"types\":[\"qm.emoji\",\"m.direct\"]},\"room\":{\"state\":{\"not_types\":[\"m.room.related_groups\",\"m.room.power_levels\",\"m.room.join_rules\",\"m.room.history_visibility\"],\"lazy_load_members\":true},\"timeline\":{\"types\":[\"m.room.message\"],\"limit\":1,\"lazy_load_members\":true},\"ephemeral\":{\"limit\":0,\"types\":[\"\"],\"lazy_load_members\":true},\"account_data\":{\"limit\":1,\"types\":[\"m.fully_read\",\"m.tag\",\"qm.last_read_message_timestamp\"],\"lazy_load_members\":true}}}"; + static const char* ADDITIONAL_MESSAGES_FILTER = "{\"presence\":{\"types\":[\"\"]},\"account_data\":{\"limit\":0,\"types\":[\"\"]},\"room\":{\"state\":{\"not_types\":[\"m.room.related_groups\",\"m.room.power_levels\",\"m.room.join_rules\",\"m.room.history_visibility\"],\"lazy_load_members\":true},\"timeline\":{\"limit\":20,\"lazy_load_members\":true},\"ephemeral\":{\"limit\":0,\"types\":[\"\"],\"lazy_load_members\":true},\"account_data\":{\"limit\":0,\"types\":[\"\"],\"lazy_load_members\":true}}}"; + static const char* CONTINUE_FILTER = "{\"presence\":{\"limit\":0,\"types\":[\"\"]},\"account_data\":{\"types\":[\"qm.emoji\",\"m.direct\"]},\"room\":{\"state\":{\"not_types\":[\"m.room.related_groups\",\"m.room.power_levels\",\"m.room.join_rules\",\"m.room.history_visibility\"],\"lazy_load_members\":true},\"timeline\":{\"lazy_load_members\":true},\"ephemeral\":{\"limit\":0,\"types\":[\"\"],\"lazy_load_members\":true},\"account_data\":{\"types\":[\"m.fully_read\",\"m.tag\",\"qm.last_read_message_timestamp\"],\"lazy_load_members\":true}}}"; static std::string capitalize(const std::string &str) { if(str.size() >= 1) @@ -49,6 +51,8 @@ namespace QuickMedia { if(tag.size() >= 2 && memcmp(tag.data(), "m.", 2) == 0) { if(strcmp(tag.c_str() + 2, "favourite") == 0) return "Favorites"; + else if(strcmp(tag.c_str() + 2, "direct") == 0) + return "Direct messages"; else if(strcmp(tag.c_str() + 2, "lowpriority") == 0) return "Low priority"; else if(strcmp(tag.c_str() + 2, "server_notice") == 0) @@ -117,7 +121,7 @@ namespace QuickMedia { return colors[color_hash_code(user_id) % num_colors]; } - static std::string remove_reply_formatting(const std::string &str) { + static std::string remove_reply_formatting(Matrix *matrix, const std::string &str) { if(strncmp(str.c_str(), "> <@", 4) == 0) { size_t index = str.find("> ", 4); if(index != std::string::npos) { @@ -126,12 +130,12 @@ namespace QuickMedia { return str.substr(msg_begin + 2); } } else { - return formatted_text_to_qm_text(str.c_str(), str.size(), false); + return formatted_text_to_qm_text(matrix, str.c_str(), str.size(), false); } return str; } - static std::string remove_reply_formatting(const Message *message, bool keep_formatted = false) { + static std::string remove_reply_formatting(Matrix *matrix, const Message *message, bool keep_formatted = false) { if(!message->body_is_formatted && strncmp(message->body.c_str(), "> <@", 4) == 0) { size_t index = message->body.find("> ", 4); if(index != std::string::npos) { @@ -144,7 +148,7 @@ namespace QuickMedia { if(keep_formatted) return message->body; else - return formatted_text_to_qm_text(message->body.c_str(), message->body.size(), false); + return formatted_text_to_qm_text(matrix, message->body.c_str(), message->body.size(), false); } } @@ -464,7 +468,7 @@ namespace QuickMedia { if(!sync_is_cache && message_dir == MessageDirection::AFTER) { for(auto &message : messages) { if(message->notification_mentions_me) { - std::string body = remove_reply_formatting(message.get()); + std::string body = remove_reply_formatting(matrix, message->body); bool read = true; // TODO: What if the message or username begins with "-"? also make the notification image be the avatar of the user if((!is_window_focused || room != current_room) && message->related_event_type != RelatedEventType::EDIT && message->related_event_type != RelatedEventType::REDACTION) { @@ -629,15 +633,15 @@ namespace QuickMedia { return nullptr; } - static std::string message_to_qm_text(const Message *message, bool allow_formatted_text = true) { + std::string message_to_qm_text(Matrix *matrix, const Message *message, bool allow_formatted_text) { if(message->body_is_formatted) - return formatted_text_to_qm_text(message->body.c_str(), message->body.size(), allow_formatted_text); + return formatted_text_to_qm_text(matrix, message->body.c_str(), message->body.size(), allow_formatted_text); else return message->body; } - static std::string message_to_room_description_text(Message *message) { - std::string body = strip(message_to_qm_text(message)); + static std::string message_to_room_description_text(Matrix *matrix, Message *message) { + std::string body = strip(formatted_text_to_qm_text(matrix, message->body.c_str(), message->body.size(), true)); if(message->type == MessageType::REACTION) return "Reacted with: " + body; else if(message->related_event_type == RelatedEventType::REPLY) @@ -704,7 +708,7 @@ namespace QuickMedia { room_desc += "Unread: "; if(last_unread_message) - room_desc += extract_first_line_remove_newline_elipses(matrix->message_get_author_displayname(last_unread_message), AUTHOR_MAX_LENGTH) + ": " + message_to_room_description_text(last_unread_message); + room_desc += extract_first_line_remove_newline_elipses(matrix->message_get_author_displayname(last_unread_message), AUTHOR_MAX_LENGTH) + ": " + message_to_room_description_text(matrix, last_unread_message); int unread_notification_count = room->unread_notification_count; if(unread_notification_count > 0 && set_room_as_unread) { @@ -724,7 +728,7 @@ namespace QuickMedia { rooms_page->move_room_to_top(room); room_tags_page->move_room_to_top(room); } else if(last_new_message) { - room->body_item->set_description(extract_first_line_remove_newline_elipses(matrix->message_get_author_displayname(last_new_message.get()), AUTHOR_MAX_LENGTH) + ": " + message_to_room_description_text(last_new_message.get())); + room->body_item->set_description(extract_first_line_remove_newline_elipses(matrix->message_get_author_displayname(last_new_message.get()), AUTHOR_MAX_LENGTH) + ": " + message_to_room_description_text(matrix, last_new_message.get())); room->body_item->set_description_color(get_theme().faded_text_color); room->body_item->set_description_max_lines(3); @@ -974,6 +978,9 @@ namespace QuickMedia { matrix->logout(); program->set_go_to_previous_page(); return PluginResult::OK; + } else if(args.url == "emoji") { + result_tabs.push_back(Tab{create_body(), std::make_unique<MatrixCustomEmojiPage>(program, matrix), create_search_bar("Search...", SEARCH_DELAY_FILTER)}); + return PluginResult::OK; } else { return PluginResult::ERR; } @@ -991,9 +998,170 @@ namespace QuickMedia { } else { show_notification("QuickMedia", "Failed to join " + args.title, Urgency::CRITICAL); } + + return PluginResult::OK; + } + + static const char* file_get_filename(const std::string &filepath) { + size_t index = filepath.rfind('/'); + if(index == std::string::npos) + return filepath.c_str(); + return filepath.c_str() + index + 1; + } + + static bool generate_random_characters(char *buffer, int buffer_size) { + int fd = open("/dev/urandom", O_RDONLY); + if(fd == -1) { + perror("/dev/urandom"); + return false; + } + + if(read(fd, buffer, buffer_size) < buffer_size) { + fprintf(stderr, "Failed to read %d bytes from /dev/urandom\n", buffer_size); + close(fd); + return false; + } + + close(fd); + return true; + } + + static std::string random_characters_to_readable_string(const char *buffer, int buffer_size) { + std::ostringstream result; + result << std::hex; + for(int i = 0; i < buffer_size; ++i) + result << (int)(unsigned char)buffer[i]; + return result.str(); + } + + PluginResult MatrixCustomEmojiPage::submit(const SubmitArgs &args, std::vector<Tab> &result_tabs) { + if(args.url == "add") { + auto submit_handler = [this](FileManagerPage*, const std::filesystem::path &filepath) { + program->run_task_with_loading_screen([this, filepath] { + std::string key = filepath.filename().string(); + if(!key.empty()) { + size_t ext_index = key.rfind('.'); + if(ext_index != std::string::npos) + key = key.substr(0, ext_index); + } + + if(key.empty()) { + char random_characters[10]; + if(!generate_random_characters(random_characters, sizeof(random_characters))) { + show_notification("QuickMedia", "Failed to generate random string", Urgency::CRITICAL); + return false; + } + key = random_characters_to_readable_string(random_characters, sizeof(random_characters)); + } + + if(matrix->does_custom_emoji_with_name_exist(key)) { + show_notification("QuickMedia", "Failed to upload custom emoji. You already have a custom emoji with the name " + key, Urgency::CRITICAL); + return false; + } + + std::string mxc_url; + std::string err_msg; + if(matrix->upload_custom_emoji(filepath, key, mxc_url, err_msg) != PluginResult::OK) { + show_notification("QuickMedia", "Failed to upload custom emoji, error: " + err_msg, Urgency::CRITICAL); + return false; + } + + return true; + }); + return std::vector<Tab>{}; + }; + + auto file_manager_body = create_body(); + auto file_manager_page = std::make_unique<FileManagerPage>(program, FILE_MANAGER_MIME_TYPE_IMAGE, std::move(submit_handler)); + file_manager_page->set_current_directory(get_home_dir().data); + BodyItems body_items; + file_manager_page->get_files_in_directory(body_items); + file_manager_body->set_items(std::move(body_items)); + + result_tabs.push_back(Tab{std::move(file_manager_body), std::move(file_manager_page), create_search_bar("Search...", SEARCH_DELAY_FILTER)}); + return PluginResult::OK; + } else if(args.url == "rename") { + result_tabs.push_back(Tab{create_body(false, true), std::make_unique<MatrixCustomEmojiRenameSelectPage>(program, matrix), create_search_bar("Search...", SEARCH_DELAY_FILTER)}); + return PluginResult::OK; + } else if(args.url == "delete") { + auto body = create_body(false, true); + BodyItems body_items; + for(auto &emoji : matrix->get_custom_emojis()) { + auto emoji_item = BodyItem::create(":" + emoji.first + ":"); + emoji_item->url = emoji.first; + emoji_item->thumbnail_url = matrix->get_media_url(emoji.second.url); + emoji_item->thumbnail_size = emoji.second.size; + body_items.push_back(std::move(emoji_item)); + } + body->set_items(std::move(body_items)); + + Body *body_p = body.get(); + result_tabs.push_back(Tab{std::move(body), std::make_unique<MatrixCustomEmojiDeletePage>(program, matrix, body_p), create_search_bar("Search...", SEARCH_DELAY_FILTER)}); + return PluginResult::OK; + } else { + return PluginResult::ERR; + } + } + + PluginResult MatrixCustomEmojiPage::lazy_fetch(BodyItems &result_items) { + auto add_emoji_item = BodyItem::create("Add emoji"); + add_emoji_item->url = "add"; + result_items.push_back(std::move(add_emoji_item)); + + auto rename_emoji_item = BodyItem::create("Rename emoji"); + rename_emoji_item->url = "rename"; + result_items.push_back(std::move(rename_emoji_item)); + + auto delete_emoji_item = BodyItem::create("Delete emoji"); + delete_emoji_item->set_title_color(mgl::Color(255, 45, 47)); + delete_emoji_item->url = "delete"; + result_items.push_back(std::move(delete_emoji_item)); + + return PluginResult::OK; + } + + bool MatrixCustomEmojiPage::is_ready() { + return matrix->is_initial_sync_finished(); + } + + PluginResult MatrixCustomEmojiRenameSelectPage::submit(const SubmitArgs &args, std::vector<Tab> &result_tabs) { + result_tabs.push_back(Tab{create_body(), std::make_unique<MatrixCustomEmojiRenamePage>(program, matrix, args.url), create_search_bar("Enter a new name for the emoji...", SEARCH_DELAY_FILTER)}); + return PluginResult::OK; + } + + PluginResult MatrixCustomEmojiRenameSelectPage::lazy_fetch(BodyItems &result_items) { + for(auto &emoji : matrix->get_custom_emojis()) { + auto emoji_item = BodyItem::create(":" + emoji.first + ":"); + emoji_item->url = emoji.first; + emoji_item->thumbnail_url = matrix->get_media_url(emoji.second.url); + emoji_item->thumbnail_size = emoji.second.size; + result_items.push_back(std::move(emoji_item)); + } return PluginResult::OK; } + PluginResult MatrixCustomEmojiRenamePage::submit(const SubmitArgs &args, std::vector<Tab>&) { + if(matrix->rename_custom_emoji(emoji_key, args.title)) { + program->set_go_to_previous_page(); + return PluginResult::OK; + } else { + show_notification("QuickMedia", "Failed to rename emoji " + emoji_key + " to " + args.title, Urgency::CRITICAL); + return PluginResult::OK; + } + } + + PluginResult MatrixCustomEmojiDeletePage::submit(const SubmitArgs &args, std::vector<Tab>&) { + if(matrix->delete_custom_emoji(args.url)) { + body->erase_item([&args](std::shared_ptr<BodyItem> &item) { + return item->url == args.url; + }); + return PluginResult::OK; + } else { + show_notification("QuickMedia", "Failed to delete emoji: " + args.url, Urgency::CRITICAL); + return PluginResult::OK; + } + } + MatrixChatPage::MatrixChatPage(Program *program, std::string room_id, MatrixRoomsPage *rooms_page, std::string jump_to_event_id) : Page(program), room_id(std::move(room_id)), rooms_page(rooms_page), jump_to_event_id(std::move(jump_to_event_id)) { @@ -1533,19 +1701,21 @@ namespace QuickMedia { notification_thread.join(); } + std::lock_guard<std::recursive_mutex> lock(room_data_mutex); delegate = nullptr; sync_failed = false; sync_fail_reason.clear(); - next_batch.clear(); + set_next_batch(""); next_notifications_token.clear(); invites.clear(); filter_cached.reset(); my_events_transaction_ids.clear(); finished_fetching_notifications = false; + custom_emoji_by_key.clear(); } - bool Matrix::is_initial_sync_finished() const { - return !next_batch.empty(); + bool Matrix::is_initial_sync_finished() { + return initial_sync_finished; } bool Matrix::did_initial_sync_fail(std::string &err_msg) { @@ -1705,14 +1875,12 @@ namespace QuickMedia { if(!root.IsObject()) return PluginResult::ERR; - //const rapidjson::Value &account_data_json = GetMember(root, "account_data"); - //std::optional<std::set<std::string>> dm_rooms; - //parse_sync_account_data(account_data_json, dm_rooms); - // TODO: Include "Direct messages" as a tag using |dm_rooms| above - const rapidjson::Value &rooms_json = GetMember(root, "rooms"); parse_sync_room_data(rooms_json, is_additional_messages_sync, initial_sync); + const rapidjson::Value &account_data_json = GetMember(root, "account_data"); + parse_sync_account_data(account_data_json); + return PluginResult::OK; } @@ -1771,7 +1939,7 @@ namespace QuickMedia { notification.room = room; notification.event_id = std::move(event_id); notification.sender_user_id.assign(sender_json.GetString(), sender_json.GetStringLength()); - notification.body = remove_reply_formatting(body_json.GetString()); + notification.body = remove_reply_formatting(this, body_json.GetString()); notification.timestamp = timestamp; notification.read = read_json.GetBool(); callback_func(notification); @@ -1781,7 +1949,7 @@ namespace QuickMedia { return PluginResult::OK; } - PluginResult Matrix::parse_sync_account_data(const rapidjson::Value &account_data_json, std::optional<std::set<std::string>> &dm_rooms) { + PluginResult Matrix::parse_sync_account_data(const rapidjson::Value &account_data_json) { if(!account_data_json.IsObject()) return PluginResult::OK; @@ -1789,36 +1957,74 @@ namespace QuickMedia { if(!events_json.IsArray()) return PluginResult::OK; - bool has_direct_rooms = false; - std::set<std::string> dm_rooms_tmp; for(const rapidjson::Value &event_item_json : events_json.GetArray()) { if(!event_item_json.IsObject()) continue; const rapidjson::Value &type_json = GetMember(event_item_json, "type"); - if(!type_json.IsString() || strcmp(type_json.GetString(), "m.direct") != 0) + if(!type_json.IsString()) continue; const rapidjson::Value &content_json = GetMember(event_item_json, "content"); if(!content_json.IsObject()) continue; - has_direct_rooms = true; - for(auto const &it : content_json.GetObject()) { - if(!it.value.IsArray()) - continue; + if(strcmp(type_json.GetString(), "m.direct") == 0) { + for(auto const &it : content_json.GetObject()) { + if(!it.name.IsString()) + continue; - for(const rapidjson::Value &room_id_json : it.value.GetArray()) { - if(!room_id_json.IsString()) + if(!it.value.IsArray()) continue; - - dm_rooms_tmp.insert(std::string(room_id_json.GetString(), room_id_json.GetStringLength())); + + for(const rapidjson::Value &room_id_json : it.value.GetArray()) { + if(!room_id_json.IsString()) + continue; + + RoomData *room = get_room_by_id(std::string(room_id_json.GetString(), room_id_json.GetStringLength())); + if(!room) { + fprintf(stderr, "Warning: got m.direct for room %s that we haven't created yet\n", room_id_json.GetString()); + continue; + } + + auto user = get_user_by_id(room, std::string(it.name.GetString(), it.name.GetStringLength()), nullptr, false); + if(!user) { + fprintf(stderr, "Warning: got m.direct for user %s that doesn't exist in the room %s yet\n", it.name.GetString(), room_id_json.GetString()); + continue; + } + + room->acquire_room_lock(); + std::set<std::string> &room_tags = room->get_tags_thread_unsafe(); + auto room_tag_it = room_tags.find("m.direct"); + if(room_tag_it == room_tags.end()) { + room_tags.insert("m.direct"); + ui_thread_tasks.push([this, room]{ delegate->room_add_tag(room, "m.direct"); }); + } + room->release_room_lock(); + } } - } - } + } else if(strcmp(type_json.GetString(), "qm.emoji") == 0) { + std::lock_guard<std::recursive_mutex> lock(room_data_mutex); + for(auto const &emoji_json : content_json.GetObject()) { + if(!emoji_json.name.IsString() || !emoji_json.value.IsObject()) + continue; - if(has_direct_rooms) - dm_rooms = std::move(dm_rooms_tmp); + const rapidjson::Value &url_json = GetMember(emoji_json.value, "url"); + const rapidjson::Value &width_json = GetMember(emoji_json.value, "width"); + const rapidjson::Value &height_json = GetMember(emoji_json.value, "height"); + if(!url_json.IsString()) + continue; + + CustomEmoji custom_emoji; + custom_emoji.url = url_json.GetString(); + if(width_json.IsInt() && height_json.IsInt()) { + custom_emoji.size.x = width_json.GetInt(); + custom_emoji.size.y = height_json.GetInt(); + } + custom_emoji_by_key[emoji_json.name.GetString()] = std::move(custom_emoji); + } + } + } return PluginResult::OK; } @@ -1986,6 +2192,20 @@ namespace QuickMedia { item_timestamp = origin_server_ts.GetInt64(); } + const rapidjson::Value &is_direct_json = GetMember(content_json, "is_direct"); + if(is_direct_json.IsBool() && is_direct_json.GetBool()) { + room_data->acquire_room_lock(); + std::set<std::string> &room_tags = room_data->get_tags_thread_unsafe(); + + auto room_tag_it = room_tags.find("m.direct"); + if(room_tag_it == room_tags.end()) { + room_tags.insert("m.direct"); + ui_thread_tasks.push([this, room_data]{ delegate->room_add_tag(room_data, "m.direct"); }); + } + + room_data->release_room_lock(); + } + parse_user_info(content_json, sender_json->GetString(), room_data, item_timestamp); } } @@ -2003,7 +2223,7 @@ namespace QuickMedia { return media_url.substr(start, end - start); } - static std::string get_thumbnail_url(const std::string &homeserver, const std::string &mxc_id) { + static std::string get_avatar_thumbnail_url(const std::string &homeserver, const std::string &mxc_id) { if(mxc_id.empty()) return ""; @@ -2011,6 +2231,10 @@ namespace QuickMedia { return homeserver + "/_matrix/media/r0/thumbnail/" + mxc_id + "?width=" + size + "&height=" + size + "&method=crop"; } + std::string Matrix::get_media_url(const std::string &mxc_id) { + return homeserver + "/_matrix/media/r0/download/" + thumbnail_url_extract_media_id(mxc_id); + } + std::shared_ptr<UserInfo> Matrix::parse_user_info(const rapidjson::Value &json, const std::string &user_id, RoomData *room_data, int64_t timestamp) { assert(json.IsObject()); std::string avatar_url_str; @@ -2023,7 +2247,7 @@ namespace QuickMedia { std::string display_name = display_name_json.IsString() ? display_name_json.GetString() : user_id; std::string avatar_url = thumbnail_url_extract_media_id(avatar_url_str); if(!avatar_url.empty()) - avatar_url = get_thumbnail_url(homeserver, avatar_url); // TODO: Remove the constant strings around to reduce memory usage (6.3mb) + avatar_url = get_avatar_thumbnail_url(homeserver, avatar_url); // TODO: Remove the constant strings around to reduce memory usage (6.3mb) //auto user_info = std::make_shared<UserInfo>(room_data, user_id, std::move(display_name), std::move(avatar_url)); // Overwrites user data //room_data->add_user(user_info); @@ -2195,8 +2419,8 @@ namespace QuickMedia { return false; } - bool message_contains_user_mention(const Message *message, const std::string &username, const std::string &user_id) { - const std::string formatted_text = message_to_qm_text(message, false); + bool message_contains_user_mention(Matrix *matrix, const Message *message, const std::string &username, const std::string &user_id) { + const std::string formatted_text = message_to_qm_text(matrix, message, false); return message_contains_user_mention(formatted_text, username) || message_contains_user_mention(formatted_text, user_id); } @@ -2263,7 +2487,7 @@ namespace QuickMedia { // TODO: Is @room ok? shouldn't we also check if the user has permission to do @room? (only when notifications are limited to @mentions) // TODO: Is comparing against read marker timestamp ok enough? if(me && message->timestamp > read_marker_message_timestamp) { - std::string message_str = message_to_qm_text(message.get(), false); + std::string message_str = message_to_qm_text(this, message.get(), false); message->notification_mentions_me = message_contains_user_mention(message_str, my_display_name) || message_contains_user_mention(message_str, me->user_id) || message_contains_user_mention(message_str, "@room"); } } @@ -2359,7 +2583,11 @@ namespace QuickMedia { bool allow_formatted_text = false; bool inside_source_highlight = false; bool supports_syntax_highlight = false; + bool inside_img_tag = false; + std::string_view img_src; + mgl::vec2i img_size; mgl::Color font_color = mgl::Color(255, 255, 255, 255); + Matrix *matrix = nullptr; }; static int accumulate_string(char *data, int size, void *userdata) { @@ -2384,6 +2612,10 @@ namespace QuickMedia { else if(html_parser->tag_name.size == 4 && memcmp(html_parser->tag_name.data, "code", 4) == 0) { parse_userdata.inside_code_tag = true; parse_userdata.code_tag_language = std::string_view(); + } else if(html_parser->tag_name.size == 3 && memcmp(html_parser->tag_name.data, "img", 3) == 0) { + parse_userdata.inside_img_tag = true; + parse_userdata.img_src = std::string_view(); + parse_userdata.img_size = { 0, 0 }; } break; } @@ -2397,6 +2629,17 @@ namespace QuickMedia { parse_userdata.mx_reply_depth = std::max(0, parse_userdata.mx_reply_depth - 1); } else if(html_parser->tag_name.size == 4 && memcmp(html_parser->tag_name.data, "code", 4) == 0) { parse_userdata.inside_code_tag = false; + } else if(html_parser->tag_name.size == 3 && memcmp(html_parser->tag_name.data, "img", 3) == 0) { + if(parse_userdata.matrix && parse_userdata.inside_img_tag && parse_userdata.img_src.size() > 0) { + std::string image_url(parse_userdata.img_src); + html_unescape_sequences(image_url); + mgl::vec2i img_size = parse_userdata.img_size; + // TODO: Better solution when size not given? + if(img_size.x == 0 || img_size.y == 0) + img_size = custom_emoji_max_size; + parse_userdata.result += Text::formatted_image(parse_userdata.matrix->get_media_url(image_url), false, img_size); + } + parse_userdata.inside_img_tag = false; } break; } @@ -2407,6 +2650,20 @@ namespace QuickMedia { } else if(parse_userdata.inside_code_tag && html_parser->attribute_key.size == 5 && memcmp(html_parser->attribute_key.data, "class", 5) == 0) { if(html_parser->attribute_value.size > 9 && memcmp(html_parser->attribute_value.data, "language-", 9) == 0) parse_userdata.code_tag_language = std::string_view(html_parser->attribute_value.data + 9, html_parser->attribute_value.size - 9); + } else if(parse_userdata.allow_formatted_text && parse_userdata.inside_img_tag) { + if(html_parser->attribute_key.size == 3 && memcmp(html_parser->attribute_key.data, "src", 3) == 0) { + parse_userdata.img_src = std::string_view(html_parser->attribute_value.data, html_parser->attribute_value.size); + } else if(html_parser->attribute_key.size == 5 && memcmp(html_parser->attribute_key.data, "width", 5) == 0) { + const std::string width(html_parser->attribute_value.data, html_parser->attribute_value.size); + parse_userdata.img_size.x = atoi(width.c_str()); + } else if(html_parser->attribute_key.size == 6 && memcmp(html_parser->attribute_key.data, "height", 6) == 0) { + const std::string height(html_parser->attribute_value.data, html_parser->attribute_value.size); + parse_userdata.img_size.y = atoi(height.c_str()); + } + } else if(!parse_userdata.allow_formatted_text && parse_userdata.inside_img_tag && html_parser->attribute_key.size == 3 && memcmp(html_parser->attribute_key.data, "alt", 3) == 0) { + std::string text_to_add(html_parser->attribute_value.data, html_parser->attribute_value.size); + html_unescape_sequences(text_to_add); + parse_userdata.result += std::move(text_to_add); } break; } @@ -2455,10 +2712,11 @@ namespace QuickMedia { return 0; } - std::string formatted_text_to_qm_text(const char *str, size_t size, bool allow_formatted_text) { + std::string formatted_text_to_qm_text(Matrix *matrix, const char *str, size_t size, bool allow_formatted_text) { FormattedTextParseUserdata parse_userdata; parse_userdata.allow_formatted_text = allow_formatted_text; parse_userdata.supports_syntax_highlight = is_program_executable_by_name("source-highlight"); + parse_userdata.matrix = matrix; html_parser_parse(str, size, formattext_text_parser_callback, &parse_userdata); return std::move(parse_userdata.result); } @@ -2648,7 +2906,7 @@ namespace QuickMedia { body = user_display_name + " changed his profile picture"; std::string new_avatar_url_str = thumbnail_url_extract_media_id(new_avatar_url_json.GetString()); if(!new_avatar_url_str.empty()) - new_avatar_url_str = get_thumbnail_url(homeserver, new_avatar_url_str); // TODO: Remove the constant strings around to reduce memory usage (6.3mb) + new_avatar_url_str = get_avatar_thumbnail_url(homeserver, new_avatar_url_str); // TODO: Remove the constant strings around to reduce memory usage (6.3mb) new_avatar_url = new_avatar_url_str; update_user_display_info = room_data->set_user_avatar_url(user, std::move(new_avatar_url_str), timestamp); } else if((!new_avatar_url_json.IsString() || new_avatar_url_json.GetStringLength() == 0) && prev_avatar_url_json.IsString()) { @@ -2989,7 +3247,7 @@ namespace QuickMedia { if(!url_json.IsString() || strncmp(url_json.GetString(), "mxc://", 6) != 0) continue; - update_room_avatar_url |= room_data->set_avatar_url(get_thumbnail_url(homeserver, thumbnail_url_extract_media_id(url_json.GetString())), item_timestamp); + update_room_avatar_url |= room_data->set_avatar_url(get_avatar_thumbnail_url(homeserver, thumbnail_url_extract_media_id(url_json.GetString())), item_timestamp); room_data->avatar_is_fallback = false; } else if(strcmp(type_json.GetString(), "m.room.topic") == 0) { const rapidjson::Value &content_json = GetMember(event_item_json, "content"); @@ -3144,7 +3402,10 @@ namespace QuickMedia { ui_thread_tasks.push([this, room_data]{ delegate->room_add_tag(room_data, OTHERS_ROOM_TAG); }); } + const bool contains_direct_messaging = room_tags.find("m.direct") != room_tags.end(); room_tags = std::move(new_tags); + if(contains_direct_messaging) + room_tags.insert("m.direct"); room_data->release_room_lock(); } } @@ -3362,31 +3623,6 @@ namespace QuickMedia { return PluginResult::OK; } - static bool generate_random_characters(char *buffer, int buffer_size) { - int fd = open("/dev/urandom", O_RDONLY); - if(fd == -1) { - perror("/dev/urandom"); - return false; - } - - if(read(fd, buffer, buffer_size) < buffer_size) { - fprintf(stderr, "Failed to read %d bytes from /dev/urandom\n", buffer_size); - close(fd); - return false; - } - - close(fd); - return true; - } - - static std::string random_characters_to_readable_string(const char *buffer, int buffer_size) { - std::ostringstream result; - result << std::hex; - for(int i = 0; i < buffer_size; ++i) - result << (int)(unsigned char)buffer[i]; - return result.str(); - } - std::string create_transaction_id() { char random_characters[18]; if(!generate_random_characters(random_characters, sizeof(random_characters))) @@ -3505,11 +3741,29 @@ namespace QuickMedia { } } + static void replace_emoji_references_with_formatted_images(std::string &str, const std::unordered_map<std::string, CustomEmoji> &custom_emojis) { + for(const auto &it : custom_emojis) { + std::string keybind = ":" + it.first + ":"; + std::string url = it.second.url; + html_escape_sequences(url); + std::string width = std::to_string(it.second.size.x); + std::string height = std::to_string(it.second.size.y); + std::string tag = "<img src=\"" + url + "\" alt=\"" + keybind + "\" width=\"" + width + "\" height=\"" + height + "\" vertical-align=\"middle\" />"; + string_replace_all(str, keybind, tag); + } + } + std::string Matrix::body_to_formatted_body(RoomData *room, const std::string &body) { + std::unordered_map<std::string, CustomEmoji> custom_emojis_copy; + { + std::lock_guard<std::recursive_mutex> lock(room_data_mutex); + custom_emojis_copy = custom_emoji_by_key; + } + std::string formatted_body; bool is_inside_code_block = false; bool is_first_line = true; - string_split(body, '\n', [this, room, &formatted_body, &is_inside_code_block, &is_first_line](const char *str, size_t size){ + string_split(body, '\n', [this, room, &formatted_body, &is_inside_code_block, &is_first_line, &custom_emojis_copy](const char *str, size_t size){ if(!is_first_line) { if(is_inside_code_block) formatted_body += '\n'; @@ -3533,6 +3787,9 @@ namespace QuickMedia { } is_first_line = true; } else { + if(!is_inside_code_block) + replace_emoji_references_with_formatted_images(line_str, custom_emojis_copy); + if(!is_inside_code_block && size > 0 && str[0] == '>') { formatted_body += "<font color=\"#789922\">"; formatted_body_add_line(room, formatted_body, line_str); @@ -3653,17 +3910,17 @@ namespace QuickMedia { return result; } - static std::string get_reply_message(const Message *message, bool keep_formatted = false) { + static std::string get_reply_message(Matrix *matrix, const Message *message, bool keep_formatted = false) { std::string related_to_body; switch(message->type) { case MessageType::TEXT: { if(message->related_event_type != RelatedEventType::NONE) { - related_to_body = remove_reply_formatting(message, keep_formatted); + related_to_body = remove_reply_formatting(matrix, message, keep_formatted); } else { if(keep_formatted && message->body_is_formatted) related_to_body = message->body; else - related_to_body = message_to_qm_text(message, false); + related_to_body = message_to_qm_text(matrix, message, false); } break; } @@ -3683,15 +3940,15 @@ namespace QuickMedia { if(keep_formatted && message->body_is_formatted) related_to_body = message->body; else - related_to_body = message_to_qm_text(message, false); + related_to_body = message_to_qm_text(matrix, message, false); break; } } return related_to_body; } - static std::string create_body_for_message_reply(const Message *message, const std::string &body) { - return "> <" + message->user->user_id + "> " + block_quote(get_reply_message(message)) + "\n\n" + body; + static std::string create_body_for_message_reply(Matrix *matrix, const Message *message, const std::string &body) { + return "> <" + message->user->user_id + "> " + block_quote(get_reply_message(matrix, message)) + "\n\n" + body; } static std::string extract_homeserver_from_room_id(const std::string &room_id) { @@ -3703,7 +3960,7 @@ namespace QuickMedia { std::string Matrix::create_formatted_body_for_message_reply(RoomData *room, const Message *message, const std::string &body) { std::string formatted_body = body_to_formatted_body(room, body); - std::string related_to_body = get_reply_message(message, true); + std::string related_to_body = get_reply_message(this, message, true); if(!message->body_is_formatted) html_escape_sequences(related_to_body); // TODO: Add keybind to navigate to the reply message, which would also depend on this formatting. @@ -3746,7 +4003,7 @@ namespace QuickMedia { rapidjson::Document relates_to_json(rapidjson::kObjectType); relates_to_json.AddMember("m.in_reply_to", std::move(in_reply_to_json), relates_to_json.GetAllocator()); - std::string message_reply_body = create_body_for_message_reply(related_to_text_message, body); // Yes, the reply is to the edited message but the event_id reference is to the original message... + std::string message_reply_body = create_body_for_message_reply(this, related_to_text_message, body); // Yes, the reply is to the edited message but the event_id reference is to the original message... std::string formatted_message_reply_body = create_formatted_body_for_message_reply(room, related_to_text_message, body); rapidjson::Document request_data(rapidjson::kObjectType); @@ -4079,11 +4336,162 @@ namespace QuickMedia { room->set_prev_batch(""); } - static const char* file_get_filename(const std::string &filepath) { - size_t index = filepath.rfind('/'); - if(index == std::string::npos) - return filepath.c_str(); - return filepath.c_str() + index + 1; + PluginResult Matrix::upload_custom_emoji(const std::string &filepath, const std::string &key, std::string &mxc_url, std::string &err_msg) { + UploadInfo file_info; + UploadInfo thumbnail_info; + // TODO: Do not create and upload thumbnail + PluginResult upload_file_result = upload_file(filepath, "", file_info, thumbnail_info, err_msg); + if(upload_file_result != PluginResult::OK) + return upload_file_result; + + mxc_url = std::move(file_info.content_uri); + + rapidjson::Document request_data(rapidjson::kObjectType); + { + std::lock_guard<std::recursive_mutex> lock(room_data_mutex); + for(const auto &it : custom_emoji_by_key) { + rapidjson::Document emoji_obj(rapidjson::kObjectType); + emoji_obj.AddMember("url", rapidjson::Value(it.second.url.c_str(), request_data.GetAllocator()).Move(), request_data.GetAllocator()); + emoji_obj.AddMember("width", it.second.size.x, request_data.GetAllocator()); + emoji_obj.AddMember("height", it.second.size.y, request_data.GetAllocator()); + request_data.AddMember(rapidjson::Value(it.first.c_str(), request_data.GetAllocator()).Move(), std::move(emoji_obj), request_data.GetAllocator()); + } + } + + CustomEmoji custom_emoji; + custom_emoji.url = mxc_url; + rapidjson::Document emoji_obj(rapidjson::kObjectType); + emoji_obj.AddMember("url", rapidjson::Value(mxc_url.c_str(), request_data.GetAllocator()).Move(), request_data.GetAllocator()); + if(file_info.dimensions) { + custom_emoji.size = clamp_to_size(mgl::vec2i(file_info.dimensions->width, file_info.dimensions->height), custom_emoji_max_size); + emoji_obj.AddMember("width", custom_emoji.size.x, request_data.GetAllocator()); + emoji_obj.AddMember("height", custom_emoji.size.y, request_data.GetAllocator()); + } + request_data.AddMember(rapidjson::Value(key.c_str(), request_data.GetAllocator()).Move(), std::move(emoji_obj), request_data.GetAllocator()); + + rapidjson::StringBuffer buffer; + rapidjson::Writer<rapidjson::StringBuffer> writer(buffer); + request_data.Accept(writer); + + std::vector<CommandArg> additional_args = { + { "-X", "PUT" }, + { "-H", "content-type: application/json" }, + { "-H", "Authorization: Bearer " + access_token }, + { "--data-binary", buffer.GetString() } + }; + + std::string server_response; + DownloadResult download_result = download_to_string(homeserver + "/_matrix/client/r0/user/" + my_user_id + "/account_data/qm.emoji", server_response, std::move(additional_args), true); + if(download_result != DownloadResult::OK) + return download_result_to_plugin_result(download_result); + + std::lock_guard<std::recursive_mutex> lock(room_data_mutex); + custom_emoji_by_key[key] = std::move(custom_emoji); + return PluginResult::OK; + } + + bool Matrix::delete_custom_emoji(const std::string &key) { + rapidjson::Document request_data(rapidjson::kObjectType); + { + std::lock_guard<std::recursive_mutex> lock(room_data_mutex); + auto it = custom_emoji_by_key.find(key); + if(it == custom_emoji_by_key.end()) + return false; + + for(const auto &it : custom_emoji_by_key) { + if(it.first == key) + continue; + + rapidjson::Document emoji_obj(rapidjson::kObjectType); + emoji_obj.AddMember("url", rapidjson::Value(it.second.url.c_str(), request_data.GetAllocator()).Move(), request_data.GetAllocator()); + emoji_obj.AddMember("width", it.second.size.x, request_data.GetAllocator()); + emoji_obj.AddMember("height", it.second.size.y, request_data.GetAllocator()); + request_data.AddMember(rapidjson::Value(it.first.c_str(), request_data.GetAllocator()).Move(), std::move(emoji_obj), request_data.GetAllocator()); + } + } + + rapidjson::StringBuffer buffer; + rapidjson::Writer<rapidjson::StringBuffer> writer(buffer); + request_data.Accept(writer); + + std::vector<CommandArg> additional_args = { + { "-X", "PUT" }, + { "-H", "content-type: application/json" }, + { "-H", "Authorization: Bearer " + access_token }, + { "--data-binary", buffer.GetString() } + }; + + std::string server_response; + DownloadResult download_result = download_to_string(homeserver + "/_matrix/client/r0/user/" + my_user_id + "/account_data/qm.emoji", server_response, std::move(additional_args), true); + if(download_result != DownloadResult::OK) + return false; + + std::lock_guard<std::recursive_mutex> lock(room_data_mutex); + auto it = custom_emoji_by_key.find(key); + if(it != custom_emoji_by_key.end()) + custom_emoji_by_key.erase(it); + + return true; + } + + bool Matrix::rename_custom_emoji(const std::string &key, const std::string &new_key) { + rapidjson::Document request_data(rapidjson::kObjectType); + { + std::lock_guard<std::recursive_mutex> lock(room_data_mutex); + auto custom_emoji_list_copy = custom_emoji_by_key; + auto it = custom_emoji_list_copy.find(key); + if(it == custom_emoji_list_copy.end()) + return false; + + auto custom_emoji_copy = it->second; + custom_emoji_list_copy.erase(it); + custom_emoji_list_copy[new_key] = std::move(custom_emoji_copy); + for(const auto &it : custom_emoji_list_copy) { + rapidjson::Document emoji_obj(rapidjson::kObjectType); + emoji_obj.AddMember("url", rapidjson::Value(it.second.url.c_str(), request_data.GetAllocator()).Move(), request_data.GetAllocator()); + emoji_obj.AddMember("width", it.second.size.x, request_data.GetAllocator()); + emoji_obj.AddMember("height", it.second.size.y, request_data.GetAllocator()); + request_data.AddMember(rapidjson::Value(it.first.c_str(), request_data.GetAllocator()).Move(), std::move(emoji_obj), request_data.GetAllocator()); + } + } + + rapidjson::StringBuffer buffer; + rapidjson::Writer<rapidjson::StringBuffer> writer(buffer); + request_data.Accept(writer); + + std::vector<CommandArg> additional_args = { + { "-X", "PUT" }, + { "-H", "content-type: application/json" }, + { "-H", "Authorization: Bearer " + access_token }, + { "--data-binary", buffer.GetString() } + }; + + std::string server_response; + DownloadResult download_result = download_to_string(homeserver + "/_matrix/client/r0/user/" + my_user_id + "/account_data/qm.emoji", server_response, std::move(additional_args), true); + if(download_result != DownloadResult::OK) + return false; + + std::lock_guard<std::recursive_mutex> lock(room_data_mutex); + auto it = custom_emoji_by_key.find(key); + if(it != custom_emoji_by_key.end()) { + auto custom_emoji_copy = it->second; + custom_emoji_by_key.erase(it); + custom_emoji_by_key[new_key] = std::move(custom_emoji_copy); + } + + return true; + } + + bool Matrix::does_custom_emoji_with_name_exist(const std::string &name) { + assert(is_initial_sync_finished()); + std::lock_guard<std::recursive_mutex> lock(room_data_mutex); + return custom_emoji_by_key.find(name) != custom_emoji_by_key.end(); + } + + std::unordered_map<std::string, CustomEmoji> Matrix::get_custom_emojis() { + assert(is_initial_sync_finished()); + std::lock_guard<std::recursive_mutex> lock(room_data_mutex); + return custom_emoji_by_key; } PluginResult Matrix::post_file(RoomData *room, const std::string &filepath, std::string filename, std::string &event_id_response, std::string &err_msg, void *relates_to) { @@ -4092,7 +4500,7 @@ namespace QuickMedia { UploadInfo file_info; UploadInfo thumbnail_info; - PluginResult upload_file_result = upload_file(room, filepath, filename, file_info, thumbnail_info, err_msg); + PluginResult upload_file_result = upload_file(filepath, filename, file_info, thumbnail_info, err_msg); if(upload_file_result != PluginResult::OK) return upload_file_result; @@ -4107,7 +4515,7 @@ namespace QuickMedia { return post_message(room, filename, event_id_response, file_info_opt, thumbnail_info_opt); } - PluginResult Matrix::upload_file(RoomData *room, const std::string &filepath, std::string filename, UploadInfo &file_info, UploadInfo &thumbnail_info, std::string &err_msg, bool upload_thumbnail) { + PluginResult Matrix::upload_file(const std::string &filepath, std::string filename, UploadInfo &file_info, UploadInfo &thumbnail_info, std::string &err_msg, bool upload_thumbnail) { FileAnalyzer file_analyzer; if(!file_analyzer.load_file(filepath.c_str(), true)) { err_msg = "Failed to load " + filepath; @@ -4149,7 +4557,7 @@ namespace QuickMedia { if(video_get_middle_frame(file_analyzer, tmp_filename, thumbnail_max_size.x, thumbnail_max_size.y)) { UploadInfo upload_info_ignored; // Ignore because it wont be set anyways. Thumbnails dont have thumbnails. - PluginResult upload_thumbnail_result = upload_file(room, tmp_filename, thumbnail_filename.data, thumbnail_info, upload_info_ignored, err_msg, false); + PluginResult upload_thumbnail_result = upload_file(tmp_filename, thumbnail_filename.data, thumbnail_info, upload_info_ignored, err_msg, false); if(upload_thumbnail_result != PluginResult::OK) { close(tmp_file); remove(tmp_filename); @@ -4177,7 +4585,7 @@ namespace QuickMedia { thumbnail_filename = thumbnail_filename.filename_no_ext() + ".thumb" + thumbnail_filename.ext(); UploadInfo upload_info_ignored; // Ignore because it wont be set anyways. Thumbnails dont have thumbnails. - PluginResult upload_thumbnail_result = upload_file(room, thumbnail_path, thumbnail_filename.data, thumbnail_info, upload_info_ignored, err_msg, false); + PluginResult upload_thumbnail_result = upload_file(thumbnail_path, thumbnail_filename.data, thumbnail_info, upload_info_ignored, err_msg, false); if(upload_thumbnail_result != PluginResult::OK) { close(tmp_file); remove(tmp_filename); @@ -4811,7 +5219,7 @@ namespace QuickMedia { if(avatar_url_json.IsString()) { std::string avatar_url = thumbnail_url_extract_media_id(avatar_url_json.GetString()); if(!avatar_url.empty()) - avatar_url = get_thumbnail_url(homeserver, avatar_url); + avatar_url = get_avatar_thumbnail_url(homeserver, avatar_url); if(!avatar_url.empty()) room_body_item->thumbnail_url = std::move(avatar_url); @@ -4883,7 +5291,7 @@ namespace QuickMedia { if(avatar_url_json.IsString()) { std::string avatar_url = thumbnail_url_extract_media_id(std::string(avatar_url_json.GetString(), avatar_url_json.GetStringLength())); if(!avatar_url.empty()) - avatar_url = get_thumbnail_url(homeserver, avatar_url); + avatar_url = get_avatar_thumbnail_url(homeserver, avatar_url); body_item->thumbnail_url = std::move(avatar_url); } body_item->thumbnail_mask_type = ThumbnailMaskType::CIRCLE; @@ -5037,6 +5445,7 @@ namespace QuickMedia { void Matrix::set_next_batch(std::string new_next_batch) { std::lock_guard<std::mutex> lock(next_batch_mutex); next_batch = std::move(new_next_batch); + initial_sync_finished = !next_batch.empty(); } std::string Matrix::get_next_batch() { @@ -5129,7 +5538,7 @@ namespace QuickMedia { if(avatar_url_json.IsString()) avatar_url = std::string(avatar_url_json.GetString(), avatar_url_json.GetStringLength()); if(!avatar_url.empty()) - avatar_url = get_thumbnail_url(homeserver, thumbnail_url_extract_media_id(avatar_url)); // TODO: Remove the constant strings around to reduce memory usage (6.3mb) + avatar_url = get_avatar_thumbnail_url(homeserver, thumbnail_url_extract_media_id(avatar_url)); // TODO: Remove the constant strings around to reduce memory usage (6.3mb) room->set_user_avatar_url(user, avatar_url, 0); room->set_user_display_name(user, display_name, 0); |