aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authordec05eba <dec05eba@protonmail.com>2022-11-07 22:21:52 +0100
committerdec05eba <dec05eba@protonmail.com>2022-11-11 00:53:46 +0100
commite19a29c7e51860144f02d7e7b08ac5e430e1f78f (patch)
treef57adcf98b77a271b4df74a20e2389b73f495df7 /src
parent5d2a7d977f9b0a1604e106f4e2b0c2c9b89c3235 (diff)
Support images in text, add custom emoji to matrix
Diffstat (limited to 'src')
-rw-r--r--src/Body.cpp6
-rw-r--r--src/QuickMedia.cpp49
-rw-r--r--src/Tabs.cpp1
-rw-r--r--src/Text.cpp138
-rw-r--r--src/main.cpp4
-rw-r--r--src/plugins/Matrix.cpp595
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, &current_room, MESSAGES_TAB_INDEX, &after_token](Messages &messages) {
+ auto add_new_messages_to_current_room = [this, &me, &tabs, &ui_tabs, &current_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);