diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/Body.cpp | 2 | ||||
-rw-r--r-- | src/Config.cpp | 1 | ||||
-rw-r--r-- | src/DownloadUtils.cpp | 8 | ||||
-rw-r--r-- | src/FileAnalyzer.cpp | 6 | ||||
-rw-r--r-- | src/Program.cpp | 8 | ||||
-rw-r--r-- | src/QuickMedia.cpp | 73 | ||||
-rw-r--r-- | src/ResourceLoader.cpp | 8 | ||||
-rw-r--r-- | src/StringUtils.cpp | 12 | ||||
-rw-r--r-- | src/plugins/Matrix.cpp | 218 |
9 files changed, 282 insertions, 54 deletions
diff --git a/src/Body.cpp b/src/Body.cpp index b21012d..fcf932c 100644 --- a/src/Body.cpp +++ b/src/Body.cpp @@ -1553,6 +1553,7 @@ namespace QuickMedia { if(item->extra) { Widgets widgets; + widgets.body_item = item.get(); if(thumbnail_drawn) { ThumbnailWidget thumbnail; thumbnail.position = image.get_position(); @@ -1624,6 +1625,7 @@ namespace QuickMedia { if(item->extra) { Widgets widgets; + widgets.body_item = item.get(); if(thumbnail_drawn) { ThumbnailWidget thumbnail; thumbnail.position = image.get_position(); diff --git a/src/Config.cpp b/src/Config.cpp index 6a73dcf..570504f 100644 --- a/src/Config.cpp +++ b/src/Config.cpp @@ -226,6 +226,7 @@ namespace QuickMedia { config->matrix.known_homeservers.push_back(known_homeserver.asString()); } } + get_json_value(matrix_json, "gpg_user_id", config->matrix.gpg_user_id); } if(!has_known_matrix_homeservers_config) diff --git a/src/DownloadUtils.cpp b/src/DownloadUtils.cpp index 3fb8b19..913857b 100644 --- a/src/DownloadUtils.cpp +++ b/src/DownloadUtils.cpp @@ -19,14 +19,6 @@ namespace QuickMedia { int64_t total_downloaded_size = 0; }; - static int accumulate_string(char *data, int size, void *userdata) { - std::string *str = (std::string*)userdata; - if(str->size() + size > 1024 * 1024 * 100) // 100mb sane limit, TODO: make configurable - return 1; - str->append(data, size); - return 0; - } - static bool http_is_redirect(const char *header, size_t size) { const void *end_of_first_line_p = memmem(header, size, "\r\n", 2); if(!end_of_first_line_p) diff --git a/src/FileAnalyzer.cpp b/src/FileAnalyzer.cpp index b02d0c2..65c33b5 100644 --- a/src/FileAnalyzer.cpp +++ b/src/FileAnalyzer.cpp @@ -139,12 +139,6 @@ namespace QuickMedia { } - static int accumulate_string(char *data, int size, void *userdata) { - std::string *str = (std::string*)userdata; - str->append(data, size); - return 0; - } - bool video_get_middle_frame(const FileAnalyzer &file, const char *destination_path, int width, int height) { Path destination_path_tmp = destination_path; destination_path_tmp.append(".tmp.jpg"); // TODO: .png, but the below code also needs to be changed for that diff --git a/src/Program.cpp b/src/Program.cpp index ee189f7..8b3cc49 100644 --- a/src/Program.cpp +++ b/src/Program.cpp @@ -13,6 +13,14 @@ #define READ_END 0 #define WRITE_END 1 +int accumulate_string(char *data, int size, void *userdata) { + std::string *str = (std::string*)userdata; + if(str->size() + size > 1024 * 1024 * 100) // 100mb sane limit, TODO: make configurable + return 1; + str->append(data, size); + return 0; +} + struct ReadWriteProgram { pid_t pid = -1; int read_fd = -1; diff --git a/src/QuickMedia.cpp b/src/QuickMedia.cpp index 7e37214..074f3e9 100644 --- a/src/QuickMedia.cpp +++ b/src/QuickMedia.cpp @@ -5311,6 +5311,11 @@ namespace QuickMedia { body_item->embedded_item = BodyItem::create(""); *body_item->embedded_item = *related_body_item; + if(related_body_item->extra) { + MatrixChatBodyItemData *other_item_data = static_cast<MatrixChatBodyItemData*>(related_body_item->extra.get()); + if(other_item_data->decrypt_state != MatrixChatBodyItemData::DecryptState::DECRYPTED) + body_item->embedded_item->extra = std::make_shared<MatrixChatBodyItemData>(other_item_data->matrix, other_item_data->text_to_decrypt); + } body_item->embedded_item->embedded_item = nullptr; body_item->embedded_item->reactions.clear(); if(message->user->user_id != my_user_id && ((related_body_item->userdata && static_cast<Message*>(related_body_item->userdata)->user.get() == me) || message_contains_user_mention(body_item, my_display_name, my_user_id))) @@ -5325,11 +5330,23 @@ 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 void matrix_body_set_text_decrypt_if_needed(Matrix *matrix, BodyItem *body_item, std::string text) { + // TODO: Check if gpg is installed + if(!get_config().matrix.gpg_user_id.empty() && text.find("-----BEGIN PGP MESSAGE-----") != std::string::npos && text.find("-----END PGP MESSAGE-----") != std::string::npos) { + body_item->extra = std::make_shared<MatrixChatBodyItemData>(matrix, std::move(text)); + body_item->set_description("🔒 Decrypting message..."); + } else { + body_item->set_description(std::move(text)); + } + } + 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) { Message *latest_message = get_latest_message_in_edit_chain(message); + 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(matrix, latest_message))); + //body_item->set_description(matrix_decrypt_gpg_message_if_needed(strip(message_to_qm_text(matrix, latest_message)))); + matrix_body_set_text_decrypt_if_needed(matrix, body_item.get(), strip(message_to_qm_text(matrix, latest_message))); body_item->set_timestamp(message->timestamp); if(!message->thumbnail_url.empty()) { body_item->thumbnail_url = message->thumbnail_url; @@ -5631,7 +5648,8 @@ namespace QuickMedia { if(message->timestamp > edited_message_ref->timestamp) { std::string qm_formatted_text = message_to_qm_text(matrix, message.get()); - body_item->set_description(std::move(qm_formatted_text)); + //body_item->set_description(matrix_decrypt_gpg_message_if_needed(std::move(qm_formatted_text))); + matrix_body_set_text_decrypt_if_needed(matrix, body_item.get(), std::move(qm_formatted_text)); 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 @@ -5674,7 +5692,8 @@ namespace QuickMedia { if(message->timestamp > edited_message_ref->timestamp) { 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)); + //body_item->set_description(matrix_decrypt_gpg_message_if_needed(std::move(qm_formatted_text))); + matrix_body_set_text_decrypt_if_needed(matrix, body_item.get(), std::move(qm_formatted_text)); 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 @@ -6028,7 +6047,8 @@ namespace QuickMedia { "/leave: Leave the current room.\n" "/me [text]: Send a message of type \"m.emote\".\n" "/react [text]: React to the selected message (also works if you are replying to a message).\n" - "/id: Show the room id."; + "/id: Show the room id.\n" + "/encrypt [text]: Send a message encrypted with gpg. gpg needs to be installed to do this. Uses the gpg key specified by the user id in your config variable \"matrix.gpg_user_id\"."; message->timestamp = time(nullptr) * 1000; // TODO: What if the user has broken local time? matrix->append_system_message(current_room, std::move(message)); @@ -6052,7 +6072,7 @@ namespace QuickMedia { } else if(strncmp(text.c_str(), "/react ", 7) == 0) { msgtype = "m.reaction"; text.erase(text.begin(), text.begin() + 7); - } else { + } else if(strncmp(text.c_str(), "/encrypt ", 9) != 0) { show_notification("QuickMedia", "Error: invalid command: " + text + ", type /help to see a list of valid commands.", Urgency::NORMAL); return false; } @@ -6061,7 +6081,27 @@ namespace QuickMedia { msgtype = "m.reaction"; text.erase(text.begin(), text.begin() + 7); } - } + } + + if((chat_state == ChatState::TYPING_MESSAGE || chat_state == ChatState::EDITING || chat_state == ChatState::REPLYING) && msgtype.empty() && text[0] == '/' && strncmp(text.c_str(), "/encrypt ", 9) == 0) { + text.erase(text.begin(), text.begin() + 9); + if(!is_program_executable_by_name("gpg")) { + show_notification("QuickMedia", "GPG needs to be installed to use the /encrypt command", Urgency::CRITICAL); + return false; + } + + if(get_config().matrix.gpg_user_id.empty()) { + show_notification("QuickMedia", "The config variable matrix.gpg_user_id needs to be set to use the /encrypt command", Urgency::CRITICAL); + return false; + } + + std::string encrypted_string; + if(!matrix_gpg_encrypt_for_each_user_in_room(matrix, current_room, get_config().matrix.gpg_user_id, text, encrypted_string)) { + show_notification("QuickMedia", "Failed to encrypt message with gpg. Make sure you used the correct gpg user id in the config variable matrix.gpg_user_id", Urgency::CRITICAL); + return false; + } + text = std::move(encrypted_string); + } auto message = std::make_shared<Message>(); message->body_is_formatted = true; @@ -6178,7 +6218,8 @@ namespace QuickMedia { 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(matrix_decrypt_gpg_message_if_needed(std::move(qm_formatted_text))); + matrix_body_set_text_decrypt_if_needed(matrix, body_item_shared_ptr.get(), 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(matrix, current_room, message.get(), current_room->get_user_avatar_url(me), me->user_id); @@ -6272,6 +6313,11 @@ namespace QuickMedia { auto related_body_item = find_body_item_by_event_id(tabs[MESSAGES_TAB_INDEX].body->get_items().data(), tabs[MESSAGES_TAB_INDEX].body->get_items().size(), event_data->event_id); if(related_body_item) { *body_item = *related_body_item; + if(related_body_item->extra) { + MatrixChatBodyItemData *other_item_data = static_cast<MatrixChatBodyItemData*>(related_body_item->extra.get()); + if(other_item_data->decrypt_state != MatrixChatBodyItemData::DecryptState::DECRYPTED) + body_item->extra = std::make_shared<MatrixChatBodyItemData>(other_item_data->matrix, other_item_data->text_to_decrypt); + } body_item->reactions.clear(); if(message_contains_user_mention(related_body_item.get(), current_room->get_user_display_name(me), me->user_id)) body_item->set_description_color(get_theme().attention_alert_text_color, true); @@ -7616,8 +7662,9 @@ namespace QuickMedia { break; } - if(last_timeline_message != -1) { - current_room->body_item->set_description(current_room->latest_message); + if(last_timeline_message != -1 && (!current_room->body_item->extra || static_cast<MatrixChatBodyItemData*>(current_room->body_item->extra.get())->decrypt_state == MatrixChatBodyItemData::DecryptState::DECRYPTED)) { + if(current_room->offset_to_latest_message_text < (int)current_room->body_item->get_description().size()) + current_room->body_item->set_description(current_room->body_item->get_description().substr(current_room->offset_to_latest_message_text)); current_room->body_item->set_description_color(get_theme().faded_text_color); // TODO: Show a line like nheko instead for unread messages, or something else current_room->body_item->set_title_color(get_theme().text_color); @@ -7876,14 +7923,6 @@ namespace QuickMedia { } } - static int accumulate_string(char *data, int size, void *userdata) { - std::string *str = (std::string*)userdata; - if(str->size() + size > 1024 * 1024 * 100) // 100mb sane limit, TODO: make configurable - return 1; - str->append(data, size); - return 0; - } - void Program::download_page(std::string url, std::string download_filename, bool no_dialog) { window.set_title(("QuickMedia - Select where you want to save " + std::string(url)).c_str()); diff --git a/src/ResourceLoader.cpp b/src/ResourceLoader.cpp index 86687d2..a120096 100644 --- a/src/ResourceLoader.cpp +++ b/src/ResourceLoader.cpp @@ -26,14 +26,6 @@ namespace QuickMedia { return resource_root.c_str(); } - static int accumulate_string(char *data, int size, void *userdata) { - std::string *str = (std::string*)userdata; - if(str->size() + size > 1024 * 1024 * 100) // 100mb sane limit, TODO: make configurable - return 1; - str->append(data, size); - return 0; - } - // If absolute, use the path; otherwise use fc-match to find the font static bool find_font(const std::string &font_name, std::string &font_filepath_result) { if(get_config().font.latin.find('/') != std::string::npos) { diff --git a/src/StringUtils.cpp b/src/StringUtils.cpp index bc59730..564f307 100644 --- a/src/StringUtils.cpp +++ b/src/StringUtils.cpp @@ -3,7 +3,7 @@ namespace QuickMedia { template <typename T> - static void string_split_t(const std::string &str, const T &delimiter, StringSplitCallback callback_func, bool include_empty) { + static void string_split_t(const std::string_view str, const T &delimiter, StringSplitCallback callback_func, bool include_empty) { size_t index = 0; while(index < str.size()) { size_t new_index = str.find(delimiter, index); @@ -23,10 +23,18 @@ namespace QuickMedia { } void string_split(const std::string &str, const std::string &delimiter, StringSplitCallback callback_func, bool include_empty) { - string_split_t(str, delimiter, callback_func, include_empty); + string_split_t(std::string_view(str), delimiter, callback_func, include_empty); } void string_split(const std::string &str, char delimiter, StringSplitCallback callback_func, bool include_empty) { + string_split_t(std::string_view(str), delimiter, callback_func, include_empty); + } + + void string_split_view(const std::string_view str, const std::string &delimiter, StringSplitCallback callback_func, bool include_empty) { + string_split_t(str, delimiter, callback_func, include_empty); + } + + void string_split_view(const std::string_view str, char delimiter, StringSplitCallback callback_func, bool include_empty) { string_split_t(str, delimiter, callback_func, include_empty); } diff --git a/src/plugins/Matrix.cpp b/src/plugins/Matrix.cpp index 973571e..953543c 100644 --- a/src/plugins/Matrix.cpp +++ b/src/plugins/Matrix.cpp @@ -41,6 +41,145 @@ namespace QuickMedia { 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\",\"m.room.canonical_alias\",\"m.space.child\"],\"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\",\"m.room.canonical_alias\",\"m.space.child\"],\"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 bool is_gpg_installed = false; + static bool gpg_installed_checked = false; + + static std::string matrix_decrypt_gpg_message_if_needed(std::string message, bool &success) { + success = false; + + if(!gpg_installed_checked) { + gpg_installed_checked = true; + is_gpg_installed = is_program_executable_by_name("gpg"); + } + + std::string result; + if(!is_gpg_installed || get_config().matrix.gpg_user_id.empty()) { + result = std::move(message); + } else { + size_t pgp_begin_index; + size_t pgp_end_index; + if((pgp_begin_index = message.find("-----BEGIN PGP MESSAGE-----")) != std::string::npos && (pgp_end_index = message.find("-----END PGP MESSAGE-----", pgp_begin_index + 27)) != std::string::npos) { + std::string decrypted_string; + const char *args[] = { "gpg", "-d", nullptr }; + const std::string_view pgp_message(message.data() + pgp_begin_index, (pgp_end_index + 25) - pgp_begin_index); + if(exec_program_write_stdin(args, pgp_message.data(), pgp_message.size(), accumulate_string, &decrypted_string) != 0) { + result = "🔒 Failed to decrypt message:\n" + std::move(message); + } else { + decrypted_string.insert(0, "🔒 ", strlen("🔒 ")); + result = std::move(message); + result.replace(result.begin() + pgp_begin_index, result.begin() + pgp_begin_index + pgp_message.size(), std::move(decrypted_string)); + success = true; + } + } else { + result = std::move(message); + } + } + return result; + } + + enum class GpgLineType { + UID, + FPR, + UNKNOWN + }; + + static std::string user_id_to_email_format(const std::string_view user_id) { + if(user_id.empty() || user_id[0] != '@') + return ""; + + size_t colon_index = user_id.find(':'); + if(colon_index == std::string::npos) + return ""; + + return std::string(user_id.substr(1, colon_index - 1)) + "@" + std::string(user_id.substr(colon_index + 1)); + } + + static std::string_view gpg_display_name_extract_email(const std::string_view display_name) { + if(display_name.empty() || display_name.back() != '>') + return std::string_view(); + + size_t email_start_index = display_name.rfind('<'); + if(email_start_index == std::string::npos) + return std::string_view(); + + email_start_index += 1; + return display_name.substr(email_start_index, display_name.size() - 1 - email_start_index); + } + + static bool for_each_user_with_matching_gpg_key(const std::vector<std::shared_ptr<UserInfo>> &users, std::function<void(std::string_view pub_key)> callback) { + std::string output; + const char *args[] = { "gpg", "--list-keys", "--with-colons", nullptr }; + if(exec_program(args, accumulate_string, &output) != 0) + return false; + + std::unordered_set<std::string> users_by_email; + for(auto &user : users) { + std::string user_email = user_id_to_email_format(user->user_id); + if(!user_email.empty()) + users_by_email.insert(std::move(user_email)); + } + + std::string_view fpr; + string_split(output, '\n', [&](const char *str, size_t size) { + GpgLineType line_type = GpgLineType::UNKNOWN; + int column = 0; + + string_split_view(std::string_view(str, size), ':', [&](const char *str, size_t size) { + std::string_view section(str, size); + if(column == 0) { + if(section == "uid") + line_type = GpgLineType::UID; + else if(section == "fpr") + line_type = GpgLineType::FPR; + } else if(column == 9) { + if(line_type == GpgLineType::UID) { + // Assumes that each uid is preceeded with fpr + const std::string user_email = std::string(gpg_display_name_extract_email(section)); + if(!user_email.empty() && users_by_email.find(user_email) != users_by_email.end()) { + fprintf(stderr, "found public key %.*s for user %s\n", (int)fpr.size(), fpr.data(), user_email.c_str()); + callback(fpr); + } + } else if(line_type == GpgLineType::FPR) { + fpr = section; + } + } + + ++column; + return true; + }); + + return true; + }); + + return true; + } + + bool matrix_gpg_encrypt_for_each_user_in_room(Matrix *matrix, RoomData *room, const std::string &my_gpg_user_id, const std::string &str, std::string &encrypted_str) { + auto me = matrix->get_me(room); + if(!me) { + fprintf(stderr, "Error: matrix->get_me failed\n"); + return false; + } + + std::vector<std::string> user_public_keys; + const bool found_users = for_each_user_with_matching_gpg_key(room->get_users_excluding_me(me->user_id), [&](std::string_view pub_key) { + user_public_keys.emplace_back(pub_key); + }); + + if(!found_users) { + fprintf(stderr, "Error: gpg --list-keys failed\n"); + return false; + } + + std::vector<const char*> args = { "gpg", "-e", "-r", my_gpg_user_id.c_str(), "-u", my_gpg_user_id.c_str(), "--armor", "--trust-model", "always", "--comment", "This message requires you to use QuickMedia to view it and the user that sent it needs to have your key imported in gpg" }; + for(const std::string &pub_key : user_public_keys) { + args.push_back("-r"); + args.push_back(pub_key.c_str()); + } + args.push_back(nullptr); + return exec_program_write_stdin(args.data(), str.c_str(), str.size(), accumulate_string, &encrypted_str) == 0; + } + static std::string capitalize(const std::string &str) { if(str.size() >= 1) return to_upper(str[0]) + str.substr(1); @@ -163,6 +302,35 @@ namespace QuickMedia { } } + MatrixChatBodyItemData::~MatrixChatBodyItemData() { + if(decrypt_job) + decrypt_job->cancel = true; + } + + void MatrixChatBodyItemData::draw_overlay(mgl::Window&, const Widgets &widgets) { + switch(decrypt_state) { + case DecryptState::NOT_DECRYPTED: { + decrypt_state = DecryptState::DECRYPTING; + decrypt_job = std::make_shared<MatrixChatBodyDecryptJob>(); + decrypt_job->text = text_to_decrypt; + decrypt_job->decrypt_state = MatrixChatBodyDecryptJob::DecryptState::DECRYPTING; + matrix->async_decrypt_message(decrypt_job); + break; + } + case DecryptState::DECRYPTING: { + if(decrypt_job->decrypt_state == MatrixChatBodyDecryptJob::DecryptState::DECRYPTED || decrypt_job->decrypt_state == MatrixChatBodyDecryptJob::DecryptState::FAILED_TO_DECRYPT) { + decrypt_state = DecryptState::DECRYPTED; + widgets.body_item->set_description(std::move(decrypt_job->text)); + decrypt_job.reset(); + } + break; + } + case DecryptState::DECRYPTED: { + break; + } + } + } + bool TimestampedDisplayData::set_data_if_newer(std::string new_data, time_t new_timestamp) { if(new_timestamp == 0) { data = std::move(new_data); @@ -307,6 +475,7 @@ namespace QuickMedia { std::vector<std::shared_ptr<UserInfo>> RoomData::get_users_excluding_me(const std::string &my_user_id) { std::lock_guard<std::mutex> lock(user_mutex); std::vector<std::shared_ptr<UserInfo>> users_excluding_me; + // TODO: Optimize for(auto &[user_id, user] : user_info_by_user_id) { if(user->user_id != my_user_id) { users_excluding_me.push_back(user); @@ -687,6 +856,10 @@ namespace QuickMedia { return body; } + static bool should_message_by_decrypted(const std::string &text) { + return !get_config().matrix.gpg_user_id.empty() && text.find("-----BEGIN PGP MESSAGE-----") != std::string::npos && text.find("-----END PGP MESSAGE-----") != std::string::npos; + } + void MatrixQuickMedia::update_room_description(RoomData *room, const Messages &new_messages, bool is_initial_sync, bool sync_is_cache) { time_t read_marker_message_timestamp = 0; std::shared_ptr<UserInfo> me = matrix->get_me(room); @@ -756,11 +929,13 @@ namespace QuickMedia { if(!unread_mentions && set_room_as_unread) room_desc += "Unread: "; - room->latest_message = 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); - room_desc += room->latest_message; + room->offset_to_latest_message_text = room_desc.size(); + 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); room->body_item->set_description(std::move(room_desc)); room->body_item->set_description_max_lines(3); + if(should_message_by_decrypted(room->body_item->get_description())) + room->body_item->extra = std::make_shared<MatrixChatBodyItemData>(matrix, room->body_item->get_description()); if(set_room_as_unread) room->body_item->set_title_color(get_theme().attention_alert_text_color, true); room->last_message_read = false; @@ -768,10 +943,12 @@ namespace QuickMedia { rooms_page->move_room_to_top(room); room_tags_page->move_room_to_top(room); } else if(last_new_message) { + room->offset_to_latest_message_text = 0; 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); - room->latest_message = room->body_item->get_description(); + if(should_message_by_decrypted(room->body_item->get_description())) + room->body_item->extra = std::make_shared<MatrixChatBodyItemData>(matrix, room->body_item->get_description()); rooms_page->move_room_to_top(room); room_tags_page->move_room_to_top(room); @@ -1763,6 +1940,10 @@ namespace QuickMedia { custom_emoji_by_key.clear(); silenced_invites.clear(); qm_read_markers_by_room_cache.clear(); + if(decrypt_thread.joinable()) { + decrypt_task.close(); + decrypt_thread.join(); + } } bool Matrix::is_initial_sync_finished() { @@ -2674,14 +2855,6 @@ namespace QuickMedia { mgl::vec2i image_max_size; }; - static int accumulate_string(char *data, int size, void *userdata) { - std::string *str = (std::string*)userdata; - if(str->size() + size > 1024 * 1024 * 100) // 100mb sane limit, TODO: make configurable - return 1; - str->append(data, size); - return 0; - } - // TODO: Full proper parsing with tag depth static int formattext_text_parser_callback(HtmlParser *html_parser, HtmlParseType parse_type, void *userdata) { FormattedTextParseUserdata &parse_userdata = *(FormattedTextParseUserdata*)userdata; @@ -3882,7 +4055,7 @@ namespace QuickMedia { if(is_inside_code_block) formatted_body += '\n'; else - formatted_body += "<br/>"; + formatted_body += "<br>"; } std::string line_str(str, size); @@ -3924,6 +4097,26 @@ namespace QuickMedia { } + void Matrix::async_decrypt_message(std::shared_ptr<MatrixChatBodyDecryptJob> decrypt_job) { + if(!decrypt_thread.joinable()) { + decrypt_thread = std::thread([this]() { + while(decrypt_task.is_running()) { + auto decrypt_job = decrypt_task.pop_wait(); + if(!decrypt_job) + return; + + if(decrypt_job.value()->cancel) + continue; + + bool success = false; + decrypt_job.value()->text = matrix_decrypt_gpg_message_if_needed(std::move(decrypt_job.value()->text), success); + decrypt_job.value()->decrypt_state = success ? MatrixChatBodyDecryptJob::DecryptState::DECRYPTED : MatrixChatBodyDecryptJob::DecryptState::FAILED_TO_DECRYPT; + } + }); + } + decrypt_task.push(std::move(decrypt_job)); + } + PluginResult Matrix::post_message(RoomData *room, const std::string &body, std::string &event_id_response, const std::optional<UploadInfo> &file_info, const std::optional<UploadInfo> &thumbnail_info, const std::string &msgtype, const std::string &custom_transaction_id) { std::string transaction_id = custom_transaction_id; if(transaction_id.empty()) @@ -5855,7 +6048,6 @@ namespace QuickMedia { } void Matrix::update() { - mgl::Clock timer; std::optional<std::function<void()>> task; while((task = ui_thread_tasks.pop_if_available()) != std::nullopt) { task.value()(); |