From 8f6803c25a46fd95e6e65858f4aaa9131e54c6c5 Mon Sep 17 00:00:00 2001 From: dec05eba Date: Sat, 3 Oct 2020 17:24:51 +0200 Subject: Matrix: show unread marker from previous session (fetch m.read marker from server) --- src/QuickMedia.cpp | 33 +++++++------ src/Text.cpp | 37 +++++++------- src/plugins/Matrix.cpp | 130 +++++++++++++++++++++++++++++++++---------------- 3 files changed, 123 insertions(+), 77 deletions(-) (limited to 'src') diff --git a/src/QuickMedia.cpp b/src/QuickMedia.cpp index 2607873..50b0608 100644 --- a/src/QuickMedia.cpp +++ b/src/QuickMedia.cpp @@ -3362,13 +3362,6 @@ namespace QuickMedia { const int MESSAGES_TAB_INDEX = 0; const int ROOMS_TAB_INDEX = 1; - /* - if(matrix->get_cached_sync(tabs[MESSAGES_TAB_INDEX].body->items) != PluginResult::OK) { - fprintf(stderr, "Failed to get matrix cached sync\n"); - } else { - fprintf(stderr, "Loaded matrix sync from cache, num items: %zu\n", tabs[MESSAGES_TAB_INDEX].body->items.size()); - } - */ // This is needed to get initial data, with joined rooms etc. TODO: Remove this once its cached // and allow asynchronous update of rooms bool synced = false; @@ -3398,7 +3391,15 @@ namespace QuickMedia { if(room_body_item_it == body_items_by_room_id.end()) continue; - if(only_show_mentions) { + // TODO: this wont always because we dont display all types of messages from server, such as "joined", "left", "kicked", "banned", "changed avatar", "changed display name", etc. + bool unread_messages_previous_session = false; + if(!messages.empty()) { + const UserInfo *me = matrix->get_me(room->id); + if(me->read_marker_event_id != messages.back()->event_id) + unread_messages_previous_session = true; + } + + if(only_show_mentions && !unread_messages_previous_session) { std::string room_desc; if(!messages.empty()) room_desc = matrix->message_get_author_displayname(room, messages.back().get()) + ": " + extract_first_line(messages.back()->body, 150); @@ -3419,7 +3420,6 @@ namespace QuickMedia { } }; - // TODO: the initial room to view should be the last viewed room when closing QuickMedia. // The room id should be saved in a file when changing viewed room. std::string current_room_id; RoomBodyData *current_room_body_data = nullptr; @@ -3586,7 +3586,8 @@ namespace QuickMedia { Body url_selection_body(this, font.get(), bold_font.get(), cjk_font.get()); sf::Clock read_marker_timer; - const sf::Int32 read_marker_timeout_ms = 3000; + const sf::Int32 read_marker_timeout_ms_default = 3000; + sf::Int32 read_marker_timeout_ms = 0; std::future set_read_marker_future; bool setting_read_marker = false; @@ -3891,6 +3892,7 @@ namespace QuickMedia { room_avatar_thumbnail_data = std::make_shared(); } + read_marker_timeout_ms = 0; redraw = true; } } @@ -4068,15 +4070,14 @@ namespace QuickMedia { tabs[MESSAGES_TAB_INDEX].body->select_last_item(); } + // Initial sync if(!synced) { tabs[ROOMS_TAB_INDEX].body->items = std::move(sync_result.rooms_body_items); for(auto body_item : tabs[ROOMS_TAB_INDEX].body->items) { - // TODO: Set |last_message_read| depending on read markers (either remote matrix read markers or locally saved ones) body_items_by_room_id[body_item->url] = { body_item, true, 0 }; } - // TODO: the initial room to view should be the last viewed room when closing QuickMedia. // The room id should be saved in a file when changing viewed room. if(!tabs[ROOMS_TAB_INDEX].body->items.empty()) current_room_id = tabs[ROOMS_TAB_INDEX].body->items[0]->url; @@ -4204,9 +4205,9 @@ namespace QuickMedia { tabs[MESSAGES_TAB_INDEX].body->draw_item(window, currently_operating_on_item.get(), body_item_pos, body_item_size); } - if(tabs[selected_tab].type == ChatTabType::MESSAGES) { + if(tabs[selected_tab].type == ChatTabType::MESSAGES && current_room_body_data) { if(tabs[selected_tab].body->is_last_item_fully_visible()) { - if(current_room_body_data && !current_room_body_data->last_message_read) { + if(!current_room_body_data->last_message_read) { std::string room_desc = current_room_body_data->body_item->get_description(); if(strncmp(room_desc.c_str(), "Unread: ", 8) == 0) room_desc = room_desc.substr(8); @@ -4217,7 +4218,7 @@ namespace QuickMedia { current_room_body_data->body_item->title_color = sf::Color::White; current_room_body_data->last_message_read = true; } - } else if(current_room_body_data && !current_room_body_data->last_message_read) { + } else if(!current_room_body_data->last_message_read) { window.draw(more_messages_below_rect); } } @@ -4233,7 +4234,9 @@ namespace QuickMedia { BodyItem *last_visible_item = tabs[selected_tab].body->get_last_fully_visible_item(); if(is_window_focused && chat_state != ChatState::URL_SELECTION && current_room_body_data && last_visible_item && !setting_read_marker && read_marker_timer.getElapsedTime().asMilliseconds() >= read_marker_timeout_ms) { Message *message = (Message*)last_visible_item->userdata; + // TODO: What if two messages have the same timestamp? if(message->timestamp > current_room_body_data->last_read_message_timestamp) { + read_marker_timeout_ms = read_marker_timeout_ms_default; current_room_body_data->last_read_message_timestamp = message->timestamp; // TODO: What if the message is no longer valid? setting_read_marker = true; diff --git a/src/Text.cpp b/src/Text.cpp index e311f14..d58473d 100644 --- a/src/Text.cpp +++ b/src/Text.cpp @@ -298,8 +298,7 @@ namespace QuickMedia vertices_index = 1; } - usize vertexOffset = vertices[vertices_index].getVertexCount(); - vertices[vertices_index].resize(vertices[vertices_index].getVertexCount() + 4 * textElement.text.size); // TODO: Precalculate + //vertices[vertices_index].resize(vertices[vertices_index].getVertexCount() + 4 * textElement.text.size); // TODO: Precalculate textElement.position = glyphPos; for(size_t i = 0; i < textElement.text.size; ++i) { @@ -310,16 +309,16 @@ namespace QuickMedia prevCodePoint = codePoint; glyphPos.x += kerning; - int vertexStart = vertexOffset + i * 4; + int vertexStart = vertices[vertices_index].getVertexCount(); switch(codePoint) { case ' ': { - vertices[vertices_index][vertexStart + 0] = { sf::Vector2f(glyphPos.x, glyphPos.y - vspace), sf::Color::Transparent, sf::Vector2f() }; - vertices[vertices_index][vertexStart + 1] = { sf::Vector2f(glyphPos.x + hspace, glyphPos.y - vspace), sf::Color::Transparent, sf::Vector2f() }; - vertices[vertices_index][vertexStart + 2] = { sf::Vector2f(glyphPos.x + hspace, glyphPos.y), sf::Color::Transparent, sf::Vector2f() }; - vertices[vertices_index][vertexStart + 3] = { sf::Vector2f(glyphPos.x, glyphPos.y), sf::Color::Transparent, sf::Vector2f() }; + vertices[vertices_index].append({ sf::Vector2f(glyphPos.x, glyphPos.y - vspace), sf::Color::Transparent, sf::Vector2f() }); + vertices[vertices_index].append({ sf::Vector2f(glyphPos.x + hspace, glyphPos.y - vspace), sf::Color::Transparent, sf::Vector2f() }); + vertices[vertices_index].append({ sf::Vector2f(glyphPos.x + hspace, glyphPos.y), sf::Color::Transparent, sf::Vector2f() }); + vertices[vertices_index].append({ sf::Vector2f(glyphPos.x, glyphPos.y), sf::Color::Transparent, sf::Vector2f() }); glyphPos.x += hspace; vertices_linear.push_back({vertices_index, vertexStart, 0, codePoint}); continue; @@ -327,20 +326,20 @@ namespace QuickMedia case '\t': { const float char_width = hspace * TAB_WIDTH; - vertices[vertices_index][vertexStart + 0] = { sf::Vector2f(glyphPos.x, glyphPos.y - vspace), sf::Color::Transparent, sf::Vector2f() }; - vertices[vertices_index][vertexStart + 1] = { sf::Vector2f(glyphPos.x + char_width, glyphPos.y - vspace), sf::Color::Transparent, sf::Vector2f() }; - vertices[vertices_index][vertexStart + 2] = { sf::Vector2f(glyphPos.x + char_width, glyphPos.y), sf::Color::Transparent, sf::Vector2f() }; - vertices[vertices_index][vertexStart + 3] = { sf::Vector2f(glyphPos.x, glyphPos.y), sf::Color::Transparent, sf::Vector2f() }; + vertices[vertices_index].append({ sf::Vector2f(glyphPos.x, glyphPos.y - vspace), sf::Color::Transparent, sf::Vector2f() }); + vertices[vertices_index].append({ sf::Vector2f(glyphPos.x + char_width, glyphPos.y - vspace), sf::Color::Transparent, sf::Vector2f() }); + vertices[vertices_index].append({ sf::Vector2f(glyphPos.x + char_width, glyphPos.y), sf::Color::Transparent, sf::Vector2f() }); + vertices[vertices_index].append({ sf::Vector2f(glyphPos.x, glyphPos.y), sf::Color::Transparent, sf::Vector2f() }); glyphPos.x += char_width; vertices_linear.push_back({vertices_index, vertexStart, 0, codePoint}); continue; } case '\n': { - vertices[vertices_index][vertexStart + 0] = { sf::Vector2f(glyphPos.x, glyphPos.y - vspace), sf::Color::Transparent, sf::Vector2f() }; - vertices[vertices_index][vertexStart + 1] = { sf::Vector2f(glyphPos.x, glyphPos.y - vspace), sf::Color::Transparent, sf::Vector2f() }; - vertices[vertices_index][vertexStart + 2] = { sf::Vector2f(glyphPos.x, glyphPos.y), sf::Color::Transparent, sf::Vector2f() }; - vertices[vertices_index][vertexStart + 3] = { sf::Vector2f(glyphPos.x, glyphPos.y), sf::Color::Transparent, sf::Vector2f() }; + vertices[vertices_index].append({ sf::Vector2f(glyphPos.x, glyphPos.y - vspace), sf::Color::Transparent, sf::Vector2f() }); + vertices[vertices_index].append({ sf::Vector2f(glyphPos.x, glyphPos.y - vspace), sf::Color::Transparent, sf::Vector2f() }); + vertices[vertices_index].append({ sf::Vector2f(glyphPos.x, glyphPos.y), sf::Color::Transparent, sf::Vector2f() }); + vertices[vertices_index].append({ sf::Vector2f(glyphPos.x, glyphPos.y), sf::Color::Transparent, sf::Vector2f() }); glyphPos.x = 0.0f; glyphPos.y += floor(vspace + lineSpacing); vertices_linear.push_back({vertices_index, vertexStart, 0, codePoint}); @@ -362,10 +361,10 @@ namespace QuickMedia sf::Color fontColor = (textElement.type == TextElement::Type::TEXT ? color : urlColor); - vertices[vertices_index][vertexStart + 0] = { vertexTopLeft, fontColor, textureTopLeft }; - vertices[vertices_index][vertexStart + 1] = { vertexTopRight, fontColor, textureTopRight }; - vertices[vertices_index][vertexStart + 2] = { vertexBottomRight, fontColor, textureBottomRight }; - vertices[vertices_index][vertexStart + 3] = { vertexBottomLeft, fontColor, textureBottomLeft }; + vertices[vertices_index].append({ vertexTopLeft, fontColor, textureTopLeft }); + vertices[vertices_index].append({ vertexTopRight, fontColor, textureTopRight }); + vertices[vertices_index].append({ vertexBottomRight, fontColor, textureBottomRight }); + vertices[vertices_index].append({ vertexBottomLeft, fontColor, textureBottomLeft }); glyphPos.x += glyph.advance + characterSpacing; vertices_linear.push_back({vertices_index, vertexStart, 0, codePoint}); diff --git a/src/plugins/Matrix.cpp b/src/plugins/Matrix.cpp index 69c2686..ec7064b 100644 --- a/src/plugins/Matrix.cpp +++ b/src/plugins/Matrix.cpp @@ -239,56 +239,41 @@ namespace QuickMedia { } const Json::Value &state_json = (*it)["state"]; - if(!state_json.isObject()) - continue; - - const Json::Value &events_json = state_json["events"]; - events_add_user_info(events_json, room_it->second.get()); - events_set_room_name(events_json, room_it->second.get()); - } - - for(Json::Value::const_iterator it = join_json.begin(); it != join_json.end(); ++it) { - if(!it->isObject()) - continue; - - Json::Value room_id = it.key(); - if(!room_id.isString()) - continue; - - std::string room_id_str = room_id.asString(); - - auto room_it = room_data_by_id.find(room_id_str); - if(room_it == room_data_by_id.end()) { - auto room_data = std::make_unique(); - room_data->id = room_id_str; - room_data_by_id.insert(std::make_pair(room_id_str, std::move(room_data))); - room_it = room_data_by_id.find(room_id_str); // TODO: Get iterator from above insert + if(state_json.isObject()) { + const Json::Value &events_json = state_json["events"]; + events_add_user_info(events_json, room_it->second.get()); + events_set_room_name(events_json, room_it->second.get()); } const Json::Value &timeline_json = (*it)["timeline"]; - if(!timeline_json.isObject()) - continue; + if(timeline_json.isObject()) { + if(room_it->second->prev_batch.empty()) { + // This may be non-existent if this is the first event in the room + const Json::Value &prev_batch_json = timeline_json["prev_batch"]; + if(prev_batch_json.isString()) + room_it->second->prev_batch = prev_batch_json.asString(); + } - if(room_it->second->prev_batch.empty()) { - // This may be non-existent if this is the first event in the room - const Json::Value &prev_batch_json = timeline_json["prev_batch"]; - if(prev_batch_json.isString()) - room_it->second->prev_batch = prev_batch_json.asString(); - } + // TODO: Is there no better way to check for notifications? this is not robust... + bool has_unread_notifications = false; + const Json::Value &unread_notification_json = (*it)["unread_notifications"]; + if(unread_notification_json.isObject()) { + const Json::Value &highlight_count_json = unread_notification_json["highlight_count"]; + if(highlight_count_json.isNumeric() && highlight_count_json.asInt64() > 0) + has_unread_notifications = true; + } - // TODO: Is there no better way to check for notifications? this is not robust... - bool has_unread_notifications = false; - const Json::Value &unread_notification_json = (*it)["unread_notifications"]; - if(unread_notification_json.isObject()) { - const Json::Value &highlight_count_json = unread_notification_json["highlight_count"]; - if(highlight_count_json.isNumeric() && highlight_count_json.asInt64() > 0) - has_unread_notifications = true; + const Json::Value &events_json = timeline_json["events"]; + events_add_user_info(events_json, room_it->second.get()); + events_add_messages(events_json, room_it->second.get(), MessageDirection::AFTER, &room_messages, has_unread_notifications); + events_set_room_name(events_json, room_it->second.get()); } - const Json::Value &events_json = timeline_json["events"]; - events_add_user_info(events_json, room_it->second.get()); - events_add_messages(events_json, room_it->second.get(), MessageDirection::AFTER, &room_messages, has_unread_notifications); - events_set_room_name(events_json, room_it->second.get()); + const Json::Value &ephemeral_json = (*it)["ephemeral"]; + if(ephemeral_json.isObject()) { + const Json::Value &events_json = ephemeral_json["events"]; + events_add_user_read_markers(events_json, room_it->second.get()); + } } return PluginResult::OK; @@ -362,6 +347,56 @@ namespace QuickMedia { } } + void Matrix::events_add_user_read_markers(const Json::Value &events_json, RoomData *room_data) { + if(!events_json.isArray()) + return; + + for(const Json::Value &event_item_json : events_json) { + if(!event_item_json.isObject()) + continue; + + const Json::Value &type_json = event_item_json["type"]; + if(!type_json.isString() || strcmp(type_json.asCString(), "m.receipt") != 0) + continue; + + const Json::Value &content_json = event_item_json["content"]; + if(!content_json.isObject()) + continue; + + for(Json::Value::const_iterator it = content_json.begin(); it != content_json.end(); ++it) { + if(!it->isObject()) + continue; + + Json::Value event_id_json = it.key(); + if(!event_id_json.isString()) + continue; + + const Json::Value &read_json = (*it)["m.read"]; + if(!read_json.isObject()) + continue; + + std::string event_id_str = event_id_json.asString(); + + for(Json::Value::const_iterator user_id_it = read_json.begin(); user_id_it != read_json.end(); ++user_id_it) { + if(!user_id_it->isObject()) + continue; + + Json::Value user_id_json = user_id_it.key(); + if(!user_id_json.isString()) + continue; + + auto user_it = room_data->user_info_by_user_id.find(user_id_json.asString()); + if(user_it == room_data->user_info_by_user_id.end()) { + fprintf(stderr, "Receipt read receipt for unknown user: %s, ignoring...\n", user_id_json.asCString()); + continue; + } + + room_data->user_info[user_it->second].read_marker_event_id = event_id_str; + } + } + } + } + static std::string message_content_extract_thumbnail_url(const Json::Value &content_json, const std::string &homeserver) { const Json::Value &info_json = content_json["info"]; if(info_json.isObject()) { @@ -1725,4 +1760,13 @@ namespace QuickMedia { *upload_size = upload_limit.value(); return PluginResult::OK; } + + const UserInfo* Matrix::get_me(const std::string &room_id) const { + auto room_it = room_data_by_id.find(room_id); + if(room_it == room_data_by_id.end()) { + fprintf(stderr, "Error: no such room: %s\n", room_id.c_str()); + return nullptr; + } + return &room_it->second->user_info[room_it->second->user_info_by_user_id[user_id]]; + } } \ No newline at end of file -- cgit v1.2.3