From d274d3a6dfc0864ec6a44e7d6948c2d873eb6f76 Mon Sep 17 00:00:00 2001 From: dec05eba Date: Fri, 11 Nov 2022 10:50:39 +0100 Subject: Add image (custom emoji) alt text for copy-pasting, limit custom emoji size in room description, change max size to 32, 32, cache custom emoji locally --- src/QuickMedia.cpp | 8 ++-- src/Text.cpp | 37 ++++++++++++++---- src/plugins/Matrix.cpp | 101 +++++++++++++++++++++++++++++++++++-------------- 3 files changed, 106 insertions(+), 40 deletions(-) (limited to 'src') diff --git a/src/QuickMedia.cpp b/src/QuickMedia.cpp index fc3cba6..433442a 100644 --- a/src/QuickMedia.cpp +++ b/src/QuickMedia.cpp @@ -7007,9 +7007,11 @@ namespace QuickMedia { struct tm t; localtime_r(&now, &t); char filename[256] = {0}; - const int num_bytes_written = strftime(filename, sizeof(filename)-1, "Clipboard_%Y-%m-%d_%H-%M-%S", &t); - if((int)sizeof(filename) - (num_bytes_written + file_ext.size()) >= 1) - strcat(filename, file_ext.c_str()); + if(tmp_file != -1) { + const int num_bytes_written = strftime(filename, sizeof(filename)-1, "Clipboard_%Y-%m-%d_%H-%M-%S", &t); + if((int)sizeof(filename) - (num_bytes_written + file_ext.size()) >= 1) + strcat(filename, file_ext.c_str()); + } upload_file(clipboard_text, filename); } diff --git a/src/Text.cpp b/src/Text.cpp index c43944b..50db0ee 100644 --- a/src/Text.cpp +++ b/src/Text.cpp @@ -124,10 +124,11 @@ 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) { + std::string Text::formatted_image(const std::string &url, bool local, mgl::vec2i size, const std::string &alt) { const uint32_t str_size = url.size(); + const uint32_t alt_str_size = alt.size(); + std::string result; result += FORMATTED_TEXT_START; result += (uint8_t)FormattedTextType::IMAGE; @@ -136,6 +137,8 @@ namespace QuickMedia result.append((const char*)&local, sizeof(local)); result.append((const char*)&str_size, sizeof(str_size)); result.append(url); + result.append((const char*)&alt_str_size, sizeof(alt_str_size)); + result.append(alt); result += FORMATTED_TEXT_END; return result; } @@ -182,6 +185,12 @@ namespace QuickMedia for(auto &text_element : tmp_text_elements) { if(text_element.type == TextElement::Type::TEXT) result.append(text_element.text); + else if(text_element.type == TextElement::Type::IMAGE) { + if(!text_element.alt.empty()) + result.append(text_element.alt); + else + result.append(text_element.url); + } } return result; } @@ -435,12 +444,13 @@ namespace QuickMedia return std::min(5 + sizeof(uint32_t) + text_size + 1, size); // + 1 for FORMATTED_TEXT_END } - static size_t parse_formatted_image(const char *str, size_t size, std::string &image_url, bool &image_local, mgl::vec2i &image_size) { + static size_t parse_formatted_image(const char *str, size_t size, std::string &image_url, bool &image_local, mgl::vec2i &image_size, std::string &alt) { image_url.clear(); image_local = true; image_size = { 0, 0 }; + alt.clear(); - if(size < sizeof(image_size.x) + sizeof(image_size.y) + sizeof(image_local) + sizeof(uint32_t)) + if(size < sizeof(image_size.x) + sizeof(image_size.y) + sizeof(image_local) + sizeof(uint32_t) + sizeof(uint32_t)) return size; size_t offset = 0; @@ -456,13 +466,24 @@ namespace QuickMedia uint32_t text_size; memcpy(&text_size, str + offset, sizeof(text_size)); offset += sizeof(text_size); + const size_t image_url_offset = offset; + + if(size < offset + text_size + sizeof(uint32_t)) + return size; + + offset += text_size; + uint32_t alt_size; + memcpy(&alt_size, str + offset, sizeof(alt_size)); + offset += sizeof(alt_size); + const size_t alt_offset = offset; - if(size < offset + text_size) + if(size < offset + alt_size) return size; - image_url.assign(str + offset, text_size); + image_url.assign(str + image_url_offset, text_size); + alt.assign(str + alt_offset, alt_size); image_size = clamp_to_size(image_size, MAX_IMAGE_SIZE); - return std::min(offset + text_size + 1, size); // + 1 for FORMATTED_TEXT_END + return std::min(offset + alt_size + 1, size); // + 1 for FORMATTED_TEXT_END } static size_t parse_formatted_text(const char *str, size_t size, TextElement &text_element) { @@ -477,7 +498,7 @@ namespace QuickMedia } case FormattedTextType::IMAGE: { text_element.type = TextElement::Type::IMAGE; - return parse_formatted_image(str + 1, size - 1, text_element.url, text_element.local, text_element.size); + return parse_formatted_image(str + 1, size - 1, text_element.url, text_element.local, text_element.size, text_element.alt); } default: break; diff --git a/src/plugins/Matrix.cpp b/src/plugins/Matrix.cpp index fd5026a..e53cf71 100644 --- a/src/plugins/Matrix.cpp +++ b/src/plugins/Matrix.cpp @@ -28,7 +28,7 @@ namespace QuickMedia { static const mgl::vec2i thumbnail_max_size(600, 337); - static const mgl::vec2i custom_emoji_max_size(64, 64); + static const mgl::vec2i custom_emoji_max_size(32, 32); 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, @@ -640,8 +640,8 @@ namespace QuickMedia { return message->body; } - 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)); + static std::string message_to_room_description_text(Matrix *matrix, Message *message, mgl::vec2i image_max_size = mgl::vec2i(0, 0)) { + std::string body = strip(formatted_text_to_qm_text(matrix, message->body.c_str(), message->body.size(), true, image_max_size)); if(message->type == MessageType::REACTION) return "Reacted with: " + body; else if(message->related_event_type == RelatedEventType::REPLY) @@ -708,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(matrix, 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, custom_emoji_max_size); int unread_notification_count = room->unread_notification_count; if(unread_notification_count > 0 && set_room_as_unread) { @@ -728,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(matrix, 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(), custom_emoji_max_size)); room->body_item->set_description_color(get_theme().faded_text_color); room->body_item->set_description_max_lines(3); @@ -1503,12 +1503,12 @@ namespace QuickMedia { sync_is_cache = false; sync_running = true; + load_custom_emoji_from_cache(); + sync_thread = std::thread([this, matrix_cache_dir]() { sync_is_cache = true; - bool cache_available = false; FILE *sync_cache_file = fopen(matrix_cache_dir.data.c_str(), "rb"); if(sync_cache_file) { - cache_available = true; rapidjson::Document doc; char read_buffer[8192]; rapidjson::FileReadStream is(sync_cache_file, read_buffer, sizeof(read_buffer)); @@ -1949,6 +1949,47 @@ namespace QuickMedia { return PluginResult::OK; } + void Matrix::parse_custom_emoji(const rapidjson::Value &custom_emoji_json) { + if(!custom_emoji_json.IsObject()) + return; + + std::lock_guard lock(room_data_mutex); + custom_emoji_by_key.clear(); + for(auto const &emoji_json : custom_emoji_json.GetObject()) { + if(!emoji_json.name.IsString() || !emoji_json.value.IsObject()) + continue; + + 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); + } + } + + void Matrix::load_custom_emoji_from_cache() { + std::string custom_emoji_file_content; + if(file_get_content(get_cache_dir().join("matrix").join("custom_emoji.json"), custom_emoji_file_content) != 0) + return; + + rapidjson::Document json_root; + rapidjson::ParseResult parse_result = json_root.Parse(custom_emoji_file_content.c_str(), custom_emoji_file_content.size()); + if(parse_result.IsError()) { + fprintf(stderr, "Warning: failed to parse custom_emoji.json, error: %d\n", parse_result.Code()); + return; + } + + parse_custom_emoji(json_root); + } + PluginResult Matrix::parse_sync_account_data(const rapidjson::Value &account_data_json) { if(!account_data_json.IsObject()) return PluginResult::OK; @@ -2004,26 +2045,12 @@ namespace QuickMedia { } } } else if(strcmp(type_json.GetString(), "qm.emoji") == 0) { - std::lock_guard lock(room_data_mutex); - for(auto const &emoji_json : content_json.GetObject()) { - if(!emoji_json.name.IsString() || !emoji_json.value.IsObject()) - continue; - - 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); - } - } + parse_custom_emoji(content_json); + rapidjson::StringBuffer buffer; + rapidjson::Writer writer(buffer); + content_json.Accept(writer); + file_overwrite_atomic(get_cache_dir().join("matrix").join("custom_emoji.json"), std::string(buffer.GetString(), buffer.GetSize())); + } } return PluginResult::OK; @@ -2585,9 +2612,11 @@ namespace QuickMedia { bool supports_syntax_highlight = false; bool inside_img_tag = false; std::string_view img_src; + std::string_view img_alt; mgl::vec2i img_size; mgl::Color font_color = mgl::Color(255, 255, 255, 255); Matrix *matrix = nullptr; + mgl::vec2i image_max_size; }; static int accumulate_string(char *data, int size, void *userdata) { @@ -2615,6 +2644,7 @@ namespace QuickMedia { } 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_alt = std::string_view(); parse_userdata.img_size = { 0, 0 }; } break; @@ -2637,7 +2667,9 @@ namespace QuickMedia { // 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); + if(parse_userdata.image_max_size.x > 0 && parse_userdata.image_max_size.y > 0) + img_size = clamp_to_size(img_size, parse_userdata.image_max_size); + parse_userdata.result += Text::formatted_image(parse_userdata.matrix->get_media_url(image_url), false, img_size, std::string(parse_userdata.img_alt)); } parse_userdata.inside_img_tag = false; } @@ -2659,6 +2691,8 @@ namespace QuickMedia { } 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(html_parser->attribute_key.size == 3 && memcmp(html_parser->attribute_key.data, "alt", 3) == 0) { + parse_userdata.img_alt = std::string_view(html_parser->attribute_value.data, html_parser->attribute_value.size); } } else if(!parse_userdata.allow_formatted_text && parse_userdata.inside_img_tag && parse_userdata.mx_reply_depth == 0 && 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); @@ -2712,11 +2746,12 @@ namespace QuickMedia { return 0; } - std::string formatted_text_to_qm_text(Matrix *matrix, 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, mgl::vec2i image_max_size) { 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; + parse_userdata.image_max_size = image_max_size; html_parser_parse(str, size, formattext_text_parser_callback, &parse_userdata); return std::move(parse_userdata.result); } @@ -4387,6 +4422,8 @@ namespace QuickMedia { if(download_result != DownloadResult::OK) return download_result_to_plugin_result(download_result); + // TODO: Is this even needed? (including in other /account_data/qm.emoji functions), wont we get qm.emoji update in sync? + file_overwrite_atomic(get_cache_dir().join("matrix").join("custom_emoji.json"), std::string(buffer.GetString(), buffer.GetSize())); std::lock_guard lock(room_data_mutex); custom_emoji_by_key[key] = std::move(custom_emoji); return PluginResult::OK; @@ -4428,6 +4465,7 @@ namespace QuickMedia { if(download_result != DownloadResult::OK) return false; + file_overwrite_atomic(get_cache_dir().join("matrix").join("custom_emoji.json"), std::string(buffer.GetString(), buffer.GetSize())); std::lock_guard lock(room_data_mutex); auto it = custom_emoji_by_key.find(key); if(it != custom_emoji_by_key.end()) @@ -4445,6 +4483,10 @@ namespace QuickMedia { if(it == custom_emoji_list_copy.end()) return false; + auto existing_new_it = custom_emoji_list_copy.find(new_key); + if(existing_new_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); @@ -4473,6 +4515,7 @@ namespace QuickMedia { if(download_result != DownloadResult::OK) return false; + file_overwrite_atomic(get_cache_dir().join("matrix").join("custom_emoji.json"), std::string(buffer.GetString(), buffer.GetSize())); std::lock_guard lock(room_data_mutex); auto it = custom_emoji_by_key.find(key); if(it != custom_emoji_by_key.end()) { -- cgit v1.2.3