diff --git a/.ci/script.sh b/.ci/script.sh index 63e7f135..f43a6efb 100755 --- a/.ci/script.sh +++ b/.ci/script.sh @@ -71,7 +71,6 @@ cmake -GNinja -H. -Bbuild \ -DHUNTER_ROOT=".hunter" \ -DHUNTER_ENABLED=ON -DBUILD_SHARED_LIBS=OFF \ -DCMAKE_BUILD_TYPE=RelWithDebInfo -DHUNTER_CONFIGURATION_TYPES=RelWithDebInfo \ - -DUSE_BUNDLED_OPENSSL=OFF \ -DCI_BUILD=ON fi cmake --build build diff --git a/CMakeLists.txt b/CMakeLists.txt index 5b47b0af..b2b8da3b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -4,6 +4,7 @@ option(APPVEYOR_BUILD "Build on appveyor" OFF) option(CI_BUILD "Set when building in CI. Enables -Werror where possible" OFF) option(ASAN "Compile with address sanitizers" OFF) option(QML_DEBUGGING "Enable qml debugging" OFF) +option(COMPILE_QML "Compile Qml. It will make Nheko faster, but you will need to recompile it, when you update Qt." OFF) set( CMAKE_TOOLCHAIN_FILE "${CMAKE_CURRENT_LIST_DIR}/toolchain.cmake" @@ -17,10 +18,9 @@ set(CMAKE_POSITION_INDEPENDENT_CODE ON CACHE BOOL "compile as PIC by default") option(HUNTER_ENABLED "Enable Hunter package manager" OFF) include("cmake/HunterGate.cmake") HunterGate( - URL "https://github.com/cpp-pm/hunter/archive/v0.23.244.tar.gz" - SHA1 "2c0f491fd0b80f7b09e3d21adb97237161ef9835" - LOCAL - ) + URL "https://github.com/cpp-pm/hunter/archive/v0.23.260.tar.gz" + SHA1 "13775235910a3fa85644568d1c5be8271de72e1c" +) option(USE_BUNDLED_BOOST "Use the bundled version of Boost." ${HUNTER_ENABLED}) option(USE_BUNDLED_SPDLOG "Use the bundled version of spdlog." @@ -35,8 +35,6 @@ option(USE_BUNDLED_JSON "Use the bundled version of nlohmann json." option(USE_BUNDLED_OPENSSL "Use the bundled version of OpenSSL." ${HUNTER_ENABLED}) option(USE_BUNDLED_MTXCLIENT "Use the bundled version of the Matrix Client library." ${HUNTER_ENABLED}) -option(USE_BUNDLED_SODIUM "Use the bundled version of libsodium." - ${HUNTER_ENABLED}) option(USE_BUNDLED_LMDB "Use the bundled version of lmdb." ${HUNTER_ENABLED}) option(USE_BUNDLED_LMDBXX "Use the bundled version of lmdb++." @@ -334,7 +332,7 @@ find_package(Boost 1.70 REQUIRED if(USE_BUNDLED_OPENSSL) hunter_add_package(OpenSSL) endif() -find_package(OpenSSL REQUIRED) +find_package(OpenSSL 1.1.0 REQUIRED) if(USE_BUNDLED_MTXCLIENT) include(FetchContent) set(BUILD_LIB_EXAMPLES OFF CACHE INTERNAL "") @@ -342,7 +340,7 @@ if(USE_BUNDLED_MTXCLIENT) FetchContent_Declare( MatrixClient GIT_REPOSITORY https://github.com/Nheko-Reborn/mtxclient.git - GIT_TAG v0.3.1 + GIT_TAG eddd95a896fad0c51fc800741d82bbc43fc6d41e ) FetchContent_MakeAvailable(MatrixClient) else() diff --git a/README.md b/README.md index cb5685d5..20340a46 100644 --- a/README.md +++ b/README.md @@ -114,7 +114,6 @@ brew cask install nheko - [cmark](https://github.com/commonmark/cmark) 0.29 or greater. - Boost 1.70 or greater. - [libolm](https://gitlab.matrix.org/matrix-org/olm) -- [libsodium](https://github.com/jedisct1/libsodium) - [spdlog](https://github.com/gabime/spdlog) - A compiler that supports C++ 17: - Clang 6 (tested on Travis CI) @@ -136,8 +135,6 @@ The bundle flags are currently: - USE_BUNDLED_JSON - USE_BUNDLED_OPENSSL - USE_BUNDLED_MTXCLIENT -- USE_BUNDLED_SODIUM -- USE_BUNDLED_ZLIB - USE_BUNDLED_LMDB - USE_BUNDLED_LMDBXX - USE_BUNDLED_TWEENY @@ -162,8 +159,7 @@ sudo pacman -S qt5-base \ fontconfig \ lmdb \ cmark \ - boost \ - libsodium + boost ``` ##### Gentoo Linux @@ -176,7 +172,7 @@ sudo emerge -a ">=dev-qt/qtgui-5.9.0" media-libs/fontconfig ```bash # Build requirements + qml modules needed at runtime (you may not need all of them, but the following seem to work according to reports): -sudo apt install g++ cmake zlib1g-dev libssl-dev qt{base,declarative,tools,multimedia,quickcontrols2-}5-dev libqt5svg5-dev libboost-system-dev libboost-thread-dev libboost-iostreams-dev libolm-dev libsodium-dev liblmdb++-dev libcmark-dev nlohmann-json3-dev libspdlog-dev libgtest-dev qml-module-qt{gstreamer,multimedia,quick-extras,-labs-settings,graphicaleffects,quick-controls2} +sudo apt install g++ cmake zlib1g-dev libssl-dev qt{base,declarative,tools,multimedia,quickcontrols2-}5-dev libqt5svg5-dev libboost-system-dev libboost-thread-dev libboost-iostreams-dev libolm-dev liblmdb++-dev libcmark-dev nlohmann-json3-dev libspdlog-dev libgtest-dev qml-module-qt{gstreamer,multimedia,quick-extras,-labs-settings,graphicaleffects,quick-controls2} ``` This will install all dependencies, except for tweeny (use bundled tweeny) and mtxclient (needs to be build separately). @@ -186,7 +182,7 @@ and mtxclient (needs to be build separately). (User report, not sure if all of those are needed) ```bash -sudo apt install cmake gcc make automake liblmdb-dev libsodium-dev \ +sudo apt install cmake gcc make automake liblmdb-dev \ qt5-default libssl-dev libqt5multimedia5-plugins libqt5multimediagsttools5 libqt5multimediaquick5 libqt5svg5-dev \ qml-module-qtgstreamer qtmultimedia5-dev qtquickcontrols2-5-dev qttools5-dev qttools5-dev-tools \ qml-module-qtgraphicaleffects qml-module-qtmultimedia qml-module-qtquick-controls2 qml-module-qtquick-layouts @@ -203,7 +199,7 @@ guix environment nheko ```bash brew update -brew install qt5 lmdb cmake llvm libsodium spdlog boost cmark libolm +brew install qt5 lmdb cmake llvm spdlog boost cmark libolm ``` ##### Windows diff --git a/cmake/Hunter/config.cmake b/cmake/Hunter/config.cmake index d2f87774..7c53e0ea 100644 --- a/cmake/Hunter/config.cmake +++ b/cmake/Hunter/config.cmake @@ -1,5 +1,5 @@ hunter_config( Boost - VERSION "1.70.0-p0" + VERSION "1.70.0-p1" CMAKE_ARGS IOSTREAMS_NO_BZIP2=1 ) diff --git a/cmake/HunterGate.cmake b/cmake/HunterGate.cmake index e78d3e89..6d9cc240 100644 --- a/cmake/HunterGate.cmake +++ b/cmake/HunterGate.cmake @@ -133,10 +133,14 @@ function(hunter_gate_self root version sha1 result) string(SUBSTRING "${sha1}" 0 7 archive_id) - set( - hunter_self - "${root}/_Base/Download/Hunter/${version}/${archive_id}/Unpacked" - ) + if(EXISTS "${root}/cmake/Hunter") + set(hunter_self "${root}") + else() + set( + hunter_self + "${root}/_Base/Download/Hunter/${version}/${archive_id}/Unpacked" + ) + endif() set("${result}" "${hunter_self}" PARENT_SCOPE) endfunction() @@ -490,37 +494,44 @@ macro(HunterGate) ) set(_master_location "${_hunter_self}/cmake/Hunter") - get_filename_component(_archive_id_location "${_hunter_self}/.." ABSOLUTE) - set(_done_location "${_archive_id_location}/DONE") - set(_sha1_location "${_archive_id_location}/SHA1") + if(EXISTS "${HUNTER_GATE_ROOT}/cmake/Hunter") + # Hunter downloaded manually (e.g. by 'git clone') + set(_unused "xxxxxxxxxx") + set(HUNTER_GATE_SHA1 "${_unused}") + set(HUNTER_GATE_VERSION "${_unused}") + else() + get_filename_component(_archive_id_location "${_hunter_self}/.." ABSOLUTE) + set(_done_location "${_archive_id_location}/DONE") + set(_sha1_location "${_archive_id_location}/SHA1") - # Check Hunter already downloaded by HunterGate - if(NOT EXISTS "${_done_location}") - hunter_gate_download("${_archive_id_location}") - endif() + # Check Hunter already downloaded by HunterGate + if(NOT EXISTS "${_done_location}") + hunter_gate_download("${_archive_id_location}") + endif() - if(NOT EXISTS "${_done_location}") - hunter_gate_internal_error("hunter_gate_download failed") - endif() + if(NOT EXISTS "${_done_location}") + hunter_gate_internal_error("hunter_gate_download failed") + endif() - if(NOT EXISTS "${_sha1_location}") - hunter_gate_internal_error("${_sha1_location} not found") - endif() - file(READ "${_sha1_location}" _sha1_value) - string(COMPARE EQUAL "${_sha1_value}" "${HUNTER_GATE_SHA1}" _is_equal) - if(NOT _is_equal) - hunter_gate_internal_error( - "Short SHA1 collision:" - " ${_sha1_value} (from ${_sha1_location})" - " ${HUNTER_GATE_SHA1} (HunterGate)" - ) - endif() - if(NOT EXISTS "${_master_location}") - hunter_gate_user_error( - "Master file not found:" - " ${_master_location}" - "try to update Hunter/HunterGate" - ) + if(NOT EXISTS "${_sha1_location}") + hunter_gate_internal_error("${_sha1_location} not found") + endif() + file(READ "${_sha1_location}" _sha1_value) + string(COMPARE EQUAL "${_sha1_value}" "${HUNTER_GATE_SHA1}" _is_equal) + if(NOT _is_equal) + hunter_gate_internal_error( + "Short SHA1 collision:" + " ${_sha1_value} (from ${_sha1_location})" + " ${HUNTER_GATE_SHA1} (HunterGate)" + ) + endif() + if(NOT EXISTS "${_master_location}") + hunter_gate_user_error( + "Master file not found:" + " ${_master_location}" + "try to update Hunter/HunterGate" + ) + endif() endif() include("${_master_location}") set_property(GLOBAL PROPERTY HUNTER_GATE_DONE YES) diff --git a/cmake/Translations.cmake b/cmake/Translations.cmake index 16120219..887697a8 100644 --- a/cmake/Translations.cmake +++ b/cmake/Translations.cmake @@ -21,7 +21,7 @@ if(NOT EXISTS ${_qrc}) endif() qt5_add_resources(LANG_QRC ${_qrc}) -if(Qt5QuickCompiler_FOUND) +if(Qt5QuickCompiler_FOUND AND COMPILE_QML) qtquick_compiler_add_resources(QRC resources/res.qrc) else() qt5_add_resources(QRC resources/res.qrc) diff --git a/io.github.NhekoReborn.Nheko.json b/io.github.NhekoReborn.Nheko.json index 33acf34b..8e4dbbe6 100644 --- a/io.github.NhekoReborn.Nheko.json +++ b/io.github.NhekoReborn.Nheko.json @@ -146,9 +146,9 @@ "name": "mtxclient", "sources": [ { - "sha256": "e4899cc4ce87397de2aef865e94ea2cdb8d9cb86253727e7d90532b925ecc770", + "sha256": "6334bb71821a0fde54fe24f02ad393cdb6836633557ffdd239b29c5d5108daaf", "type": "archive", - "url": "https://github.com/Nheko-Reborn/mtxclient/archive/v0.3.1.tar.gz" + "url": "https://github.com/Nheko-Reborn/mtxclient/archive/eddd95a896fad0c51fc800741d82bbc43fc6d41e.tar.gz" } ] }, @@ -171,7 +171,8 @@ { "config-opts": [ "-DCMAKE_BUILD_TYPE=Release", - "-DLMDBXX_INCLUDE_DIR=.deps/lmdbxx" + "-DLMDBXX_INCLUDE_DIR=.deps/lmdbxx", + "-DCOMPILE_QML=ON" ], "buildsystem": "cmake-ninja", "name": "nheko", diff --git a/resources/qml/MatrixText.qml b/resources/qml/MatrixText.qml index 9a4f7348..d56143dd 100644 --- a/resources/qml/MatrixText.qml +++ b/resources/qml/MatrixText.qml @@ -16,7 +16,7 @@ TextEdit { timelineManager.setHistoryView(match[1]) chat.positionViewAtIndex(chat.model.idToIndex(match[2]), ListView.Contain) } - else Qt.openUrlExternally(link) + else timelineManager.openLink(link) } MouseArea { diff --git a/src/ChatPage.cpp b/src/ChatPage.cpp index 1bea8564..c83ce350 100644 --- a/src/ChatPage.cpp +++ b/src/ChatPage.cpp @@ -589,8 +589,12 @@ ChatPage::ChatPage(QSharedPointer userSettings, QWidget *parent) emit notificationsRetrieved(std::move(res)); }); }); - connect(this, &ChatPage::syncRoomlist, room_list_, &RoomList::sync); - connect(this, &ChatPage::syncTags, communitiesList_, &CommunitiesList::syncTags); + connect(this, &ChatPage::syncRoomlist, room_list_, &RoomList::sync, Qt::QueuedConnection); + connect(this, + &ChatPage::syncTags, + communitiesList_, + &CommunitiesList::syncTags, + Qt::QueuedConnection); connect( this, &ChatPage::syncTopBar, this, [this](const std::map &updates) { if (updates.find(currentRoom()) != updates.end()) @@ -605,11 +609,15 @@ ChatPage::ChatPage(QSharedPointer userSettings, QWidget *parent) user_info_widget_->setDisplayName(name); }); - connect(this, &ChatPage::tryInitialSyncCb, this, &ChatPage::tryInitialSync); - connect(this, &ChatPage::trySyncCb, this, &ChatPage::trySync); - connect(this, &ChatPage::tryDelayedSyncCb, this, [this]() { - QTimer::singleShot(RETRY_TIMEOUT, this, &ChatPage::trySync); - }); + connect( + this, &ChatPage::tryInitialSyncCb, this, &ChatPage::tryInitialSync, Qt::QueuedConnection); + connect(this, &ChatPage::trySyncCb, this, &ChatPage::trySync, Qt::QueuedConnection); + connect( + this, + &ChatPage::tryDelayedSyncCb, + this, + [this]() { QTimer::singleShot(RETRY_TIMEOUT, this, &ChatPage::trySync); }, + Qt::QueuedConnection); connect(this, &ChatPage::dropToLoginPageCb, this, &ChatPage::dropToLoginPage); diff --git a/src/Config.h b/src/Config.h index f99cf36b..c0624709 100644 --- a/src/Config.h +++ b/src/Config.h @@ -53,9 +53,9 @@ namespace strings { const QString url_html = "\\1"; const QRegularExpression url_regex( // match an URL, that is not quoted, i.e. - // vvvvvv match quote via negative lookahead/lookbehind vv - // vvvv atomic match url -> fail if there is a " before or after vvv - R"((?((www\.(?!\.)|[a-z][a-z0-9+.-]*://)[^\s<>'"]+[^!,\.\s<>'"\]\)\:]))(?!"))"); + // vvvvvv match quote via negative lookahead/lookbehind vv + // vvvv atomic match url -> fail if there is a " before or after vvv + R"((?((www\.(?!\.)|[a-z][a-z0-9+.-]*://)[^\s<>'"]+[^!,\.\s<>'"\]\)\:]))(?!["']))"); } // Window geometry. diff --git a/src/EventAccessors.cpp b/src/EventAccessors.cpp index a2d8adbb..7071819b 100644 --- a/src/EventAccessors.cpp +++ b/src/EventAccessors.cpp @@ -85,8 +85,10 @@ struct EventFormattedBody template std::string operator()(const mtx::events::RoomEvent &e) { - if constexpr (is_detected::value) - return e.content.formatted_body; + if constexpr (is_detected::value) { + if (e.content.format == "org.matrix.custom.html") + return e.content.formatted_body; + } return ""; } }; diff --git a/src/timeline/TimelineModel.cpp b/src/timeline/TimelineModel.cpp index cdbd36c5..aaaf7d4a 100644 --- a/src/timeline/TimelineModel.cpp +++ b/src/timeline/TimelineModel.cpp @@ -159,72 +159,96 @@ TimelineModel::TimelineModel(TimelineViewManager *manager, QString room_id, QObj , room_id_(room_id) , manager_(manager) { + connect(this, + &TimelineModel::oldMessagesRetrieved, + this, + &TimelineModel::addBackwardsEvents, + Qt::QueuedConnection); connect( - this, &TimelineModel::oldMessagesRetrieved, this, &TimelineModel::addBackwardsEvents); - connect(this, &TimelineModel::messageFailed, this, [this](QString txn_id) { - nhlog::ui()->error("Failed to send {}, retrying", txn_id.toStdString()); - - QTimer::singleShot(5000, this, [this]() { emit nextPendingMessage(); }); - }); - connect(this, &TimelineModel::messageSent, this, [this](QString txn_id, QString event_id) { - pending.removeOne(txn_id); - - auto ev = events.value(txn_id); - - if (auto reaction = - std::get_if>(&ev)) { - QString reactedTo = - QString::fromStdString(reaction->content.relates_to.event_id); - auto &rModel = reactions[reactedTo]; - rModel.removeReaction(*reaction); - auto rCopy = *reaction; - rCopy.event_id = event_id.toStdString(); - rModel.addReaction(room_id_.toStdString(), rCopy); - } - - int idx = idToIndex(txn_id); - if (idx < 0) { - // transaction already received via sync - return; - } - eventOrder[idx] = event_id; - ev = std::visit( - [event_id](const auto &e) -> mtx::events::collections::TimelineEvents { - auto eventCopy = e; - eventCopy.event_id = event_id.toStdString(); - return eventCopy; - }, - ev); - - events.remove(txn_id); - events.insert(event_id, ev); - - // mark our messages as read - readEvent(event_id.toStdString()); - - emit dataChanged(index(idx, 0), index(idx, 0)); - - if (pending.size() > 0) - emit nextPendingMessage(); - }); - connect(this, &TimelineModel::redactionFailed, this, [](const QString &msg) { - emit ChatPage::instance()->showNotification(msg); - }); + this, + &TimelineModel::messageFailed, + this, + [this](QString txn_id) { + nhlog::ui()->error("Failed to send {}, retrying", txn_id.toStdString()); + QTimer::singleShot(5000, this, [this]() { emit nextPendingMessage(); }); + }, + Qt::QueuedConnection); connect( - this, &TimelineModel::nextPendingMessage, this, &TimelineModel::processOnePendingMessage); - connect(this, &TimelineModel::newMessageToSend, this, &TimelineModel::addPendingMessage); + this, + &TimelineModel::messageSent, + this, + [this](QString txn_id, QString event_id) { + pending.removeOne(txn_id); + + auto ev = events.value(txn_id); + + if (auto reaction = + std::get_if>(&ev)) { + QString reactedTo = + QString::fromStdString(reaction->content.relates_to.event_id); + auto &rModel = reactions[reactedTo]; + rModel.removeReaction(*reaction); + auto rCopy = *reaction; + rCopy.event_id = event_id.toStdString(); + rModel.addReaction(room_id_.toStdString(), rCopy); + } + + int idx = idToIndex(txn_id); + if (idx < 0) { + // transaction already received via sync + return; + } + eventOrder[idx] = event_id; + ev = std::visit( + [event_id](const auto &e) -> mtx::events::collections::TimelineEvents { + auto eventCopy = e; + eventCopy.event_id = event_id.toStdString(); + return eventCopy; + }, + ev); + + events.remove(txn_id); + events.insert(event_id, ev); + + // mark our messages as read + readEvent(event_id.toStdString()); + + emit dataChanged(index(idx, 0), index(idx, 0)); + + if (pending.size() > 0) + emit nextPendingMessage(); + }, + Qt::QueuedConnection); + connect( + this, + &TimelineModel::redactionFailed, + this, + [](const QString &msg) { emit ChatPage::instance()->showNotification(msg); }, + Qt::QueuedConnection); connect(this, - &TimelineModel::eventFetched, + &TimelineModel::nextPendingMessage, this, - [this](QString requestingEvent, mtx::events::collections::TimelineEvents event) { - events.insert(QString::fromStdString(mtx::accessors::event_id(event)), - event); - auto idx = idToIndex(requestingEvent); - if (idx >= 0) - emit dataChanged(index(idx, 0), index(idx, 0)); - }); + &TimelineModel::processOnePendingMessage, + Qt::QueuedConnection); + connect(this, + &TimelineModel::newMessageToSend, + this, + &TimelineModel::addPendingMessage, + Qt::QueuedConnection); + + connect( + this, + &TimelineModel::eventFetched, + this, + [this](QString requestingEvent, mtx::events::collections::TimelineEvents event) { + events.insert(QString::fromStdString(mtx::accessors::event_id(event)), event); + auto idx = idToIndex(requestingEvent); + if (idx >= 0) + emit dataChanged(index(idx, 0), index(idx, 0)); + }, + Qt::QueuedConnection); } QHash diff --git a/src/timeline/TimelineViewManager.cpp b/src/timeline/TimelineViewManager.cpp index b652b78e..84be895f 100644 --- a/src/timeline/TimelineViewManager.cpp +++ b/src/timeline/TimelineViewManager.cpp @@ -1,5 +1,6 @@ #include "TimelineViewManager.h" +#include #include #include #include @@ -112,7 +113,10 @@ TimelineViewManager::TimelineViewManager(QSharedPointer userSettin container = view; view->setResizeMode(QQuickWidget::SizeRootObjectToView); container->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); + +#if (QT_VERSION >= QT_VERSION_CHECK(5, 10, 0)) view->quickWindow()->setTextRenderType(QQuickWindow::NativeTextRendering); +#endif connect(view, &QQuickWidget::statusChanged, this, [](QQuickWidget::Status status) { nhlog::ui()->debug("Status changed to {}", status); @@ -231,6 +235,12 @@ TimelineViewManager::openImageOverlay(QString mxcUrl, QString eventId) const }); } +void +TimelineViewManager::openLink(QString link) const +{ + QDesktopServices::openUrl(link); +} + void TimelineViewManager::updateReadReceipts(const QString &room_id, const std::vector &event_ids) diff --git a/src/timeline/TimelineViewManager.h b/src/timeline/TimelineViewManager.h index 5224cd56..902dc047 100644 --- a/src/timeline/TimelineViewManager.h +++ b/src/timeline/TimelineViewManager.h @@ -50,6 +50,8 @@ public: Q_INVOKABLE QString userPresence(QString id) const; Q_INVOKABLE QString userStatus(QString id) const; + Q_INVOKABLE void openLink(QString link) const; + signals: void clearRoomMessageCount(QString roomid); void updateRoomsLastMessage(QString roomid, const DescInfo &info); diff --git a/src/ui/SnackBar.cpp b/src/ui/SnackBar.cpp index 5daa697e..51a0ff38 100644 --- a/src/ui/SnackBar.cpp +++ b/src/ui/SnackBar.cpp @@ -63,7 +63,7 @@ SnackBar::hideMessage() // Moving on to the next message. messages_.pop_front(); - // Reseting the starting position of the widget. + // Resetting the starting position of the widget. offset_ = STARTING_OFFSET; if (!messages_.empty())