aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authordec05eba <dec05eba@protonmail.com>2022-11-11 10:50:39 +0100
committerdec05eba <dec05eba@protonmail.com>2022-11-11 10:50:39 +0100
commitd274d3a6dfc0864ec6a44e7d6948c2d873eb6f76 (patch)
treea4308da3afd0e7c567c40f8a26ab1bdcbc58a898 /src
parent25f6303ae40e9245f42545d120efa8b6f9be98d7 (diff)
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
Diffstat (limited to 'src')
-rw-r--r--src/QuickMedia.cpp8
-rw-r--r--src/Text.cpp37
-rw-r--r--src/plugins/Matrix.cpp101
3 files changed, 106 insertions, 40 deletions
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<std::recursive_mutex> 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<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;
-
- 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<rapidjson::StringBuffer> 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<std::recursive_mutex> 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<std::recursive_mutex> 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<std::recursive_mutex> lock(room_data_mutex);
auto it = custom_emoji_by_key.find(key);
if(it != custom_emoji_by_key.end()) {